Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
67c8d41
initial
narefyev91 Jul 9, 2025
3d33647
fix: resolve conflicts
koko57 Jul 22, 2025
7704057
feat: add missing translations and missing fields
koko57 Jul 22, 2025
37c6cd1
Merge branch 'main' into duplicate-workspace
koko57 Jul 23, 2025
4f3ef0f
feat: add select all button, add old name validation
koko57 Jul 23, 2025
db8a981
fix: revert validating against the old name
koko57 Jul 25, 2025
92dcb9b
Merge branch 'main' into duplicate-workspace
koko57 Jul 25, 2025
23fb394
feat: display the option for admin, add distance rates
koko57 Jul 25, 2025
5c0b93b
Merge branch 'refs/heads/main' into duplicate-workspace
narefyev91 Jul 29, 2025
a6b461a
add logic for menu items
narefyev91 Jul 30, 2025
0fbd9a1
add logic for rules
narefyev91 Jul 31, 2025
acc70ae
workflows logic
narefyev91 Aug 1, 2025
56cb90a
Merge branch 'refs/heads/main' into duplicate-workspace
narefyev91 Aug 4, 2025
3cb2ac2
connection to old dot api
narefyev91 Aug 5, 2025
401e21b
rollback pod.lock
narefyev91 Aug 5, 2025
f66b25d
Merge branch 'refs/heads/main' into duplicate-workspace
narefyev91 Aug 5, 2025
8c7834b
fix checks
narefyev91 Aug 5, 2025
7605427
add logic to auto scroll and highlight
narefyev91 Aug 6, 2025
545623f
add api layer
narefyev91 Aug 6, 2025
2ed3d61
Merge branch 'refs/heads/main' into duplicate-workspace
narefyev91 Aug 6, 2025
92c8b71
fix ts
narefyev91 Aug 6, 2025
66fd6a7
add correct scroll and translations
narefyev91 Aug 7, 2025
08c298d
fix ts
narefyev91 Aug 7, 2025
7a94f5d
fix ts
narefyev91 Aug 7, 2025
2bf10b7
fixes after c+
narefyev91 Aug 8, 2025
f5d84af
fix eslint
narefyev91 Aug 8, 2025
8f9be80
remove scrollview
narefyev91 Aug 8, 2025
18853e0
Merge branch 'refs/heads/main' into duplicate-workspace
narefyev91 Aug 13, 2025
a4f386c
fix lint
narefyev91 Aug 13, 2025
04298e6
set select all selected by defaul
narefyev91 Aug 13, 2025
076c9ed
fix drawer
narefyev91 Aug 13, 2025
b4ae220
updates after c+
narefyev91 Aug 14, 2025
58fbead
fix ts
narefyev91 Aug 14, 2025
d2529ea
fix ts
narefyev91 Aug 14, 2025
bc898da
updates after review
narefyev91 Aug 18, 2025
32e4541
Merge branch 'refs/heads/main' into duplicate-workspace
narefyev91 Aug 18, 2025
9a8e844
Merge branch 'refs/heads/main' into duplicate-workspace
narefyev91 Aug 19, 2025
4d473a9
updates after review
narefyev91 Aug 19, 2025
e99ded3
updates after review
narefyev91 Aug 20, 2025
32e651d
Merge branch 'main' into duplicate-workspace
narefyev91 Aug 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,9 @@ const ONYXKEYS = {

NVP_PRIVATE_CANCELLATION_DETAILS: 'nvp_private_cancellationDetails',

/** Stores the information about duplicated workspace */
DUPLICATE_WORKSPACE: 'duplicateWorkspace',

/** Stores the information about currently edited advanced approval workflow */
APPROVAL_WORKFLOW: 'approvalWorkflow',

Expand Down Expand Up @@ -682,6 +685,8 @@ const ONYXKEYS = {
WORKSPACE_CATEGORY_FORM: 'workspaceCategoryForm',
WORKSPACE_CONFIRMATION_FORM: 'workspaceConfirmationForm',
WORKSPACE_CONFIRMATION_FORM_DRAFT: 'workspaceConfirmationFormDraft',
WORKSPACE_DUPLICATE_FORM: 'workspaceDuplicateForm',
WORKSPACE_DUPLICATE_FORM_DRAFT: 'workspaceDuplicateFormDraft',
WORKSPACE_CATEGORY_FORM_DRAFT: 'workspaceCategoryFormDraft',
WORKSPACE_CATEGORY_DESCRIPTION_HINT_FORM: 'workspaceCategoryDescriptionHintForm',
WORKSPACE_CATEGORY_DESCRIPTION_HINT_FORM_DRAFT: 'workspaceCategoryDescriptionHintFormDraft',
Expand Down Expand Up @@ -888,6 +893,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM]: FormTypes.WorkspaceSettingsForm;
[ONYXKEYS.FORMS.WORKSPACE_CATEGORY_FORM]: FormTypes.WorkspaceCategoryForm;
[ONYXKEYS.FORMS.WORKSPACE_CONFIRMATION_FORM]: FormTypes.WorkspaceConfirmationForm;
[ONYXKEYS.FORMS.WORKSPACE_DUPLICATE_FORM]: FormTypes.WorkspaceDuplicateForm;
[ONYXKEYS.FORMS.ONBOARDING_WORKSPACE_DETAILS_FORM]: FormTypes.WorkspaceConfirmationForm;
[ONYXKEYS.FORMS.WORKSPACE_TAG_FORM]: FormTypes.WorkspaceTagForm;
[ONYXKEYS.FORMS.WORKSPACE_TAX_CUSTOM_NAME]: FormTypes.WorkspaceTaxCustomName;
Expand Down Expand Up @@ -1190,6 +1196,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.ADD_NEW_COMPANY_CARD]: OnyxTypes.AddNewCompanyCardFeed;
[ONYXKEYS.ASSIGN_CARD]: OnyxTypes.AssignCard;
[ONYXKEYS.MOBILE_SELECTION_MODE]: boolean;
[ONYXKEYS.DUPLICATE_WORKSPACE]: OnyxTypes.DuplicateWorkspace;
[ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: string;
[ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL]: string;
[ONYXKEYS.NVP_BILLING_FUND_ID]: number;
Expand Down
8 changes: 8 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,14 @@ const ROUTES = {
return getUrlWithBackToParam(`workspaces/${policyID}/per-diem`, backTo);
},
},
WORKSPACE_DUPLICATE: {
route: 'workspace/:policyID/duplicate',
getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/duplicate`, backTo),
},
WORKSPACE_DUPLICATE_SELECT_FEATURES: {
route: 'workspace/:policyID/duplicate/select-features',
getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/duplicate/select-features`, backTo),
},
WORKSPACE_RECEIPT_PARTNERS: {
route: 'workspaces/:policyID/receipt-partners',
getRoute: (policyID: string | undefined, backTo?: string) => {
Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ const SCREENS = {
REPORT_DETAILS: 'Report_Details',
REPORT_CHANGE_WORKSPACE: 'ReportChangeWorkspace',
WORKSPACE_CONFIRMATION: 'Workspace_Confirmation',
WORKSPACE_DUPLICATE: 'Workspace_Duplicate',
REPORT_SETTINGS: 'Report_Settings',
REPORT_DESCRIPTION: 'Report_Description',
PARTICIPANTS: 'Participants',
Expand Down Expand Up @@ -387,6 +388,7 @@ const SCREENS = {
},

WORKSPACE_CONFIRMATION: {ROOT: 'Workspace_Confirmation_Root'},
WORKSPACE_DUPLICATE: {ROOT: 'Workspace_Duplicate_Root', SELECT_FEATURES: 'Workspace_Duplicate_Select_Features'},

WORKSPACES_LIST: 'Workspaces_List',

Expand Down
32 changes: 8 additions & 24 deletions src/components/WorkspaceConfirmationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import useWorkspaceConfirmationAvatar from '@hooks/useWorkspaceConfirmationAvatar';
import {generateDefaultWorkspaceName, generatePolicyID} from '@libs/actions/Policy/Policy';
import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types';
import {addErrorMessage} from '@libs/ErrorUtils';
import getFirstAlphaNumericCharacter from '@libs/getFirstAlphaNumericCharacter';
import Navigation from '@libs/Navigation/Navigation';
import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils';
import {isRequiredFulfilled} from '@libs/ValidationUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import INPUT_IDS from '@src/types/form/WorkspaceConfirmationForm';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import Avatar from './Avatar';
import AvatarWithImagePicker from './AvatarWithImagePicker';
import CurrencyPicker from './CurrencyPicker';
import FormProvider from './Form/FormProvider';
Expand All @@ -26,13 +27,6 @@ import ScrollView from './ScrollView';
import Text from './Text';
import TextInput from './TextInput';

function getFirstAlphaNumericCharacter(str = '') {
return str
.normalize('NFD')
.replace(/[^0-9a-z]/gi, '')
.toUpperCase()[0];
}

type WorkspaceConfirmationSubmitFunctionParams = {
name: string;
currency: string;
Expand Down Expand Up @@ -100,22 +94,12 @@ function WorkspaceConfirmationForm({onSubmit, policyOwnerEmail = '', onBackButto

const stashedLocalAvatarImage = workspaceAvatar?.avatarUri ?? undefined;

const DefaultAvatar = useCallback(
() => (
<Avatar
containerStyles={styles.avatarXLarge}
imageStyles={[styles.avatarXLarge, styles.alignSelfCenter]}
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing cannot be used if left side can be empty string
source={workspaceAvatar?.avatarUri || getDefaultWorkspaceAvatar(workspaceNameFirstCharacter)}
fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
size={CONST.AVATAR_SIZE.X_LARGE}
name={workspaceNameFirstCharacter}
avatarID={policyID}
type={CONST.ICON_TYPE_WORKSPACE}
/>
),
[workspaceAvatar?.avatarUri, workspaceNameFirstCharacter, styles.alignSelfCenter, styles.avatarXLarge, policyID],
);
const DefaultAvatar = useWorkspaceConfirmationAvatar({
policyID,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing cannot be used if left side can be empty string
source: stashedLocalAvatarImage || getDefaultWorkspaceAvatar(workspaceNameFirstCharacter),
name: workspaceNameFirstCharacter,
});

return (
<>
Expand Down
28 changes: 28 additions & 0 deletions src/hooks/useWorkspaceConfirmationAvatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, {useCallback} from 'react';
import Avatar from '@components/Avatar';
import * as Expensicons from '@components/Icon/Expensicons';
import type {AvatarSource} from '@libs/UserUtils';
import CONST from '@src/CONST';
import useThemeStyles from './useThemeStyles';

function useWorkspaceConfirmationAvatar({policyID, source, name}: {policyID: string | undefined; source: AvatarSource; name: string}) {
const styles = useThemeStyles();

return useCallback(
() => (
<Avatar
containerStyles={styles.avatarXLarge}
imageStyles={[styles.avatarXLarge, styles.alignSelfCenter]}
source={source}
fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
size={CONST.AVATAR_SIZE.X_LARGE}
name={name}
avatarID={policyID}
type={CONST.ICON_TYPE_WORKSPACE}
/>
),
[name, policyID, source, styles.alignSelfCenter, styles.avatarXLarge],
);
}

export default useWorkspaceConfirmationAvatar;
18 changes: 18 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ const translations = {
count: 'Zählen',
cancel: 'Abbrechen',
dismiss: 'Verwerfen',
proceed: 'Fortfahren',
yes: 'Ja',
no: 'No',
ok: 'OK',
Expand Down Expand Up @@ -3442,12 +3443,14 @@ const translations = {
customField1: 'Benutzerdefiniertes Feld 1',
customField2: 'Benutzerdefiniertes Feld 2',
customFieldHint: 'Fügen Sie benutzerdefinierten Code hinzu, der für alle Ausgaben dieses Mitglieds gilt.',
reports: 'Berichte',
reportFields: 'Berichtsfelder',
reportTitle: 'Berichtstitel',
reportField: 'Berichtsfeld',
taxes: 'Steuern',
bills: 'Rechnungen',
invoices: 'Rechnungen',
perDiem: 'Per diem',
travel: 'Reisen',
members: 'Mitglieder',
accounting: 'Buchhaltung',
Expand All @@ -3460,6 +3463,7 @@ const translations = {
testTransactions: 'Transaktionen testen',
issueAndManageCards: 'Karten ausstellen und verwalten',
reconcileCards: 'Karten abstimmen',
selectAll: 'Alle auswählen',
selected: () => ({
one: '1 ausgewählt',
other: (count: number) => `${count} ausgewählt`,
Expand All @@ -3473,6 +3477,8 @@ const translations = {
memberNotFound: 'Mitglied nicht gefunden. Um ein neues Mitglied zum Arbeitsbereich einzuladen, verwenden Sie bitte die Einladungsschaltfläche oben.',
notAuthorized: `Sie haben keinen Zugriff auf diese Seite. Wenn Sie versuchen, diesem Arbeitsbereich beizutreten, bitten Sie einfach den Besitzer des Arbeitsbereichs, Sie als Mitglied hinzuzufügen. Etwas anderes? Kontaktieren Sie ${CONST.EMAIL.CONCIERGE}.`,
goToWorkspace: 'Zum Arbeitsbereich gehen',
duplicateWorkspace: 'Arbeitsbereich duplizieren',
duplicateWorkspacePrefix: 'Duplizieren',
goToWorkspaces: 'Zu Arbeitsbereichen gehen',
clearFilter: 'Filter löschen',
workspaceName: 'Arbeitsbereichsname',
Expand Down Expand Up @@ -4881,6 +4887,18 @@ const translations = {
taxCode: 'Steuercode',
updateTaxCodeFailureMessage: 'Beim Aktualisieren des Steuercodes ist ein Fehler aufgetreten, bitte versuchen Sie es erneut.',
},
duplicateWorkspace: {
title: 'Benennen Sie Ihren neuen Arbeitsbereich',
selectFeatures: 'Auswählen der zu kopierenden Features',
whichFeatures: 'Welche Funktionen möchten Sie in Ihren neuen Arbeitsbereich kopieren?',
confirmDuplicate: '\n\nMöchten Sie fortfahren?',
categories: 'Kategorien und Ihre Auto-Kategorisierungsregeln',
reimbursementAccount: 'Erstattungskonto',
delayedSubmission: 'verspätete Einreichung',
welcomeNote: 'Bitte beginnen Sie mit der Nutzung meines neuen Arbeitsbereichs',
confirmTitle: ({newWorkspaceName, totalMembers}: {newWorkspaceName?: string; totalMembers?: number}) =>
`Sie sind dabei, ${newWorkspaceName ?? ''} zu erstellen und mit ${totalMembers ?? 0} Mitgliedern aus dem ursprünglichen Arbeitsbereich zu teilen.`,
},
emptyWorkspace: {
title: 'Erstellen Sie einen Arbeitsbereich',
subtitle:
Expand Down
18 changes: 18 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ const translations = {
count: 'Count',
cancel: 'Cancel',
dismiss: 'Dismiss',
proceed: 'Proceed',
yes: 'Yes',
no: 'No',
ok: 'OK',
Expand Down Expand Up @@ -3439,12 +3440,14 @@ const translations = {
customField1: 'Custom field 1',
customField2: 'Custom field 2',
customFieldHint: 'Add custom coding that applies to all spend from this member.',
reports: 'Reports',
reportFields: 'Report fields',
reportTitle: 'Report title',
reportField: 'Report field',
taxes: 'Taxes',
bills: 'Bills',
invoices: 'Invoices',
perDiem: 'Per diem',
travel: 'Travel',
members: 'Members',
accounting: 'Accounting',
Expand All @@ -3457,6 +3460,7 @@ const translations = {
testTransactions: 'Test transactions',
issueAndManageCards: 'Issue and manage cards',
reconcileCards: 'Reconcile cards',
selectAll: 'Select all',
selected: () => ({
one: '1 selected',
other: (count: number) => `${count} selected`,
Expand All @@ -3470,6 +3474,8 @@ const translations = {
memberNotFound: 'Member not found. To invite a new member to the workspace, please use the invite button above.',
notAuthorized: `You don't have access to this page. If you're trying to join this workspace, just ask the workspace owner to add you as a member. Something else? Reach out to ${CONST.EMAIL.CONCIERGE}.`,
goToWorkspace: 'Go to workspace',
duplicateWorkspace: 'Duplicate Workspace',
duplicateWorkspacePrefix: 'Duplicate',
goToWorkspaces: 'Go to workspaces',
clearFilter: 'Clear filter',
workspaceName: 'Workspace name',
Expand Down Expand Up @@ -4864,6 +4870,18 @@ const translations = {
taxCode: 'Tax code',
updateTaxCodeFailureMessage: 'An error occurred while updating the tax code, please try again',
},
duplicateWorkspace: {
title: 'Name your new workspace',
selectFeatures: 'Select features to copy',
whichFeatures: 'Which features do you want to copy over to your new workspace?',
confirmDuplicate: '\n\nDo you want to continue?',
categories: 'categories and your auto-categorization rules',
reimbursementAccount: 'reimbursement account',
welcomeNote: 'Please start using my new workspace',
delayedSubmission: 'delayed submission',
confirmTitle: ({newWorkspaceName, totalMembers}: {newWorkspaceName?: string; totalMembers?: number}) =>
`You’re about to create and share ${newWorkspaceName ?? ''} with ${totalMembers ?? 0} members from the original workspace.`,
},
emptyWorkspace: {
title: 'Create a workspace',
subtitle: 'Create a workspace to track receipts, reimburse expenses, manage travel, send invoices, and more — all at the speed of chat.',
Expand Down
18 changes: 18 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ const translations = {
count: 'Contar',
cancel: 'Cancelar',
dismiss: 'Descartar',
proceed: 'Proceed',
yes: 'Sí',
no: 'No',
ok: 'OK',
Expand Down Expand Up @@ -3427,11 +3428,13 @@ const translations = {
customField1: 'Campo personalizado 1',
customField2: 'Campo personalizado 2',
customFieldHint: 'Añade una codificación personalizada que se aplique a todos los gastos de este miembro.',
reports: 'Informes',
reportFields: 'Campos de informe',
reportTitle: 'El título del informe.',
taxes: 'Impuestos',
bills: 'Pagar facturas',
invoices: 'Facturas',
perDiem: 'Per diem',
travel: 'Viajes',
members: 'Miembros',
accounting: 'Contabilidad',
Expand All @@ -3444,6 +3447,7 @@ const translations = {
testTransactions: 'Transacciones de prueba',
issueAndManageCards: 'Emitir y gestionar tarjetas',
reconcileCards: 'Reconciliar tarjetas',
selectAll: 'Seleccionar todo',
selected: () => ({
one: '1 seleccionado',
other: (count: number) => `${count} seleccionados`,
Expand All @@ -3457,6 +3461,8 @@ const translations = {
memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro al espacio de trabajo, por favor, utiliza el botón invitar que está arriba.',
notAuthorized: `No tienes acceso a esta página. Si estás intentando unirte a este espacio de trabajo, pide al dueño del espacio de trabajo que te añada como miembro. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`,
goToWorkspace: 'Ir al espacio de trabajo',
duplicateWorkspace: 'Duplicar espacio de trabajo',
duplicateWorkspacePrefix: 'Duplicar',
goToWorkspaces: 'Ir a espacios de trabajo',
clearFilter: 'Borrar filtro',
workspaceName: 'Nombre del espacio de trabajo',
Expand Down Expand Up @@ -4874,6 +4880,18 @@ const translations = {
taxCode: 'Código de impuesto',
updateTaxCodeFailureMessage: 'Se produjo un error al actualizar el código tributario, inténtelo nuevamente',
},
duplicateWorkspace: {
title: 'Nombra tu nuevo espacio de trabajo',
selectFeatures: 'Selecciona las funciones a copiar',
whichFeatures: '¿Qué funciones deseas copiar a tu nuevo espacio de trabajo?',
confirmDuplicate: '\n\n¿Quieres continuar?',
categories: 'categorías y tus reglas de auto-categorización',
reimbursementAccount: 'cuenta de reembolso',
delayedSubmission: 'presentación retrasada',
welcomeNote: 'Por favor, comience a utilizar mi nuevo espacio de trabajo.',
confirmTitle: ({newWorkspaceName, totalMembers}: {newWorkspaceName?: string; totalMembers?: number}) =>
`Estás a punto de crear y compartir ${newWorkspaceName ?? ''} con ${totalMembers ?? 0} miembros del espacio de trabajo original.`,
},
emptyWorkspace: {
title: 'Crea un espacio de trabajo',
subtitle: 'Crea un espacio de trabajo para organizar recibos, reembolsar gastos, gestionar viajes, enviar facturas y mucho más, todo a la velocidad del chat.',
Expand Down
Loading
Loading