From e69dd460c76176e324a374bf6905fafa18f2d5cc Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 22 Jul 2024 16:09:51 +0530 Subject: [PATCH 001/114] Added pluralization system for lang translations --- src/components/MoneyReportHeader.tsx | 4 +- src/components/ProcessMoneyReportHoldMenu.tsx | 2 +- src/components/ReceiptAudit.tsx | 2 +- .../ExportWithDropdownMenu.tsx | 2 +- .../ReportActionItem/ReportPreview.tsx | 13 +- .../Search/SearchListWithHeader.tsx | 4 +- src/components/Search/SearchPageHeader.tsx | 2 +- src/languages/en.ts | 108 ++++++++++------ src/languages/es.ts | 115 +++++++++++------- src/languages/types.ts | 63 ++++++++-- src/libs/Localize/index.ts | 37 +++++- src/libs/PolicyUtils.ts | 5 +- src/libs/ReportActionsUtils.ts | 2 +- src/pages/ReportDetailsPage.tsx | 4 +- src/pages/ReportParticipantsPage.tsx | 2 +- src/pages/Search/SearchSelectedNarrow.tsx | 2 +- .../ReportActionCompose.tsx | 12 +- .../home/report/ReportDetailsExportPage.tsx | 2 +- .../FloatingActionButtonAndPopover.tsx | 2 +- src/pages/wallet/WalletStatementPage.tsx | 2 +- src/pages/workspace/WorkspaceMembersPage.tsx | 2 +- .../intacct/ExistingConnectionsPage.tsx | 4 +- .../intacct/import/SageIntacctImportPage.tsx | 2 +- .../NetSuiteExistingConnectionsPage.tsx | 4 +- .../categories/WorkspaceCategoriesPage.tsx | 2 +- .../PolicyDistanceRateDetailsPage.tsx | 2 +- .../distanceRates/PolicyDistanceRatesPage.tsx | 10 +- .../ReportFieldsListValuesPage.tsx | 2 +- .../WorkspaceReportFieldsPage.tsx | 2 +- .../workspace/tags/WorkspaceTagsPage.tsx | 2 +- .../workspace/tags/WorkspaceViewTagsPage.tsx | 2 +- .../workspace/taxes/WorkspaceTaxesPage.tsx | 2 +- 32 files changed, 273 insertions(+), 148 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 1acee187c67b..18a8c0b9e1a1 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -387,12 +387,12 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea /> )} setIsDeleteRequestModalVisible(false)} onModalHide={() => ReportUtils.navigateBackAfterDeleteTransaction(navigateBackToAfterDelete.current)} - prompt={translate('iou.deleteConfirmation')} + prompt={translate('iou.deleteConfirmation', undefined, 1)} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} danger diff --git a/src/components/ProcessMoneyReportHoldMenu.tsx b/src/components/ProcessMoneyReportHoldMenu.tsx index 872464d8a5b0..ffc1150a454b 100644 --- a/src/components/ProcessMoneyReportHoldMenu.tsx +++ b/src/components/ProcessMoneyReportHoldMenu.tsx @@ -76,7 +76,7 @@ function ProcessMoneyReportHoldMenu({ if (nonHeldAmount) { return translate(isApprove ? 'iou.confirmApprovalAmount' : 'iou.confirmPayAmount'); } - return translate(isApprove ? 'iou.confirmApprovalAllHoldAmount' : 'iou.confirmPayAllHoldAmount', {transactionCount}); + return translate(isApprove ? 'iou.confirmApprovalAllHoldAmount' : 'iou.confirmPayAllHoldAmount', undefined, transactionCount); }, [nonHeldAmount, transactionCount, translate, isApprove]); return ( diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index bb704def1836..1057736621cd 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -22,7 +22,7 @@ function ReceiptAudit({notes, shouldShowAuditResult}: ReceiptAuditProps) { let auditText = ''; if (notes.length > 0 && shouldShowAuditResult) { - auditText = translate('iou.receiptIssuesFound', notes.length); + auditText = translate('iou.receiptIssuesFound', undefined, notes.length); } else if (!notes.length && shouldShowAuditResult) { auditText = translate('common.verified'); } diff --git a/src/components/ReportActionItem/ExportWithDropdownMenu.tsx b/src/components/ReportActionItem/ExportWithDropdownMenu.tsx index a13c0a266689..2906bee5758f 100644 --- a/src/components/ReportActionItem/ExportWithDropdownMenu.tsx +++ b/src/components/ReportActionItem/ExportWithDropdownMenu.tsx @@ -118,7 +118,7 @@ function ExportWithDropdownMenu({policy, report, connectionName}: ExportWithDrop title={translate('workspace.exportAgainModal.title')} onConfirm={confirmExport} onCancel={() => setModalStatus(null)} - prompt={translate('workspace.exportAgainModal.description', report?.reportName ?? '', connectionName)} + prompt={translate('workspace.exportAgainModal.description', {reportName: report?.reportName ?? '', connectionName})} confirmText={translate('workspace.exportAgainModal.confirmText')} cancelText={translate('workspace.exportAgainModal.cancelText')} isVisible={!!modalStatus} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index ae6ace23c64e..c794113d0f50 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -328,11 +328,14 @@ function ReportPreview({ return {supportText: formattedMerchant}; } return { - supportText: translate('iou.expenseCount', { - count: numberOfRequests - numberOfScanningReceipts - numberOfPendingRequests, - scanningReceipts: numberOfScanningReceipts, - pendingReceipts: numberOfPendingRequests, - }), + supportText: translate( + 'iou.expenseCount', + { + scanningReceipts: numberOfScanningReceipts, + pendingReceipts: numberOfPendingRequests, + }, + numberOfRequests - numberOfScanningReceipts - numberOfPendingRequests, + ), }; }, [formattedMerchant, formattedDescription, moneyRequestComment, translate, numberOfRequests, numberOfScanningReceipts, numberOfPendingRequests]); diff --git a/src/components/Search/SearchListWithHeader.tsx b/src/components/Search/SearchListWithHeader.tsx index bd4b843bbd60..edfa55a53b26 100644 --- a/src/components/Search/SearchListWithHeader.tsx +++ b/src/components/Search/SearchListWithHeader.tsx @@ -213,8 +213,8 @@ function SearchListWithHeader( isVisible={deleteExpensesConfirmModalVisible} onConfirm={handleDeleteExpenses} onCancel={handleOnCancelConfirmModal} - title={translate('iou.deleteExpense', {count: selectedTransactionsToDelete.length})} - prompt={translate('iou.deleteConfirmation', {count: selectedTransactionsToDelete.length})} + title={translate('iou.deleteExpense', undefined, selectedTransactionsToDelete.length)} + prompt={translate('iou.deleteConfirmation', undefined, selectedTransactionsToDelete.length)} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} danger diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index bd1e053b1c7e..0f4843b52a58 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -221,7 +221,7 @@ function SearchPageHeader({ shouldAlwaysShowDropdownMenu pressOnEnter buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} - customText={translate('workspace.common.selected', {selectedNumber: selectedTransactionsKeys.length})} + customText={translate('workspace.common.selected', undefined, selectedTransactionsKeys.length)} options={headerButtonsOptions} isSplitButton={false} /> diff --git a/src/languages/en.ts b/src/languages/en.ts index b49ad50421a4..5e3b9241aae9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1,4 +1,4 @@ -import {CONST as COMMON_CONST, Str} from 'expensify-common'; +import {CONST as COMMON_CONST} from 'expensify-common'; import {startCase} from 'lodash'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; @@ -17,25 +17,25 @@ import type { ChangePolicyParams, ChangeTypeParams, CharacterLimitParams, - ConfirmHoldExpenseParams, ConfirmThatParams, + CustomersOrJobsLabelTranslationParams, DateShouldBeAfterParams, DateShouldBeBeforeParams, DelegateSubmitParams, DeleteActionParams, DeleteConfirmationParams, - DeleteExpenseTranslationParams, DidSplitAmountMessageParams, - DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, EnterMagicCodeParams, + ExportAgainModalDescriptionTranslationParams, ExportedToIntegrationParams, FormattedMaxLengthParams, ForwardedParams, GoBackMessageParams, GoToRoomParams, InstantSummaryParams, + LastSyncDateTranslationParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -66,6 +66,7 @@ import type { ReportArchiveReasonsMergedParams, ReportArchiveReasonsPolicyDeletedParams, ReportArchiveReasonsRemovedFromPolicyParams, + ReportIntegrationMessageTranslationParams, RequestAmountParams, RequestCountParams, RequestedAmountMessageParams, @@ -80,6 +81,7 @@ import type { SignUpNewFaceCodeParams, SizeExceededParams, SplitAmountParams, + StatementPageTitleTranslationParams, StepCounterParams, StripePaidParams, TaskCreatedActionParams, @@ -496,16 +498,14 @@ export default { sendAttachment: 'Send attachment', addAttachment: 'Add attachment', writeSomething: 'Write something...', - conciergePlaceholderOptions: [ - 'Ask for help!', - 'Ask me anything!', - 'Ask me to book travel!', - 'Ask me what I can do!', - 'Ask me how to pay people!', - 'Ask me how to send an invoice!', - 'Ask me how to scan a receipt!', - 'Ask me how to get a free corporate card!', - ], + conciergePlaceholderOptions: (isSmallScreenWidth: boolean) => { + // If we are on a small width device then don't show last 3 items from conciergePlaceholderOptions + const options = ['Ask for help!', 'Ask me anything!', 'Ask me to book travel!', 'Ask me what I can do!', 'Ask me how to pay people!']; + if (!isSmallScreenWidth) { + options.push('Ask me how to send an invoice!', 'Ask me how to scan a receipt!', 'Ask me how to get a free corporate card!'); + } + return options[Math.floor(Math.random() * options.length)]; + }, blockedFromConcierge: 'Communication is barred', fileUploadFailed: 'Upload failed. File is not supported.', localTime: ({user, time}: LocalTimeParams) => `It's ${time} for ${user}`, @@ -654,7 +654,7 @@ export default { splitBill: 'Split expense', splitScan: 'Split receipt', splitDistance: 'Split distance', - paySomeone: (name: string) => `Pay ${name ?? 'someone'}`, + paySomeone: ({name}: PaySomeoneParams) => `Pay ${name ?? 'someone'}`, assignTask: 'Assign task', header: 'Quick action', trackManual: 'Track expense', @@ -700,7 +700,10 @@ export default { receiptScanning: 'Receipt scanning...', receiptScanInProgress: 'Receipt scan in progress', receiptScanInProgressDescription: 'Receipt scan in progress. Check back later or enter the details now.', - receiptIssuesFound: (count: number) => `${count === 1 ? 'Issue' : 'Issues'} found`, + receiptIssuesFound: () => ({ + one: `Issue found`, + other: () => `Issues found`, + }), fieldPending: 'Pending...', defaultRate: 'Default rate', receiptMissingDetails: 'Receipt missing details', @@ -710,12 +713,19 @@ export default { receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.", receiptScanningFailed: 'Receipt scanning failed. Please enter the details manually.', transactionPendingDescription: 'Transaction pending. It may take a few days to post.', - expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => - `${count} ${Str.pluralize('expense', 'expenses', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${ - pendingReceipts > 0 ? `, ${pendingReceipts} pending` : '' - }`, - deleteExpense: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `Delete ${Str.pluralize('expense', 'expenses', count)}`, - deleteConfirmation: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `Are you sure that you want to delete ${Str.pluralize('this expense', 'these expenses', count)}?`, + expenseCount: ({scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => ({ + zero: `0 expenses${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pending` : ''}`, + one: `1 expense${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pending` : ''}`, + other: (count: number) => `${count} expenses${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pending` : ''}`, + }), + deleteExpense: () => ({ + one: `Delete expense`, + other: () => `Delete expenses`, + }), + deleteConfirmation: () => ({ + one: `Are you sure that you want to delete this expense?`, + other: () => `Are you sure that you want to delete these expenses?`, + }), settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', individual: 'Individual', @@ -808,12 +818,18 @@ export default { keepAll: 'Keep all', confirmApprove: 'Confirm approval amount', confirmApprovalAmount: 'Approve only compliant expenses, or approve the entire report.', - confirmApprovalAllHoldAmount: ({transactionCount}: ConfirmHoldExpenseParams) => - `${Str.pluralize('This expense is', 'These expenses are', transactionCount)} on hold. Do you want to approve anyway?`, + confirmApprovalAllHoldAmount: () => ({ + zero: `This expense is on hold. Do you want to approve anyway?`, + one: `This expense is on hold. Do you want to approve anyway?`, + other: () => `These expenses are on hold. Do you want to approve anyway?`, + }), confirmPay: 'Confirm payment amount', confirmPayAmount: "Pay what's not on hold, or pay the entire report.", - confirmPayAllHoldAmount: ({transactionCount}: ConfirmHoldExpenseParams) => - `${Str.pluralize('This expense is', 'These expenses are', transactionCount)} on hold. Do you want to pay anyway?`, + confirmPayAllHoldAmount: () => ({ + zero: `This expense is on hold. Do you want to pay anyway?`, + one: `This expense is on hold. Do you want to pay anyway?`, + other: () => `These expenses are on hold. Do you want to pay anyway?`, + }), payOnly: 'Pay only', approveOnly: 'Approve only', holdEducationalTitle: 'This expense is on', @@ -2040,7 +2056,10 @@ export default { testTransactions: 'Test transactions', issueAndManageCards: 'Issue and manage cards', reconcileCards: 'Reconcile cards', - selected: ({selectedNumber}) => `${selectedNumber} selected`, + selected: () => ({ + one: `1 selected`, + other: (count: number) => `${count} selected`, + }), settlementFrequency: 'Settlement frequency', deleteConfirmation: 'Are you sure you want to delete this workspace?', unavailable: 'Unavailable workspace', @@ -2075,7 +2094,7 @@ export default { createNewConnection: 'Create new connection', reuseExistingConnection: 'Reuse existing connection', existingConnections: 'Existing connections', - lastSyncDate: (connectionName: string, formattedDate: string) => `${connectionName} - Last synced ${formattedDate}`, + lastSyncDate: ({connectionName, formattedDate}: LastSyncDateTranslationParams) => `${connectionName} - Last synced ${formattedDate}`, }, qbo: { importDescription: 'Choose which coding configurations to import from QuickBooks Online to Expensify.', @@ -2507,7 +2526,7 @@ export default { importJobs: 'Import projects', customers: 'customers', jobs: 'projects', - label: (importFields: string[], importType: string) => `${importFields.join(' and ')}, ${importType}`, + label: ({importFields, importType}: CustomersOrJobsLabelTranslationParams) => `${importFields.join(' and ')}, ${importType}`, }, importTaxDescription: 'Import tax groups from NetSuite.', importCustomFields: { @@ -2629,7 +2648,10 @@ export default { addAUserDefinedDimension: 'Add a user-defined dimension', detailedInstructionsLink: 'View detailed instructions', detailedInstructionsRestOfSentence: ' on adding user-defined dimensions.', - userDimensionsAdded: (dimensionsCount: number) => `${dimensionsCount} ${Str.pluralize('UDD', `UDDs`, dimensionsCount)} added`, + userDimensionsAdded: () => ({ + one: `1 UDD added`, + other: (count: number) => `${count} UDDs added`, + }), mappingTitle: (mappingName: SageIntacctMappingName): string => { switch (mappingName) { case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.DEPARTMENTS: @@ -3279,9 +3301,18 @@ export default { rate: 'Rate', addRate: 'Add rate', trackTax: 'Track tax', - deleteRates: ({count}: DistanceRateOperationsParams) => `Delete ${Str.pluralize('rate', 'rates', count)}`, - enableRates: ({count}: DistanceRateOperationsParams) => `Enable ${Str.pluralize('rate', 'rates', count)}`, - disableRates: ({count}: DistanceRateOperationsParams) => `Disable ${Str.pluralize('rate', 'rates', count)}`, + deleteRates: () => ({ + one: `Delete 1 rate`, + other: (count: number) => `Delete ${count} rates`, + }), + enableRates: () => ({ + one: `Enable 1 rate`, + other: (count: number) => `Enable ${count} rates`, + }), + disableRates: () => ({ + one: `Disable 1 rate`, + other: (count: number) => `Disable ${count} rates`, + }), enableRate: 'Enable rate', status: 'Status', unit: 'Unit', @@ -3289,7 +3320,10 @@ export default { changePromptMessage: ' to make that change.', defaultCategory: 'Default category', deleteDistanceRate: 'Delete distance rate', - areYouSureDelete: ({count}: DistanceRateOperationsParams) => `Are you sure you want to delete ${Str.pluralize('this rate', 'these rates', count)}?`, + areYouSureDelete: () => ({ + one: `Are you sure you want to delete 1 rate?`, + other: (count: number) => `Are you sure you want to delete ${count} rates?`, + }), }, editor: { descriptionInputLabel: 'Description', @@ -3376,7 +3410,7 @@ export default { }, exportAgainModal: { title: 'Careful!', - description: (reportName: string, connectionName: ConnectionName) => + description: ({reportName, connectionName}: ExportAgainModalDescriptionTranslationParams) => `The following reports have already been exported to ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]}:\n\n${reportName}\n\nAre you sure you want to export them again?`, confirmText: 'Yes, export again', cancelText: 'Cancel', @@ -3530,7 +3564,7 @@ export default { deleteConfirmation: 'Are you sure you want to delete this task?', }, statementPage: { - title: (year, monthName) => `${monthName} ${year} statement`, + title: ({year, monthName}: StatementPageTitleTranslationParams) => `${monthName} ${year} statement`, generatingPDF: "We're generating your PDF right now. Please check back soon!", }, keyboardShortcutsPage: { @@ -3688,7 +3722,7 @@ export default { pending: ({label}: ExportedToIntegrationParams) => `started exporting this report to ${label}...`, }, forwarded: ({amount, currency}: ForwardedParams) => `approved ${currency}${amount}`, - integrationsMessage: (errorMessage: string, label: string) => `failed to export this report to ${label} ("${errorMessage}").`, + integrationsMessage: ({errorMessage, label}: ReportIntegrationMessageTranslationParams) => `failed to export this report to ${label} ("${errorMessage}").`, managerAttachReceipt: `added a receipt`, managerDetachReceipt: `removed a receipt`, markedReimbursed: ({amount, currency}: MarkedReimbursedParams) => `paid ${currency}${amount} elsewhere`, diff --git a/src/languages/es.ts b/src/languages/es.ts index ff06d4f4f97e..1ce4ac35586b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,4 +1,3 @@ -import {Str} from 'expensify-common'; import CONST from '@src/CONST'; import type {ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type { @@ -15,26 +14,26 @@ import type { ChangePolicyParams, ChangeTypeParams, CharacterLimitParams, - ConfirmHoldExpenseParams, ConfirmThatParams, + CustomersOrJobsLabelTranslationParams, DateShouldBeAfterParams, DateShouldBeBeforeParams, DelegateSubmitParams, DeleteActionParams, DeleteConfirmationParams, - DeleteExpenseTranslationParams, DidSplitAmountMessageParams, - DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, EnglishTranslation, EnterMagicCodeParams, + ExportAgainModalDescriptionTranslationParams, ExportedToIntegrationParams, FormattedMaxLengthParams, ForwardedParams, GoBackMessageParams, GoToRoomParams, InstantSummaryParams, + LastSyncDateTranslationParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -65,6 +64,7 @@ import type { ReportArchiveReasonsMergedParams, ReportArchiveReasonsPolicyDeletedParams, ReportArchiveReasonsRemovedFromPolicyParams, + ReportIntegrationMessageTranslationParams, RequestAmountParams, RequestCountParams, RequestedAmountMessageParams, @@ -79,6 +79,7 @@ import type { SignUpNewFaceCodeParams, SizeExceededParams, SplitAmountParams, + StatementPageTitleTranslationParams, StepCounterParams, StripePaidParams, TaskCreatedActionParams, @@ -487,16 +488,13 @@ export default { sendAttachment: 'Enviar adjunto', addAttachment: 'Añadir archivo adjunto', writeSomething: 'Escribe algo...', - conciergePlaceholderOptions: [ - '¡Pide ayuda!', - '¡Pregúntame lo que sea!', - '¡Pídeme que te reserve un viaje!', - '¡Pregúntame qué puedo hacer!', - '¡Pregúntame cómo pagar a la gente!', - '¡Pregúntame cómo enviar una factura!', - '¡Pregúntame cómo escanear un recibo!', - '¡Pregúntame cómo obtener una tarjeta de crédito corporativa gratis!', - ], + conciergePlaceholderOptions: (isSmallScreenWidth: boolean) => { + const options = ['¡Pide ayuda!', '¡Pregúntame lo que sea!', '¡Pídeme que te reserve un viaje!', '¡Pregúntame qué puedo hacer!', '¡Pregúntame cómo pagar a la gente!']; + if (!isSmallScreenWidth) { + options.push('¡Pregúntame cómo enviar una factura!', '¡Pregúntame cómo escanear un recibo!', '¡Pregúntame cómo obtener una tarjeta de crédito corporativa gratis!'); + } + return options[Math.floor(Math.random() * options.length)]; + }, blockedFromConcierge: 'Comunicación no permitida', fileUploadFailed: 'Subida fallida. El archivo no es compatible.', localTime: ({user, time}: LocalTimeParams) => `Son las ${time} para ${user}`, @@ -649,7 +647,7 @@ export default { splitBill: 'Dividir gasto', splitScan: 'Dividir recibo', splitDistance: 'Dividir distancia', - paySomeone: (name: string) => `Pagar a ${name ?? 'alguien'}`, + paySomeone: ({name}: PaySomeoneParams) => `Pagar a ${name ?? 'alguien'}`, assignTask: 'Assignar tarea', header: 'Acción rápida', trackManual: 'Crear gasto', @@ -692,7 +690,10 @@ export default { pendingMatchWithCreditCardDescription: 'Recibo pendiente de adjuntar con la transacción de la tarjeta. Márcalo como efectivo para cancelar.', markAsCash: 'Marcar como efectivo', routePending: 'Ruta pendiente...', - receiptIssuesFound: (count: number) => `${count === 1 ? 'Problema encontrado' : 'Problemas encontrados'}`, + receiptIssuesFound: () => ({ + one: `Problema encontrado`, + other: () => `Problemas encontrados`, + }), fieldPending: 'Pendiente...', receiptScanning: 'Escaneando recibo...', receiptScanInProgress: 'Escaneado de recibo en proceso', @@ -705,13 +706,19 @@ export default { receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.', transactionPendingDescription: 'Transacción pendiente. Puede tardar unos días en contabilizarse.', - expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => - `${count} ${Str.pluralize('gasto', 'gastos', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${ - pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : '' - }`, - - deleteExpense: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `Eliminar ${Str.pluralize('gasto', 'gastos', count)}`, - deleteConfirmation: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `¿Estás seguro de que quieres eliminar ${Str.pluralize('esta solicitud', 'estas solicitudes', count)}?`, + expenseCount: ({scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => ({ + zero: `0 gasto${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : ''}`, + one: `1 gasto${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : ''}`, + other: (count: number) => `${count} gastos${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : ''}`, + }), + deleteExpense: () => ({ + one: `Eliminar gasto`, + other: () => `Eliminar gastos`, + }), + deleteConfirmation: () => ({ + one: `¿Estás seguro de que quieres eliminar esta solicitud?`, + other: () => `¿Estás seguro de que quieres eliminar estas solicitudes?`, + }), settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', individual: 'Individual', @@ -804,20 +811,18 @@ export default { keepAll: 'Mantener todos', confirmApprove: 'Confirmar importe a aprobar', confirmApprovalAmount: 'Aprueba sólo los gastos conformes, o aprueba todo el informe.', - confirmApprovalAllHoldAmount: ({transactionCount}: ConfirmHoldExpenseParams) => - `${Str.pluralize('Este gasto está bloqueado', 'Estos gastos están bloqueados', transactionCount)}. ¿Quieres ${Str.pluralize( - 'aprobar', - 'aprobarlos', - transactionCount, - )} de todos modos?`, + confirmApprovalAllHoldAmount: () => ({ + zero: `Ningún gasto está bloqueado. ¿Quieres aprobar todo el informe?`, + one: `Este gasto está bloqueado. ¿Quieres aprobarlo de todos modos?`, + other: () => `Estos gastos están bloqueados. ¿Quieres aprobarlos de todos modos?`, + }), confirmPay: 'Confirmar importe de pago', confirmPayAmount: 'Paga lo que no está bloqueado, o paga el informe completo.', - confirmPayAllHoldAmount: ({transactionCount}: ConfirmHoldExpenseParams) => - `${Str.pluralize('Este gasto está bloqueado', 'Estos gastos están bloqueados', transactionCount)}. ¿Quieres ${Str.pluralize( - 'pagar', - 'pagarlo', - transactionCount, - )} de todos modos?`, + confirmPayAllHoldAmount: () => ({ + zero: `Ningún gasto está bloqueado. ¿Quieres pagar todo el informe?`, + one: `Este gasto está bloqueado. ¿Quieres pagarlo de todos modos?`, + other: () => `Estos gastos están bloqueados. ¿Quieres pagarlos de todos modos?`, + }), payOnly: 'Solo pagar', approveOnly: 'Solo aprobar', hold: 'Bloquear', @@ -2074,7 +2079,10 @@ export default { testTransactions: 'Transacciones de prueba', issueAndManageCards: 'Emitir y gestionar tarjetas', reconcileCards: 'Reconciliar tarjetas', - selected: ({selectedNumber}) => `${selectedNumber} seleccionados`, + selected: () => ({ + one: `1 seleccionado`, + other: (count: number) => `${count} seleccionados`, + }), settlementFrequency: 'Frecuencia de liquidación', deleteConfirmation: '¿Estás seguro de que quieres eliminar este espacio de trabajo?', unavailable: 'Espacio de trabajo no disponible', @@ -2110,7 +2118,7 @@ export default { createNewConnection: 'Crear una nueva conexión', reuseExistingConnection: 'Reutilizar la conexión existente', existingConnections: 'Conexiones existentes', - lastSyncDate: (connectionName: string, formattedDate: string) => `${connectionName} - Última sincronización ${formattedDate}`, + lastSyncDate: ({connectionName, formattedDate}: LastSyncDateTranslationParams) => `${connectionName} - Última sincronización ${formattedDate}`, }, qbo: { importDescription: 'Elige que configuraciónes de codificación son importadas desde QuickBooks Online a Expensify.', @@ -2556,7 +2564,7 @@ export default { importJobs: 'Importar proyectos', customers: 'clientes', jobs: 'proyectos', - label: (importFields: string[], importType: string) => `${importFields.join(' y ')}, ${importType}`, + label: ({importFields, importType}: CustomersOrJobsLabelTranslationParams) => `${importFields.join(' y ')}, ${importType}`, }, importTaxDescription: 'Importar grupos de impuestos desde NetSuite.', importCustomFields: { @@ -2678,7 +2686,10 @@ export default { addAUserDefinedDimension: 'Añadir una dimensión definida por el usuario', detailedInstructionsLink: 'Ver instrucciones detalladas', detailedInstructionsRestOfSentence: ' para añadir dimensiones definidas por el usuario.', - userDimensionsAdded: (dimensionsCount: number) => `${dimensionsCount} ${Str.pluralize('UDD', `UDDs`, dimensionsCount)} añadido`, + userDimensionsAdded: () => ({ + one: `1 UDD añadido`, + other: (count: number) => `${count} UDDs añadido`, + }), mappingTitle: (mappingName: SageIntacctMappingName): string => { switch (mappingName) { case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.DEPARTMENTS: @@ -3333,9 +3344,18 @@ export default { rate: 'Tasa', addRate: 'Agregar tasa', trackTax: 'Impuesto de seguimiento', - deleteRates: ({count}: DistanceRateOperationsParams) => `Eliminar ${Str.pluralize('tasa', 'tasas', count)}`, - enableRates: ({count}: DistanceRateOperationsParams) => `Activar ${Str.pluralize('tasa', 'tasas', count)}`, - disableRates: ({count}: DistanceRateOperationsParams) => `Desactivar ${Str.pluralize('tasa', 'tasas', count)}`, + deleteRates: () => ({ + one: `Eliminar 1 tasa`, + other: (count: number) => `Eliminar ${count} tasas`, + }), + enableRates: () => ({ + one: `Activar 1 tasa`, + other: (count: number) => `Activar ${count} tasas`, + }), + disableRates: () => ({ + one: `Desactivar 1 tasa`, + other: (count: number) => `Desactivar ${count} tasas`, + }), enableRate: 'Activar tasa', status: 'Estado', unit: 'Unidad', @@ -3343,7 +3363,10 @@ export default { changePromptMessage: ' para hacer ese cambio.', defaultCategory: 'Categoría predeterminada', deleteDistanceRate: 'Eliminar tasa de distancia', - areYouSureDelete: ({count}: DistanceRateOperationsParams) => `¿Estás seguro de que quieres eliminar ${Str.pluralize('esta tasa', 'estas tasas', count)}?`, + areYouSureDelete: () => ({ + one: `¿Estás seguro de que quieres eliminar 1 tasa?`, + other: (count: number) => `¿Estás seguro de que quieres eliminar ${count} tasas?`, + }), }, editor: { nameInputLabel: 'Nombre', @@ -3432,7 +3455,7 @@ export default { exportAgainModal: { title: '¡Cuidado!', - description: (reportName: string, connectionName: ConnectionName) => + description: ({reportName, connectionName}: ExportAgainModalDescriptionTranslationParams) => `Los siguientes informes ya se han exportado a ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]}:\n\n${reportName}\n\n¿Estás seguro de que deseas exportarlos de nuevo?`, confirmText: 'Sí, exportar de nuevo', cancelText: 'Cancelar', @@ -3587,7 +3610,7 @@ export default { deleteConfirmation: '¿Estás seguro de que quieres eliminar esta tarea?', }, statementPage: { - title: (year, monthName) => `Estado de cuenta de ${monthName} ${year}`, + title: ({year, monthName}: StatementPageTitleTranslationParams) => `Estado de cuenta de ${monthName} ${year}`, generatingPDF: 'Estamos generando tu PDF ahora mismo. ¡Por favor, vuelve más tarde!', }, keyboardShortcutsPage: { @@ -3746,7 +3769,7 @@ export default { pending: ({label}: ExportedToIntegrationParams) => `comenzó a exportar este informe a ${label}...`, }, forwarded: ({amount, currency}: ForwardedParams) => `aprobado ${currency}${amount}`, - integrationsMessage: (errorMessage: string, label: string) => `no se pudo exportar este informe a ${label} ("${errorMessage}").`, + integrationsMessage: ({errorMessage, label}: ReportIntegrationMessageTranslationParams) => `no se pudo exportar este informe a ${label} ("${errorMessage}").`, managerAttachReceipt: `agregó un recibo`, managerDetachReceipt: `quitó un recibo`, markedReimbursed: ({amount, currency}: MarkedReimbursedParams) => `pagó ${currency}${amount} en otro lugar`, diff --git a/src/languages/types.ts b/src/languages/types.ts index 24117f257d8f..62c96beb323e 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -1,5 +1,5 @@ import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; -import type {Unit} from '@src/types/onyx/Policy'; +import type {ConnectionName, Unit} from '@src/types/onyx/Policy'; import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; import type en from './en'; @@ -96,7 +96,6 @@ type ReportArchiveReasonsPolicyDeletedParams = { }; type RequestCountParams = { - count: number; scanningReceipts: number; pendingReceipts: number; }; @@ -251,9 +250,21 @@ type PaySomeoneParams = {name?: string}; type TaskCreatedActionParams = {title: string}; +type PluralFormPhase = { + zero?: string; + one: string; + two?: string; + few?: (count: number) => string; + many?: (count: number) => string; + other: (count: number) => string; +}; + /* Translation Object types */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -type TranslationBaseValue = string | string[] | ((...args: any[]) => string); +type TranslationPluralPhaseValue = (arg?: any) => PluralFormPhase; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type TranslationSingularFunctionValue = (arg?: any) => string; +type TranslationBaseValue = string | TranslationPluralPhaseValue | TranslationSingularFunctionValue; type TranslationBase = {[key: string]: TranslationBaseValue | TranslationBase}; @@ -261,7 +272,7 @@ type TranslationBase = {[key: string]: TranslationBaseValue | TranslationBase}; // Flattens an object and returns concatenations of all the keys of nested objects type FlattenObject = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - [TKey in keyof TObject]: TObject[TKey] extends (...args: any[]) => any + [TKey in keyof TObject]: TObject[TKey] extends (arg?: any) => any ? `${TPrefix}${TKey & string}` : // eslint-disable-next-line @typescript-eslint/no-explicit-any TObject[TKey] extends any[] @@ -289,6 +300,11 @@ type TranslationFlatObject = { [TKey in TranslationPaths]: TranslateType; }; +type PluralTranslationFlatObject = Pick< + TranslationFlatObject, + {[K in keyof TranslationFlatObject]: TranslationFlatObject[K] extends TranslationPluralPhaseValue ? K : never}[keyof TranslationFlatObject] +>; + type TermsParams = {amount: string}; type ElectronicFundsParams = {percentage: string; amount: string}; @@ -297,12 +313,8 @@ type LogSizeParams = {size: number}; type HeldRequestParams = {comment: string}; -type DistanceRateOperationsParams = {count: number}; - type ReimbursementRateParams = {unit: Unit}; -type ConfirmHoldExpenseParams = {transactionCount: number}; - type ChangeFieldParams = {oldValue?: string; newValue: string; fieldName: string}; type ChangePolicyParams = {fromPolicy: string; toPolicy: string}; @@ -344,8 +356,29 @@ type RemoveMembersWarningPrompt = { ownerName: string; }; -type DeleteExpenseTranslationParams = { - count: number; +type LastSyncDateTranslationParams = { + connectionName: string; + formattedDate: string; +}; + +type CustomersOrJobsLabelTranslationParams = { + importFields: string[]; + importType: string; +}; + +type ExportAgainModalDescriptionTranslationParams = { + reportName: string; + connectionName: ConnectionName; +}; + +type StatementPageTitleTranslationParams = { + year: string | number; + monthName: string; +}; + +type ReportIntegrationMessageTranslationParams = { + errorMessage: string; + label: string; }; export type { @@ -359,14 +392,12 @@ export type { BeginningOfChatHistoryDomainRoomPartOneParams, CanceledRequestParams, CharacterLimitParams, - ConfirmHoldExpenseParams, ConfirmThatParams, DateShouldBeAfterParams, DateShouldBeBeforeParams, DeleteActionParams, DeleteConfirmationParams, DidSplitAmountMessageParams, - DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, EnglishTranslation, @@ -423,9 +454,11 @@ export type { ThreadSentMoneyReportNameParams, ToValidateLoginParams, TransferParams, + PluralFormPhase, TranslationBase, TranslationFlatObject, TranslationPaths, + PluralTranslationFlatObject, UntilTimeParams, UpdatedTheDistanceParams, UpdatedTheRequestParams, @@ -468,5 +501,9 @@ export type { StripePaidParams, UnapprovedParams, RemoveMembersWarningPrompt, - DeleteExpenseTranslationParams, + LastSyncDateTranslationParams, + CustomersOrJobsLabelTranslationParams, + ExportAgainModalDescriptionTranslationParams, + StatementPageTitleTranslationParams, + ReportIntegrationMessageTranslationParams, }; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index c9eef3170245..f1b705398067 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -6,7 +6,7 @@ import type {MessageElementBase, MessageTextElement} from '@libs/MessageElement' import Config from '@src/CONFIG'; import CONST from '@src/CONST'; import translations from '@src/languages/translations'; -import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; +import type {PluralFormPhase, TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Locale} from '@src/types/onyx'; import LocaleListener from './LocaleListener'; @@ -45,8 +45,8 @@ function init() { }, {}); } -type PhraseParameters = T extends (...args: infer A) => string ? A : never[]; -type Phrase = TranslationFlatObject[TKey] extends (...args: infer A) => unknown ? (...args: A) => string : string; +type PhraseParameters = T extends (arg: infer A) => string ? [A] : T extends (arg: infer A) => PluralFormPhase ? [A, number] : never[]; +type Phrase = TranslationFlatObject[TKey]; /** * Map to store translated values for each locale. @@ -70,6 +70,23 @@ const translationCache = new Map, Map, Map]>), ); +function handlePluralForm(translatedPhrase: PluralFormPhase, pluralForm: Intl.LDMLPluralRule, count: number) { + switch (pluralForm) { + case 'zero': + return translatedPhrase.zero; + case 'one': + return translatedPhrase.one; + case 'two': + return translatedPhrase.two; + case 'few': + return translatedPhrase.few?.(count); + case 'many': + return translatedPhrase.many?.(count); + default: + return translatedPhrase.other(count); + } +} + /** * Helper function to get the translated string for given * locale and phrase. This function is used to avoid @@ -106,11 +123,21 @@ function getTranslatedPhrase( return valueFromCache; } - const translatedPhrase = translations?.[language]?.[phraseKey] as Phrase; + const translatedPhrase = translations?.[language]?.[phraseKey]; if (translatedPhrase) { if (typeof translatedPhrase === 'function') { - return translatedPhrase(...phraseParameters); + const calledTranslatedPhrase = translatedPhrase(phraseParameters[0]); + if (typeof calledTranslatedPhrase === 'string') { + return calledTranslatedPhrase; + } + const count = phraseParameters[1] ?? 0; + const pluralForm = new Intl.PluralRules(language, {type: 'ordinal'}).select(count); + if (pluralForm in calledTranslatedPhrase) { + return handlePluralForm(calledTranslatedPhrase, pluralForm, count) ?? ''; + } + Log.alert(`Plural form ${pluralForm} is not found for ${phraseKey}, using 'other' form`); + return calledTranslatedPhrase.other(count); } // We set the translated value in the cache only for the phrases without parameters. diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index fe53a7bcd5ce..c3410fbd6516 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -649,7 +649,10 @@ function getCustomersOrJobsLabelNetSuite(policy: Policy | undefined, translate: importFields.push(translate('workspace.netsuite.import.customersOrJobs.jobs')); } - const importedValueLabel = translate(`workspace.netsuite.import.customersOrJobs.label`, importFields, translate(`workspace.accounting.importTypes.${importedValue}`).toLowerCase()); + const importedValueLabel = translate(`workspace.netsuite.import.customersOrJobs.label`, { + importFields, + importType: translate(`workspace.accounting.importTypes.${importedValue}`).toLowerCase(), + }); return importedValueLabel.charAt(0).toUpperCase() + importedValueLabel.slice(1); } diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 73784873201a..f4683abf6100 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1248,7 +1248,7 @@ function getMessageOfOldDotReportAction(oldDotAction: PartialReportAction | OldD case CONST.REPORT.ACTIONS.TYPE.INTEGRATIONS_MESSAGE: { const {result, label} = originalMessage; const errorMessage = result?.messages?.join(', ') ?? ''; - return Localize.translateLocal('report.actions.type.integrationsMessage', errorMessage, label); + return Localize.translateLocal('report.actions.type.integrationsMessage', {errorMessage, label}); } case CONST.REPORT.ACTIONS.TYPE.MANAGER_ATTACH_RECEIPT: return Localize.translateLocal('report.actions.type.managerAttachReceipt'); diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 4e82900372b9..5e8c311a79e4 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -763,7 +763,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD shouldEnableNewFocusManagement /> setIsDeleteModalVisible(false)} @@ -779,7 +779,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD ReportUtils.navigateBackAfterDeleteTransaction(navigateBackToAfterDelete.current, true); } }} - prompt={caseID === CASES.DEFAULT ? translate('task.deleteConfirmation') : translate('iou.deleteConfirmation')} + prompt={caseID === CASES.DEFAULT ? translate('task.deleteConfirmation') : translate('iou.deleteConfirmation', undefined, 1)} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} danger diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index 15dd063ec6ee..300122db9be5 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -268,7 +268,7 @@ function ReportParticipantsPage({report, personalDetails, session}: ReportPartic shouldAlwaysShowDropdownMenu pressOnEnter - customText={translate('workspace.common.selected', {selectedNumber: selectedMembers.length})} + customText={translate('workspace.common.selected', undefined, selectedMembers.length)} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} onPress={() => null} options={bulkActionsButtonOptions} diff --git a/src/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx index 1cc34d6bf53a..039c54dc609a 100644 --- a/src/pages/Search/SearchSelectedNarrow.tsx +++ b/src/pages/Search/SearchSelectedNarrow.tsx @@ -50,7 +50,7 @@ function SearchSelectedNarrow({options, itemsLength}: SearchSelectedNarrowProps) onPress={openMenu} ref={buttonRef} style={[styles.w100, styles.ph5]} - text={translate('workspace.common.selected', {selectedNumber: itemsLength})} + text={translate('workspace.common.selected', undefined, itemsLength)} isContentCentered iconRight={Expensicons.DownArrow} isDisabled={options.length === 0} diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 6ff163f6ec37..403b7864102f 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -195,13 +195,6 @@ function ReportActionCompose({ const userBlockedFromConcierge = useMemo(() => User.isBlockedFromConcierge(blockedFromConcierge), [blockedFromConcierge]); const isBlockedFromConcierge = useMemo(() => includesConcierge && userBlockedFromConcierge, [includesConcierge, userBlockedFromConcierge]); - // If we are on a small width device then don't show last 3 items from conciergePlaceholderOptions - const conciergePlaceholderRandomIndex = useMemo( - () => Math.floor(Math.random() * (translate('reportActionCompose.conciergePlaceholderOptions').length - (isSmallScreenWidth ? 4 : 1) + 1)), - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - [], - ); - // Placeholder to display in the chat input. const inputPlaceholder = useMemo(() => { if (includesConcierge) { @@ -209,11 +202,12 @@ function ReportActionCompose({ return translate('reportActionCompose.blockedFromConcierge'); } - return translate('reportActionCompose.conciergePlaceholderOptions')[conciergePlaceholderRandomIndex]; + return translate('reportActionCompose.conciergePlaceholderOptions', isSmallScreenWidth); } return translate('reportActionCompose.writeSomething'); - }, [includesConcierge, translate, userBlockedFromConcierge, conciergePlaceholderRandomIndex]); + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps + }, [includesConcierge, translate, userBlockedFromConcierge]); const focus = () => { if (composerRef.current === null) { diff --git a/src/pages/home/report/ReportDetailsExportPage.tsx b/src/pages/home/report/ReportDetailsExportPage.tsx index 99b7305cc7a9..d6861bd54dd0 100644 --- a/src/pages/home/report/ReportDetailsExportPage.tsx +++ b/src/pages/home/report/ReportDetailsExportPage.tsx @@ -120,7 +120,7 @@ function ReportDetailsExportPage({route}: ReportDetailsExportPageProps) { title={translate('workspace.exportAgainModal.title')} onConfirm={confirmExport} onCancel={() => setModalStatus(null)} - prompt={translate('workspace.exportAgainModal.description', report?.reportName ?? '', connectionName)} + prompt={translate('workspace.exportAgainModal.description', {reportName: report?.reportName ?? '', connectionName})} confirmText={translate('workspace.exportAgainModal.confirmText')} cancelText={translate('workspace.exportAgainModal.cancelText')} isVisible={!!modalStatus} diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index cde3750fc37f..ab18013d6b23 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -212,7 +212,7 @@ function FloatingActionButtonAndPopover( } if (quickAction?.action === CONST.QUICK_ACTIONS.SEND_MONEY && quickActionAvatars.length > 0) { const name: string = ReportUtils.getDisplayNameForParticipant(+(quickActionAvatars[0]?.id ?? -1), true) ?? ''; - return translate('quickAction.paySomeone', name); + return translate('quickAction.paySomeone', {name}); } const titleKey = getQuickActionTitle(quickAction?.action ?? ('' as QuickActionName)); return titleKey ? translate(titleKey) : ''; diff --git a/src/pages/wallet/WalletStatementPage.tsx b/src/pages/wallet/WalletStatementPage.tsx index 0db72877bd2e..54b601a00350 100644 --- a/src/pages/wallet/WalletStatementPage.tsx +++ b/src/pages/wallet/WalletStatementPage.tsx @@ -70,7 +70,7 @@ function WalletStatementPage({walletStatement, route}: WalletStatementPageProps) const year = yearMonth?.substring(0, 4) || getYear(new Date()); const month = yearMonth?.substring(4) || getMonth(new Date()); const monthName = format(new Date(Number(year), Number(month) - 1), CONST.DATE.MONTH_FORMAT); - const title = translate('statementPage.title', year, monthName); + const title = translate('statementPage.title', {year, monthName}); const url = `${CONFIG.EXPENSIFY.EXPENSIFY_URL}statement.php?period=${yearMonth}${themePreference === CONST.THEME.DARK ? '&isDarkMode=true' : ''}`; return ( diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index adbf5a664c82..8b2ca4e09ca1 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -503,7 +503,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, shouldAlwaysShowDropdownMenu pressOnEnter - customText={translate('workspace.common.selected', {selectedNumber: selectedEmployees.length})} + customText={translate('workspace.common.selected', undefined, selectedEmployees.length)} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} onPress={() => null} options={getBulkActionsButtonOptions()} diff --git a/src/pages/workspace/accounting/intacct/ExistingConnectionsPage.tsx b/src/pages/workspace/accounting/intacct/ExistingConnectionsPage.tsx index c55e46083470..3475d726ba84 100644 --- a/src/pages/workspace/accounting/intacct/ExistingConnectionsPage.tsx +++ b/src/pages/workspace/accounting/intacct/ExistingConnectionsPage.tsx @@ -30,7 +30,9 @@ function ExistingConnectionsPage({route}: ExistingConnectionsPageProps) { key: policy.id, icon: policy.avatarURL ? policy.avatarURL : ReportUtils.getDefaultWorkspaceAvatar(policy.name), iconType: policy.avatarURL ? CONST.ICON_TYPE_AVATAR : CONST.ICON_TYPE_WORKSPACE, - description: date ? translate('workspace.common.lastSyncDate', CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.intacct, date) : translate('workspace.accounting.intacct'), + description: date + ? translate('workspace.common.lastSyncDate', {connectionName: CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.intacct, formattedDate: date}) + : translate('workspace.accounting.intacct'), onPress: () => { copyExistingPolicyConnection(policy.id, policyID, CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT); Navigation.dismissModal(); diff --git a/src/pages/workspace/accounting/intacct/import/SageIntacctImportPage.tsx b/src/pages/workspace/accounting/intacct/import/SageIntacctImportPage.tsx index 543d3f4d0154..958e35c5b572 100644 --- a/src/pages/workspace/accounting/intacct/import/SageIntacctImportPage.tsx +++ b/src/pages/workspace/accounting/intacct/import/SageIntacctImportPage.tsx @@ -128,7 +128,7 @@ function SageIntacctImportPage({policy}: WithPolicyProps) { 0 - ? translate('workspace.intacct.userDimensionsAdded', sageIntacctConfig?.mappings?.dimensions?.length) + ? translate('workspace.intacct.userDimensionsAdded', undefined, sageIntacctConfig?.mappings?.dimensions?.length) : undefined } description={translate('workspace.intacct.userDefinedDimensions')} diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteExistingConnectionsPage.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteExistingConnectionsPage.tsx index ef98625a64e2..5fdca30600c2 100644 --- a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteExistingConnectionsPage.tsx +++ b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteExistingConnectionsPage.tsx @@ -30,7 +30,9 @@ function NetSuiteExistingConnectionsPage({route}: ExistingConnectionsPageProps) key: policy.id, icon: policy.avatarURL ? policy.avatarURL : ReportUtils.getDefaultWorkspaceAvatar(policy.name), iconType: policy.avatarURL ? CONST.ICON_TYPE_AVATAR : CONST.ICON_TYPE_WORKSPACE, - description: date ? translate('workspace.common.lastSyncDate', CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.netsuite, date) : translate('workspace.accounting.netsuite'), + description: date + ? translate('workspace.common.lastSyncDate', {connectionName: CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.netsuite, formattedDate: date}) + : translate('workspace.accounting.netsuite'), onPress: () => { copyExistingPolicyConnection(policy.id, policyID, CONST.POLICY.CONNECTIONS.NAME.NETSUITE); Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING.getRoute(policyID)); diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 174251a80d5f..12f76fe15abb 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -224,7 +224,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { shouldAlwaysShowDropdownMenu pressOnEnter buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} - customText={translate('workspace.common.selected', {selectedNumber: selectedCategoriesArray.length})} + customText={translate('workspace.common.selected', undefined, selectedCategoriesArray.length)} options={options} isSplitButton={false} style={[shouldUseNarrowLayout && styles.flexGrow1, shouldUseNarrowLayout && styles.mb3]} diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx index 00204d1e40c7..85e602228149 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx @@ -194,7 +194,7 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail isVisible={isDeleteModalVisible} onConfirm={deleteRate} onCancel={() => setIsDeleteModalVisible(false)} - prompt={translate('workspace.distanceRates.areYouSureDelete', {count: 1})} + prompt={translate('workspace.distanceRates.areYouSureDelete', undefined, 1)} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} danger diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 1ef1319973a1..4749c3483571 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -200,7 +200,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const getBulkActionsButtonOptions = () => { const options: Array> = [ { - text: translate('workspace.distanceRates.deleteRates', {count: selectedDistanceRates.length}), + text: translate('workspace.distanceRates.deleteRates', undefined, selectedDistanceRates.length), value: CONST.POLICY.BULK_ACTION_TYPES.DELETE, icon: Expensicons.Trashcan, onSelected: () => (canDisableOrDeleteSelectedRates ? setIsDeleteModalVisible(true) : setIsWarningModalVisible(true)), @@ -210,7 +210,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const enabledRates = selectedDistanceRates.filter((rate) => rate.enabled); if (enabledRates.length > 0) { options.push({ - text: translate('workspace.distanceRates.disableRates', {count: enabledRates.length}), + text: translate('workspace.distanceRates.disableRates', undefined, enabledRates.length), value: CONST.POLICY.BULK_ACTION_TYPES.DISABLE, icon: Expensicons.DocumentSlash, onSelected: () => (canDisableOrDeleteSelectedRates ? disableRates() : setIsWarningModalVisible(true)), @@ -220,7 +220,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const disabledRates = selectedDistanceRates.filter((rate) => !rate.enabled); if (disabledRates.length > 0) { options.push({ - text: translate('workspace.distanceRates.enableRates', {count: disabledRates.length}), + text: translate('workspace.distanceRates.enableRates', undefined, disabledRates.length), value: CONST.POLICY.BULK_ACTION_TYPES.ENABLE, icon: Expensicons.Document, onSelected: enableRates, @@ -257,7 +257,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) shouldAlwaysShowDropdownMenu pressOnEnter - customText={translate('workspace.common.selected', {selectedNumber: selectedDistanceRates.length})} + customText={translate('workspace.common.selected', undefined, selectedDistanceRates.length)} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} onPress={() => null} options={getBulkActionsButtonOptions()} @@ -332,7 +332,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) isVisible={isDeleteModalVisible} onConfirm={deleteRates} onCancel={() => setIsDeleteModalVisible(false)} - prompt={translate('workspace.distanceRates.areYouSureDelete', {count: selectedDistanceRates.length})} + prompt={translate('workspace.distanceRates.areYouSureDelete', undefined, selectedDistanceRates.length)} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} danger diff --git a/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx b/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx index adbe33397950..0d5a6e617aec 100644 --- a/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx +++ b/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx @@ -249,7 +249,7 @@ function ReportFieldsListValuesPage({ shouldAlwaysShowDropdownMenu pressOnEnter buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} - customText={translate('workspace.common.selected', {selectedNumber: selectedValuesArray.length})} + customText={translate('workspace.common.selected', undefined, selectedValuesArray.length)} options={options} isSplitButton={false} style={[isSmallScreenWidth && styles.flexGrow1, isSmallScreenWidth && styles.mb3]} diff --git a/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx b/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx index f4e3b01145da..4b231f1f2c5f 100644 --- a/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx +++ b/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx @@ -164,7 +164,7 @@ function WorkspaceReportFieldsPage({ shouldAlwaysShowDropdownMenu pressOnEnter buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} - customText={translate('workspace.common.selected', {selectedNumber: selectedReportFields.length})} + customText={translate('workspace.common.selected', undefined, selectedReportFields.length)} options={options} isSplitButton={false} style={[shouldUseNarrowLayout && styles.flexGrow1, shouldUseNarrowLayout && styles.mb3]} diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index cf9952720fc9..dc0dab1634b0 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -276,7 +276,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { pressOnEnter isSplitButton={false} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} - customText={translate('workspace.common.selected', {selectedNumber: selectedTagsArray.length})} + customText={translate('workspace.common.selected', undefined, selectedTagsArray.length)} options={options} style={[isSmallScreenWidth && styles.flexGrow1, isSmallScreenWidth && styles.mb3]} /> diff --git a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx index 9ac1fc7583ae..02a9ba0fe0c2 100644 --- a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx @@ -202,7 +202,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { pressOnEnter isSplitButton={false} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} - customText={translate('workspace.common.selected', {selectedNumber: selectedTagsArray.length})} + customText={translate('workspace.common.selected', undefined, selectedTagsArray.length)} options={options} style={[isSmallScreenWidth && styles.flexGrow1, isSmallScreenWidth && styles.mb3]} /> diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index edd2ef4e3a65..f40b2fc054b9 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -235,7 +235,7 @@ function WorkspaceTaxesPage({ onPress={() => {}} options={dropdownMenuOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} - customText={translate('workspace.common.selected', {selectedNumber: selectedTaxesIDs.length})} + customText={translate('workspace.common.selected', undefined, selectedTaxesIDs.length)} shouldAlwaysShowDropdownMenu pressOnEnter isSplitButton={false} From e7df141dd5b4a843ddd128da78d06085a67c28be Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 22 Jul 2024 16:54:25 +0530 Subject: [PATCH 002/114] Fix lint --- src/components/AddressForm.tsx | 2 +- src/components/ArchivedReportFooter.tsx | 37 +++++++++++++++++++------ tests/unit/TranslateTest.ts | 11 ++------ 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index 7ca4cc3273ca..d6ba5813a101 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -126,7 +126,7 @@ function AddressForm({ } } } else if (!CONST.GENERIC_ZIP_CODE_REGEX.test(values?.zipPostCode?.trim()?.toUpperCase() ?? '')) { - errors.zipPostCode = translate('privatePersonalDetails.error.incorrectZipFormat'); + errors.zipPostCode = translate('privatePersonalDetails.error.incorrectZipFormat', undefined); } return errors; diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 35f5aeecb5a4..7e0121a3ae4f 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -53,14 +53,35 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}} policyName = lodashEscape(policyName); } - const text = shouldRenderHTML - ? translate(`reportArchiveReasons.${archiveReason}`, { - displayName: `${displayName}`, - oldDisplayName: `${oldDisplayName}`, - policyName: `${policyName}`, - shouldUseYou: actorPersonalDetails?.accountID === getCurrentUserAccountID(), - }) - : translate(`reportArchiveReasons.${archiveReason}`); + let text; + switch (archiveReason) { + case CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED: + text = translate(`reportArchiveReasons.${archiveReason}`, { + displayName: `${displayName}`, + }); + break; + case CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED: + text = translate(`reportArchiveReasons.${archiveReason}`, { + displayName: `${displayName}`, + oldDisplayName: `${oldDisplayName}`, + }); + break; + case CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY: + text = translate(`reportArchiveReasons.${archiveReason}`, { + displayName: `${displayName}`, + policyName: `${policyName}`, + shouldUseYou: actorPersonalDetails?.accountID === getCurrentUserAccountID(), + }); + break; + case CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED: + text = translate(`reportArchiveReasons.${archiveReason}`, { + policyName: `${policyName}`, + }); + break; + default: + text = translate(`reportArchiveReasons.${archiveReason}`); + break; + } return ( { expect(Localize.translate(CONST.LOCALES.ES_ES, 'testKey4' as TranslationPaths)).toBe('testKey4'); asMutable(CONFIG).IS_IN_PRODUCTION = ORIGINAL_IS_IN_PRODUCTION; }); - - it('Test when translation value is a function', () => { - const expectedValue = 'With variable Test Variable'; - const testVariable = 'Test Variable'; - // @ts-expect-error - TranslationPaths doesn't include testKeyGroup.testFunction as a valid key - expect(Localize.translate(CONST.LOCALES.EN, 'testKeyGroup.testFunction' as TranslationPaths, {testVariable})).toBe(expectedValue); - }); }); describe('Translation Keys', () => { @@ -126,7 +119,7 @@ describe('flattenObject', () => { none: 'No description', }, content: func, - messages: ['Hello', 'Hi', 'Sup!'], + messages: 'Hello!', }, }, }; @@ -141,7 +134,7 @@ describe('flattenObject', () => { 'complex.report.title.task': 'Task', 'complex.report.description.none': 'No description', 'complex.report.content': func, - 'complex.report.messages': ['Hello', 'Hi', 'Sup!'], + 'complex.report.messages': 'Hello!', }); }); }); From dbc34e6ee66d91bd0c9835a8099315d41827ca18 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:10:43 +0530 Subject: [PATCH 003/114] Apply suggestions from code review Co-authored-by: Jayesh Mangwani <35371050+jayeshmangwani@users.noreply.github.com> --- src/languages/en.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 5e3b9241aae9..80e37460ac05 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3302,16 +3302,16 @@ export default { addRate: 'Add rate', trackTax: 'Track tax', deleteRates: () => ({ - one: `Delete 1 rate`, - other: (count: number) => `Delete ${count} rates`, + one: 'Delete rate', + other: () => 'Delete rates', }), enableRates: () => ({ - one: `Enable 1 rate`, - other: (count: number) => `Enable ${count} rates`, + one: 'Enable rate', + other: () => 'Enable rates', }), disableRates: () => ({ - one: `Disable 1 rate`, - other: (count: number) => `Disable ${count} rates`, + one: 'Disable rate', + other: () => 'Disable rates', }), enableRate: 'Enable rate', status: 'Status', @@ -3321,8 +3321,8 @@ export default { defaultCategory: 'Default category', deleteDistanceRate: 'Delete distance rate', areYouSureDelete: () => ({ - one: `Are you sure you want to delete 1 rate?`, - other: (count: number) => `Are you sure you want to delete ${count} rates?`, + one: 'Are you sure you want to delete this rate?', + other: () => 'Are you sure you want to delete these rates?', }), }, editor: { From 7f91f9be8ce90c02b2980225267ed120f45cce7c Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:14:16 +0530 Subject: [PATCH 004/114] Apply suggestions from code review --- src/languages/es.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 1ce4ac35586b..0ae384b5b075 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3345,16 +3345,16 @@ export default { addRate: 'Agregar tasa', trackTax: 'Impuesto de seguimiento', deleteRates: () => ({ - one: `Eliminar 1 tasa`, - other: (count: number) => `Eliminar ${count} tasas`, + one: 'Eliminar tasa', + other: () => 'Eliminar tasas', }), enableRates: () => ({ - one: `Activar 1 tasa`, - other: (count: number) => `Activar ${count} tasas`, + one: 'Activar 1 tasa', + other: () => 'Activar tasas', }), disableRates: () => ({ - one: `Desactivar 1 tasa`, - other: (count: number) => `Desactivar ${count} tasas`, + one: 'Desactivar tasa', + other: () => 'Desactivar tasas', }), enableRate: 'Activar tasa', status: 'Estado', @@ -3364,8 +3364,8 @@ export default { defaultCategory: 'Categoría predeterminada', deleteDistanceRate: 'Eliminar tasa de distancia', areYouSureDelete: () => ({ - one: `¿Estás seguro de que quieres eliminar 1 tasa?`, - other: (count: number) => `¿Estás seguro de que quieres eliminar ${count} tasas?`, + one: '¿Estás seguro de que quieres eliminar esta tasa?', + other: () => '¿Estás seguro de que quieres eliminar estas tasas?', }), }, editor: { From c8dd8d1e696031b4c438c7ac607776c62f3a19ad Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Mon, 29 Jul 2024 08:19:44 +0530 Subject: [PATCH 005/114] Apply suggestions from code review --- .../home/report/ReportActionCompose/ReportActionCompose.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 77e8f366d866..275fe000fe79 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -202,7 +202,7 @@ function ReportActionCompose({ return translate('reportActionCompose.blockedFromConcierge'); } - return translate('reportActionCompose.conciergePlaceholderOptions', isSmallScreenWidth); + return translate('reportActionCompose.conciergePlaceholderOptions', shouldUseNarrowLayout); } return translate('reportActionCompose.writeSomething'); From 3a15ab360099f7427680f2ca6327a84338eea4f2 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 30 Jul 2024 18:11:49 +0530 Subject: [PATCH 006/114] Fixed unused type --- src/languages/types.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/languages/types.ts b/src/languages/types.ts index 62c96beb323e..611deac1e854 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -300,11 +300,6 @@ type TranslationFlatObject = { [TKey in TranslationPaths]: TranslateType; }; -type PluralTranslationFlatObject = Pick< - TranslationFlatObject, - {[K in keyof TranslationFlatObject]: TranslationFlatObject[K] extends TranslationPluralPhaseValue ? K : never}[keyof TranslationFlatObject] ->; - type TermsParams = {amount: string}; type ElectronicFundsParams = {percentage: string; amount: string}; @@ -458,7 +453,6 @@ export type { TranslationBase, TranslationFlatObject, TranslationPaths, - PluralTranslationFlatObject, UntilTimeParams, UpdatedTheDistanceParams, UpdatedTheRequestParams, From fd0b5e7175b1be67111891cc67130ffd3e536419 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 30 Jul 2024 18:27:46 +0530 Subject: [PATCH 007/114] Fixed lint --- src/pages/Search/SearchFiltersDatePage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Search/SearchFiltersDatePage.tsx b/src/pages/Search/SearchFiltersDatePage.tsx index 9ac9973c8ca2..ae11e972dc20 100644 --- a/src/pages/Search/SearchFiltersDatePage.tsx +++ b/src/pages/Search/SearchFiltersDatePage.tsx @@ -47,7 +47,7 @@ function SearchFiltersDatePage() { Date: Tue, 30 Jul 2024 19:19:32 +0530 Subject: [PATCH 008/114] Fixed lint --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index e87a5bb71e3d..f1961dd7b449 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -35,8 +35,8 @@ import type { GoBackMessageParams, GoToRoomParams, InstantSummaryParams, - LastSyncDateTranslationParams, IssueVirtualCardParams, + LastSyncDateTranslationParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, diff --git a/src/languages/es.ts b/src/languages/es.ts index 488cbbca626a..f44850067036 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -33,8 +33,8 @@ import type { GoBackMessageParams, GoToRoomParams, InstantSummaryParams, - LastSyncDateTranslationParams, IssueVirtualCardParams, + LastSyncDateTranslationParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, From 5eed7b5482abfeef2dc723ec9797c04944ce688d Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 1 Aug 2024 18:00:10 +0530 Subject: [PATCH 009/114] Apply suggestions --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index bc91fa1ab428..ec03c97d8948 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -823,7 +823,6 @@ export default { confirmApprove: 'Confirm approval amount', confirmApprovalAmount: 'Approve only compliant expenses, or approve the entire report.', confirmApprovalAllHoldAmount: () => ({ - zero: `This expense is on hold. Do you want to approve anyway?`, one: `This expense is on hold. Do you want to approve anyway?`, other: () => `These expenses are on hold. Do you want to approve anyway?`, }), diff --git a/src/languages/es.ts b/src/languages/es.ts index 3133d6446db2..4a0b72ad237b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -816,7 +816,6 @@ export default { confirmApprove: 'Confirmar importe a aprobar', confirmApprovalAmount: 'Aprueba sólo los gastos conformes, o aprueba todo el informe.', confirmApprovalAllHoldAmount: () => ({ - zero: `Ningún gasto está bloqueado. ¿Quieres aprobar todo el informe?`, one: `Este gasto está bloqueado. ¿Quieres aprobarlo de todos modos?`, other: () => `Estos gastos están bloqueados. ¿Quieres aprobarlos de todos modos?`, }), From 426d50e80e2809e4b6e0b42a643574bf0e51eb53 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 1 Aug 2024 18:01:53 +0530 Subject: [PATCH 010/114] Apply suggestions --- src/components/ArchivedReportFooter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 7e0121a3ae4f..eeee6c68b73f 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -53,7 +53,7 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}} policyName = lodashEscape(policyName); } - let text; + let text: string; switch (archiveReason) { case CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED: text = translate(`reportArchiveReasons.${archiveReason}`, { From 158b4c9bd0a56333bf6134d5772468447fd37fe0 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 5 Aug 2024 22:45:35 +0530 Subject: [PATCH 011/114] Polyfill plural rules for hermes --- src/languages/es.ts | 2 +- src/libs/IntlPolyfill/index.android.ts | 4 ++++ src/libs/IntlPolyfill/index.ios.ts | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 27d4b40380ed..a2d51f58cd89 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3388,7 +3388,7 @@ export default { other: () => 'Eliminar tasas', }), enableRates: () => ({ - one: 'Activar 1 tasa', + one: 'Activar tasa', other: () => 'Activar tasas', }), disableRates: () => ({ diff --git a/src/libs/IntlPolyfill/index.android.ts b/src/libs/IntlPolyfill/index.android.ts index 7a21ae26bfa4..0f852457e3db 100644 --- a/src/libs/IntlPolyfill/index.android.ts +++ b/src/libs/IntlPolyfill/index.android.ts @@ -11,6 +11,10 @@ const intlPolyfill: IntlPolyfill = () => { require('@formatjs/intl-locale/polyfill'); + require('@formatjs/intl-pluralrules/polyfill'); + require('@formatjs/intl-pluralrules/locale-data/en'); + require('@formatjs/intl-pluralrules/locale-data/es'); + polyfillListFormat(); }; diff --git a/src/libs/IntlPolyfill/index.ios.ts b/src/libs/IntlPolyfill/index.ios.ts index 569b666eb434..0ee189b4d329 100644 --- a/src/libs/IntlPolyfill/index.ios.ts +++ b/src/libs/IntlPolyfill/index.ios.ts @@ -16,6 +16,8 @@ const intlPolyfill: IntlPolyfill = () => { // Required to polyfill NumberFormat on iOS // see: https://github.com/facebook/hermes/issues/1172#issuecomment-1776156538 require('@formatjs/intl-pluralrules/polyfill'); + require('@formatjs/intl-pluralrules/locale-data/en'); + require('@formatjs/intl-pluralrules/locale-data/es'); polyfillNumberFormat(); // Required to polyfill DateTimeFormat on iOS From 2c7a93778e27c7e6faa9b5357abbfc87801a8528 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 5 Aug 2024 22:51:47 +0530 Subject: [PATCH 012/114] Adjusting position of code --- src/libs/IntlPolyfill/index.ios.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/IntlPolyfill/index.ios.ts b/src/libs/IntlPolyfill/index.ios.ts index 0ee189b4d329..3a41790aa8b6 100644 --- a/src/libs/IntlPolyfill/index.ios.ts +++ b/src/libs/IntlPolyfill/index.ios.ts @@ -13,11 +13,12 @@ const intlPolyfill: IntlPolyfill = () => { require('@formatjs/intl-locale/polyfill'); - // Required to polyfill NumberFormat on iOS - // see: https://github.com/facebook/hermes/issues/1172#issuecomment-1776156538 require('@formatjs/intl-pluralrules/polyfill'); require('@formatjs/intl-pluralrules/locale-data/en'); require('@formatjs/intl-pluralrules/locale-data/es'); + + // Required to polyfill NumberFormat on iOS + // see: https://github.com/facebook/hermes/issues/1172#issuecomment-1776156538 polyfillNumberFormat(); // Required to polyfill DateTimeFormat on iOS From 2428dfac03e32a62f735c6d829072764e361d4e1 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 8 Aug 2024 16:54:27 +0530 Subject: [PATCH 013/114] Applying suggestion --- src/components/AddressForm.tsx | 2 +- src/libs/Localize/index.ts | 2 +- src/pages/Search/SearchFiltersDatePage.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index d6ba5813a101..7ca4cc3273ca 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -126,7 +126,7 @@ function AddressForm({ } } } else if (!CONST.GENERIC_ZIP_CODE_REGEX.test(values?.zipPostCode?.trim()?.toUpperCase() ?? '')) { - errors.zipPostCode = translate('privatePersonalDetails.error.incorrectZipFormat', undefined); + errors.zipPostCode = translate('privatePersonalDetails.error.incorrectZipFormat'); } return errors; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index f1b705398067..70db13c32851 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -45,7 +45,7 @@ function init() { }, {}); } -type PhraseParameters = T extends (arg: infer A) => string ? [A] : T extends (arg: infer A) => PluralFormPhase ? [A, number] : never[]; +type PhraseParameters = T extends (arg?: infer A) => string ? [A?] : T extends (arg: infer A) => string ? [A] : T extends (arg: infer A) => PluralFormPhase ? [A, number] : never[]; type Phrase = TranslationFlatObject[TKey]; /** diff --git a/src/pages/Search/SearchFiltersDatePage.tsx b/src/pages/Search/SearchFiltersDatePage.tsx index e2a465f6fa9e..4bc95aa21351 100644 --- a/src/pages/Search/SearchFiltersDatePage.tsx +++ b/src/pages/Search/SearchFiltersDatePage.tsx @@ -53,7 +53,7 @@ function SearchFiltersDatePage() { Date: Thu, 8 Aug 2024 21:12:35 +0530 Subject: [PATCH 014/114] Adjusting code --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 5bcdfdc1cb14..bccd8bdfba18 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -731,7 +731,6 @@ export default { invalidDomainError: 'You have entered an invalid domain. To continue, please enter a valid domain.', publicDomainError: 'You have entered a public domain. To continue, please enter a private domain.', expenseCount: ({scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => ({ - zero: `0 expenses${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pending` : ''}`, one: `1 expense${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pending` : ''}`, other: (count: number) => `${count} expenses${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pending` : ''}`, }), diff --git a/src/languages/es.ts b/src/languages/es.ts index bfab43641f85..61cd8baba543 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -724,7 +724,6 @@ export default { invalidDomainError: 'Ha introducido un dominio no válido. Para continuar, introduzca un dominio válido.', publicDomainError: 'Ha introducido un dominio público. Para continuar, introduzca un dominio privado.', expenseCount: ({scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => ({ - zero: `0 gasto${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : ''}`, one: `1 gasto${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : ''}`, other: (count: number) => `${count} gastos${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : ''}`, }), From b38931602767fe3bcb2d222957343aefb02886a6 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:10:34 +0530 Subject: [PATCH 015/114] Apply suggestions from code review --- src/libs/IntlPolyfill/index.android.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/IntlPolyfill/index.android.ts b/src/libs/IntlPolyfill/index.android.ts index 84761382b3fd..e6ab02d15c25 100644 --- a/src/libs/IntlPolyfill/index.android.ts +++ b/src/libs/IntlPolyfill/index.android.ts @@ -11,7 +11,7 @@ const intlPolyfill: IntlPolyfill = () => { require('@formatjs/intl-locale/polyfill-force'); - require('@formatjs/intl-pluralrules/polyfill'); + require('@formatjs/intl-pluralrules/polyfill-force'); require('@formatjs/intl-pluralrules/locale-data/en'); require('@formatjs/intl-pluralrules/locale-data/es'); From 6dfb0cd941be2203e0a1b6fc1ae6e04c6439b95b Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:16:54 +0530 Subject: [PATCH 016/114] Apply suggestions from code review --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 495bec63a5d7..71a26d93c623 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -866,7 +866,6 @@ export default { confirmPay: 'Confirm payment amount', confirmPayAmount: "Pay what's not on hold, or pay the entire report.", confirmPayAllHoldAmount: () => ({ - zero: `This expense is on hold. Do you want to pay anyway?`, one: `This expense is on hold. Do you want to pay anyway?`, other: () => `These expenses are on hold. Do you want to pay anyway?`, }), diff --git a/src/languages/es.ts b/src/languages/es.ts index e1187603afd8..92a23c0afb49 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -859,7 +859,6 @@ export default { confirmPay: 'Confirmar importe de pago', confirmPayAmount: 'Paga lo que no está bloqueado, o paga el informe completo.', confirmPayAllHoldAmount: () => ({ - zero: `Ningún gasto está bloqueado. ¿Quieres pagar todo el informe?`, one: `Este gasto está bloqueado. ¿Quieres pagarlo de todos modos?`, other: () => `Estos gastos están bloqueados. ¿Quieres pagarlos de todos modos?`, }), From 895108439ea8f158373287d987b978fa37e5a4ca Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Fri, 23 Aug 2024 08:41:56 +0530 Subject: [PATCH 017/114] Apply suggestions from code review --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 16571e7e35e8..4a6b86830587 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -754,7 +754,7 @@ export default { invalidDomainError: 'You have entered an invalid domain. To continue, please enter a valid domain.', publicDomainError: 'You have entered a public domain. To continue, please enter a private domain.', expenseCount: ({scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => { - const statusText = []; + const statusText: string[] = []; if (scanningReceipts > 0) { statusText.push(`${scanningReceipts} scanning`); } diff --git a/src/languages/es.ts b/src/languages/es.ts index e670a003648a..5f753962ff34 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -745,7 +745,7 @@ export default { invalidDomainError: 'Ha introducido un dominio no válido. Para continuar, introduzca un dominio válido.', publicDomainError: 'Ha introducido un dominio público. Para continuar, introduzca un dominio privado.', expenseCount: ({scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => { - const statusText = []; + const statusText: string[] = []; if (scanningReceipts > 0) { statusText.push(`${scanningReceipts} escaneando`); } From 29124ea861c38f311a46da8b8f79b19ecafefedd Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 23 Aug 2024 09:22:10 +0530 Subject: [PATCH 018/114] Post merge fixes --- src/languages/en.ts | 17 ++++++++++------- src/languages/es.ts | 17 ++++++++++------- src/languages/types.ts | 19 +++++++++++++++++++ src/libs/ReportActionsUtils.ts | 6 +++--- src/pages/Search/AdvancedSearchFilters.tsx | 5 ++++- .../report/ContextMenu/ContextMenuActions.tsx | 2 +- src/pages/home/report/ReportActionItem.tsx | 2 +- 7 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 4a6b86830587..6bd72a3790f7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -69,12 +69,14 @@ import type { ReportArchiveReasonsPolicyDeletedParams, ReportArchiveReasonsRemovedFromPolicyParams, ReportIntegrationMessageTranslationParams, + ReportMemberRoleParams, RequestAmountParams, RequestCountParams, RequestedAmountMessageParams, ResolutionConstraintsParams, RoomNameReservedErrorParams, RoomRenamedToParams, + SearchFilterAmountBetweenParams, SetTheDistanceParams, SetTheRequestParams, SettledAfterAddedBankAccountParams, @@ -98,6 +100,7 @@ import type { UntilTimeParams, UpdatedTheDistanceParams, UpdatedTheRequestParams, + UpdateReportMemberRoleParams, UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, @@ -763,8 +766,8 @@ export default { } return { one: statusText.length > 0 ? `1 expense (${statusText.join(', ')})` : `1 expense`, - other: (count: number) => statusText.length > 0 ? `${count} expenses (${statusText.join(', ')})` : `${count} expenses`, - } + other: (count: number) => (statusText.length > 0 ? `${count} expenses (${statusText.join(', ')})` : `${count} expenses`), + }; }, deleteExpense: () => ({ one: `Delete expense`, @@ -3786,7 +3789,7 @@ export default { amount: { lessThan: (amount?: string) => `Less than ${amount ?? ''}`, greaterThan: (amount?: string) => `Greater than ${amount ?? ''}`, - between: (greaterThan: string, lessThan: string) => `Between ${greaterThan} and ${lessThan}`, + between: ({greaterThan, lessThan}: SearchFilterAmountBetweenParams) => `Between ${greaterThan} and ${lessThan}`, }, }, expenseType: 'Expense type', @@ -3921,10 +3924,10 @@ export default { stripePaid: ({amount, currency}: StripePaidParams) => `paid ${currency}${amount}`, takeControl: `took control`, unapproved: ({amount, currency}: UnapprovedParams) => `unapproved ${currency}${amount}`, - integrationSyncFailed: (label: string, errorMessage: string) => `failed to sync with ${label} ("${errorMessage}")`, - addEmployee: (email: string, role: string) => `added ${email} as ${role === 'user' ? 'member' : 'admin'}`, - updateRole: (email: string, currentRole: string, newRole: string) => `updated the role of ${email} from ${currentRole} to ${newRole}`, - removeMember: (email: string, role: string) => `removed ${role} ${email}`, + integrationSyncFailed: ({label, errorMessage}: ReportIntegrationMessageTranslationParams) => `failed to sync with ${label} ("${errorMessage}")`, + addEmployee: ({email, role}: ReportMemberRoleParams) => `added ${email} as ${role === 'user' ? 'member' : 'admin'}`, + updateRole: ({email, currentRole, newRole}: UpdateReportMemberRoleParams) => `updated the role of ${email} from ${currentRole} to ${newRole}`, + removeMember: ({email, role}: ReportMemberRoleParams) => `removed ${role} ${email}`, }, }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 5f753962ff34..a12fd53009c0 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -67,12 +67,14 @@ import type { ReportArchiveReasonsPolicyDeletedParams, ReportArchiveReasonsRemovedFromPolicyParams, ReportIntegrationMessageTranslationParams, + ReportMemberRoleParams, RequestAmountParams, RequestCountParams, RequestedAmountMessageParams, ResolutionConstraintsParams, RoomNameReservedErrorParams, RoomRenamedToParams, + SearchFilterAmountBetweenParams, SetTheDistanceParams, SetTheRequestParams, SettledAfterAddedBankAccountParams, @@ -95,6 +97,7 @@ import type { UntilTimeParams, UpdatedTheDistanceParams, UpdatedTheRequestParams, + UpdateReportMemberRoleParams, UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, @@ -754,8 +757,8 @@ export default { } return { one: statusText.length > 0 ? `1 gasto (${statusText.join(', ')})` : `1 gasto`, - other: (count: number) => statusText.length > 0 ? `${count} gastos (${statusText.join(', ')})` : `${count} gastos`, - } + other: (count: number) => (statusText.length > 0 ? `${count} gastos (${statusText.join(', ')})` : `${count} gastos`), + }; }, deleteExpense: () => ({ one: `Eliminar gasto`, @@ -3826,7 +3829,7 @@ export default { amount: { lessThan: (amount?: string) => `Menos de ${amount ?? ''}`, greaterThan: (amount?: string) => `Más que ${amount ?? ''}`, - between: (greaterThan: string, lessThan: string) => `Entre ${greaterThan} y ${lessThan}`, + between: ({greaterThan, lessThan}: SearchFilterAmountBetweenParams) => `Entre ${greaterThan} y ${lessThan}`, }, }, expenseType: 'Tipo de gasto', @@ -3962,11 +3965,11 @@ export default { stripePaid: ({amount, currency}: StripePaidParams) => `pagado ${currency}${amount}`, takeControl: `tomó el control`, unapproved: ({amount, currency}: UnapprovedParams) => `no aprobado ${currency}${amount}`, - integrationSyncFailed: (label: string, errorMessage: string) => `no se pudo sincronizar con ${label} ("${errorMessage}")`, - addEmployee: (email: string, role: string) => `agregó a ${email} como ${role === 'user' ? 'miembro' : 'administrador'}`, - updateRole: (email: string, currentRole: string, newRole: string) => + integrationSyncFailed: ({label, errorMessage}: ReportIntegrationMessageTranslationParams) => `no se pudo sincronizar con ${label} ("${errorMessage}")`, + addEmployee: ({email, role}: ReportMemberRoleParams) => `agregó a ${email} como ${role === 'user' ? 'miembro' : 'administrador'}`, + updateRole: ({email, currentRole, newRole}: UpdateReportMemberRoleParams) => `actualicé el rol ${email} de ${currentRole === 'user' ? 'miembro' : 'administrador'} a ${newRole === 'user' ? 'miembro' : 'administrador'}`, - removeMember: (email: string, role: string) => `eliminado ${role === 'user' ? 'miembro' : 'administrador'} ${email}`, + removeMember: ({email, role}: ReportMemberRoleParams) => `eliminado ${role === 'user' ? 'miembro' : 'administrador'} ${email}`, }, }, }, diff --git a/src/languages/types.ts b/src/languages/types.ts index 444a97d26249..b95122979df8 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -386,6 +386,22 @@ type ApprovalWorkflowErrorParams = { name2: string; }; +type SearchFilterAmountBetweenParams = { + greaterThan: string; + lessThan: string; +}; + +type ReportMemberRoleParams = { + email: string; + role: string; +}; + +type UpdateReportMemberRoleParams = { + email: string; + currentRole: string; + newRole: string; +}; + export type { AddressLineParams, AdminCanceledRequestParams, @@ -512,4 +528,7 @@ export type { StatementPageTitleTranslationParams, ReportIntegrationMessageTranslationParams, ApprovalWorkflowErrorParams, + SearchFilterAmountBetweenParams, + ReportMemberRoleParams, + UpdateReportMemberRoleParams, }; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 5fdfec3fd96b..008a5a4be225 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1613,7 +1613,7 @@ function getPolicyChangeLogAddEmployeeMessage(reportAction: OnyxInputOrEntry): reportAction is ReportAction { @@ -1628,7 +1628,7 @@ function getPolicyChangeLogChangeRoleMessage(reportAction: OnyxInputOrEntry>) { diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 5143a2d70008..d0929e33db52 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -69,7 +69,10 @@ function getFilterDisplayTitle(filters: Partial, fiel if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT) { const {lessThan, greaterThan} = filters; if (lessThan && greaterThan) { - return translate('search.filters.amount.between', convertToDisplayStringWithoutCurrency(Number(greaterThan)), convertToDisplayStringWithoutCurrency(Number(lessThan))); + return translate('search.filters.amount.between', { + greaterThan: convertToDisplayStringWithoutCurrency(Number(greaterThan)), + lessThan: convertToDisplayStringWithoutCurrency(Number(lessThan)), + }); } if (lessThan) { return translate('search.filters.amount.lessThan', convertToDisplayStringWithoutCurrency(Number(lessThan))); diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index db7e482a0457..4174557c2e05 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -472,7 +472,7 @@ const ContextMenuActions: ContextMenuAction[] = [ setClipboardMessage(ReportActionsUtils.getPolicyChangeLogDeleteMemberMessage(reportAction)); } else if (ReportActionsUtils.isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.INTEGRATION_SYNC_FAILED)) { const {label, errorMessage} = ReportActionsUtils.getOriginalMessage(reportAction) ?? {label: '', errorMessage: ''}; - setClipboardMessage(Localize.translateLocal('report.actions.type.integrationSyncFailed', label, errorMessage)); + setClipboardMessage(Localize.translateLocal('report.actions.type.integrationSyncFailed', {label, errorMessage})); } else if (content) { setClipboardMessage( content.replace(/()(.*?)(<\/mention-user>)/gi, (match, openTag: string, innerContent: string, closeTag: string): string => { diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index e4dab8518eb2..012fb84b5bc5 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -675,7 +675,7 @@ function ReportActionItem({ children = ; } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.INTEGRATION_SYNC_FAILED)) { const {label, errorMessage} = ReportActionsUtils.getOriginalMessage(action) ?? {label: '', errorMessage: ''}; - children = ; + children = ; } else { const hasBeenFlagged = ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === moderationDecision) && From c337d6f6d0557ff68f7ceefbb83f2c1d3e931285 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 2 Sep 2024 17:07:23 +0200 Subject: [PATCH 019/114] remove focusing durty --- .../ReportActionCompose.tsx | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 72e89c8de013..f6cf812fd908 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -233,22 +233,8 @@ function ReportActionCompose({ return translate('reportActionCompose.writeSomething'); }, [includesConcierge, translate, userBlockedFromConcierge, conciergePlaceholderRandomIndex]); - const focus = () => { - if (composerRef.current === null) { - return; - } - composerRef.current?.focus(true); - }; - const isKeyboardVisibleWhenShowingModalRef = useRef(false); const isNextModalWillOpenRef = useRef(false); - const restoreKeyboardState = useCallback(() => { - if (!isKeyboardVisibleWhenShowingModalRef.current || isNextModalWillOpenRef.current) { - return; - } - focus(); - isKeyboardVisibleWhenShowingModalRef.current = false; - }, []); const containerRef = useRef(null); const measureContainer = useCallback( @@ -298,8 +284,7 @@ function ReportActionCompose({ const onAttachmentPreviewClose = useCallback(() => { updateShouldShowSuggestionMenuToFalse(); setIsAttachmentPreviewActive(false); - restoreKeyboardState(); - }, [updateShouldShowSuggestionMenuToFalse, restoreKeyboardState]); + }, [updateShouldShowSuggestionMenuToFalse]); /** * Add a new comment to this chat @@ -490,11 +475,18 @@ function ReportActionCompose({ isMenuVisible={isMenuVisible} onTriggerAttachmentPicker={onTriggerAttachmentPicker} raiseIsScrollLikelyLayoutTriggered={raiseIsScrollLikelyLayoutTriggered} + // TODO: Remove onCanceledAttachmentPicker={() => { - isNextModalWillOpenRef.current = false; - restoreKeyboardState(); + // isNextModalWillOpenRef.current = false; + isNextModalWillOpenRef.current = true; + console.debug('[TEST] onCanceledAttachmentPicker'); + // restoreKeyboardState(); + }} + // TODO: Remove + onMenuClosed={() => { + console.debug('[TEST] onMenuClosed'); + // restoreKeyboardState(); }} - onMenuClosed={restoreKeyboardState} onAddActionPressed={onAddActionPressed} onItemSelected={onItemSelected} actionButtonRef={actionButtonRef} @@ -559,11 +551,13 @@ function ReportActionCompose({ {DeviceCapabilities.canUseTouchScreen() && isMediumScreenWidth ? null : ( { if (isNavigating) { return; } - focus(); + console.debug('[TEST] ReportActionCompose focus 3'); + // focus(); }} onEmojiSelected={(...args) => composerRef.current?.replaceSelectionWithText(...args)} emojiPickerID={report?.reportID} From 6ff977ca0c160baf37ec9181852a0ce063fff4b1 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 3 Sep 2024 13:53:53 +0200 Subject: [PATCH 020/114] clear redundant code --- .../EmojiPicker/EmojiPickerButton.tsx | 4 ++-- .../AttachmentPickerWithMenuItems.tsx | 6 +++--- .../ReportActionCompose.tsx | 20 ------------------- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index 6e0944e5a913..a702a554cee3 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -24,12 +24,12 @@ type EmojiPickerButtonProps = { /** Emoji popup anchor offset shift vertical */ shiftVertical?: number; - onModalHide: EmojiPickerAction.OnModalHideValue; + onModalHide?: EmojiPickerAction.OnModalHideValue; onEmojiSelected: EmojiPickerAction.OnEmojiSelected; }; -function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shiftVertical = 0, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { +function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shiftVertical = 0, onModalHide = () => {}, onEmojiSelected}: EmojiPickerButtonProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index 6e3c3a48de74..ea23759edbf2 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -70,10 +70,10 @@ type AttachmentPickerWithMenuItemsProps = AttachmentPickerWithMenuItemsOnyxProps onTriggerAttachmentPicker: () => void; /** Called when cancelling the attachment picker */ - onCanceledAttachmentPicker: () => void; + onCanceledAttachmentPicker?: () => void; /** Called when the menu with the items is closed after it was open */ - onMenuClosed: () => void; + onMenuClosed?: () => void; /** Called when the add action button is pressed */ onAddActionPressed: () => void; @@ -187,7 +187,7 @@ function AttachmentPickerWithMenuItems({ const onPopoverMenuClose = () => { setMenuVisibility(false); - onMenuClosed(); + onMenuClosed?.(); }; const prevIsFocused = usePrevious(isFocused); diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index f6cf812fd908..2632fdf4f719 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -475,18 +475,6 @@ function ReportActionCompose({ isMenuVisible={isMenuVisible} onTriggerAttachmentPicker={onTriggerAttachmentPicker} raiseIsScrollLikelyLayoutTriggered={raiseIsScrollLikelyLayoutTriggered} - // TODO: Remove - onCanceledAttachmentPicker={() => { - // isNextModalWillOpenRef.current = false; - isNextModalWillOpenRef.current = true; - console.debug('[TEST] onCanceledAttachmentPicker'); - // restoreKeyboardState(); - }} - // TODO: Remove - onMenuClosed={() => { - console.debug('[TEST] onMenuClosed'); - // restoreKeyboardState(); - }} onAddActionPressed={onAddActionPressed} onItemSelected={onItemSelected} actionButtonRef={actionButtonRef} @@ -551,14 +539,6 @@ function ReportActionCompose({ {DeviceCapabilities.canUseTouchScreen() && isMediumScreenWidth ? null : ( { - if (isNavigating) { - return; - } - console.debug('[TEST] ReportActionCompose focus 3'); - // focus(); - }} onEmojiSelected={(...args) => composerRef.current?.replaceSelectionWithText(...args)} emojiPickerID={report?.reportID} shiftVertical={emojiShiftVertical} From 29bd0658236b5c6fcd28857ac965c78cb0fd558f Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 3 Sep 2024 14:41:50 +0200 Subject: [PATCH 021/114] do not focus on web --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 085aa2f7db9a..bbb56eee0a75 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -661,9 +661,7 @@ function ComposerWithSuggestions( if (editFocused) { InputFocus.inputFocusChange(false); - return; } - focus(true); }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal?.isVisible, isNextModalWillOpenRef, shouldAutoFocus]); useEffect(() => { From 4722de5a087b48f28ebc8c49ed9ab4ffe6f265b0 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 3 Sep 2024 19:24:27 +0530 Subject: [PATCH 022/114] Post merge fixes --- src/languages/en.ts | 6 ++++-- src/languages/es.ts | 6 ++++-- src/pages/workspace/rules/IndividualExpenseRulesSection.tsx | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index b5bb2e89e29b..7ee11c73ca1e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -20,7 +20,6 @@ import type { ChangeTypeParams, CharacterLimitParams, CompanyCardFeedNameParams, - ConfirmHoldExpenseParams, ConfirmThatParams, CustomersOrJobsLabelTranslationParams, DateShouldBeAfterParams, @@ -3705,7 +3704,10 @@ export default { maxAge: 'Max age', maxExpenseAge: 'Max expense age', maxExpenseAgeDescription: 'Flag spend older than a specific number of days.', - maxExpenseAgeDays: (age: number) => `${age} ${Str.pluralize('day', 'days', age)}`, + maxExpenseAgeDays: () => ({ + one: `1 day`, + other: (count: number) => `${count} days`, + }), billableDefault: 'Billable default', billableDefaultDescription: 'Choose whether cash and credit card expenses should be billable by default. Billable expenses are enabled or disabled in', billable: 'Billable', diff --git a/src/languages/es.ts b/src/languages/es.ts index 84e106476ed0..154f6bf56d58 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -17,7 +17,6 @@ import type { ChangeTypeParams, CharacterLimitParams, CompanyCardFeedNameParams, - ConfirmHoldExpenseParams, ConfirmThatParams, CustomersOrJobsLabelTranslationParams, DateShouldBeAfterParams, @@ -3747,7 +3746,10 @@ export default { maxAge: 'Antigüedad máxima', maxExpenseAge: 'Antigüedad máxima de los gastos', maxExpenseAgeDescription: 'Marca los gastos de más de un número determinado de días.', - maxExpenseAgeDays: (age: number) => `${age} ${Str.pluralize('día', 'días', age)}`, + maxExpenseAgeDays: () => ({ + one: `1 día`, + other: (count: number) => `${count} días`, + }), billableDefault: 'Valor predeterminado facturable', billableDefaultDescription: 'Elige si los gastos en efectivo y con tarjeta de crédito deben ser facturables por defecto. Los gastos facturables se activan o desactivan en', billable: 'Facturable', diff --git a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx index 78dbcb3b16c8..a8a7f0652a4d 100644 --- a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx +++ b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx @@ -108,7 +108,7 @@ function IndividualExpenseRulesSection({policyID}: IndividualExpenseRulesSection return ''; } - return translate('workspace.rules.individualExpenseRules.maxExpenseAgeDays', policy?.maxExpenseAge); + return translate('workspace.rules.individualExpenseRules.maxExpenseAgeDays', undefined, policy?.maxExpenseAge); }, [policy?.maxExpenseAge, translate]); const billableModeText = translate(`workspace.rules.individualExpenseRules.${policy?.defaultBillable ? 'billable' : 'nonBillable'}`); From 6cb68e38ae3475f66049c1e429abd4f1cd67a27e Mon Sep 17 00:00:00 2001 From: Roji Philip Date: Thu, 5 Sep 2024 22:25:41 +0530 Subject: [PATCH 023/114] create announce room when 3 or more participants --- .../CreateWorkspaceFromIOUPaymentParams.ts | 2 - .../API/parameters/CreateWorkspaceParams.ts | 2 - src/libs/PolicyUtils.ts | 9 +- src/libs/ReportUtils.ts | 110 ++++++++++++++--- src/libs/actions/Policy/Member.ts | 9 +- src/libs/actions/Policy/Policy.ts | 113 ------------------ .../workspace/WorkspaceInviteMessagePage.tsx | 3 +- src/pages/workspace/WorkspacesListPage.tsx | 2 +- 8 files changed, 111 insertions(+), 139 deletions(-) diff --git a/src/libs/API/parameters/CreateWorkspaceFromIOUPaymentParams.ts b/src/libs/API/parameters/CreateWorkspaceFromIOUPaymentParams.ts index 761a6c2f5008..a1256f5ad051 100644 --- a/src/libs/API/parameters/CreateWorkspaceFromIOUPaymentParams.ts +++ b/src/libs/API/parameters/CreateWorkspaceFromIOUPaymentParams.ts @@ -1,13 +1,11 @@ type CreateWorkspaceFromIOUPaymentParams = { policyID: string; - announceChatReportID: string; adminsChatReportID: string; expenseChatReportID: string; ownerEmail: string; makeMeAdmin: boolean; policyName: string; type: string; - announceCreatedReportActionID: string; adminsCreatedReportActionID: string; expenseCreatedReportActionID: string; customUnitID: string; diff --git a/src/libs/API/parameters/CreateWorkspaceParams.ts b/src/libs/API/parameters/CreateWorkspaceParams.ts index c86598b48953..18ef4a0e763f 100644 --- a/src/libs/API/parameters/CreateWorkspaceParams.ts +++ b/src/libs/API/parameters/CreateWorkspaceParams.ts @@ -1,13 +1,11 @@ type CreateWorkspaceParams = { policyID: string; - announceChatReportID: string; adminsChatReportID: string; expenseChatReportID: string; ownerEmail: string; makeMeAdmin: boolean; policyName: string; type: string; - announceCreatedReportActionID: string; adminsCreatedReportActionID: string; expenseCreatedReportActionID: string; customUnitID: string; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 07ce5b1539df..961a8bce41d3 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -218,7 +218,7 @@ const isPolicyOwner = (policy: OnyxInputOrEntry, currentUserAccountID: n * * If includeMemberWithErrors is false, We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. */ -function getMemberAccountIDsForWorkspace(employeeList: PolicyEmployeeList | undefined, includeMemberWithErrors = false): MemberEmailsToAccountIDs { +function getMemberAccountIDsForWorkspace(employeeList: PolicyEmployeeList | undefined, includeMemberWithErrors = false, includeMemberWithPendingDelete = true): MemberEmailsToAccountIDs { const members = employeeList ?? {}; const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; Object.keys(members).forEach((email) => { @@ -228,6 +228,13 @@ function getMemberAccountIDsForWorkspace(employeeList: PolicyEmployeeList | unde return; } } + if (!includeMemberWithPendingDelete) { + const member = members?.[email]; + if (member.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { + console.log('RETURNING FOR [' + email + ']'); + return; + } + } const personalDetail = getPersonalDetailByEmail(email); if (!personalDetail?.login) { return; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7b226b2e5c8e..dac51f3d0dd0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -323,11 +323,13 @@ type OptimisticTaskReportAction = Pick< | 'linkMetadata' >; +type AnnounceRoomOnyxData = { + onyxOptimisticData: OnyxUpdate[]; + onyxSuccessData: OnyxUpdate[]; + onyxFailureData: OnyxUpdate[]; +}; + type OptimisticWorkspaceChats = { - announceChatReportID: string; - announceChatData: OptimisticChatReport; - announceReportActionData: Record; - announceCreatedReportActionID: string; adminsChatReportID: string; adminsChatData: OptimisticChatReport; adminsReportActionData: Record; @@ -5472,26 +5474,107 @@ function buildOptimisticDismissedViolationReportAction( }; } -function buildOptimisticWorkspaceChats(policyID: string, policyName: string, expenseReportId?: string): OptimisticWorkspaceChats { +function buildOptimisticAnnounceChat(policyID: string, accountIDs: number[]): AnnounceRoomOnyxData { + const announceReport = getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID); + const policy = getPolicy(policyID); + const announceRoomOnyxData: AnnounceRoomOnyxData = { + onyxOptimisticData: [], + onyxSuccessData: [], + onyxFailureData: [], + }; + + // Do not create #announce room if the room already exists or if there are less than 3 participants in workspace + if (announceReport || accountIDs.length < 3) { + return announceRoomOnyxData; + } + const announceChatData = buildOptimisticChatReport( - currentUserAccountID ? [currentUserAccountID] : [], + accountIDs, CONST.REPORT.WORKSPACE_CHAT_ROOMS.ANNOUNCE, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID, CONST.POLICY.OWNER_ACCOUNT_ID_FAKE, false, - policyName, + policy?.name, undefined, undefined, // #announce contains all policy members so notifying always should be opt-in only. CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, ); - const announceChatReportID = announceChatData.reportID; const announceCreatedAction = buildOptimisticCreatedReportAction(CONST.POLICY.OWNER_EMAIL_FAKE); - const announceReportActionData = { - [announceCreatedAction.reportActionID]: announceCreatedAction, - }; + announceRoomOnyxData.onyxOptimisticData.push( + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatData.reportID}`, + value: { + pendingFields: { + addWorkspaceRoom: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + ...announceChatData, + }, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_DRAFT}${announceChatData.reportID}`, + value: null, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatData.reportID}`, + value: { + [announceCreatedAction.reportActionID]: announceCreatedAction, + }, + }, + ); + announceRoomOnyxData.onyxSuccessData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatData.reportID}`, + value: { + pendingFields: { + addWorkspaceRoom: null, + }, + pendingAction: null, + isOptimisticReport: false, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatData.reportID}`, + value: { + [announceCreatedAction.reportActionID]: { + pendingAction: null, + }, + }, + }, + ); + announceRoomOnyxData.onyxFailureData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatData.reportID}`, + value: { + pendingFields: { + addWorkspaceRoom: null, + }, + pendingAction: null, + isOptimisticReport: false, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatData.reportID}`, + value: { + [announceCreatedAction.reportActionID]: { + pendingAction: null, + }, + }, + }, + ); + return announceRoomOnyxData; +} + +function buildOptimisticWorkspaceChats(policyID: string, policyName: string, expenseReportId?: string): OptimisticWorkspaceChats { const pendingChatMembers = getPendingChatMembers(currentUserAccountID ? [currentUserAccountID] : [], [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); const adminsChatData = { ...buildOptimisticChatReport( @@ -5535,10 +5618,6 @@ function buildOptimisticWorkspaceChats(policyID: string, policyName: string, exp }; return { - announceChatReportID, - announceChatData, - announceReportActionData, - announceCreatedReportActionID: announceCreatedAction.reportActionID, adminsChatReportID, adminsChatData, adminsReportActionData, @@ -7812,6 +7891,7 @@ export { buildOptimisticTaskReport, buildOptimisticTaskReportAction, buildOptimisticUnHoldReportAction, + buildOptimisticAnnounceChat, buildOptimisticWorkspaceChats, buildParticipantsFromAccountIDs, buildTransactionThread, diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts index 1b881f65c831..1f33c769dca1 100644 --- a/src/libs/actions/Policy/Member.ts +++ b/src/libs/actions/Policy/Member.ts @@ -566,7 +566,7 @@ function clearWorkspaceOwnerChangeFlow(policyID: string) { * Adds members to the specified workspace/policyID * Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details */ -function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs, welcomeNote: string, policyID: string) { +function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs, welcomeNote: string, policyID: string, policyMemberAccountIDs: number[]) { const policyKey = `${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const; const logins = Object.keys(invitedEmailsToAccountIDs).map((memberLogin) => PhoneNumber.addSMSDomainIfPhoneNumber(memberLogin)); const accountIDs = Object.values(invitedEmailsToAccountIDs); @@ -575,6 +575,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const newPersonalDetailsOnyxData = PersonalDetailsUtils.getPersonalDetailsOnyxDataForOptimisticUsers(newLogins, newAccountIDs); const announceRoomMembers = buildAnnounceRoomMembersOnyxData(policyID, accountIDs); + const announceRoomChat = ReportUtils.buildOptimisticAnnounceChat(policyID, [...policyMemberAccountIDs, ...accountIDs]); // create onyx data for policy expense chats for each new member const membersChats = createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs); @@ -601,7 +602,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount }, }, ]; - optimisticData.push(...newPersonalDetailsOnyxData.optimisticData, ...membersChats.onyxOptimisticData, ...announceRoomMembers.onyxOptimisticData); + optimisticData.push(...newPersonalDetailsOnyxData.optimisticData, ...membersChats.onyxOptimisticData, ...announceRoomChat.onyxOptimisticData, ...announceRoomMembers.onyxOptimisticData); const successData: OnyxUpdate[] = [ { @@ -612,7 +613,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount }, }, ]; - successData.push(...newPersonalDetailsOnyxData.finallyData, ...membersChats.onyxSuccessData, ...announceRoomMembers.onyxSuccessData); + successData.push(...newPersonalDetailsOnyxData.finallyData, ...membersChats.onyxSuccessData, ...announceRoomChat.onyxSuccessData, ...announceRoomMembers.onyxSuccessData); const failureData: OnyxUpdate[] = [ { @@ -626,7 +627,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount }, }, ]; - failureData.push(...membersChats.onyxFailureData, ...announceRoomMembers.onyxFailureData); + failureData.push(...membersChats.onyxFailureData, ...announceRoomChat.onyxFailureData, ...announceRoomMembers.onyxFailureData); const params: AddMembersToWorkspaceParams = { employees: JSON.stringify(logins.map((login) => ({email: login}))), diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 81d440a89a49..21f183a2d75d 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1574,10 +1574,6 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticCustomUnits(); const { - announceChatReportID, - announceChatData, - announceReportActionData, - announceCreatedReportActionID, adminsChatReportID, adminsChatData, adminsReportActionData, @@ -1635,26 +1631,6 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName }, }, }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - }, - ...announceChatData, - }, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_DRAFT}${announceChatReportID}`, - value: null, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, - value: announceReportActionData, - }, { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, @@ -1714,26 +1690,6 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName }, }, }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: null, - }, - pendingAction: null, - isOptimisticReport: false, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, - value: { - [announceCreatedReportActionID]: { - pendingAction: null, - }, - }, - }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, @@ -1783,16 +1739,6 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: {employeeList: null}, }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, - value: null, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, - value: null, - }, { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, @@ -1829,14 +1775,12 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName const params: CreateWorkspaceParams = { policyID, - announceChatReportID, adminsChatReportID, expenseChatReportID, ownerEmail: policyOwnerEmail, makeMeAdmin, policyName: workspaceName, type: CONST.POLICY.TYPE.TEAM, - announceCreatedReportActionID, adminsCreatedReportActionID, expenseCreatedReportActionID, customUnitID, @@ -2208,10 +2152,6 @@ function createWorkspaceFromIOUPayment(iouReport: OnyxEntry): WorkspaceF const iouReportID = iouReport.reportID; const { - announceChatReportID, - announceChatData, - announceReportActionData, - announceCreatedReportActionID, adminsChatReportID, adminsChatData, adminsReportActionData, @@ -2278,21 +2218,6 @@ function createWorkspaceFromIOUPayment(iouReport: OnyxEntry): WorkspaceF key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: newWorkspace, }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - }, - ...announceChatData, - }, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, - value: announceReportActionData, - }, { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, @@ -2349,25 +2274,6 @@ function createWorkspaceFromIOUPayment(iouReport: OnyxEntry): WorkspaceF }, }, }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: null, - }, - pendingAction: null, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, - value: { - [Object.keys(announceChatData)[0]]: { - pendingAction: null, - }, - }, - }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, @@ -2410,23 +2316,6 @@ function createWorkspaceFromIOUPayment(iouReport: OnyxEntry): WorkspaceF successData.push(...employeeWorkspaceChat.onyxSuccessData); const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: null, - }, - pendingAction: null, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, - value: { - pendingAction: null, - }, - }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, @@ -2626,14 +2515,12 @@ function createWorkspaceFromIOUPayment(iouReport: OnyxEntry): WorkspaceF const params: CreateWorkspaceFromIOUPaymentParams = { policyID, - announceChatReportID, adminsChatReportID, expenseChatReportID: workspaceChatReportID, ownerEmail: '', makeMeAdmin: false, policyName: workspaceName, type: CONST.POLICY.TYPE.TEAM, - announceCreatedReportActionID, adminsCreatedReportActionID, expenseCreatedReportActionID: workspaceChatCreatedReportActionID, customUnitID, diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index df34875f5fa6..39c4f6d4b18e 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -107,8 +107,9 @@ function WorkspaceInviteMessagePage({ const sendInvitation = () => { Keyboard.dismiss(); + const policyMemberAccountIDs = Object.values(PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, false, false)); // Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details - Member.addMembersToWorkspace(invitedEmailsToAccountIDsDraft ?? {}, `${welcomeNoteSubject}\n\n${welcomeNote}`, route.params.policyID); + Member.addMembersToWorkspace(invitedEmailsToAccountIDsDraft ?? {}, `${welcomeNoteSubject}\n\n${welcomeNote}`, route.params.policyID, policyMemberAccountIDs); debouncedSaveDraft(null); SearchInputManager.searchInput = ''; Navigation.dismissModal(); diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index c0f83662006f..b17a70a308ad 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -354,7 +354,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}: fallbackIcon: Expensicons.FallbackWorkspaceAvatar, policyID: policy.id, adminRoom: policyRooms?.[policy.id]?.adminRoom ?? policy.chatReportIDAdmins?.toString(), - announceRoom: policyRooms?.[policy.id]?.announceRoom ?? policy.chatReportIDAnnounce?.toString(), + announceRoom: policyRooms?.[policy.id]?.announceRoom ?? (policy.chatReportIDAnnounce ? policy.chatReportIDAnnounce?.toString() : ''), ownerAccountID: policy.ownerAccountID, role: policy.role, type: policy.type, From e326b2c8f8f37f74fdbcda969b927e06f35e098a Mon Sep 17 00:00:00 2001 From: Roji Philip Date: Thu, 5 Sep 2024 22:28:44 +0530 Subject: [PATCH 024/114] remove console log --- src/libs/PolicyUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 961a8bce41d3..160dd87df0ab 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -231,7 +231,6 @@ function getMemberAccountIDsForWorkspace(employeeList: PolicyEmployeeList | unde if (!includeMemberWithPendingDelete) { const member = members?.[email]; if (member.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { - console.log('RETURNING FOR [' + email + ']'); return; } } From dbebceba862d5f54e1387ca0070dd18fd4869927 Mon Sep 17 00:00:00 2001 From: Roji Philip Date: Thu, 5 Sep 2024 23:43:42 +0530 Subject: [PATCH 025/114] jest, lint and typecheck fixes --- src/libs/ReportUtils.ts | 2 +- src/libs/actions/IOU.ts | 4 ---- src/libs/actions/Policy/Policy.ts | 8 ++++---- tests/actions/PolicyTest.ts | 17 +++++------------ 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index dac51f3d0dd0..a522f65db73c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5484,7 +5484,7 @@ function buildOptimisticAnnounceChat(policyID: string, accountIDs: number[]): An }; // Do not create #announce room if the room already exists or if there are less than 3 participants in workspace - if (announceReport || accountIDs.length < 3) { + if (accountIDs.length < 3 || announceReport) { return announceRoomOnyxData; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 29d481737790..85b475b88e36 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3346,8 +3346,6 @@ function categorizeTrackedExpense( receipt, policyExpenseChatReportID: createdWorkspaceParams?.expenseChatReportID, policyExpenseCreatedReportActionID: createdWorkspaceParams?.expenseCreatedReportActionID, - announceChatReportID: createdWorkspaceParams?.announceChatReportID, - announceCreatedReportActionID: createdWorkspaceParams?.announceCreatedReportActionID, adminsChatReportID: createdWorkspaceParams?.adminsChatReportID, adminsCreatedReportActionID: createdWorkspaceParams?.adminsCreatedReportActionID, }; @@ -3423,8 +3421,6 @@ function shareTrackedExpense( receipt, policyExpenseChatReportID: createdWorkspaceParams?.expenseChatReportID, policyExpenseCreatedReportActionID: createdWorkspaceParams?.expenseCreatedReportActionID, - announceChatReportID: createdWorkspaceParams?.announceChatReportID, - announceCreatedReportActionID: createdWorkspaceParams?.announceCreatedReportActionID, adminsChatReportID: createdWorkspaceParams?.adminsChatReportID, adminsCreatedReportActionID: createdWorkspaceParams?.adminsCreatedReportActionID, }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 21f183a2d75d..34dbb7beac23 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1818,8 +1818,10 @@ function createDraftWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policy const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticCustomUnits(); - const {expenseChatData, announceChatReportID, announceCreatedReportActionID, adminsChatReportID, adminsCreatedReportActionID, expenseChatReportID, expenseCreatedReportActionID} = - ReportUtils.buildOptimisticWorkspaceChats(policyID, workspaceName); + const {expenseChatData, adminsChatReportID, adminsCreatedReportActionID, expenseChatReportID, expenseCreatedReportActionID} = ReportUtils.buildOptimisticWorkspaceChats( + policyID, + workspaceName, + ); const optimisticData: OnyxUpdate[] = [ { @@ -1883,14 +1885,12 @@ function createDraftWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policy const params: CreateWorkspaceParams = { policyID, - announceChatReportID, adminsChatReportID, expenseChatReportID, ownerEmail: policyOwnerEmail, makeMeAdmin, policyName: workspaceName, type: CONST.POLICY.TYPE.TEAM, - announceCreatedReportActionID, adminsCreatedReportActionID, expenseCreatedReportActionID, customUnitID, diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index a9af7b9da7d8..82fb52ae1d76 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -35,7 +35,6 @@ describe('actions/Policy', () => { await waitForBatchedUpdates(); let adminReportID; - let announceReportID; let expenseReportID; const policyID = Policy.generatePolicyID(); @@ -75,7 +74,7 @@ describe('actions/Policy', () => { // Three reports should be created: #announce, #admins and expense report const workspaceReports = Object.values(allReports ?? {}).filter((report) => report?.policyID === policyID); - expect(workspaceReports.length).toBe(3); + expect(workspaceReports.length).toBe(2); workspaceReports.forEach((report) => { expect(report?.pendingFields?.addWorkspaceRoom).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); expect(report?.participants).toEqual({[ESH_ACCOUNT_ID]: ESH_PARTICIPANT}); @@ -84,10 +83,6 @@ describe('actions/Policy', () => { adminReportID = report.reportID; break; } - case CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE: { - announceReportID = report.reportID; - break; - } case CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT: { expenseReportID = report.reportID; break; @@ -110,13 +105,12 @@ describe('actions/Policy', () => { // Each of the three reports should have a a `CREATED` action. let adminReportActions: ReportAction[] = Object.values(reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminReportID}`] ?? {}); - let announceReportActions: ReportAction[] = Object.values(reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceReportID}`] ?? {}); let expenseReportActions: ReportAction[] = Object.values(reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReportID}`] ?? {}); - let workspaceReportActions: ReportAction[] = adminReportActions.concat(announceReportActions, expenseReportActions); - [adminReportActions, announceReportActions, expenseReportActions].forEach((actions) => { + let workspaceReportActions: ReportAction[] = adminReportActions.concat(expenseReportActions); + [adminReportActions, expenseReportActions].forEach((actions) => { expect(actions.length).toBe(1); }); - [...adminReportActions, ...announceReportActions, ...expenseReportActions].forEach((reportAction) => { + [...adminReportActions, ...expenseReportActions].forEach((reportAction) => { expect(reportAction.actionName).toBe(CONST.REPORT.ACTIONS.TYPE.CREATED); expect(reportAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); expect(reportAction.actorAccountID).toBe(ESH_ACCOUNT_ID); @@ -170,9 +164,8 @@ describe('actions/Policy', () => { // Check if the report action pending action was cleared adminReportActions = Object.values(reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminReportID}`] ?? {}); - announceReportActions = Object.values(reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceReportID}`] ?? {}); expenseReportActions = Object.values(reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReportID}`] ?? {}); - workspaceReportActions = adminReportActions.concat(announceReportActions, expenseReportActions); + workspaceReportActions = adminReportActions.concat(expenseReportActions); workspaceReportActions.forEach((reportAction) => { expect(reportAction.pendingAction).toBeFalsy(); }); From 376502a7ab59f1e16765eee27e3b7c83a8ae5bff Mon Sep 17 00:00:00 2001 From: Roji Philip Date: Tue, 10 Sep 2024 21:07:58 +0530 Subject: [PATCH 026/114] lint fix --- tests/actions/PolicyTest.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 487b9e62f8c9..2ede9f5e5228 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -12,7 +12,6 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const ESH_EMAIL = 'eshgupta1217@gmail.com'; const ESH_ACCOUNT_ID = 1; -const ESH_PARTICIPANT_ANNOUNCE_ROOM: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}; const ESH_PARTICIPANT_ADMINS_ROOM: Participant = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}; const ESH_PARTICIPANT_EXPENSE_CHAT = {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}; const WORKSPACE_NAME = "Esh's Workspace"; @@ -74,7 +73,7 @@ describe('actions/Policy', () => { }); }); - // Three reports should be created: #announce, #admins and expense report + // Two reports should be created: #admins and expense report const workspaceReports = Object.values(allReports ?? {}).filter((report) => report?.policyID === policyID); expect(workspaceReports.length).toBe(2); workspaceReports.forEach((report) => { From 76ec71a9b7062f4191543f05362c1ac69e2414dc Mon Sep 17 00:00:00 2001 From: Roji Philip Date: Tue, 10 Sep 2024 22:41:40 +0530 Subject: [PATCH 027/114] pass announce params to addmemberstoworkspace --- .../parameters/AddMembersToWorkspaceParams.ts | 2 ++ src/libs/ReportUtils.ts | 20 ++++++++++++++++--- src/libs/actions/Policy/Member.ts | 5 ++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/libs/API/parameters/AddMembersToWorkspaceParams.ts b/src/libs/API/parameters/AddMembersToWorkspaceParams.ts index 4e96fd07d301..abfed55e2df3 100644 --- a/src/libs/API/parameters/AddMembersToWorkspaceParams.ts +++ b/src/libs/API/parameters/AddMembersToWorkspaceParams.ts @@ -3,6 +3,8 @@ type AddMembersToWorkspaceParams = { welcomeNote: string; policyID: string; reportCreationData?: string; + announceChatReportID?: string; + announceCreatedReportActionID?: string; }; export default AddMembersToWorkspaceParams; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a7a90f987c82..38c49c7661e3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -327,6 +327,12 @@ type AnnounceRoomOnyxData = { onyxFailureData: OnyxUpdate[]; }; +type OptimisticAnnounceChat = { + announceChatReportID: string; + announceChatReportActionID: string; + announceChatData: AnnounceRoomOnyxData; +}; + type OptimisticWorkspaceChats = { adminsChatReportID: string; adminsChatData: OptimisticChatReport; @@ -5494,7 +5500,7 @@ function buildOptimisticDismissedViolationReportAction( }; } -function buildOptimisticAnnounceChat(policyID: string, accountIDs: number[]): AnnounceRoomOnyxData { +function buildOptimisticAnnounceChat(policyID: string, accountIDs: number[]): OptimisticAnnounceChat { const announceReport = getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID); const policy = getPolicy(policyID); const announceRoomOnyxData: AnnounceRoomOnyxData = { @@ -5505,7 +5511,11 @@ function buildOptimisticAnnounceChat(policyID: string, accountIDs: number[]): An // Do not create #announce room if the room already exists or if there are less than 3 participants in workspace if (accountIDs.length < 3 || announceReport) { - return announceRoomOnyxData; + return { + announceChatReportID: '', + announceChatReportActionID: '', + announceChatData: announceRoomOnyxData, + }; } const announceChatData = buildOptimisticChatReport( @@ -5589,7 +5599,11 @@ function buildOptimisticAnnounceChat(policyID: string, accountIDs: number[]): An }, }, ); - return announceRoomOnyxData; + return { + announceChatReportID: announceChatData.reportID, + announceChatReportActionID: announceCreatedAction.reportActionID, + announceChatData: announceRoomOnyxData, + }; } function buildOptimisticWorkspaceChats(policyID: string, policyName: string, expenseReportId?: string): OptimisticWorkspaceChats { diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts index 1f33c769dca1..0dad5632baed 100644 --- a/src/libs/actions/Policy/Member.ts +++ b/src/libs/actions/Policy/Member.ts @@ -575,7 +575,8 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const newPersonalDetailsOnyxData = PersonalDetailsUtils.getPersonalDetailsOnyxDataForOptimisticUsers(newLogins, newAccountIDs); const announceRoomMembers = buildAnnounceRoomMembersOnyxData(policyID, accountIDs); - const announceRoomChat = ReportUtils.buildOptimisticAnnounceChat(policyID, [...policyMemberAccountIDs, ...accountIDs]); + const optimisticAnnounceChat = ReportUtils.buildOptimisticAnnounceChat(policyID, [...policyMemberAccountIDs, ...accountIDs]); + const announceRoomChat = optimisticAnnounceChat.announceChatData; // create onyx data for policy expense chats for each new member const membersChats = createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs); @@ -631,6 +632,8 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const params: AddMembersToWorkspaceParams = { employees: JSON.stringify(logins.map((login) => ({email: login}))), + ...(optimisticAnnounceChat.announceChatReportID ? {announceChatReportID: optimisticAnnounceChat.announceChatReportID} : {}), + ...(optimisticAnnounceChat.announceChatReportActionID ? {announceCreatedReportActionID: optimisticAnnounceChat.announceChatReportActionID} : {}), welcomeNote: Parser.replace(welcomeNote), policyID, }; From 3c4b392981e8413e6bc71b96091c039bc4941bba Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 16 Sep 2024 14:49:55 +0200 Subject: [PATCH 028/114] refocus when RHP closed --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 0a8070130053..ddb64435f77a 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -672,7 +672,9 @@ function ComposerWithSuggestions( if (editFocused) { InputFocus.inputFocusChange(false); + return; } + focus(true); }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal?.isVisible, isNextModalWillOpenRef, shouldAutoFocus]); useEffect(() => { From 110c6933e87d4108c757435dcc747cd325fbf768 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 18 Sep 2024 12:42:55 +0200 Subject: [PATCH 029/114] focus after emojis --- src/components/EmojiPicker/EmojiPickerButton.tsx | 4 ++-- .../ReportActionCompose/ReportActionCompose.tsx | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index 07badc1799b8..412c6655bdb0 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -28,12 +28,12 @@ type EmojiPickerButtonProps = { /** Emoji popup anchor offset shift vertical */ shiftVertical?: number; - onModalHide?: EmojiPickerAction.OnModalHideValue; + onModalHide: EmojiPickerAction.OnModalHideValue; onEmojiSelected: EmojiPickerAction.OnEmojiSelected; }; -function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shiftVertical = 0, onPress, onModalHide = () => {}, onEmojiSelected}: EmojiPickerButtonProps) { +function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shiftVertical = 0, onPress, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index b091485443ca..d3dbb7ac8927 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -208,6 +208,13 @@ function ReportActionCompose({ return translate('reportActionCompose.writeSomething'); }, [includesConcierge, translate, userBlockedFromConcierge]); + const focus = () => { + if (composerRef.current === null) { + return; + } + composerRef.current?.focus(true); + }; + const isKeyboardVisibleWhenShowingModalRef = useRef(false); const isNextModalWillOpenRef = useRef(false); @@ -514,6 +521,12 @@ function ReportActionCompose({ {DeviceCapabilities.canUseTouchScreen() && isMediumScreenWidth ? null : ( { + if (isNavigating) { + return; + } + focus(); + }} onEmojiSelected={(...args) => composerRef.current?.replaceSelectionWithText(...args)} emojiPickerID={report?.reportID} shiftVertical={emojiShiftVertical} From 138290acd9b76c9368ae6fc951f513ced967155c Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 18 Sep 2024 16:49:33 +0200 Subject: [PATCH 030/114] refactor AttachmentPickerWithMenuItems --- .../AttachmentPickerWithMenuItems.tsx | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index ea23759edbf2..526e13b02c01 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -2,7 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import type {FileObject} from '@components/AttachmentModal'; import AttachmentPicker from '@components/AttachmentPicker'; import Icon from '@components/Icon'; @@ -33,12 +33,7 @@ import type * as OnyxTypes from '@src/types/onyx'; type MoneyRequestOptions = Record, PopoverMenuItem>; -type AttachmentPickerWithMenuItemsOnyxProps = { - /** The policy tied to the report */ - policy: OnyxEntry; -}; - -type AttachmentPickerWithMenuItemsProps = AttachmentPickerWithMenuItemsOnyxProps & { +type AttachmentPickerWithMenuItemsProps = { /** The report currently being looked at */ report: OnyxEntry; @@ -97,7 +92,6 @@ type AttachmentPickerWithMenuItemsProps = AttachmentPickerWithMenuItemsOnyxProps */ function AttachmentPickerWithMenuItems({ report, - policy, reportParticipantIDs, displayFileInModal, isFullComposerAvailable, @@ -121,6 +115,9 @@ function AttachmentPickerWithMenuItems({ const {translate} = useLocalize(); const {windowHeight, windowWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, { + initialValue: {} as OnyxTypes.Policy, + }); /** * Returns the list of IOU Options @@ -336,9 +333,4 @@ function AttachmentPickerWithMenuItems({ AttachmentPickerWithMenuItems.displayName = 'AttachmentPickerWithMenuItems'; -export default withOnyx({ - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, - initialValue: {} as OnyxTypes.Policy, - }, -})(AttachmentPickerWithMenuItems); +export default AttachmentPickerWithMenuItems; From 522277ccd4a3f310bee2d0fdca08ec7a56a13084 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 18 Sep 2024 16:52:09 +0200 Subject: [PATCH 031/114] refactor ReportActionCompose --- .../ReportActionCompose.tsx | 30 +++++-------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index d3dbb7ac8927..f48bb8f9b6a4 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -4,7 +4,7 @@ import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 're import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInputFocusEventData, TextInputSelectionChangeEventData} from 'react-native'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import {runOnUI, useSharedValue} from 'react-native-reanimated'; import type {Emoji} from '@assets/emojis/types'; import type {FileObject} from '@components/AttachmentModal'; @@ -63,16 +63,7 @@ type SuggestionsRef = { getIsSuggestionsMenuVisible: () => boolean; }; -type ReportActionComposeOnyxProps = { - /** The NVP describing a user's block status */ - blockedFromConcierge: OnyxEntry; - - /** Whether the composer input should be shown */ - shouldShowComposeInput: OnyxEntry; -}; - -type ReportActionComposeProps = ReportActionComposeOnyxProps & - WithCurrentUserPersonalDetailsProps & +type ReportActionComposeProps = WithCurrentUserPersonalDetailsProps & Pick & { /** A method to call when the form is submitted */ onSubmit: (newComment: string) => void; @@ -109,7 +100,6 @@ const willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutsideFunc(); let onSubmitAction = noop; function ReportActionCompose({ - blockedFromConcierge, currentUserPersonalDetails, disabled = false, isComposerFullSize = false, @@ -117,7 +107,6 @@ function ReportActionCompose({ pendingAction, report, reportID, - shouldShowComposeInput = true, isReportReadyForDisplay = true, isEmptyChat, lastReportAction, @@ -133,6 +122,10 @@ function ReportActionCompose({ const actionButtonRef = useRef(null); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const navigation = useNavigation(); + const [blockedFromConcierge] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); + const [shouldShowComposeInput] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, { + initialValue: true, + }); /** * Updates the Highlight state of the composer @@ -558,15 +551,6 @@ function ReportActionCompose({ ReportActionCompose.displayName = 'ReportActionCompose'; -export default withCurrentUserPersonalDetails( - withOnyx({ - blockedFromConcierge: { - key: ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE, - }, - shouldShowComposeInput: { - key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, - }, - })(memo(ReportActionCompose)), -); +export default withCurrentUserPersonalDetails(memo(ReportActionCompose)); export {onSubmitAction}; export type {SuggestionsRef, ComposerRef}; From 36cf24fe4d7dcbb748a3da4f411b4c4bfc6126ed Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 18 Sep 2024 23:31:09 +0700 Subject: [PATCH 032/114] fix ws name and avatar are not updated dynamically --- src/libs/ReportUtils.ts | 2 +- src/pages/home/HeaderView.tsx | 4 +- .../home/report/ReportActionItemCreated.tsx | 57 +++---------------- .../home/report/ReportActionItemSingle.tsx | 6 +- 4 files changed, 16 insertions(+), 53 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f37f3f940516..41b9f922cba9 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1955,7 +1955,7 @@ function getWorkspaceIcon(report: OnyxInputOrEntry, policy?: OnyxInputOr const iconFromCache = workSpaceIconsCache.get(cacheKey); // disabling to protect against empty strings // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const policyAvatarURL = report?.policyAvatar || allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatarURL; + const policyAvatarURL = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatarURL || report?.policyAvatar; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const policyExpenseChatAvatarSource = policyAvatarURL || getDefaultWorkspaceAvatar(workspaceName); diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 217686c0cad1..17c5fba500fe 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -78,7 +78,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const isTaskReport = ReportUtils.isTaskReport(report); const reportHeaderData = !isTaskReport && !isChatThread && report.parentReportID ? parentReport : report; // Use sorted display names for the title for group chats on native small screen widths - const title = ReportUtils.getReportName(reportHeaderData, undefined, parentReportAction, personalDetails, invoiceReceiverPolicy); + const title = ReportUtils.getReportName(reportHeaderData, policy, parentReportAction, personalDetails, invoiceReceiverPolicy); const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(reportHeaderData); const reportDescription = ReportUtils.getReportDescriptionText(report); @@ -124,7 +124,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); const defaultSubscriptSize = ReportUtils.isExpenseRequest(report) ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; - const icons = ReportUtils.getIcons(reportHeaderData, personalDetails, null, '', -1, undefined, invoiceReceiverPolicy); + const icons = ReportUtils.getIcons(reportHeaderData, personalDetails, null, '', -1, policy, invoiceReceiverPolicy); const brickRoadIndicator = ReportUtils.hasReportNameError(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; const shouldShowBorderBottom = !isTaskReport || !shouldUseNarrowLayout; const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report); diff --git a/src/pages/home/report/ReportActionItemCreated.tsx b/src/pages/home/report/ReportActionItemCreated.tsx index ec9f5ea9915f..26d6d7e8f3c2 100644 --- a/src/pages/home/report/ReportActionItemCreated.tsx +++ b/src/pages/home/report/ReportActionItemCreated.tsx @@ -1,34 +1,21 @@ -import lodashIsEqual from 'lodash/isEqual'; import React, {memo} from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx, withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import ReportWelcomeText from '@components/ReportWelcomeText'; import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; import {navigateToConciergeChatAndDeleteReport} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetailsList, Policy, Report} from '@src/types/onyx'; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; -type ReportActionItemCreatedOnyxProps = { - /** The report currently being looked at */ - report: OnyxEntry; - - /** The policy object for the current route */ - policy: OnyxEntry; - - /** Personal details of all the users */ - personalDetails: OnyxEntry; -}; - -type ReportActionItemCreatedProps = ReportActionItemCreatedOnyxProps & { +type ReportActionItemCreatedProps = { /** The id of the report */ reportID: string; @@ -36,7 +23,10 @@ type ReportActionItemCreatedProps = ReportActionItemCreatedOnyxProps & { // eslint-disable-next-line react/no-unused-prop-types policyID: string | undefined; }; -function ReportActionItemCreated({report, personalDetails, policy, reportID}: ReportActionItemCreatedProps) { +function ReportActionItemCreated({policyID, reportID}: ReportActionItemCreatedProps) { + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const policy = usePolicy(policyID); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -47,7 +37,7 @@ function ReportActionItemCreated({report, personalDetails, policy, reportID}: Re return null; } - let icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, undefined, invoiceReceiverPolicy); + let icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, policy, invoiceReceiverPolicy); const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report); if (ReportUtils.isInvoiceRoom(report) && ReportUtils.isCurrentUserInvoiceReceiver(report)) { @@ -98,33 +88,4 @@ function ReportActionItemCreated({report, personalDetails, policy, reportID}: Re ReportActionItemCreated.displayName = 'ReportActionItemCreated'; -export default withOnyx({ - report: { - key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - }, - - policy: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - }, - - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, -})( - memo( - ReportActionItemCreated, - (prevProps, nextProps) => - prevProps.policy?.name === nextProps.policy?.name && - prevProps.policy?.avatarURL === nextProps.policy?.avatarURL && - prevProps.report?.stateNum === nextProps.report?.stateNum && - prevProps.report?.statusNum === nextProps.report?.statusNum && - prevProps.report?.lastReadTime === nextProps.report?.lastReadTime && - prevProps.report?.description === nextProps.report?.description && - prevProps.personalDetails === nextProps.personalDetails && - prevProps.policy?.description === nextProps.policy?.description && - prevProps.report?.reportName === nextProps.report?.reportName && - prevProps.report?.avatarUrl === nextProps.report?.avatarUrl && - lodashIsEqual(prevProps.report?.invoiceReceiver, nextProps.report?.invoiceReceiver) && - prevProps.report?.errorFields === nextProps.report?.errorFields, - ), -); +export default memo(ReportActionItemCreated); diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 0e58296d39f2..491c2877955a 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -14,6 +14,7 @@ import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -82,6 +83,7 @@ function ReportActionItemSingle({ const {translate} = useLocalize(); const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT; const actorAccountID = ReportUtils.getReportActionActorAccountID(action, iouReport); + const policy = usePolicy(report.policyID); const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`); let displayName = ReportUtils.getDisplayNameForParticipant(actorAccountID); @@ -98,9 +100,9 @@ function ReportActionItemSingle({ let avatarId: number | string | undefined = actorAccountID; if (isWorkspaceActor) { - displayName = ReportUtils.getPolicyName(report); + displayName = ReportUtils.getPolicyName(report, undefined, policy); actorHint = displayName; - avatarSource = ReportUtils.getWorkspaceIcon(report).source; + avatarSource = ReportUtils.getWorkspaceIcon(report, policy).source; avatarId = report.policyID; } else if (action?.delegateAccountID && personalDetails[action?.delegateAccountID]) { // We replace the actor's email, name, and avatar with the Copilot manually for now. And only if we have their From 0b0f3b20b524a6b28325583bd84add4b088c1dc9 Mon Sep 17 00:00:00 2001 From: Roji Philip Date: Thu, 19 Sep 2024 14:49:22 +0530 Subject: [PATCH 033/114] cleanup --- src/libs/actions/IOU.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index e2b374e9829c..67a8910c83c7 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6635,8 +6635,6 @@ function getPayMoneyRequestParams( params, } = Policy.buildPolicyData(currentUserEmail, true, undefined, payerPolicyID); const { - announceChatReportID, - announceCreatedReportActionID, adminsChatReportID, adminsCreatedReportActionID, expenseChatReportID, @@ -6649,8 +6647,6 @@ function getPayMoneyRequestParams( policyParams = { policyID: payerPolicyID, - announceChatReportID, - announceCreatedReportActionID, adminsChatReportID, adminsCreatedReportActionID, expenseChatReportID, @@ -7561,8 +7557,6 @@ function payInvoice(paymentMethodType: PaymentMethodType, chatReport: OnyxTypes. params: { reportActionID, policyID, - announceChatReportID, - announceCreatedReportActionID, adminsChatReportID, adminsCreatedReportActionID, expenseChatReportID, @@ -7588,8 +7582,6 @@ function payInvoice(paymentMethodType: PaymentMethodType, chatReport: OnyxTypes. params = { ...params, policyID, - announceChatReportID, - announceCreatedReportActionID, adminsChatReportID, adminsCreatedReportActionID, expenseChatReportID, From 568e66c55b42b4fdde143e52c79c59f8cb3a0d39 Mon Sep 17 00:00:00 2001 From: Roji Philip Date: Thu, 19 Sep 2024 15:00:42 +0530 Subject: [PATCH 034/114] prettier fix --- src/libs/actions/IOU.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 67a8910c83c7..bae17d09a2e8 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6634,16 +6634,7 @@ function getPayMoneyRequestParams( successData: policySuccessData, params, } = Policy.buildPolicyData(currentUserEmail, true, undefined, payerPolicyID); - const { - adminsChatReportID, - adminsCreatedReportActionID, - expenseChatReportID, - expenseCreatedReportActionID, - customUnitRateID, - customUnitID, - ownerEmail, - policyName, - } = params; + const {adminsChatReportID, adminsCreatedReportActionID, expenseChatReportID, expenseCreatedReportActionID, customUnitRateID, customUnitID, ownerEmail, policyName} = params; policyParams = { policyID: payerPolicyID, From bdba824643b7542c00253f0d7e0b2f5d854da9ef Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 20 Sep 2024 15:41:22 +0700 Subject: [PATCH 035/114] fix: Search Save add Onyx optimistic and failure data --- src/ONYXKEYS.ts | 2 +- src/libs/actions/Search.ts | 71 ++++++++++++++++++++++++++++- src/pages/Search/SearchTypeMenu.tsx | 3 +- src/types/onyx/SaveSearch.ts | 6 ++- 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 68a9ca2f8502..e425bc69bcb8 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -860,7 +860,7 @@ type OnyxValuesMapping = { // ONYXKEYS.NVP_TRYNEWDOT is HybridApp onboarding data [ONYXKEYS.NVP_TRYNEWDOT]: OnyxTypes.TryNewDot; - [ONYXKEYS.SAVED_SEARCHES]: OnyxTypes.SaveSearch[]; + [ONYXKEYS.SAVED_SEARCHES]: OnyxTypes.SaveSearch; [ONYXKEYS.RECENTLY_USED_CURRENCIES]: string[]; [ONYXKEYS.ACTIVE_CLIENTS]: string[]; [ONYXKEYS.DEVICE_ID]: string; diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index a4f0e59ef976..952763c1d313 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -55,11 +55,78 @@ function saveSearch({queryJSON, name}: {queryJSON: SearchQueryJSON; name?: strin const saveSearchName = name ?? queryJSON?.inputQuery ?? ''; const jsonQuery = JSON.stringify(queryJSON); - API.write(WRITE_COMMANDS.SAVE_SEARCH, {jsonQuery, name: saveSearchName}); + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [queryJSON.hash]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + name: saveSearchName, + query: queryJSON.inputQuery, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [queryJSON.hash]: null, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [queryJSON.hash]: { + pendingAction: null, + }, + }, + }, + ]; + API.write(WRITE_COMMANDS.SAVE_SEARCH, {jsonQuery, name: saveSearchName}, {optimisticData, failureData, successData}); } function deleteSavedSearch(hash: number) { - API.write(WRITE_COMMANDS.DELETE_SAVED_SEARCH, {hash}); + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [hash]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }, + }, + ]; + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [hash]: null, + }, + }, + ]; + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [hash]: { + pendingAction: null, + }, + }, + }, + ]; + + API.write(WRITE_COMMANDS.DELETE_SAVED_SEARCH, {hash}, {optimisticData, failureData, successData}); } function search({queryJSON, offset}: {queryJSON: SearchQueryJSON; offset?: number}) { diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 7c8af2388f52..e5e44169635d 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -34,7 +34,7 @@ import type IconAsset from '@src/types/utils/IconAsset'; import SavedSearchItemThreeDotMenu from './SavedSearchItemThreeDotMenu'; import SearchTypeMenuNarrow from './SearchTypeMenuNarrow'; -type SavedSearchMenuItem = MenuItemBaseProps & { +type SavedSearchMenuItem = MenuItemWithLink & { key: string; hash: string; query: string; @@ -119,6 +119,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { }, rightComponent: , styles: [styles.alignItemsCenter], + pendingAction: item.pendingAction, }; if (!isNarrow) { diff --git a/src/types/onyx/SaveSearch.ts b/src/types/onyx/SaveSearch.ts index d8f8bf32f2a1..129e063f77a3 100644 --- a/src/types/onyx/SaveSearch.ts +++ b/src/types/onyx/SaveSearch.ts @@ -1,13 +1,15 @@ +import type * as OnyxCommon from './OnyxCommon'; + /** * Model of a single saved search */ -type SaveSearchItem = { +type SaveSearchItem = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Name of the saved search */ name: string; /** Query string for the saved search */ query: string; -}; +}>; /** * Model of saved searches From 6e59afcac8920ba482374472e1c679509be5abba Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 20 Sep 2024 15:56:31 +0700 Subject: [PATCH 036/114] fix lint --- src/pages/Search/SearchTypeMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index e5e44169635d..08340fe0279e 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -4,7 +4,6 @@ import {View} from 'react-native'; // eslint-disable-next-line no-restricted-imports import type {ScrollView as RNScrollView, ScrollViewProps, TextStyle, ViewStyle} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import type {MenuItemBaseProps} from '@components/MenuItem'; import MenuItem from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import type {MenuItemWithLink} from '@components/MenuItemList'; @@ -179,6 +178,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { if (!savedSearches) { return []; } + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style return Object.entries(savedSearches).map(([key, item]) => createSavedSearchMenuItem(item as SaveSearchItem, key, shouldUseNarrowLayout)); }; From 9f7e4649a03700be35e91a8867422d1afead7082 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 20 Sep 2024 17:53:55 +0700 Subject: [PATCH 037/114] fix: add offline feedback in narrow --- src/components/PopoverMenu.tsx | 90 ++++++++++++----------- src/pages/Search/SearchTypeMenuNarrow.tsx | 7 +- 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 3b074bf772e6..e3a04903f5ca 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -14,6 +14,7 @@ import * as Browser from '@libs/Browser'; import * as Modal from '@userActions/Modal'; import CONST from '@src/CONST'; import type {AnchorPosition} from '@src/styles'; +import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import type AnchorAlignment from '@src/types/utils/AnchorAlignment'; import FocusableMenuItem from './FocusableMenuItem'; import FocusTrapForModal from './FocusTrap/FocusTrapForModal'; @@ -21,6 +22,7 @@ import * as Expensicons from './Icon/Expensicons'; import type {MenuItemProps} from './MenuItem'; import MenuItem from './MenuItem'; import type BaseModalProps from './Modal/types'; +import OfflineWithFeedback from './OfflineWithFeedback'; import PopoverWithMeasuredContent from './PopoverWithMeasuredContent'; import ScrollView from './ScrollView'; import Text from './Text'; @@ -48,6 +50,8 @@ type PopoverMenuItem = MenuItemProps & { /** Whether to close all modals */ shouldCloseAllModals?: boolean; + + pendingAction?: PendingAction; }; type PopoverModalProps = Pick; @@ -262,49 +266,53 @@ function PopoverMenu({ {renderHeaderText()} {enteredSubMenuIndexes.length > 0 && renderBackButtonItem()} {currentMenuItems.map((item, menuIndex) => ( - selectItem(menuIndex)} - focused={focusedIndex === menuIndex} - displayInDefaultIconColor={item.displayInDefaultIconColor} - shouldShowRightIcon={item.shouldShowRightIcon} - shouldShowRightComponent={item.shouldShowRightComponent} - iconRight={item.iconRight} - rightComponent={item.rightComponent} - shouldPutLeftPaddingWhenNoIcon={item.shouldPutLeftPaddingWhenNoIcon} - label={item.label} - style={{backgroundColor: item.isSelected ? theme.activeComponentBG : undefined}} - isLabelHoverable={item.isLabelHoverable} - floatRightAvatars={item.floatRightAvatars} - floatRightAvatarSize={item.floatRightAvatarSize} - shouldShowSubscriptRightAvatar={item.shouldShowSubscriptRightAvatar} - disabled={item.disabled} - onFocus={() => setFocusedIndex(menuIndex)} - success={item.success} - containerStyle={item.containerStyle} - shouldRenderTooltip={item.shouldRenderTooltip} - tooltipAnchorAlignment={item.tooltipAnchorAlignment} - tooltipShiftHorizontal={item.tooltipShiftHorizontal} - tooltipShiftVertical={item.tooltipShiftVertical} - tooltipWrapperStyle={item.tooltipWrapperStyle} - renderTooltipContent={item.renderTooltipContent} - numberOfLinesTitle={item.numberOfLinesTitle} - interactive={item.interactive} - isSelected={item.isSelected} - badgeText={item.badgeText} - /> + pendingAction={item.pendingAction} + > + selectItem(menuIndex)} + focused={focusedIndex === menuIndex} + displayInDefaultIconColor={item.displayInDefaultIconColor} + shouldShowRightIcon={item.shouldShowRightIcon} + shouldShowRightComponent={item.shouldShowRightComponent} + iconRight={item.iconRight} + rightComponent={item.rightComponent} + shouldPutLeftPaddingWhenNoIcon={item.shouldPutLeftPaddingWhenNoIcon} + label={item.label} + style={{backgroundColor: item.isSelected ? theme.activeComponentBG : undefined}} + isLabelHoverable={item.isLabelHoverable} + floatRightAvatars={item.floatRightAvatars} + floatRightAvatarSize={item.floatRightAvatarSize} + shouldShowSubscriptRightAvatar={item.shouldShowSubscriptRightAvatar} + disabled={item.disabled} + onFocus={() => setFocusedIndex(menuIndex)} + success={item.success} + containerStyle={item.containerStyle} + shouldRenderTooltip={item.shouldRenderTooltip} + tooltipAnchorAlignment={item.tooltipAnchorAlignment} + tooltipShiftHorizontal={item.tooltipShiftHorizontal} + tooltipShiftVertical={item.tooltipShiftVertical} + tooltipWrapperStyle={item.tooltipWrapperStyle} + renderTooltipContent={item.renderTooltipContent} + numberOfLinesTitle={item.numberOfLinesTitle} + interactive={item.interactive} + isSelected={item.isSelected} + badgeText={item.badgeText} + /> + ))} diff --git a/src/pages/Search/SearchTypeMenuNarrow.tsx b/src/pages/Search/SearchTypeMenuNarrow.tsx index 0158a15bfc41..0b55c32c2224 100644 --- a/src/pages/Search/SearchTypeMenuNarrow.tsx +++ b/src/pages/Search/SearchTypeMenuNarrow.tsx @@ -3,7 +3,7 @@ import {Animated, View} from 'react-native'; import type {TextStyle, ViewStyle} from 'react-native'; import Button from '@components/Button'; import Icon from '@components/Icon'; -import type {MenuItemBaseProps} from '@components/MenuItem'; +import type {MenuItemWithLink} from '@components/MenuItemList'; import PopoverMenu from '@components/PopoverMenu'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; @@ -26,7 +26,7 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {SearchTypeMenuItem} from './SearchTypeMenu'; -type SavedSearchMenuItem = MenuItemBaseProps & { +type SavedSearchMenuItem = MenuItemWithLink & { key: string; hash: string; query: string; @@ -121,8 +121,8 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title, /> ), isSelected: currentSavedSearch?.hash === item.hash, + pendingAction: item.pendingAction, })); - const allMenuItems = []; allMenuItems.push(...popoverMenuItems); @@ -134,7 +134,6 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title, }); allMenuItems.push(...savedSearchItems); } - return ( Date: Fri, 20 Sep 2024 18:03:34 +0700 Subject: [PATCH 038/114] remove disable eslint --- src/pages/Search/SearchTypeMenu.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 08340fe0279e..940fbf959582 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -178,8 +178,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { if (!savedSearches) { return []; } - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style - return Object.entries(savedSearches).map(([key, item]) => createSavedSearchMenuItem(item as SaveSearchItem, key, shouldUseNarrowLayout)); + return Object.entries(savedSearches).map(([key, item]) => createSavedSearchMenuItem(item ?? ({} as SaveSearchItem), key, shouldUseNarrowLayout)); }; const renderSavedSearchesSection = useCallback( From 5429647be60d3693b325c999fd0e4afd5ed65375 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 20 Sep 2024 18:15:43 +0700 Subject: [PATCH 039/114] update type --- src/pages/Search/SearchTypeMenu.tsx | 2 +- src/types/onyx/SaveSearch.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 940fbf959582..adb680541882 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -178,7 +178,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { if (!savedSearches) { return []; } - return Object.entries(savedSearches).map(([key, item]) => createSavedSearchMenuItem(item ?? ({} as SaveSearchItem), key, shouldUseNarrowLayout)); + return Object.entries(savedSearches).map(([key, item]) => createSavedSearchMenuItem(item, key, shouldUseNarrowLayout)); }; const renderSavedSearchesSection = useCallback( diff --git a/src/types/onyx/SaveSearch.ts b/src/types/onyx/SaveSearch.ts index 129e063f77a3..6b3a903b1639 100644 --- a/src/types/onyx/SaveSearch.ts +++ b/src/types/onyx/SaveSearch.ts @@ -14,6 +14,6 @@ type SaveSearchItem = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** * Model of saved searches */ -type SaveSearch = Record; +type SaveSearch = Record; export type {SaveSearch, SaveSearchItem}; From 15acd10149feeb8321e08b583fc4774027750268 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 23 Sep 2024 10:36:15 +0200 Subject: [PATCH 040/114] feat: fix reanimated console warnings --- ...eact-native-draggable-flatlist+4.0.1.patch | 94 +++++++++++++++++++ src/App.tsx | 3 - 2 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 patches/react-native-draggable-flatlist+4.0.1.patch diff --git a/patches/react-native-draggable-flatlist+4.0.1.patch b/patches/react-native-draggable-flatlist+4.0.1.patch new file mode 100644 index 000000000000..348f1aa5de8a --- /dev/null +++ b/patches/react-native-draggable-flatlist+4.0.1.patch @@ -0,0 +1,94 @@ +diff --git a/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx b/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx +index d7d98c2..2f59c7a 100644 +--- a/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx ++++ b/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx +@@ -295,7 +295,7 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { + const springTo = placeholderOffset.value - activeCellOffset.value; + touchTranslate.value = withSpring( + springTo, +- animationConfigRef.current, ++ animationConfigRef.value, + () => { + runOnJS(onDragEnd)({ + from: activeIndexAnim.value, +diff --git a/node_modules/react-native-draggable-flatlist/src/context/refContext.tsx b/node_modules/react-native-draggable-flatlist/src/context/refContext.tsx +index ea21575..66c5eed 100644 +--- a/node_modules/react-native-draggable-flatlist/src/context/refContext.tsx ++++ b/node_modules/react-native-draggable-flatlist/src/context/refContext.tsx +@@ -1,14 +1,14 @@ + import React, { useContext } from "react"; + import { useMemo, useRef } from "react"; + import { FlatList } from "react-native-gesture-handler"; +-import Animated, { WithSpringConfig } from "react-native-reanimated"; ++import Animated, { type SharedValue, useSharedValue, WithSpringConfig } from "react-native-reanimated"; + import { DEFAULT_PROPS } from "../constants"; + import { useProps } from "./propsContext"; + import { CellData, DraggableFlatListProps } from "../types"; + + type RefContextValue = { + propsRef: React.MutableRefObject>; +- animationConfigRef: React.MutableRefObject; ++ animationConfigRef: SharedValue; + cellDataRef: React.MutableRefObject>; + keyToIndexRef: React.MutableRefObject>; + containerRef: React.RefObject; +@@ -54,8 +54,8 @@ function useSetupRefs({ + ...DEFAULT_PROPS.animationConfig, + ...animationConfig, + } as WithSpringConfig; +- const animationConfigRef = useRef(animConfig); +- animationConfigRef.current = animConfig; ++ const animationConfigRef = useSharedValue(animConfig); ++ animationConfigRef.value = animConfig; + + const cellDataRef = useRef(new Map()); + const keyToIndexRef = useRef(new Map()); +diff --git a/node_modules/react-native-draggable-flatlist/src/hooks/useCellTranslate.tsx b/node_modules/react-native-draggable-flatlist/src/hooks/useCellTranslate.tsx +index ce4ab68..efea240 100644 +--- a/node_modules/react-native-draggable-flatlist/src/hooks/useCellTranslate.tsx ++++ b/node_modules/react-native-draggable-flatlist/src/hooks/useCellTranslate.tsx +@@ -101,7 +101,7 @@ export function useCellTranslate({ cellIndex, cellSize, cellOffset }: Params) { + ? activeCellSize.value * (isAfterActive ? -1 : 1) + : 0; + +- return withSpring(translationAmt, animationConfigRef.current); ++ return withSpring(translationAmt, animationConfigRef.value); + }, [activeKey, cellIndex]); + + return translate; +diff --git a/node_modules/react-native-draggable-flatlist/src/hooks/useOnCellActiveAnimation.ts b/node_modules/react-native-draggable-flatlist/src/hooks/useOnCellActiveAnimation.ts +index 7c20587..857c7d0 100644 +--- a/node_modules/react-native-draggable-flatlist/src/hooks/useOnCellActiveAnimation.ts ++++ b/node_modules/react-native-draggable-flatlist/src/hooks/useOnCellActiveAnimation.ts +@@ -1,8 +1,9 @@ +-import { useRef } from "react"; +-import Animated, { ++ ++import { + useDerivedValue, + withSpring, + WithSpringConfig, ++ useSharedValue, + } from "react-native-reanimated"; + import { DEFAULT_ANIMATION_CONFIG } from "../constants"; + import { useAnimatedValues } from "../context/animatedValueContext"; +@@ -15,8 +16,8 @@ type Params = { + export function useOnCellActiveAnimation( + { animationConfig }: Params = { animationConfig: {} } + ) { +- const animationConfigRef = useRef(animationConfig); +- animationConfigRef.current = animationConfig; ++ const animationConfigRef = useSharedValue(animationConfig); ++ animationConfigRef.value = animationConfig; + + const isActive = useIsActive(); + +@@ -26,7 +27,7 @@ export function useOnCellActiveAnimation( + const toVal = isActive && isTouchActiveNative.value ? 1 : 0; + return withSpring(toVal, { + ...DEFAULT_ANIMATION_CONFIG, +- ...animationConfigRef.current, ++ ...animationConfigRef.value, + }); + }, [isActive]); + diff --git a/src/App.tsx b/src/App.tsx index 35254fa29b2a..177cc00c7dee 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -50,9 +50,6 @@ LogBox.ignoreLogs([ // the timer is lost. Currently Expensify is using a 30 minutes interval to refresh personal details. // More details here: https://git.io/JJYeb 'Setting a timer for a long period of time', - // We silence this warning for now and will address all the places where it happens separately. - // Then we can remove this line so the problem does not occur in the future. - '[Reanimated] Tried to modify key `current`', ]); const fill = {flex: 1}; From af9e9906014f7def16b4e4a2dcef92becf737d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 23 Sep 2024 13:58:56 +0200 Subject: [PATCH 041/114] add back qr code download --- .../workspace/WorkspaceProfileSharePage.tsx | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/pages/workspace/WorkspaceProfileSharePage.tsx b/src/pages/workspace/WorkspaceProfileSharePage.tsx index 16a076205ad3..32743bf290de 100644 --- a/src/pages/workspace/WorkspaceProfileSharePage.tsx +++ b/src/pages/workspace/WorkspaceProfileSharePage.tsx @@ -1,12 +1,15 @@ import React, {useMemo, useRef} from 'react'; import {View} from 'react-native'; import type {ImageSourcePropType} from 'react-native'; +import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png'; import ContextMenuItem from '@components/ContextMenuItem'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; import {useSession} from '@components/OnyxProvider'; import QRShare from '@components/QRShare'; -import type {QRShareHandle} from '@components/QRShare/types'; +import QRShareWithDownload from '@components/QRShare/QRShareWithDownload'; +import QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; @@ -19,6 +22,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import shouldAllowDownloadQRCode from '@libs/shouldAllowDownloadQRCode'; import * as Url from '@libs/Url'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; @@ -31,7 +35,7 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const {environmentURL} = useEnvironment(); - const qrCodeRef = useRef(null); + const qrCodeRef = useRef(null); const {shouldUseNarrowLayout} = useResponsiveLayout(); const session = useSession(); @@ -96,21 +100,14 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { - {/* - Right now QR code download button is not shown anymore - This is a temporary measure because right now it's broken because of the Fabric update. - We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. - - Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. - */} - @@ -126,6 +123,15 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { shouldLimitWidth={false} wrapperStyle={themeStyles.sectionMenuItemTopDescription} /> + {shouldAllowDownloadQRCode && ( + qrCodeRef.current?.download?.()} + wrapperStyle={themeStyles.sectionMenuItemTopDescription} + /> + )} From f4b92c2488a64629a700272b7220cc118c0fe900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 23 Sep 2024 14:11:34 +0200 Subject: [PATCH 042/114] fix lint --- src/pages/workspace/WorkspaceProfileSharePage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/workspace/WorkspaceProfileSharePage.tsx b/src/pages/workspace/WorkspaceProfileSharePage.tsx index 32743bf290de..21db727f6450 100644 --- a/src/pages/workspace/WorkspaceProfileSharePage.tsx +++ b/src/pages/workspace/WorkspaceProfileSharePage.tsx @@ -7,9 +7,8 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import {useSession} from '@components/OnyxProvider'; -import QRShare from '@components/QRShare'; import QRShareWithDownload from '@components/QRShare/QRShareWithDownload'; -import QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; +import type QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; From f87cbac7194400211196e640ccd909dacc04f076 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Mon, 23 Sep 2024 12:24:31 -0700 Subject: [PATCH 043/114] First commit for NewHelp --- help/.gitignore | 5 + help/404.html | 25 +++ help/Gemfile | 33 ++++ help/Gemfile.lock | 182 ++++++++++++++++++ help/_config.yml | 55 ++++++ .../2024-09-23-welcome-to-jekyll.markdown | 29 +++ help/about.markdown | 18 ++ help/index.markdown | 6 + help/robots.txt | 3 + 9 files changed, 356 insertions(+) create mode 100644 help/.gitignore create mode 100644 help/404.html create mode 100644 help/Gemfile create mode 100644 help/Gemfile.lock create mode 100644 help/_config.yml create mode 100644 help/_posts/2024-09-23-welcome-to-jekyll.markdown create mode 100644 help/about.markdown create mode 100644 help/index.markdown create mode 100644 help/robots.txt diff --git a/help/.gitignore b/help/.gitignore new file mode 100644 index 000000000000..f40fbd8ba564 --- /dev/null +++ b/help/.gitignore @@ -0,0 +1,5 @@ +_site +.sass-cache +.jekyll-cache +.jekyll-metadata +vendor diff --git a/help/404.html b/help/404.html new file mode 100644 index 000000000000..086a5c9ea988 --- /dev/null +++ b/help/404.html @@ -0,0 +1,25 @@ +--- +permalink: /404.html +layout: default +--- + + + +
+

404

+ +

Page not found :(

+

The requested page could not be found.

+
diff --git a/help/Gemfile b/help/Gemfile new file mode 100644 index 000000000000..2c345b82ae4d --- /dev/null +++ b/help/Gemfile @@ -0,0 +1,33 @@ +source "https://rubygems.org" +# Hello! This is where you manage which Jekyll version is used to run. +# When you want to use a different version, change it below, save the +# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: +# +# bundle exec jekyll serve +# +# This will help ensure the proper Jekyll version is running. +# Happy Jekylling! +gem "jekyll", "~> 4.3.4" +# This is the default theme for new Jekyll sites. You may change this to anything you like. +gem "minima", "~> 2.5" +# If you want to use GitHub Pages, remove the "gem "jekyll"" above and +# uncomment the line below. To upgrade, run `bundle update github-pages`. +# gem "github-pages", group: :jekyll_plugins +# If you have any plugins, put them here! +group :jekyll_plugins do + gem "jekyll-feed", "~> 0.12" +end + +# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem +# and associated library. +platforms :mingw, :x64_mingw, :mswin, :jruby do + gem "tzinfo", ">= 1", "< 3" + gem "tzinfo-data" +end + +# Performance-booster for watching directories on Windows +gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin] + +# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem +# do not have a Java counterpart. +gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] diff --git a/help/Gemfile.lock b/help/Gemfile.lock new file mode 100644 index 000000000000..77ac51524535 --- /dev/null +++ b/help/Gemfile.lock @@ -0,0 +1,182 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + bigdecimal (3.1.8) + colorator (1.1.0) + concurrent-ruby (1.3.4) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + ffi (1.17.0) + ffi (1.17.0-aarch64-linux-gnu) + ffi (1.17.0-aarch64-linux-musl) + ffi (1.17.0-arm-linux-gnu) + ffi (1.17.0-arm-linux-musl) + ffi (1.17.0-arm64-darwin) + ffi (1.17.0-x86-linux-gnu) + ffi (1.17.0-x86-linux-musl) + ffi (1.17.0-x86_64-darwin) + ffi (1.17.0-x86_64-linux-gnu) + ffi (1.17.0-x86_64-linux-musl) + forwardable-extended (2.6.0) + google-protobuf (4.28.2) + bigdecimal + rake (>= 13) + google-protobuf (4.28.2-aarch64-linux) + bigdecimal + rake (>= 13) + google-protobuf (4.28.2-arm64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.28.2-x86-linux) + bigdecimal + rake (>= 13) + google-protobuf (4.28.2-x86_64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.28.2-x86_64-linux) + bigdecimal + rake (>= 13) + http_parser.rb (0.8.0) + i18n (1.14.6) + concurrent-ruby (~> 1.0) + jekyll (4.3.4) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (>= 0.3.6, < 0.5) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-feed (0.17.0) + jekyll (>= 3.7, < 5.0) + jekyll-sass-converter (3.0.0) + sass-embedded (~> 1.54) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.4.0) + minima (2.5.2) + jekyll (>= 3.5, < 5.0) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (6.0.1) + rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.3.7) + rouge (4.4.0) + safe_yaml (1.0.5) + sass-embedded (1.79.3) + google-protobuf (~> 4.27) + rake (>= 13) + sass-embedded (1.79.3-aarch64-linux-android) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-aarch64-linux-gnu) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-aarch64-linux-musl) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-aarch64-mingw-ucrt) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-arm-linux-androideabi) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-arm-linux-gnueabihf) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-arm-linux-musleabihf) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-arm64-darwin) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-riscv64-linux-android) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-riscv64-linux-gnu) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-riscv64-linux-musl) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-x86-cygwin) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-x86-linux-android) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-x86-linux-gnu) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-x86-linux-musl) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-x86-mingw-ucrt) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-x86_64-cygwin) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-x86_64-darwin) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-x86_64-linux-android) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-x86_64-linux-gnu) + google-protobuf (~> 4.27) + sass-embedded (1.79.3-x86_64-linux-musl) + google-protobuf (~> 4.27) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + unicode-display_width (2.6.0) + webrick (1.8.1) + +PLATFORMS + aarch64-linux + aarch64-linux-android + aarch64-linux-gnu + aarch64-linux-musl + aarch64-mingw-ucrt + arm-linux-androideabi + arm-linux-gnu + arm-linux-gnueabihf + arm-linux-musl + arm-linux-musleabihf + arm64-darwin + riscv64-linux-android + riscv64-linux-gnu + riscv64-linux-musl + ruby + x86-cygwin + x86-linux + x86-linux-android + x86-linux-gnu + x86-linux-musl + x86-mingw-ucrt + x86_64-cygwin + x86_64-darwin + x86_64-linux + x86_64-linux-android + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + http_parser.rb (~> 0.6.0) + jekyll (~> 4.3.4) + jekyll-feed (~> 0.12) + minima (~> 2.5) + tzinfo (>= 1, < 3) + tzinfo-data + wdm (~> 0.1) + +BUNDLED WITH + 2.5.19 diff --git a/help/_config.yml b/help/_config.yml new file mode 100644 index 000000000000..ef7ba7c13913 --- /dev/null +++ b/help/_config.yml @@ -0,0 +1,55 @@ +# Welcome to Jekyll! +# +# This config file is meant for settings that affect your whole blog, values +# which you are expected to set up once and rarely edit after that. If you find +# yourself editing this file very often, consider using Jekyll's data files +# feature for the data you need to update frequently. +# +# For technical reasons, this file is *NOT* reloaded automatically when you use +# 'bundle exec jekyll serve'. If you change this file, please restart the server process. +# +# If you need help with YAML syntax, here are some quick references for you: +# https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml +# https://learnxinyminutes.com/docs/yaml/ +# +# Site settings +# These are used to personalize your new site. If you look in the HTML files, +# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. +# You can create any custom variable you would like, and they will be accessible +# in the templates via {{ site.myvariable }}. + +title: Your awesome title +email: your-email@example.com +description: >- # this means to ignore newlines until "baseurl:" + Write an awesome description for your new site here. You can edit this + line in _config.yml. It will appear in your document head meta (for + Google search results) and in your feed.xml site description. +baseurl: "" # the subpath of your site, e.g. /blog +url: "" # the base hostname & protocol for your site, e.g. http://example.com +twitter_username: jekyllrb +github_username: jekyll + +# Build settings +theme: minima +plugins: + - jekyll-feed + +# Exclude from processing. +# The following items will not be processed, by default. +# Any item listed under the `exclude:` key here will be automatically added to +# the internal "default list". +# +# Excluded items can be processed by explicitly listing the directories or +# their entries' file path in the `include:` list. +# +# exclude: +# - .sass-cache/ +# - .jekyll-cache/ +# - gemfiles/ +# - Gemfile +# - Gemfile.lock +# - node_modules/ +# - vendor/bundle/ +# - vendor/cache/ +# - vendor/gems/ +# - vendor/ruby/ diff --git a/help/_posts/2024-09-23-welcome-to-jekyll.markdown b/help/_posts/2024-09-23-welcome-to-jekyll.markdown new file mode 100644 index 000000000000..c53c7643163e --- /dev/null +++ b/help/_posts/2024-09-23-welcome-to-jekyll.markdown @@ -0,0 +1,29 @@ +--- +layout: post +title: "Welcome to Jekyll!" +date: 2024-09-23 12:16:06 -0700 +categories: jekyll update +--- +You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated. + +Jekyll requires blog post files to be named according to the following format: + +`YEAR-MONTH-DAY-title.MARKUP` + +Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `MARKUP` is the file extension representing the format used in the file. After that, include the necessary front matter. Take a look at the source for this post to get an idea about how it works. + +Jekyll also offers powerful support for code snippets: + +{% highlight ruby %} +def print_hi(name) + puts "Hi, #{name}" +end +print_hi('Tom') +#=> prints 'Hi, Tom' to STDOUT. +{% endhighlight %} + +Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk]. + +[jekyll-docs]: https://jekyllrb.com/docs/home +[jekyll-gh]: https://github.com/jekyll/jekyll +[jekyll-talk]: https://talk.jekyllrb.com/ diff --git a/help/about.markdown b/help/about.markdown new file mode 100644 index 000000000000..8b4e0b28c83e --- /dev/null +++ b/help/about.markdown @@ -0,0 +1,18 @@ +--- +layout: page +title: About +permalink: /about/ +--- + +This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](https://jekyllrb.com/) + +You can find the source code for Minima at GitHub: +[jekyll][jekyll-organization] / +[minima](https://github.com/jekyll/minima) + +You can find the source code for Jekyll at GitHub: +[jekyll][jekyll-organization] / +[jekyll](https://github.com/jekyll/jekyll) + + +[jekyll-organization]: https://github.com/jekyll diff --git a/help/index.markdown b/help/index.markdown new file mode 100644 index 000000000000..06715078416f --- /dev/null +++ b/help/index.markdown @@ -0,0 +1,6 @@ +--- +# Feel free to add content and custom Front Matter to this file. +# To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults + +layout: home +--- diff --git a/help/robots.txt b/help/robots.txt new file mode 100644 index 000000000000..6ffbc308f73e --- /dev/null +++ b/help/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Disallow: / + From bf2b7f9c857b5dd9a8d95695c8f04cfc31e1054e Mon Sep 17 00:00:00 2001 From: David Barrett Date: Mon, 23 Sep 2024 21:14:07 -0700 Subject: [PATCH 044/114] Configured Jekyll action and build steps --- .github/workflows/deployNewHelp.yml | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/deployNewHelp.yml diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml new file mode 100644 index 000000000000..c9061e99861b --- /dev/null +++ b/.github/workflows/deployNewHelp.yml @@ -0,0 +1,68 @@ +on: + # Run on any push to main that has changes to the help directory + push: + branches: + - main + paths: + - 'help/**' + + # Run on any pull request (except PRs against staging or production) that has changes to the docs directory + pull_request: + types: [opened, synchronize] + branches-ignore: [staging, production] + paths: + - 'help/**' + + # Run on any manual trigger + workflow_dispatch: + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "newhelp" + cancel-in-progress: false + +jobs: + build: + env: + IS_PR_FROM_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1.0.13 # Using the latest stable version + with: + source: ./help/ + destination: ./help/_site + + - name: Deploy to Cloudflare Pages + uses: cloudflare/pages-action@v1 # Using the latest stable version + id: deploy + if: env.IS_PR_FROM_FORK != 'true' + with: + apiToken: ${{ secrets.CLOUDFLARE_PAGES_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + projectName: newhelp + directory: ./help/_site + + - name: Setup Cloudflare CLI + if: env.IS_PR_FROM_FORK != 'true' + run: pip3 install cloudflare==2.19.0 + + - name: Purge Cloudflare cache + if: env.IS_PR_FROM_FORK != 'true' + run: $HOME/.local/bin/cli4 --verbose --delete hosts=["help.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache + env: + CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} + + - name: Leave a comment on the PR + uses: actions-cool/maintain-one-comment@v3.2.0 # Using the latest stable version + if: ${{ github.event_name == 'pull_request' && env.IS_PR_FROM_FORK != 'true' }} + with: + token: ${{ secrets.OS_BOTIFY_TOKEN }} + body: ${{ format('A preview of your New Help changes have been deployed to {0} :zap:️', steps.deploy.outputs.alias) }} + From d0bbf5646ae5ad7d4eb63055269b2130afc22d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Tue, 24 Sep 2024 11:39:43 +0200 Subject: [PATCH 045/114] add back the qr code download option on native platforms --- src/pages/ShareCodePage.tsx | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index e11e99a6e852..2a417a9a8174 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -8,8 +8,8 @@ import ContextMenuItem from '@components/ContextMenuItem'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; -import QRShare from '@components/QRShare'; -import type {QRShareHandle} from '@components/QRShare/types'; +import QRShareWithDownload from '@components/QRShare/QRShareWithDownload'; +import type QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -18,6 +18,7 @@ import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; +import getPlatform from '@libs/getPlatform'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import * as Url from '@libs/Url'; @@ -58,7 +59,8 @@ function ShareCodePage({report, policy}: ShareCodePageProps) { const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const {environmentURL} = useEnvironment(); - const qrCodeRef = useRef(null); + const qrCodeRef = useRef(null); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isReport = !!report?.reportID; @@ -82,6 +84,8 @@ function ShareCodePage({report, policy}: ShareCodePageProps) { }, [report, currentUserPersonalDetails, isReport]); const title = isReport ? ReportUtils.getReportName(report) : currentUserPersonalDetails.displayName ?? ''; + const platform = getPlatform(); + const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); const url = isReport ? `${urlWithTrailingSlash}${ROUTES.REPORT_WITH_ID.getRoute(report.reportID)}` @@ -111,24 +115,14 @@ function ShareCodePage({report, policy}: ShareCodePageProps) { /> - {/* - Right now QR code download button is not shown anymore - This is a temporary measure because right now it's broken because of the Fabric update. - We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. - - Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. - */} - @@ -142,6 +136,15 @@ function ShareCodePage({report, policy}: ShareCodePageProps) { onPress={() => Clipboard.setString(url)} shouldLimitWidth={false} /> + {isNative && ( + qrCodeRef.current?.download?.()} + /> + )} Date: Tue, 24 Sep 2024 11:48:01 +0200 Subject: [PATCH 046/114] fix lint --- src/pages/ShareCodePage.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index 2a417a9a8174..0f8edf2c8d63 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -123,6 +123,9 @@ function ShareCodePage({report, policy}: ShareCodePageProps) { logo={isReport ? expensifyLogo : (UserUtils.getAvatarUrl(currentUserPersonalDetails?.avatar, currentUserPersonalDetails?.accountID) as ImageSourcePropType)} logoRatio={isReport ? CONST.QR.EXPENSIFY_LOGO_SIZE_RATIO : CONST.QR.DEFAULT_LOGO_SIZE_RATIO} logoMarginRatio={isReport ? CONST.QR.EXPENSIFY_LOGO_MARGIN_RATIO : CONST.QR.DEFAULT_LOGO_MARGIN_RATIO} + svgLogo={svgLogo} + svgLogoFillColor={svgLogoFillColor} + logoBackgroundColor={logoBackgroundColor} />
From d8cda530c0ebfbae9cec7be1daacf08735d16b11 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Tue, 24 Sep 2024 11:15:18 +0200 Subject: [PATCH 047/114] Add backTo param handling when opening Report from Search --- src/ROUTES.ts | 14 +++++++---- src/components/PromotedActionsBar.tsx | 2 +- src/components/Search/index.tsx | 8 ++++--- .../SelectionList/Search/ReportListItem.tsx | 4 +++- src/components/SelectionList/types.ts | 5 ++++ .../linkingConfig/getAdaptedStateFromPath.ts | 24 +++++++++---------- src/libs/SearchUtils.ts | 20 ++++++++++++---- 7 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c0ec944b71e1..3d24e3a2dafc 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -59,11 +59,9 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_IN: 'search/filters/in', SEARCH_REPORT: { route: 'search/view/:reportID/:reportActionID?', - getRoute: (reportID: string, reportActionID?: string) => { - if (reportActionID) { - return `search/view/${reportID}/${reportActionID}` as const; - } - return `search/view/${reportID}` as const; + getRoute: ({reportID, reportActionID, backTo}: {reportID: string; reportActionID?: string; backTo?: string}) => { + const baseRoute = reportActionID ? (`search/view/${reportID}/${reportActionID}` as const) : (`search/view/${reportID}` as const); + return getUrlWithBackToParam(baseRoute, backTo); }, }, TRANSACTION_HOLD_REASON_RHP: 'search/hold', @@ -1552,6 +1550,12 @@ type Route = { type RoutesValidationError = 'Error: One or more routes defined within `ROUTES` have not correctly used `as const` in their `getRoute` function return value.'; +/** + * Represents all routes in the app as a union of literal strings. + * + * If TS throws on this line, it implies that one or more routes defined within `ROUTES` have not correctly used + * `as const` in their `getRoute` function return value. + */ // eslint-disable-next-line @typescript-eslint/no-unused-vars type RouteIsPlainString = AssertTypesNotEqual; diff --git a/src/components/PromotedActionsBar.tsx b/src/components/PromotedActionsBar.tsx index c374259f0447..a2e3566b159d 100644 --- a/src/components/PromotedActionsBar.tsx +++ b/src/components/PromotedActionsBar.tsx @@ -97,7 +97,7 @@ const PromotedActions = { return; } - ReportUtils.changeMoneyRequestHoldStatus(reportAction, ROUTES.SEARCH_REPORT.getRoute(targetedReportID)); + ReportUtils.changeMoneyRequestHoldStatus(reportAction, ROUTES.SEARCH_REPORT.getRoute({reportID: targetedReportID})); }, }), } satisfies PromotedActionsType; diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index d1080da19932..d9c8f751ac70 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -227,7 +227,7 @@ function Search({queryJSON}: SearchProps) { } const ListItem = SearchUtils.getListItem(type, status); - const data = SearchUtils.getSections(type, status, searchResults.data, searchResults.search); + const data = SearchUtils.getSections(type, status, searchResults.data, searchResults.search, queryJSON.inputQuery); const sortedData = SearchUtils.getSortedSections(type, status, data, sortBy, sortOrder); const sortedSelectedData = sortedData.map((item) => mapToItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple)); @@ -294,13 +294,15 @@ function Search({queryJSON}: SearchProps) { SearchActions.createTransactionThread(hash, item.transactionID, reportID, item.moneyRequestReportActionID); } + const backTo = ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: queryJSON.inputQuery}); + if (SearchUtils.isReportActionListItemType(item)) { const reportActionID = item.reportActionID; - Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(reportID, reportActionID)); + Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo})); return; } - Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(reportID)); + Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo})); }; const fetchMoreResults = () => { diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx index a0b96547bcd8..63705bda90f1 100644 --- a/src/components/SelectionList/Search/ReportListItem.tsx +++ b/src/components/SelectionList/Search/ReportListItem.tsx @@ -84,7 +84,9 @@ function ReportListItem({ }; const openReportInRHP = (transactionItem: TransactionListItemType) => { - Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(transactionItem.transactionThreadReportID)); + const backTo = ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: reportItem.currentSearchQuery}); + + Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: transactionItem.transactionThreadReportID, backTo})); }; if (!reportItem?.reportName && reportItem.transactions.length > 1) { diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 14c83ef25ed4..3fa901073e0e 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -1,5 +1,6 @@ import type {MutableRefObject, ReactElement, ReactNode} from 'react'; import type {GestureResponderEvent, InputModeOptions, LayoutChangeEvent, SectionListData, StyleProp, TextInput, TextStyle, ViewStyle} from 'react-native'; +import type {SearchQueryString} from '@components/Search/types'; import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; // eslint-disable-next-line no-restricted-imports import type CursorStyles from '@styles/utils/cursor/types'; @@ -233,7 +234,11 @@ type ReportListItemType = ListItem & /** The personal details of the user paying the request */ to: SearchPersonalDetails; + /** List of transactions that belong to this report */ transactions: TransactionListItemType[]; + + /** The current search query that was used to display these report items */ + currentSearchQuery: SearchQueryString; }; type ListItemProps = CommonListItemProps & { diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts index 2c96e5796309..f92b133d719a 100644 --- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts @@ -114,22 +114,22 @@ function getMatchingRootRouteForRHPRoute(route: NavigationPartialRoute): Navigat if (route.params && 'backTo' in route.params && typeof route.params.backTo === 'string') { const stateForBackTo = getStateFromPath(route.params.backTo, config); if (stateForBackTo) { - // eslint-disable-next-line @typescript-eslint/no-shadow - const rhpNavigator = stateForBackTo.routes.find((route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR); - - const centralPaneOrFullScreenNavigator = stateForBackTo.routes.find( - // eslint-disable-next-line @typescript-eslint/no-shadow - (route) => isCentralPaneName(route.name) || route.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR, - ); - // If there is rhpNavigator in the state generated for backTo url, we want to get root route matching to this rhp screen. + const rhpNavigator = stateForBackTo.routes.find((rt) => rt.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR); if (rhpNavigator && rhpNavigator.state) { return getMatchingRootRouteForRHPRoute(findFocusedRoute(stateForBackTo) as NavigationPartialRoute); } - // If we know that backTo targets the root route (central pane or full screen) we want to use it. - if (centralPaneOrFullScreenNavigator && centralPaneOrFullScreenNavigator.state) { - return centralPaneOrFullScreenNavigator as NavigationPartialRoute; + // If we know that backTo targets the root route (full screen) we want to use it. + const fullScreenNavigator = stateForBackTo.routes.find((rt) => rt.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR); + if (fullScreenNavigator && fullScreenNavigator.state) { + return fullScreenNavigator as NavigationPartialRoute; + } + + // If we know that backTo targets a central pane screen we want to use it. + const centralPaneScreen = stateForBackTo.routes.find((rt) => isCentralPaneName(rt.name)); + if (centralPaneScreen) { + return centralPaneScreen as NavigationPartialRoute; } } } @@ -191,7 +191,7 @@ function getAdaptedState(state: PartialState if (focusedRHPRoute) { let matchingRootRoute = getMatchingRootRouteForRHPRoute(focusedRHPRoute); const isRHPScreenOpenedFromLHN = focusedRHPRoute?.name && RHP_SCREENS_OPENED_FROM_LHN.includes(focusedRHPRoute?.name as RHPScreenOpenedFromLHN); - // This may happen if this RHP doens't have a route that should be under the overlay defined. + // This may happen if this RHP doesn't have a route that should be under the overlay defined. if (!matchingRootRoute || isRHPScreenOpenedFromLHN) { metainfo.isCentralPaneAndBottomTabMandatory = false; metainfo.isFullScreenNavigatorMandatory = false; diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index e178c7dcb77b..fa50fbc0ce47 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -250,7 +250,7 @@ function getIOUReportName(data: OnyxTypes.SearchResults['data'], reportItem: Sea return reportItem.reportName; } -function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']): ReportListItemType[] { +function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search'], currentSearchQuery: SearchQueryString): ReportListItemType[] { const shouldShowMerchant = getShouldShowMerchant(data); const doesDataContainAPastYearTransaction = shouldShowYear(data); @@ -269,6 +269,7 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx from: data.personalDetailsList?.[reportItem.accountID ?? -1], to: reportItem.managerID ? data.personalDetailsList?.[reportItem.managerID] : emptyPersonalDetails, transactions, + currentSearchQuery, reportName: isIOUReport ? getIOUReportName(data, reportItem) : reportItem.reportName, }; } else if (isTransactionEntry(key)) { @@ -311,21 +312,30 @@ function getListItem(type: SearchDataTypes, status: SearchStatus): ListItemType< if (type === CONST.SEARCH.DATA_TYPES.CHAT) { return ChatListItem; } - return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? TransactionListItem : ReportListItem; + if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) { + return TransactionListItem; + } + return ReportListItem; } -function getSections(type: SearchDataTypes, status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']) { +function getSections(type: SearchDataTypes, status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search'], currentSearchQuery: SearchQueryString) { if (type === CONST.SEARCH.DATA_TYPES.CHAT) { return getReportActionsSections(data); } - return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? getTransactionsSections(data, metadata) : getReportSections(data, metadata); + if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) { + return getTransactionsSections(data, metadata); + } + return getReportSections(data, metadata, currentSearchQuery); } function getSortedSections(type: SearchDataTypes, status: SearchStatus, data: ListItemDataType, sortBy?: SearchColumnType, sortOrder?: SortOrder) { if (type === CONST.SEARCH.DATA_TYPES.CHAT) { return getSortedReportActionData(data as ReportActionListItemType[]); } - return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? getSortedTransactionData(data as TransactionListItemType[], sortBy, sortOrder) : getSortedReportData(data as ReportListItemType[]); + if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) { + return getSortedTransactionData(data as TransactionListItemType[], sortBy, sortOrder); + } + return getSortedReportData(data as ReportListItemType[]); } function getSortedTransactionData(data: TransactionListItemType[], sortBy?: SearchColumnType, sortOrder?: SortOrder) { From 6e108b14c6d5b28516bd139034c7ea095293b30e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 25 Sep 2024 00:36:02 +0800 Subject: [PATCH 048/114] add null safety --- src/pages/home/report/ReportActionItemSingle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 483247fa28a8..c6b879746baf 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -83,7 +83,7 @@ function ReportActionItemSingle({ const {translate} = useLocalize(); const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT; const actorAccountID = ReportUtils.getReportActionActorAccountID(action, iouReport); - const policy = usePolicy(report.policyID); + const policy = usePolicy(report?.policyID); const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`); let displayName = ReportUtils.getDisplayNameForParticipant(actorAccountID); From fd09dfbf1831e3695af99905bc50c467208f274b Mon Sep 17 00:00:00 2001 From: Christina Dobrzynski <51066321+Christinadobrzyn@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:58:03 -0600 Subject: [PATCH 049/114] Update Send-an-invoice.md Adding screenshots to the Send Invoice section of the help article based on this GH - https://github.com/Expensify/Expensify/issues/407250 --- .../expenses-&-payments/Send-an-invoice.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md index 85bd6b655186..d320feb9a93d 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md +++ b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md @@ -57,6 +57,18 @@ Only workspace admins can send invoices. Invoices can be sent directly from Expe {% include end-selector.html %} +![Go to Account Settings click Workspace]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_01.png)){:width="100%"} + +![Click More Features for the workspace and enable Invoices]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_02.png)){:width="100%"} + +![Click the green button Send Invoice]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_03.png)){:width="100%"} + +![Enter Invoice amount]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_04.png)){:width="100%"} + +![Choose a recipient]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_05.png)){:width="100%"} + +![Add Invoice details and Send Invoice]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_06.png)){:width="100%"} + # Receive invoice payment If you have not [connected a business bank account](https://help.expensify.com/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account) to receive invoice payments, you will see an **Invoice balance** in your [Wallet](https://help.expensify.com/articles/new-expensify/expenses-&-payments/Set-up-your-wallet). Expensify will automatically transfer these invoice payments once a business bank account is connected. From 9548df38308885fde366c564253467ac8d928dd7 Mon Sep 17 00:00:00 2001 From: Christina Dobrzynski <51066321+Christinadobrzyn@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:09:09 -0600 Subject: [PATCH 050/114] Update Pay-an-invoice.md Adding images to the Pay-an-invoice help article https://help.expensify.com/articles/new-expensify/expenses-&-payments/Pay-an-invoice --- .../new-expensify/expenses-&-payments/Pay-an-invoice.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md index 727c6b86b7a6..a1786f8350a4 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md +++ b/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md @@ -32,6 +32,10 @@ To pay an invoice, You can also view all unpaid invoices by searching for the sender’s email or phone number on the left-hand side of the app. The invoices waiting for your payment will have a green dot. +![Click Pay Button on the Invoice]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoice_01.png)){:width="100%"} + +![Choose how to pay the invoice from the options]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoice_02.png)){:width="100%"} + {% include faq-begin.md %} **Can someone else pay an invoice besides the person who received it?** From 8460793e25f42116170a8a306d4326cdcb7e82e9 Mon Sep 17 00:00:00 2001 From: Christina Dobrzynski <51066321+Christinadobrzyn@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:40:13 -0600 Subject: [PATCH 051/114] Update Pay-an-invoice.md Updated the image link as I entered it wrong --- .../new-expensify/expenses-&-payments/Pay-an-invoice.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md index a1786f8350a4..d7ded144415b 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md +++ b/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md @@ -32,9 +32,9 @@ To pay an invoice, You can also view all unpaid invoices by searching for the sender’s email or phone number on the left-hand side of the app. The invoices waiting for your payment will have a green dot. -![Click Pay Button on the Invoice]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoice_01.png)){:width="100%"} +![Click Pay Button on the Invoice]({{site.url}}/assets/images/invoice_01.png){:width="100%"} -![Choose how to pay the invoice from the options]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoice_02.png)){:width="100%"} +![Choose how to pay the invoice from the options]({{site.url}}/assets/images/invoice_02.png){:width="100%"} {% include faq-begin.md %} From 116348cf7ef588a9d384af1d3f8380d98f6f86ef Mon Sep 17 00:00:00 2001 From: Christina Dobrzynski <51066321+Christinadobrzyn@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:41:28 -0600 Subject: [PATCH 052/114] Update Send-an-invoice.md Updated the image link because I did it wrong the first time. --- .../expenses-&-payments/Send-an-invoice.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md index d320feb9a93d..4d7f4e0dc55f 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md +++ b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md @@ -57,17 +57,17 @@ Only workspace admins can send invoices. Invoices can be sent directly from Expe {% include end-selector.html %} -![Go to Account Settings click Workspace]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_01.png)){:width="100%"} +![Go to Account Settings click Workspace]({{site.url}}/assets/images/invoices_01.png)){:width="100%"} -![Click More Features for the workspace and enable Invoices]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_02.png)){:width="100%"} +![Click More Features for the workspace and enable Invoices]({{site.url}}/assets/images/invoices_02.png)){:width="100%"} -![Click the green button Send Invoice]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_03.png)){:width="100%"} +![Click the green button Send Invoice]({{site.url}}/assets/images/invoices_03.png)){:width="100%"} -![Enter Invoice amount]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_04.png)){:width="100%"} +![Enter Invoice amount]({{site.url}}/assets/images/invoices_04.png)){:width="100%"} -![Choose a recipient]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_05.png)){:width="100%"} +![Choose a recipient]({{site.url}}/assets/images/invoices_05.png)){:width="100%"} -![Add Invoice details and Send Invoice]({{site.url}}/assets/images/[image-name.png](https://github.com/Expensify/App/blob/main/docs/assets/images/invoices_06.png)){:width="100%"} +![Add Invoice details and Send Invoice]({{site.url}}/assets/images/invoices_06.png)){:width="100%"} # Receive invoice payment From 288ccae9d218dde3a2c4094469f9871a6b94167a Mon Sep 17 00:00:00 2001 From: David Barrett Date: Tue, 24 Sep 2024 18:38:28 -0700 Subject: [PATCH 053/114] Slight updates from review --- .github/workflows/deployNewHelp.yml | 2 +- help/_config.yml | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index c9061e99861b..35c6c1f184dc 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -55,7 +55,7 @@ jobs: - name: Purge Cloudflare cache if: env.IS_PR_FROM_FORK != 'true' - run: $HOME/.local/bin/cli4 --verbose --delete hosts=["help.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache + run: "$HOME/.local/bin/cli4" --verbose --delete hosts=["newhelp.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache env: CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} diff --git a/help/_config.yml b/help/_config.yml index ef7ba7c13913..72bb4d942f9a 100644 --- a/help/_config.yml +++ b/help/_config.yml @@ -18,16 +18,14 @@ # You can create any custom variable you would like, and they will be accessible # in the templates via {{ site.myvariable }}. -title: Your awesome title -email: your-email@example.com +title: New Expensify Help +email: concierge@expensify.com description: >- # this means to ignore newlines until "baseurl:" - Write an awesome description for your new site here. You can edit this - line in _config.yml. It will appear in your document head meta (for - Google search results) and in your feed.xml site description. + Comprehensive help documentation for New Expensify. baseurl: "" # the subpath of your site, e.g. /blog -url: "" # the base hostname & protocol for your site, e.g. http://example.com -twitter_username: jekyllrb -github_username: jekyll +url: "https://newhelp.expensify.com" # the base hostname & protocol for your site, e.g. http://example.com +twitter_username: expensify +github_username: expensify # Build settings theme: minima From 903aedf404ee877a63a0fb5137d676f5e4fb8b22 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Tue, 24 Sep 2024 18:41:43 -0700 Subject: [PATCH 054/114] Undoing suggested fix to see if it fixees worse syntax error --- .github/workflows/deployNewHelp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 35c6c1f184dc..915469f97b0d 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -55,7 +55,7 @@ jobs: - name: Purge Cloudflare cache if: env.IS_PR_FROM_FORK != 'true' - run: "$HOME/.local/bin/cli4" --verbose --delete hosts=["newhelp.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache + run: $HOME/.local/bin/cli4 --verbose --delete hosts=["newhelp.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache env: CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} From 2b4f0d2a58c7d709dab646d68b22576d7ef575af Mon Sep 17 00:00:00 2001 From: David Barrett Date: Tue, 24 Sep 2024 19:39:48 -0700 Subject: [PATCH 055/114] Added basic superapp page --- help/_layouts/default.html | 25 ++++++++ help/_layouts/product.html | 11 ++++ help/superapp.md | 116 +++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 help/_layouts/default.html create mode 100644 help/_layouts/product.html create mode 100644 help/superapp.md diff --git a/help/_layouts/default.html b/help/_layouts/default.html new file mode 100644 index 000000000000..cf95e1f54b06 --- /dev/null +++ b/help/_layouts/default.html @@ -0,0 +1,25 @@ + + + + + + {{ page.title }} + + +
+ +
+ + +
+ {{ content }} +
+ +
+

© 2024 Your Website

+
+ + + diff --git a/help/_layouts/product.html b/help/_layouts/product.html new file mode 100644 index 000000000000..cb8b5e882f24 --- /dev/null +++ b/help/_layouts/product.html @@ -0,0 +1,11 @@ +--- +layout: default +--- + +

{{ page.title }}

+ + +
+ {{ content }} +
+ diff --git a/help/superapp.md b/help/superapp.md new file mode 100644 index 000000000000..c09ca61bcf46 --- /dev/null +++ b/help/superapp.md @@ -0,0 +1,116 @@ +--- +layout: product +title: Expensify Superapp +--- +# Expensify Superapp + +## Introduction +The Expensify Superapp packs the full power of 6 world class business, finance, and collaboration products, into a single app that works identically on desktop and mobile, efficiently with your colleagues, and seamlessly with your customers, vendors, family, and friends. + +### When should I use Expensify? +Expensify can do a lot. You should check us out whenever you need to: + +Track and manage expenses +: Whether you are reimbursing employee receipts, deducting personal expenses, or just splitting the bill, Expensify Expense is for you. + +Issue corporate cards +: Skip the reimbursement and capture receipts electronically in realtime by issuing the Expensify Card to yourself and your employees. + +Book and manage travel +: If you are booking your own business trip, arranging a trip for a colleague, or managing the travel of your whole company, Expensify Travel has got you covered. + +Chat with friends and coworkers +: Whether it's collaborating with your team, supporting you client, negotiating with your vendor, or just saying Hi to a friend, Expensify Chat connects you with anyone with an email address or SMS number + +Collect invoice payments online +: Expensify Invoice allows you to collect online payments from consumers and businesses alike – anyone with an email address or SMS number. + +Approve and pay bills online +: Scan, process, and approve bills online using Expensify Billpay, then we'll pay them electronically or via check, whatever they prefer. + +If you send, receive, or spend money – or even just talk to literally anyone, about literally anything – Expensify is the tool for you. + +### Who uses Expensify? +Expensify offers something for everyone. Some people who commonly use us include: + +Individuals +: Millions of individuals use Expensify to track personal expenses to maximize their tax deductions, stay within personal budgets, or just see where their money is going. + +Friends +: Expensify is a great way to split bills with friends, whether it's monthly rent and household expenses, a big ticket bachelorette party, or just grabbing drinks with friends. + +Employees +: Road warriors and desk jockeys alike count on Expensify to reimburse expense reports they create in international airports, swanky hotels, imposing conference centers, quaint coffeeshops, and boring office supply stores around the world. + +Managers +: Bosses manage corporate spend with Expensify to empower their best (and keep tabs on their… not so best), staying ahead of schedule and under budget. + +Accountants +: Internal accountants, fractional CFOs, CAS practices – you name it, they use Expensify to Invoice customers, process vendor bills, capture eReceipts, manage corporate spend: the whole shebang. If you're an accountant, we're already best friends. + +Travel managers +: Anyone looking to manage employee travel has come to the right place. + +If you are a person online who does basically anything, you can probably do it with Expensify. + +### Why should I use Expensify? +Though we do a lot, you've got a lot of options for everything we do. But you should use us because we are: +Simple enough for individuals - We've worked extremely hard to make a product that strips out all the complex jargon and enterprise baggage, and gives you a simple tool that doesn't overwhelm you with functionality and language you don't understand. + +Powerful enough for enterprises +: We've worked extremely hard to make a product that "scales up" to reveal increasingly sophisticated features, but only to those who need it, and only when they need it. Expensify is used by public companies, multinational companies, companies with tens of thousands of employees, non-profits, investment firms, accounting firms, manufacturers, and basically every industry in every currency and in every country around the world. If you are a company, we can support your needs, no matter how big or small. + +6 products for the price of 1 +: Do you pay for an expense management system? A corporate card? A travel management platform? An enterprise chat tool? An invoicing tool? A billpay tool? Now you don't need to. Expensify's superapp design allows us to offer ALL these features on a single platform, at probably less than what you pay for any of them individually. + +Supports everyone everywhere +: Expensify works on iPhones and Androids, desktops and browsers. We support every currency, and can reimburse to almost any country. You don't need to be an IT wizard – if you can type in their email address or SMS number, you can do basically everything with them. + +You get paid to use it +: Do you spend money? Spend it on the Expensify Card and we pay you up to 2% cashback. It's your money after all. + +Revenue share for accountants +: Do you manage the books for a bunch of clients? Become an Expensify Approved Accountant and take home 0.5% revenue share. Or share it with your clients as a discount, up to you! + +You are in the driver's seat; we're here to earn your business. But we're going to work harder for you than the other guys, and you won't be disappointed. + +## Concepts +The Expensify Superapp has a lot of moving pieces, so let's break them down one by one. + +### What makes Expensify a superapp? +A "superapp" is a single app that combines multiple products into one seamlessly interconnected experience. Expensify isn't a "suite" of separate products linked through a single account – Expensify is a single app with a single core design that can perform multiple product functions. The secret to making such a seamless experience is that we build all product functions atop the same common core: + +App +: The basis of the superapp experience is the actual app itself, which runs on your mobile phone or desktop computer. (What is the Expensify app?) + +Chats +: Even if you don't plan on using Expensify Chat for enterprise-grade workspace collaboration, chat is infused through the entire product. (What is a chat?) + +Expense +: Even if you aren't actively managing your expenses, you've still got them. Every product that deals with money is ultimately dealing with expenses of some kind. (What is an expense?) + +Workspace +: Though Expensify works great for our millions of individual members, every product really shines when used between groups of members sharing a "workspace". (What is a workspace?) + +Domain +: To support more advanced security features, many products provide extra functionality to members who are on the same email "domain". (What is a domain?) + +These are the foundational concepts you'll see again and again that underpin the superapp as a whole. + +### What is the Expensify app? +Just like your eyes are a window to your soul, the Expensify App is the doorway through which you experience the entire global world of interconnected chat-centric collaborative data that comprises the Expensify network. The main tools of this app consist of: + +Inbox +: The main screen of the app is the Inbox, which highlights exactly what you should do next, consolidated across all products. (What does the Inbox do?) + +Search +: The next major screen is Search, which as you'd expect, let's you search everything across all products, from one convenient and powerful place. (What does Search do?) + +Settings +: Settings wraps up all your personal, workspace, and domain configuration options, all in one helpful space. (What are Expensify's settings?) + +Create +: Finally, the big green plus button is the Create button, which lets you create pretty much anything, across all the products. (What does the Create button do?) + +It's a deceptively simple app, with a few very familiar looking screens and buttons that unlock an incredible range of sophisticated multi-product power. + From 808d5a92b315659452e590ab8ff974a3a813253d Mon Sep 17 00:00:00 2001 From: David Barrett Date: Tue, 24 Sep 2024 21:56:13 -0700 Subject: [PATCH 056/114] Updated for custom plugin for deep linking --- help/Gemfile | 6 +++ help/Gemfile.lock | 20 ++++++++- help/_plugins/51_HeaderIDPostRender.rb | 59 ++++++++++++++++++++++++++ help/superapp.md | 1 - 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 help/_plugins/51_HeaderIDPostRender.rb diff --git a/help/Gemfile b/help/Gemfile index 2c345b82ae4d..3bdd94c99fb9 100644 --- a/help/Gemfile +++ b/help/Gemfile @@ -8,8 +8,10 @@ source "https://rubygems.org" # This will help ensure the proper Jekyll version is running. # Happy Jekylling! gem "jekyll", "~> 4.3.4" + # This is the default theme for new Jekyll sites. You may change this to anything you like. gem "minima", "~> 2.5" + # If you want to use GitHub Pages, remove the "gem "jekyll"" above and # uncomment the line below. To upgrade, run `bundle update github-pages`. # gem "github-pages", group: :jekyll_plugins @@ -31,3 +33,7 @@ gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin] # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem # do not have a Java counterpart. gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] + +# For HTML editing +gem "nokogiri" + diff --git a/help/Gemfile.lock b/help/Gemfile.lock index 77ac51524535..7434e1c4e935 100644 --- a/help/Gemfile.lock +++ b/help/Gemfile.lock @@ -76,13 +76,30 @@ GEM rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.4.0) + mini_portile2 (2.8.7) minima (2.5.2) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) + nokogiri (1.16.7) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + nokogiri (1.16.7-aarch64-linux) + racc (~> 1.4) + nokogiri (1.16.7-arm-linux) + racc (~> 1.4) + nokogiri (1.16.7-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.7-x86-linux) + racc (~> 1.4) + nokogiri (1.16.7-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.7-x86_64-linux) + racc (~> 1.4) pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (6.0.1) + racc (1.8.1) rake (13.2.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) @@ -138,7 +155,7 @@ GEM terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) unicode-display_width (2.6.0) - webrick (1.8.1) + webrick (1.8.2) PLATFORMS aarch64-linux @@ -174,6 +191,7 @@ DEPENDENCIES jekyll (~> 4.3.4) jekyll-feed (~> 0.12) minima (~> 2.5) + nokogiri tzinfo (>= 1, < 3) tzinfo-data wdm (~> 0.1) diff --git a/help/_plugins/51_HeaderIDPostRender.rb b/help/_plugins/51_HeaderIDPostRender.rb new file mode 100644 index 000000000000..4af97cc788f6 --- /dev/null +++ b/help/_plugins/51_HeaderIDPostRender.rb @@ -0,0 +1,59 @@ +require 'nokogiri' +require 'cgi' # Use CGI for URL encoding + +module Jekyll + class HeaderIDPostRender + # Hook into Jekyll's post_render stage to ensure we work with the final HTML + Jekyll::Hooks.register :pages, :post_render, priority: 51 do |page| + process_page(page) + end + + Jekyll::Hooks.register :documents, :post_render, priority: 51 do |post| + process_page(post) + end + + def self.process_page(page) + return unless page.output_ext == ".html" # Only apply to HTML pages + return if page.output.nil? # Skip if no output has been generated + + puts " Processing page: #{page.path}" + + # Parse the page's content for header elements + doc = Nokogiri::HTML(page.output) + h1_id = "" + h2_id = "" + h3_id = "" + + # Process all

,

, and

elements + (2..4).each do |level| + doc.css("h#{level}").each do |header| + header_text = header.text.strip.downcase + header_id = CGI.escape(header_text.gsub(/\s+/, '-').gsub(/[^\w\-]/, '')) + + puts " Found h#{level}: '#{header_text}' -> ID: '#{header_id}'" + + # Create hierarchical IDs by appending to the parent header IDs + if level == 2 + h2_id = header_id + header['id'] = h2_id + elsif level == 3 + h3_id = "#{h2_id}:#{header_id}" + header['id'] = h3_id + elsif level == 4 + h4_id = "#{h3_id}:#{header_id}" + header['id'] = h4_id + end + + puts " Assigned ID: #{header['id']}" + end + end + + # Log the final output being written + puts " Writing updated HTML for page: #{page.path}" + + # Write the updated HTML back to the page + page.output = doc.to_html + end + end +end + diff --git a/help/superapp.md b/help/superapp.md index c09ca61bcf46..d09860a1ce7e 100644 --- a/help/superapp.md +++ b/help/superapp.md @@ -2,7 +2,6 @@ layout: product title: Expensify Superapp --- -# Expensify Superapp ## Introduction The Expensify Superapp packs the full power of 6 world class business, finance, and collaboration products, into a single app that works identically on desktop and mobile, efficiently with your colleagues, and seamlessly with your customers, vendors, family, and friends. From 203403cbedf15b19a848e0742491888ef99a7f20 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 25 Sep 2024 15:32:42 +0700 Subject: [PATCH 057/114] fix: deleted item remains actionable --- src/pages/Search/SavedSearchItemThreeDotMenu.tsx | 11 ++++++++--- src/pages/Search/SearchTypeMenu.tsx | 8 +++++++- src/pages/Search/SearchTypeMenuNarrow.tsx | 2 ++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/pages/Search/SavedSearchItemThreeDotMenu.tsx b/src/pages/Search/SavedSearchItemThreeDotMenu.tsx index fdb06828901e..bd7a94bc1840 100644 --- a/src/pages/Search/SavedSearchItemThreeDotMenu.tsx +++ b/src/pages/Search/SavedSearchItemThreeDotMenu.tsx @@ -2,18 +2,23 @@ import React, {useRef, useState} from 'react'; import {View} from 'react-native'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import ThreeDotsMenu from '@components/ThreeDotsMenu'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; type SavedSearchItemThreeDotMenuProps = { menuItems: PopoverMenuItem[]; + isDisabledItem: boolean; }; -function SavedSearchItemThreeDotMenu({menuItems}: SavedSearchItemThreeDotMenuProps) { +function SavedSearchItemThreeDotMenu({menuItems, isDisabledItem}: SavedSearchItemThreeDotMenuProps) { const threeDotsMenuContainerRef = useRef(null); const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState({horizontal: 0, vertical: 0}); - + const styles = useThemeStyles(); return ( - + { diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 704ebbb5316e..947f81ac4e8d 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -116,9 +116,15 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { SearchActions.clearAllFilters(); Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: item?.query ?? ''})); }, - rightComponent: , + rightComponent: ( + + ), styles: [styles.alignItemsCenter], pendingAction: item.pendingAction, + disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }; if (!isNarrow) { diff --git a/src/pages/Search/SearchTypeMenuNarrow.tsx b/src/pages/Search/SearchTypeMenuNarrow.tsx index 5c466b95d461..06bc016669df 100644 --- a/src/pages/Search/SearchTypeMenuNarrow.tsx +++ b/src/pages/Search/SearchTypeMenuNarrow.tsx @@ -118,10 +118,12 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title, horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, }} + disabled={item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE} /> ), isSelected: currentSavedSearch?.hash === item.hash, pendingAction: item.pendingAction, + disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, })); const allMenuItems = []; allMenuItems.push(...popoverMenuItems); From f04c1e9533a695dbf7ed1776ffd7ed54beae30ee Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 25 Sep 2024 15:34:39 +0700 Subject: [PATCH 058/114] enable form when offline --- src/pages/Search/SavedSearchRenamePage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Search/SavedSearchRenamePage.tsx b/src/pages/Search/SavedSearchRenamePage.tsx index 2b227e581ac4..bc9a41597fa2 100644 --- a/src/pages/Search/SavedSearchRenamePage.tsx +++ b/src/pages/Search/SavedSearchRenamePage.tsx @@ -56,6 +56,7 @@ function SavedSearchRenamePage({route}: {route: {params: {q: string; name: strin submitButtonText={translate('common.save')} onSubmit={onSaveSearch} style={[styles.mh5, styles.flex1]} + enabledWhenOffline > Date: Wed, 25 Sep 2024 11:09:23 +0200 Subject: [PATCH 059/114] use compatible version with new Fabric arch --- package-lock.json | 7 ++++--- package.json | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87c7dd3d2600..b2638d613999 100644 --- a/package-lock.json +++ b/package-lock.json @@ -112,7 +112,7 @@ "react-native-svg": "15.6.0", "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", - "react-native-view-shot": "3.8.0", + "react-native-view-shot": "4.0.0-alpha.3", "react-native-vision-camera": "4.0.0-beta.13", "react-native-web": "^0.19.12", "react-native-web-sound": "^0.1.3", @@ -35666,8 +35666,9 @@ } }, "node_modules/react-native-view-shot": { - "version": "3.8.0", - "license": "MIT", + "version": "4.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-4.0.0-alpha.3.tgz", + "integrity": "sha512-o0KVgC6XZqWmLUKVc4q6Ev1QW1kA4g/TF45wj8CgYS13wJuWYJ+nPGCHT9C2jvX/L65mtTollKXp2L8hbDnelg==", "dependencies": { "html2canvas": "^1.4.1" }, diff --git a/package.json b/package.json index 056d29a36c19..a80a12758745 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,7 @@ "react-native-svg": "15.6.0", "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", - "react-native-view-shot": "3.8.0", + "react-native-view-shot": "4.0.0-alpha.3", "react-native-vision-camera": "4.0.0-beta.13", "react-native-web": "^0.19.12", "react-native-web-sound": "^0.1.3", From 1e689679442cb286bad5e6dec5df1ed7187924f2 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:41:21 +1200 Subject: [PATCH 060/114] Fix tooltip size becoming small after suspending and unsuspending --- src/components/Tooltip/BaseGenericTooltip/index.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/Tooltip/BaseGenericTooltip/index.tsx b/src/components/Tooltip/BaseGenericTooltip/index.tsx index 41f3e97c8087..4254080f2e96 100644 --- a/src/components/Tooltip/BaseGenericTooltip/index.tsx +++ b/src/components/Tooltip/BaseGenericTooltip/index.tsx @@ -50,8 +50,17 @@ function BaseGenericTooltip({ useLayoutEffect(() => { // Calculate the tooltip width and height before the browser repaints the screen to prevent flicker // because of the late update of the width and the height from onLayout. + const rootWrapperStyle = rootWrapper?.current?.style; + const isScaled = rootWrapperStyle?.transform === 'scale(0)'; + if (isScaled) { + // Temporarily reset the scale caused by animation to get the untransformed size. + rootWrapperStyle.transform = 'scale(1)'; + } setContentMeasuredWidth(contentRef.current?.getBoundingClientRect().width); setWrapperMeasuredHeight(rootWrapper.current?.getBoundingClientRect().height); + if (isScaled) { + rootWrapperStyle.transform = 'scale(0)'; + } }, []); const {animationStyle, rootWrapperStyle, textStyle, pointerWrapperStyle, pointerStyle} = useMemo( From a452a8effdb298b1bf4185223163557715923975 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Wed, 25 Sep 2024 05:41:35 -0700 Subject: [PATCH 061/114] Removed Github pages dependency to get plugins --- .github/workflows/deployNewHelp.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 915469f97b0d..dfe3f63571dc 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -27,17 +27,26 @@ jobs: env: IS_PR_FROM_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }} runs-on: ubuntu-latest + steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Build with Jekyll - uses: actions/jekyll-build-pages@v1.0.13 # Using the latest stable version + - name: Set up Ruby + uses: ruby/setup-ruby@v1 with: - source: ./help/ - destination: ./help/_site + ruby-version: '3.3.4' # Set to your Ruby version + + - name: Install dependencies + run: | + gem install bundler + bundle install --path vendor/bundle + + - name: Build Jekyll site + run: | + bundle exec jekyll build --source ./help --destination ./help/_site - name: Deploy to Cloudflare Pages uses: cloudflare/pages-action@v1 # Using the latest stable version From 304fdb55ac392bfb717280c399b66e010f4e92c9 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Wed, 25 Sep 2024 05:49:43 -0700 Subject: [PATCH 062/114] Trying to fix build issues in github acitons --- help/Gemfile | 36 ++++++++++++++---------------------- help/Gemfile.lock | 4 +++- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/help/Gemfile b/help/Gemfile index 3bdd94c99fb9..5afaaed2bc64 100644 --- a/help/Gemfile +++ b/help/Gemfile @@ -1,39 +1,31 @@ source "https://rubygems.org" -# Hello! This is where you manage which Jekyll version is used to run. -# When you want to use a different version, change it below, save the -# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: -# -# bundle exec jekyll serve -# -# This will help ensure the proper Jekyll version is running. -# Happy Jekylling! + +# Manage Jekyll version gem "jekyll", "~> 4.3.4" -# This is the default theme for new Jekyll sites. You may change this to anything you like. +# Default theme for new Jekyll sites gem "minima", "~> 2.5" -# If you want to use GitHub Pages, remove the "gem "jekyll"" above and -# uncomment the line below. To upgrade, run `bundle update github-pages`. -# gem "github-pages", group: :jekyll_plugins -# If you have any plugins, put them here! +# Plugins for Jekyll group :jekyll_plugins do gem "jekyll-feed", "~> 0.12" end -# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem -# and associated library. +# For development on macOS and Linux (add webrick for Jekyll 4.3+) +gem "webrick", "~> 1.8" + +# Nokogiri for HTML editing +gem "nokogiri", "~> 1.12" + +# Optional: Add tzinfo for timezone support (mainly for Windows/JRuby environments) platforms :mingw, :x64_mingw, :mswin, :jruby do gem "tzinfo", ">= 1", "< 3" gem "tzinfo-data" end -# Performance-booster for watching directories on Windows -gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin] - -# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem -# do not have a Java counterpart. +# Lock http_parser.rb gem to v0.6.x for JRuby gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] -# For HTML editing -gem "nokogiri" +# Performance-booster for watching directories on Windows +gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin] diff --git a/help/Gemfile.lock b/help/Gemfile.lock index 7434e1c4e935..064546f52759 100644 --- a/help/Gemfile.lock +++ b/help/Gemfile.lock @@ -190,11 +190,13 @@ DEPENDENCIES http_parser.rb (~> 0.6.0) jekyll (~> 4.3.4) jekyll-feed (~> 0.12) + jekyll-seo-tag minima (~> 2.5) - nokogiri + nokogiri (~> 1.12) tzinfo (>= 1, < 3) tzinfo-data wdm (~> 0.1) + webrick (~> 1.8) BUNDLED WITH 2.5.19 From c2080347f0de54ece199c332c50fb37183772a2a Mon Sep 17 00:00:00 2001 From: David Barrett Date: Wed, 25 Sep 2024 05:57:53 -0700 Subject: [PATCH 063/114] More attempted fixes --- .github/workflows/deployNewHelp.yml | 14 +++++--------- help/Gemfile | 20 +++----------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index dfe3f63571dc..24cb8e79fdcf 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -1,23 +1,18 @@ on: - # Run on any push to main that has changes to the help directory push: branches: - main paths: - 'help/**' - # Run on any pull request (except PRs against staging or production) that has changes to the docs directory pull_request: types: [opened, synchronize] branches-ignore: [staging, production] paths: - 'help/**' - # Run on any manual trigger workflow_dispatch: -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "newhelp" cancel-in-progress: false @@ -37,19 +32,20 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.3.4' # Set to your Ruby version + ruby-version: '3.3.4' - name: Install dependencies run: | gem install bundler - bundle install --path vendor/bundle + bundle config set --local path 'vendor/bundle' + bundle install - name: Build Jekyll site run: | bundle exec jekyll build --source ./help --destination ./help/_site - name: Deploy to Cloudflare Pages - uses: cloudflare/pages-action@v1 # Using the latest stable version + uses: cloudflare/pages-action@v1 id: deploy if: env.IS_PR_FROM_FORK != 'true' with: @@ -69,7 +65,7 @@ jobs: CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} - name: Leave a comment on the PR - uses: actions-cool/maintain-one-comment@v3.2.0 # Using the latest stable version + uses: actions-cool/maintain-one-comment@v3.2.0 if: ${{ github.event_name == 'pull_request' && env.IS_PR_FROM_FORK != 'true' }} with: token: ${{ secrets.OS_BOTIFY_TOKEN }} diff --git a/help/Gemfile b/help/Gemfile index 5afaaed2bc64..9ff999c94703 100644 --- a/help/Gemfile +++ b/help/Gemfile @@ -1,31 +1,17 @@ source "https://rubygems.org" -# Manage Jekyll version gem "jekyll", "~> 4.3.4" - -# Default theme for new Jekyll sites gem "minima", "~> 2.5" +gem "nokogiri" -# Plugins for Jekyll group :jekyll_plugins do gem "jekyll-feed", "~> 0.12" end -# For development on macOS and Linux (add webrick for Jekyll 4.3+) -gem "webrick", "~> 1.8" - -# Nokogiri for HTML editing -gem "nokogiri", "~> 1.12" - -# Optional: Add tzinfo for timezone support (mainly for Windows/JRuby environments) platforms :mingw, :x64_mingw, :mswin, :jruby do gem "tzinfo", ">= 1", "< 3" gem "tzinfo-data" end -# Lock http_parser.rb gem to v0.6.x for JRuby -gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] - -# Performance-booster for watching directories on Windows -gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin] - +gem "wdm", "~> 0.1", platforms: [:mingw, :x64_mingw, :mswin] +gem "http_parser.rb", "~> 0.6.0", platforms: [:jruby] From 1f50571e3272dfe9247efb36601cdd8dd85a8808 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Wed, 25 Sep 2024 14:59:42 +0200 Subject: [PATCH 064/114] useOnyx migration on FormProvider --- src/components/Form/FormProvider.tsx | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index db52c45751b7..9474a4a6de99 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -4,13 +4,13 @@ import type {ForwardedRef, MutableRefObject, ReactNode, RefAttributes} from 'rea import React, {createRef, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import type {NativeSyntheticEvent, StyleProp, TextInputSubmitEditingEventData, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import * as ValidationUtils from '@libs/ValidationUtils'; import Visibility from '@libs/Visibility'; import * as FormActions from '@userActions/FormActions'; import CONST from '@src/CONST'; -import type {OnyxFormKey} from '@src/ONYXKEYS'; +import type {OnyxFormDraftKey, OnyxFormKey} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Form} from '@src/types/form'; import type {Network} from '@src/types/onyx'; @@ -89,17 +89,17 @@ function FormProvider( shouldValidateOnBlur = true, shouldValidateOnChange = true, children, - formState, - network, enabledWhenOffline = false, - draftValues, onSubmit, shouldTrimValues = true, allowHTML = false, ...rest - }: FormProviderProps, + }: Omit, forwardedRef: ForwardedRef, ) { + const [network] = useOnyx(ONYXKEYS.NETWORK); + const [formState] = useOnyx(`${formID}`); + const [draftValues] = useOnyx(`${formID}Draft`); const {preferredLocale, translate} = useLocalize(); const inputRefs = useRef({}); const touchedInputs = useRef>({}); @@ -404,19 +404,6 @@ function FormProvider( FormProvider.displayName = 'Form'; -export default withOnyx({ - network: { - key: ONYXKEYS.NETWORK, - }, - // withOnyx typings are not able to handle such generic cases like this one, since it's a generic component we need to cast the keys to any - formState: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any - key: ({formID}) => formID as any, - }, - draftValues: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any - key: (props) => `${props.formID}Draft` as any, - }, -})(forwardRef(FormProvider)) as (props: Omit & RefAttributes, keyof FormProviderOnyxProps>) => ReactNode; +export default forwardRef(FormProvider) as (props: Omit & RefAttributes, keyof FormProviderOnyxProps>) => ReactNode; export type {FormProviderProps}; From 216ded6456ff80ac022e2ccc5f516605ddb1e2b2 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Wed, 25 Sep 2024 06:16:39 -0700 Subject: [PATCH 065/114] Explicitly installing Jekyll --- .github/workflows/deployNewHelp.yml | 11 +++++++++-- help/Gemfile | 2 ++ help/Gemfile.lock | 4 +--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 24cb8e79fdcf..784d5934e395 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -1,18 +1,23 @@ on: + # Run on any push to main that has changes to the help directory push: branches: - main paths: - 'help/**' + # Run on any pull request (except PRs against staging or production) that has changes to the docs directory pull_request: types: [opened, synchronize] branches-ignore: [staging, production] paths: - 'help/**' + # Run on any manual trigger workflow_dispatch: +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "newhelp" cancel-in-progress: false @@ -32,13 +37,14 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.3.4' + ruby-version: '3.3.4' - name: Install dependencies run: | - gem install bundler bundle config set --local path 'vendor/bundle' bundle install + gem install jekyll # Explicitly install Jekyll + jekyll -v - name: Build Jekyll site run: | @@ -71,3 +77,4 @@ jobs: token: ${{ secrets.OS_BOTIFY_TOKEN }} body: ${{ format('A preview of your New Help changes have been deployed to {0} :zap:️', steps.deploy.outputs.alias) }} + diff --git a/help/Gemfile b/help/Gemfile index 9ff999c94703..4f2e425b8aba 100644 --- a/help/Gemfile +++ b/help/Gemfile @@ -8,6 +8,7 @@ group :jekyll_plugins do gem "jekyll-feed", "~> 0.12" end +# If using tzinfo-data for timezone support, ensure it's bundled for relevant platforms platforms :mingw, :x64_mingw, :mswin, :jruby do gem "tzinfo", ">= 1", "< 3" gem "tzinfo-data" @@ -15,3 +16,4 @@ end gem "wdm", "~> 0.1", platforms: [:mingw, :x64_mingw, :mswin] gem "http_parser.rb", "~> 0.6.0", platforms: [:jruby] + diff --git a/help/Gemfile.lock b/help/Gemfile.lock index 064546f52759..7434e1c4e935 100644 --- a/help/Gemfile.lock +++ b/help/Gemfile.lock @@ -190,13 +190,11 @@ DEPENDENCIES http_parser.rb (~> 0.6.0) jekyll (~> 4.3.4) jekyll-feed (~> 0.12) - jekyll-seo-tag minima (~> 2.5) - nokogiri (~> 1.12) + nokogiri tzinfo (>= 1, < 3) tzinfo-data wdm (~> 0.1) - webrick (~> 1.8) BUNDLED WITH 2.5.19 From aeadd2aa3b2d72b16eeaf29121e738fb4484b6f0 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Wed, 25 Sep 2024 06:24:33 -0700 Subject: [PATCH 066/114] Explicitly installing ffi --- .github/workflows/deployNewHelp.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 784d5934e395..bba841eaafb6 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -39,6 +39,10 @@ jobs: with: ruby-version: '3.3.4' + # Explicitly install ffi gem to avoid platform issues + - name: Install ffi gem + run: gem install ffi --platform ruby + - name: Install dependencies run: | bundle config set --local path 'vendor/bundle' @@ -46,6 +50,13 @@ jobs: gem install jekyll # Explicitly install Jekyll jekyll -v + # Clear Bundler cache if needed to resolve caching issues + - name: Clear Bundler cache + run: | + bundle clean --force + bundle install --path vendor/bundle + + # Use bundle exec to ensure Jekyll uses the correct gem versions - name: Build Jekyll site run: | bundle exec jekyll build --source ./help --destination ./help/_site @@ -77,4 +88,3 @@ jobs: token: ${{ secrets.OS_BOTIFY_TOKEN }} body: ${{ format('A preview of your New Help changes have been deployed to {0} :zap:️', steps.deploy.outputs.alias) }} - From 0cfd8403ab6329230e3948118db0fcc6183abb4f Mon Sep 17 00:00:00 2001 From: David Barrett Date: Wed, 25 Sep 2024 06:30:18 -0700 Subject: [PATCH 067/114] More fixes --- .github/workflows/deployNewHelp.yml | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index bba841eaafb6..d4abb54ba653 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -1,23 +1,16 @@ on: - # Run on any push to main that has changes to the help directory push: branches: - main paths: - 'help/**' - - # Run on any pull request (except PRs against staging or production) that has changes to the docs directory pull_request: types: [opened, synchronize] branches-ignore: [staging, production] paths: - 'help/**' - - # Run on any manual trigger workflow_dispatch: -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "newhelp" cancel-in-progress: false @@ -37,26 +30,15 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.3.4' - - # Explicitly install ffi gem to avoid platform issues - - name: Install ffi gem - run: gem install ffi --platform ruby + ruby-version: '3.3.4' - name: Install dependencies run: | bundle config set --local path 'vendor/bundle' bundle install - gem install jekyll # Explicitly install Jekyll - jekyll -v - - # Clear Bundler cache if needed to resolve caching issues - - name: Clear Bundler cache - run: | - bundle clean --force - bundle install --path vendor/bundle + bundle exec gem install jekyll # Use bundle exec to avoid conflicts + bundle exec jekyll -v - # Use bundle exec to ensure Jekyll uses the correct gem versions - name: Build Jekyll site run: | bundle exec jekyll build --source ./help --destination ./help/_site From 0108a68715c49f2840246deb4fb230f274836a49 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Wed, 25 Sep 2024 15:58:54 +0200 Subject: [PATCH 068/114] fix props --- src/components/Form/FormProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 9474a4a6de99..7173cd653458 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -94,7 +94,7 @@ function FormProvider( shouldTrimValues = true, allowHTML = false, ...rest - }: Omit, + }: FormProviderProps, forwardedRef: ForwardedRef, ) { const [network] = useOnyx(ONYXKEYS.NETWORK); From a14bd8fc22610c4ab71031f7b8b79bd61d022b15 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 25 Sep 2024 19:00:54 +0200 Subject: [PATCH 069/114] Refactor willBlurTextInputOnTapOutside to use getIsNarrowLayout --- src/libs/willBlurTextInputOnTapOutside/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/willBlurTextInputOnTapOutside/index.ts b/src/libs/willBlurTextInputOnTapOutside/index.ts index 987d8a1dfeea..31a40932189e 100644 --- a/src/libs/willBlurTextInputOnTapOutside/index.ts +++ b/src/libs/willBlurTextInputOnTapOutside/index.ts @@ -1,5 +1,6 @@ +import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import type WillBlurTextInputOnTapOutside from './types'; -const willBlurTextInputOnTapOutside: WillBlurTextInputOnTapOutside = () => true; +const willBlurTextInputOnTapOutside: WillBlurTextInputOnTapOutside = () => !getIsNarrowLayout(); export default willBlurTextInputOnTapOutside; From 103cacdd1f19db1def4b4c143897b30bbe75f0bf Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 25 Sep 2024 23:45:57 +0300 Subject: [PATCH 070/114] avoid double closeModal execution --- src/libs/actions/Modal.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/actions/Modal.ts b/src/libs/actions/Modal.ts index 01ac832336ab..00853e9546d5 100644 --- a/src/libs/actions/Modal.ts +++ b/src/libs/actions/Modal.ts @@ -32,9 +32,11 @@ function closeTop() { } if (onModalClose) { closeModals[closeModals.length - 1](isNavigate); + closeModals.pop(); return; } closeModals[closeModals.length - 1](); + closeModals.pop(); } /** From 5bfaa7ec59d249396016b3ebee7351b08ac4adbc Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 25 Sep 2024 23:46:36 +0300 Subject: [PATCH 071/114] replace goBack with dismissModal --- src/pages/TransactionReceiptPage.tsx | 32 +++++++--------------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/pages/TransactionReceiptPage.tsx b/src/pages/TransactionReceiptPage.tsx index 60f5ad4dbd86..495458c6b80f 100644 --- a/src/pages/TransactionReceiptPage.tsx +++ b/src/pages/TransactionReceiptPage.tsx @@ -1,7 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useEffect} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import AttachmentModal from '@components/AttachmentModal'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; @@ -13,19 +12,14 @@ import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import * as ReportActions from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {Report, ReportMetadata, Transaction} from '@src/types/onyx'; -type TransactionReceiptOnyxProps = { - report: OnyxEntry; - transaction: OnyxEntry; - reportMetadata: OnyxEntry; -}; +type TransactionReceiptProps = StackScreenProps; -type TransactionReceiptProps = TransactionReceiptOnyxProps & StackScreenProps; - -function TransactionReceipt({transaction, report, reportMetadata = {isLoadingInitialReportActions: true}, route}: TransactionReceiptProps) { +function TransactionReceipt({route}: TransactionReceiptProps) { + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '-1'}`); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${route.params.transactionID ?? '-1'}`); + const [reportMetadata = {isLoadingInitialReportActions: true}] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${route.params.reportID ?? '-1'}`); const receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction); const imageSource = tryResolveUrlFromApiRoot(receiptURIs.image ?? ''); @@ -65,7 +59,7 @@ function TransactionReceipt({transaction, report, reportMetadata = {isLoadingIni originalFileName={receiptURIs?.filename} defaultOpen onModalClose={() => { - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(report?.reportID ?? '-1')); + Navigation.dismissModal(report?.reportID ?? '-1'); }} isLoading={!transaction && reportMetadata?.isLoadingInitialReportActions} shouldShowNotFoundPage={shouldShowNotFoundPage} @@ -75,14 +69,4 @@ function TransactionReceipt({transaction, report, reportMetadata = {isLoadingIni TransactionReceipt.displayName = 'TransactionReceipt'; -export default withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '-1'}`, - }, - transaction: { - key: ({route}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${route.params.transactionID ?? '-1'}`, - }, - reportMetadata: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_METADATA}${route.params.reportID ?? '-1'}`, - }, -})(TransactionReceipt); +export default TransactionReceipt; From 6e85e5f21623cd72e42a31305574d6f0bf3b43e8 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 25 Sep 2024 23:48:23 +0300 Subject: [PATCH 072/114] revert the wrong fix --- src/pages/home/report/ReportAttachments.tsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index 369d5cef6ee4..3168f8b84617 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -19,14 +19,6 @@ function ReportAttachments({route}: ReportAttachmentsProps) { const accountID = route.params.accountID; const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID || -1}`); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); - const hasDismissedModalRef = useRef(false); - - useEffect( - () => () => { - hasDismissedModalRef.current = false; - }, - [], - ); // In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource const source = Number(route.params.source) || route.params.source; @@ -48,10 +40,7 @@ function ReportAttachments({route}: ReportAttachmentsProps) { report={report} source={source} onModalClose={() => { - if (!hasDismissedModalRef.current) { - Navigation.dismissModal(); - hasDismissedModalRef.current = true; - } + Navigation.dismissModal(); // This enables Composer refocus when the attachments modal is closed by the browser navigation ComposerFocusManager.setReadyToFocus(); }} From 84776659ccfb67e6d534b54a59826c02a02ff791 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 25 Sep 2024 23:49:00 +0300 Subject: [PATCH 073/114] minor fix --- src/pages/home/report/ReportAttachments.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index 3168f8b84617..1e16cfdddf4f 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useRef} from 'react'; +import React, {useCallback} from 'react'; import {useOnyx} from 'react-native-onyx'; import AttachmentModal from '@components/AttachmentModal'; import type {Attachment} from '@components/Attachments/types'; From 6c427798cd04c2f5010bb3e5ca9a7c6dea4e1b7c Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Sep 2024 16:04:58 -0700 Subject: [PATCH 074/114] Don't apply failureData if we receive a 460 response --- src/libs/actions/OnyxUpdates.ts | 4 ++++ src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx | 6 +----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 672f325be58a..f60fa15891ab 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -42,6 +42,10 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) { return updateHandler(request.successData); } if (response.jsonCode !== 200 && request.failureData) { + if (response.jsonCode === 460) { + Log.info('[OnyxUpdateManager] Received 460 status code, not applying failure data'); + return Promise.resolve(); + } return updateHandler(request.failureData); } return Promise.resolve(); diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index cc80705534b2..bbbf62d48c7e 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -109,14 +109,10 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { }, [policy, route.params.policyID, availableMembers, usedApproverEmails]); const optionItems: ToggleSettingOptionRowProps[] = useMemo(() => { - const {accountNumber, addressName, bankName, bankAccountID} = policy?.achAccount ?? {}; + const {addressName, bankName, bankAccountID} = policy?.achAccount ?? {}; const shouldShowBankAccount = !!bankAccountID && policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES; const bankIcon = getBankIcon({bankName: bankName as BankName, isCard: false, styles}); - let bankDisplayName = bankName ?? addressName; - if (accountNumber && bankDisplayName !== accountNumber) { - bankDisplayName += ` ${accountNumber.slice(-5)}`; - } const hasReimburserError = !!policy?.errorFields?.reimburser; const hasApprovalError = !!policy?.errorFields?.approvalMode; const hasDelayedSubmissionError = !!policy?.errorFields?.autoReporting ?? !!policy?.errorFields?.autoReportingFrequency; From 0fa776ae798c3637ab9a133f84d4b08c88e2847e Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Sep 2024 16:07:19 -0700 Subject: [PATCH 075/114] Add a comment explaining 460 errors --- src/libs/actions/OnyxUpdates.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index f60fa15891ab..1ba50d08e449 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -42,6 +42,10 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) { return updateHandler(request.successData); } if (response.jsonCode !== 200 && request.failureData) { + // 460 jsonCode in Expensify world means "admin required". + // Typically, this would only happen if a user attempts an API command that requires policy admin access when they aren't an admin. + // In this case, we don't want to apply failureData because it will likely result in a RedBrickRoad error on a policy field which is not accessible. + // Meaning that there's a red dot you can't dismiss. if (response.jsonCode === 460) { Log.info('[OnyxUpdateManager] Received 460 status code, not applying failure data'); return Promise.resolve(); From edc6980a6beec44e8e85cfa57754d7e37f902460 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 26 Sep 2024 05:45:02 +0530 Subject: [PATCH 076/114] fix: Web - Sage - Two console errors appear when downloading the Expensify package for Sage Intacct. Signed-off-by: krishna2323 --- .../accounting/intacct/SageIntacctPrerequisitesPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/intacct/SageIntacctPrerequisitesPage.tsx b/src/pages/workspace/accounting/intacct/SageIntacctPrerequisitesPage.tsx index 4e5863d48317..1c60065a08c8 100644 --- a/src/pages/workspace/accounting/intacct/SageIntacctPrerequisitesPage.tsx +++ b/src/pages/workspace/accounting/intacct/SageIntacctPrerequisitesPage.tsx @@ -40,7 +40,7 @@ function SageIntacctPrerequisitesPage({route}: SageIntacctPrerequisitesPageProps iconRight: Expensicons.NewWindow, shouldShowRightIcon: true, onPress: () => { - fileDownload(CONST.EXPENSIFY_PACKAGE_FOR_SAGE_INTACCT, CONST.EXPENSIFY_PACKAGE_FOR_SAGE_INTACCT_FILE_NAME); + fileDownload(CONST.EXPENSIFY_PACKAGE_FOR_SAGE_INTACCT, CONST.EXPENSIFY_PACKAGE_FOR_SAGE_INTACCT_FILE_NAME, '', true); }, onSecondaryInteraction: (event: GestureResponderEvent | MouseEvent) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, CONST.EXPENSIFY_PACKAGE_FOR_SAGE_INTACCT, popoverAnchor.current), From 978018866759bf1b08e69451129f8301cb34d1a5 Mon Sep 17 00:00:00 2001 From: Jason Li <30815269+jliexpensify@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:13:57 +1000 Subject: [PATCH 077/114] Update Configure-Quickbooks-Online.md Copied info from Connect doc to Configure. --- .../Configure-Quickbooks-Online.md | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/docs/articles/new-expensify/connections/quickbooks-online/Configure-Quickbooks-Online.md b/docs/articles/new-expensify/connections/quickbooks-online/Configure-Quickbooks-Online.md index 787602337bd2..73e3340d41a2 100644 --- a/docs/articles/new-expensify/connections/quickbooks-online/Configure-Quickbooks-Online.md +++ b/docs/articles/new-expensify/connections/quickbooks-online/Configure-Quickbooks-Online.md @@ -1,9 +1,76 @@ --- title: Configure Quickbooks Online -description: Coming Soon +description: Configure your QuickBooks Online connection with Expensify --- -# FAQ +Once you've set up your QuickBooks Online connection, you'll be able to configure your import and export settings. + +# Step 1: Configure import settings + +The following steps help you determine how data will be imported from QuickBooks Online to Expensify. + +
    +
  1. Under the Accounting settings for your workspace, click Import under the QuickBooks Online connection.
  2. +
  3. Review each of the following import settings:
  4. +
      +
    • Chart of accounts: The chart of accounts are automatically imported from QuickBooks Online as categories. This cannot be amended.
    • +
    • Classes: Choose whether to import classes, which will be shown in Expensify as tags for expense-level coding.
    • +
    • Customers/projects: Choose whether to import customers/projects, which will be shown in Expensify as tags for expense-level coding.
    • +
    • Locations: Choose whether to import locations, which will be shown in Expensify as tags for expense-level coding.
    • +{% include info.html %} +As Locations are only configurable as tags, you cannot export expense reports as vendor bills or checks to QuickBooks Online. To unlock these export options, either disable locations import or upgrade to the Control Plan to export locations encoded as a report field. +{% include end-info.html %} +
    • Taxes: Choose whether to import tax rates and defaults.
    • +
    +
+ +# Step 2: Configure export settings + +The following steps help you determine how data will be exported from Expensify to QuickBooks Online. + +
    +
  1. Under the Accounting settings for your workspace, click Export under the QuickBooks Online connection.
  2. +
  3. Review each of the following export settings:
  4. +
      +
    • Preferred Exporter: Choose whether to assign a Workspace Admin as the Preferred Exporter. Once selected, the Preferred Exporter automatically receives reports for export in their account to help automate the exporting process.
    • + +{% include info.html %} +* Other Workspace Admins will still be able to export to QuickBooks Online. +* If you set different export accounts for individual company cards under your domain settings, then your Preferred Exporter must be a Domain Admin. +{% include end-info.html %} + +
    • Date: Choose whether to use the date of last expense, export date, or submitted date.
    • +
    • Export Out-of-Pocket Expenses as: Select whether out-of-pocket expenses will be exported as a check, journal entry, or vendor bill.
    • + +{% include info.html %} +These settings may vary based on whether tax is enabled for your workspace. +* If tax is not enabled on the workspace, you’ll also select the Accounts Payable/AP. +* If tax is enabled on the workspace, journal entry will not be available as an option. If you select the journal entries option first and later enable tax on the workspace, you will see a red dot and an error message under the “Export Out-of-Pocket Expenses as” options. To resolve this error, you must change your export option to vendor bill or check to successfully code and export expense reports. +{% include end-info.html %} + +
    • Invoices: Select the QuickBooks Online invoice account that invoices will be exported to.
    • +
    • Export as: Select whether company cards export to QuickBooks Online as a credit card (the default), debit card, or vendor bill. Then select the account they will export to.
    • +
    • If you select vendor bill, you’ll also select the accounts payable account that vendor bills will be created from, as well as whether to set a default vendor for credit card transactions upon export. If this option is enabled, you will select the vendor that all credit card transactions will be applied to.
    • +
    +
+ +# Step 3: Configure advanced settings + +The following steps help you determine the advanced settings for your connection, like auto-sync and employee invitation settings. + +
    +
  1. Under the Accounting settings for your workspace, click Advanced under the QuickBooks Online connection.
  2. +
  3. Select an option for each of the following settings:
  4. +
      +
    • Auto-sync: Choose whether to enable QuickBooks Online to automatically communicate changes with Expensify to ensure that the data shared between the two systems is up-to-date. New report approvals/reimbursements will be synced during the next auto-sync period.
    • +
    • Invite Employees: Choose whether to enable Expensify to import employee records from QuickBooks Online and invite them to this workspace.
    • +
    • Automatically Create Entities: Choose whether to enable Expensify to automatically create vendors and customers in QuickBooks Online if a matching vendor or customer does not exist.
    • +
    • Sync Reimbursed Reports: Choose whether to enable report syncing for reimbursed expenses. If enabled, all reports that are marked as Paid in QuickBooks Online will also show in Expensify as Paid. If enabled, you must also select the QuickBooks Online account that reimbursements are coming out of, and Expensify will automatically create the payment in QuickBooks Online.
    • +
    • Invoice Collection Account: Select the invoice collection account that you want invoices to appear under once the invoice is marked as paid.
    • +
    +
+ +{% include faq-begin.md %} ## How do I know if a report is successfully exported to QuickBooks Online? @@ -22,3 +89,5 @@ When an admin manually exports a report, Expensify will notify them if the repor - If a report has been exported and marked as paid in QuickBooks Online, it will be automatically marked as reimbursed in Expensify during the next sync. Reports that have yet to be exported to QuickBooks Online won’t be automatically exported. + +{% include faq-end.md %} From 2841d26220ae5799c4b4c1d638006dd7790674b3 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 26 Sep 2024 14:44:45 +0700 Subject: [PATCH 078/114] fix: fixed 4 decimals for distance rates --- src/components/AmountForm.tsx | 6 +++++- src/libs/CurrencyUtils.ts | 2 +- src/libs/PolicyDistanceRatesUtils.ts | 2 +- .../workspace/distanceRates/CreateDistanceRatePage.tsx | 2 +- .../workspace/distanceRates/PolicyDistanceRateEditPage.tsx | 2 +- .../PolicyDistanceRateTaxReclaimableEditPage.tsx | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 564077387d5b..4848577bdea0 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -48,6 +48,9 @@ type AmountFormProps = { /** Whether the form should use a standard TextInput as a base */ displayAsTextInput?: boolean; + + /** Number of decimals to display */ + fixedDecimals?: number; } & Pick & Pick; @@ -75,6 +78,7 @@ function AmountForm( displayAsTextInput = false, isCurrencyPressable = true, label, + fixedDecimals, ...rest }: AmountFormProps, forwardedRef: ForwardedRef, @@ -84,7 +88,7 @@ function AmountForm( const textInput = useRef(null); - const decimals = CurrencyUtils.getCurrencyDecimals(currency) + extraDecimals; + const decimals = fixedDecimals ?? CurrencyUtils.getCurrencyDecimals(currency) + extraDecimals; const currentAmount = useMemo(() => (typeof amount === 'string' ? amount : ''), [amount]); const [shouldUpdateSelection, setShouldUpdateSelection] = useState(true); diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index c3b80797d750..9e54efa7a03d 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -164,7 +164,7 @@ function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRE return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, - minimumFractionDigits: getCurrencyDecimals(currency) + 1, + minimumFractionDigits: 4, }); } diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts index 0c5493f2f97b..a9c681c9ee2b 100644 --- a/src/libs/PolicyDistanceRatesUtils.ts +++ b/src/libs/PolicyDistanceRatesUtils.ts @@ -18,7 +18,7 @@ function validateRateValue(values: FormOnyxValues, currency: stri const decimalSeparator = toLocaleDigit('.'); // Allow one more decimal place for accuracy - const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{0,${CurrencyUtils.getCurrencyDecimals(currency) + 1}})?$`, 'i'); + const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{0,4})?$`, 'i'); if (!rateValueRegex.test(parsedRate) || parsedRate === '') { errors.rate = Localize.translateLocal('common.error.invalidRateError'); } else if (NumberUtils.parseFloatAnyLocale(parsedRate) <= 0) { diff --git a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index b2dca63ecd34..d68c7687ae48 100644 --- a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -90,7 +90,7 @@ function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { Date: Thu, 26 Sep 2024 15:07:41 +0700 Subject: [PATCH 079/114] fix: lint --- src/libs/PolicyDistanceRatesUtils.ts | 1 - .../distanceRates/CreateDistanceRatePage.tsx | 19 ++++----------- .../PolicyDistanceRateEditPage.tsx | 23 ++++++------------- ...licyDistanceRateTaxReclaimableEditPage.tsx | 5 +--- 4 files changed, 13 insertions(+), 35 deletions(-) diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts index a9c681c9ee2b..415fcc627b4e 100644 --- a/src/libs/PolicyDistanceRatesUtils.ts +++ b/src/libs/PolicyDistanceRatesUtils.ts @@ -2,7 +2,6 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import CONST from '@src/CONST'; import type ONYXKEYS from '@src/ONYXKEYS'; import type {Rate} from '@src/types/onyx/Policy'; -import * as CurrencyUtils from './CurrencyUtils'; import getPermittedDecimalSeparator from './getPermittedDecimalSeparator'; import * as Localize from './Localize'; import * as MoneyRequestUtils from './MoneyRequestUtils'; diff --git a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index d68c7687ae48..353bf42afe29 100644 --- a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -1,8 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback} from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import FormProvider from '@components/Form/FormProvider'; @@ -12,6 +10,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import {getOptimisticRateName, validateRateValue} from '@libs/PolicyDistanceRatesUtils'; import Navigation from '@navigation/Navigation'; @@ -24,18 +23,14 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/PolicyCreateDistanceRateForm'; import type {Rate} from '@src/types/onyx/Policy'; -import type Policy from '@src/types/onyx/Policy'; -type CreateDistanceRatePageOnyxProps = { - policy: OnyxEntry; -}; +type CreateDistanceRatePageProps = StackScreenProps; -type CreateDistanceRatePageProps = CreateDistanceRatePageOnyxProps & StackScreenProps; - -function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { +function CreateDistanceRatePage({route}: CreateDistanceRatePageProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const policyID = route.params.policyID; + const policy = usePolicy(policyID); const currency = policy?.outputCurrency ?? CONST.CURRENCY.USD; const customUnits = policy?.customUnits ?? {}; const customUnitID = customUnits[Object.keys(customUnits)[0]]?.customUnitID ?? ''; @@ -104,8 +99,4 @@ function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { CreateDistanceRatePage.displayName = 'CreateDistanceRatePage'; -export default withOnyx({ - policy: { - key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, - }, -})(CreateDistanceRatePage); +export default CreateDistanceRatePage; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateEditPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateEditPage.tsx index d782e6507f3f..059c11298360 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateEditPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateEditPage.tsx @@ -1,8 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback} from 'react'; import {Keyboard} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapperWithRef from '@components/Form/InputWrapper'; @@ -11,6 +9,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import {validateRateValue} from '@libs/PolicyDistanceRatesUtils'; @@ -22,27 +21,23 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/PolicyDistanceRateEditForm'; -import type * as OnyxTypes from '@src/types/onyx'; -type PolicyDistanceRateEditPageOnyxProps = { - /** Policy details */ - policy: OnyxEntry; -}; +type PolicyDistanceRateEditPageProps = StackScreenProps; -type PolicyDistanceRateEditPageProps = PolicyDistanceRateEditPageOnyxProps & StackScreenProps; - -function PolicyDistanceRateEditPage({policy, route}: PolicyDistanceRateEditPageProps) { +function PolicyDistanceRateEditPage({route}: PolicyDistanceRateEditPageProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); const policyID = route.params.policyID; const rateID = route.params.rateID; + + const policy = usePolicy(policyID); const customUnits = policy?.customUnits ?? {}; const customUnit = customUnits[Object.keys(customUnits)[0]]; const rate = customUnit?.rates[rateID]; const currency = rate?.currency ?? CONST.CURRENCY.USD; - const currentRateValue = (parseFloat((rate?.rate ?? 0).toString()) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toFixed(3); + const currentRateValue = (parseFloat((rate?.rate ?? 0).toString()) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toFixed(4); const submitRate = (values: FormOnyxValues) => { if (currentRateValue === values.rate) { @@ -108,8 +103,4 @@ function PolicyDistanceRateEditPage({policy, route}: PolicyDistanceRateEditPageP PolicyDistanceRateEditPage.displayName = 'PolicyDistanceRateEditPage'; -export default withOnyx({ - policy: { - key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, - }, -})(PolicyDistanceRateEditPage); +export default PolicyDistanceRateEditPage; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableEditPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableEditPage.tsx index 156954816c7f..0fd6baa89ef3 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableEditPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableEditPage.tsx @@ -9,7 +9,6 @@ import ScreenWrapper from '@components/ScreenWrapper'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import {validateTaxClaimableValue} from '@libs/PolicyDistanceRatesUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; @@ -35,9 +34,7 @@ function PolicyDistanceRateTaxReclaimableEditPage({route, policy}: PolicyDistanc const customUnit = customUnits[Object.keys(customUnits)[0]]; const rate = customUnit.rates[rateID]; const currency = rate.currency ?? CONST.CURRENCY.USD; - const extraDecimals = 1; - const decimals = CurrencyUtils.getCurrencyDecimals(currency) + extraDecimals; - const currentTaxReclaimableOnValue = rate.attributes?.taxClaimablePercentage && rate.rate ? ((rate.attributes.taxClaimablePercentage * rate.rate) / 100).toFixed(decimals) : ''; + const currentTaxReclaimableOnValue = rate.attributes?.taxClaimablePercentage && rate.rate ? ((rate.attributes.taxClaimablePercentage * rate.rate) / 100).toFixed(4) : ''; const submitTaxReclaimableOn = (values: FormOnyxValues) => { if (values.taxClaimableValue === currentTaxReclaimableOnValue) { From 4ed4b90da00a2c34375ab972b2f45083e41700c6 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Thu, 26 Sep 2024 11:52:43 +0200 Subject: [PATCH 080/114] fix types --- src/components/Form/FormProvider.tsx | 56 +++++++++++----------------- src/stories/Form.stories.tsx | 20 ++++++++-- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 7173cd653458..80f52c8053da 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -3,7 +3,6 @@ import lodashIsEqual from 'lodash/isEqual'; import type {ForwardedRef, MutableRefObject, ReactNode, RefAttributes} from 'react'; import React, {createRef, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import type {NativeSyntheticEvent, StyleProp, TextInputSubmitEditingEventData, ViewStyle} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -13,7 +12,6 @@ import CONST from '@src/CONST'; import type {OnyxFormDraftKey, OnyxFormKey} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Form} from '@src/types/form'; -import type {Network} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {RegisterInput} from './FormContext'; import FormContext from './FormContext'; @@ -41,46 +39,34 @@ function getInitialValueByType(valueType?: ValueTypeKey): InitialDefaultValue { } } -type FormProviderOnyxProps = { - /** Contains the form state that must be accessed outside the component */ - formState: OnyxEntry
; +type FormProviderProps = FormProps & { + /** Children to render. */ + children: ((props: {inputValues: FormOnyxValues}) => ReactNode) | ReactNode; - /** Contains draft values for each input in the form */ - draftValues: OnyxEntry; + /** Callback to validate the form */ + validate?: (values: FormOnyxValues) => FormInputErrors; - /** Information about the network */ - network: OnyxEntry; -}; - -type FormProviderProps = FormProviderOnyxProps & - FormProps & { - /** Children to render. */ - children: ((props: {inputValues: FormOnyxValues}) => ReactNode) | ReactNode; - - /** Callback to validate the form */ - validate?: (values: FormOnyxValues) => FormInputErrors; + /** Should validate function be called when input loose focus */ + shouldValidateOnBlur?: boolean; - /** Should validate function be called when input loose focus */ - shouldValidateOnBlur?: boolean; + /** Should validate function be called when the value of the input is changed */ + shouldValidateOnChange?: boolean; - /** Should validate function be called when the value of the input is changed */ - shouldValidateOnChange?: boolean; + /** Whether to remove invisible characters from strings before validation and submission */ + shouldTrimValues?: boolean; - /** Whether to remove invisible characters from strings before validation and submission */ - shouldTrimValues?: boolean; + /** Styles that will be applied to the submit button only */ + submitButtonStyles?: StyleProp; - /** Styles that will be applied to the submit button only */ - submitButtonStyles?: StyleProp; + /** Whether to apply flex to the submit button */ + submitFlexEnabled?: boolean; - /** Whether to apply flex to the submit button */ - submitFlexEnabled?: boolean; + /** Whether button is disabled */ + isSubmitDisabled?: boolean; - /** Whether button is disabled */ - isSubmitDisabled?: boolean; - - /** Whether HTML is allowed in form inputs */ - allowHTML?: boolean; - }; + /** Whether HTML is allowed in form inputs */ + allowHTML?: boolean; +}; function FormProvider( { @@ -404,6 +390,6 @@ function FormProvider( FormProvider.displayName = 'Form'; -export default forwardRef(FormProvider) as (props: Omit & RefAttributes, keyof FormProviderOnyxProps>) => ReactNode; +export default forwardRef(FormProvider) as (props: FormProviderProps & RefAttributes) => ReactNode; export type {FormProviderProps}; diff --git a/src/stories/Form.stories.tsx b/src/stories/Form.stories.tsx index ab29612b0556..c15fcb982239 100644 --- a/src/stories/Form.stories.tsx +++ b/src/stories/Form.stories.tsx @@ -2,6 +2,7 @@ import type {Meta, StoryFn} from '@storybook/react'; import React, {useState} from 'react'; import type {ComponentType} from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import AddressSearch from '@components/AddressSearch'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; import DatePicker from '@components/DatePicker'; @@ -18,8 +19,10 @@ import * as FormActions from '@userActions/FormActions'; import CONST from '@src/CONST'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import {defaultStyles} from '@src/styles'; +import type {Form} from '@src/types/form'; +import type {Network} from '@src/types/onyx'; -type FormStory = StoryFn; +type FormStory = StoryFn; type StorybookFormValues = { routingNumber?: string; @@ -32,6 +35,17 @@ type StorybookFormValues = { checkbox?: boolean; }; +type FormProviderOnyxProps = { + /** Contains the form state that must be accessed outside the component */ + formState: OnyxEntry; + + /** Contains draft values for each input in the form */ + draftValues: OnyxEntry; + + /** Information about the network */ + network: OnyxEntry; +}; + type StorybookFormErrors = Partial>; const STORYBOOK_FORM_ID = 'TestForm' as keyof OnyxFormValuesMapping; @@ -50,7 +64,7 @@ const story: Meta = { }, }; -function Template(props: FormProviderProps) { +function Template(props: FormProviderProps & FormProviderOnyxProps) { // Form consumes data from Onyx, so we initialize Onyx with the necessary data here NetworkConnection.setOfflineStatus(false); FormActions.setIsLoading(props.formID, !!props.formState?.isLoading); @@ -162,7 +176,7 @@ function Template(props: FormProviderProps) { /** * Story to exhibit the native event handlers for TextInput in the Form Component */ -function WithNativeEventHandler(props: FormProviderProps) { +function WithNativeEventHandler(props: FormProviderProps & FormProviderOnyxProps) { const [log, setLog] = useState(''); // Form consumes data from Onyx, so we initialize Onyx with the necessary data here From 8090f14eb2c76aef7baf33216266066d6c483148 Mon Sep 17 00:00:00 2001 From: Christina Dobrzynski <51066321+Christinadobrzyn@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:52:30 -0600 Subject: [PATCH 081/114] Update Send-an-invoice.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ){:width=”100%”} was showing for each image in the preview - not what we want. So I made a change --- .../expenses-&-payments/Send-an-invoice.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md index 4d7f4e0dc55f..57b81a031a01 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md +++ b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md @@ -57,17 +57,17 @@ Only workspace admins can send invoices. Invoices can be sent directly from Expe {% include end-selector.html %} -![Go to Account Settings click Workspace]({{site.url}}/assets/images/invoices_01.png)){:width="100%"} +![Go to Account Settings click Workspace]({{site.url}}/assets/images/invoices_01.png){:width="100%"} -![Click More Features for the workspace and enable Invoices]({{site.url}}/assets/images/invoices_02.png)){:width="100%"} +![Click More Features for the workspace and enable Invoices]({{site.url}}/assets/images/invoices_02.png){:width="100%"} -![Click the green button Send Invoice]({{site.url}}/assets/images/invoices_03.png)){:width="100%"} +![Click the green button Send Invoice]({{site.url}}/assets/images/invoices_03.png){:width="100%"} -![Enter Invoice amount]({{site.url}}/assets/images/invoices_04.png)){:width="100%"} +![Enter Invoice amount]({{site.url}}/assets/images/invoices_04.png){:width="100%"} -![Choose a recipient]({{site.url}}/assets/images/invoices_05.png)){:width="100%"} +![Choose a recipient]({{site.url}}/assets/images/invoices_05.png){:width="100%"} -![Add Invoice details and Send Invoice]({{site.url}}/assets/images/invoices_06.png)){:width="100%"} +![Add Invoice details and Send Invoice]({{site.url}}/assets/images/invoices_06.png){:width="100%"} # Receive invoice payment From edd29e7623527e7af1dbbede657ce7deb76f6ef5 Mon Sep 17 00:00:00 2001 From: Christina Dobrzynski <51066321+Christinadobrzyn@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:55:33 -0600 Subject: [PATCH 082/114] Update Pay-an-invoice.md There was another image that was better for this so I replaced the others with it. --- .../new-expensify/expenses-&-payments/Pay-an-invoice.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md index d7ded144415b..615fac731c41 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md +++ b/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md @@ -32,9 +32,7 @@ To pay an invoice, You can also view all unpaid invoices by searching for the sender’s email or phone number on the left-hand side of the app. The invoices waiting for your payment will have a green dot. -![Click Pay Button on the Invoice]({{site.url}}/assets/images/invoice_01.png){:width="100%"} - -![Choose how to pay the invoice from the options]({{site.url}}/assets/images/invoice_02.png){:width="100%"} +![Click Pay Button on the Invoice]({{site.url}}/assets/images/ExpensifyHelp-Invoice-1.png){:width="100%"} {% include faq-begin.md %} From ee36a05eb8b6c3fe660584ff3ef4af0f530f6a6c Mon Sep 17 00:00:00 2001 From: David Barrett Date: Thu, 26 Sep 2024 21:07:12 -0700 Subject: [PATCH 083/114] Reverted a bunch, trying again with better directory control --- .github/workflows/deployNewHelp.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index d4abb54ba653..06062d47aa9a 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -69,4 +69,3 @@ jobs: with: token: ${{ secrets.OS_BOTIFY_TOKEN }} body: ${{ format('A preview of your New Help changes have been deployed to {0} :zap:️', steps.deploy.outputs.alias) }} - From d65098ea51e75152536dacdda8444816258a168f Mon Sep 17 00:00:00 2001 From: David Barrett Date: Thu, 26 Sep 2024 21:13:56 -0700 Subject: [PATCH 084/114] Reverted a bunch, trying again with better directory control --- .github/workflows/deployNewHelp.yml | 34 ++++++++++++----------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 06062d47aa9a..066845bbf49e 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -1,16 +1,23 @@ on: + # Run on any push to main that has changes to the help directory push: branches: - main paths: - 'help/**' + + # Run on any pull request (except PRs against staging or production) that has changes to the docs directory pull_request: types: [opened, synchronize] branches-ignore: [staging, production] paths: - 'help/**' + + # Run on any manual trigger workflow_dispatch: +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "newhelp" cancel-in-progress: false @@ -20,31 +27,18 @@ jobs: env: IS_PR_FROM_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }} runs-on: ubuntu-latest - steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1.0.13 # Using the latest stable version with: - ruby-version: '3.3.4' - - - name: Install dependencies - run: | - bundle config set --local path 'vendor/bundle' - bundle install - bundle exec gem install jekyll # Use bundle exec to avoid conflicts - bundle exec jekyll -v - - - name: Build Jekyll site - run: | - bundle exec jekyll build --source ./help --destination ./help/_site + source: ./help/ + destination: ./help/_site - name: Deploy to Cloudflare Pages - uses: cloudflare/pages-action@v1 + uses: cloudflare/pages-action@v1 # Using the latest stable version id: deploy if: env.IS_PR_FROM_FORK != 'true' with: @@ -59,12 +53,12 @@ jobs: - name: Purge Cloudflare cache if: env.IS_PR_FROM_FORK != 'true' - run: $HOME/.local/bin/cli4 --verbose --delete hosts=["newhelp.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache + run: $HOME/.local/bin/cli4 --verbose --delete hosts=["help.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache env: CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} - name: Leave a comment on the PR - uses: actions-cool/maintain-one-comment@v3.2.0 + uses: actions-cool/maintain-one-comment@v3.2.0 # Using the latest stable version if: ${{ github.event_name == 'pull_request' && env.IS_PR_FROM_FORK != 'true' }} with: token: ${{ secrets.OS_BOTIFY_TOKEN }} From ce36bfb845f4e50af743021bc77d286238dd420f Mon Sep 17 00:00:00 2001 From: David Barrett Date: Thu, 26 Sep 2024 21:25:58 -0700 Subject: [PATCH 085/114] Removing cruft --- help/_config.yml | 50 +------------------ .../2024-09-23-welcome-to-jekyll.markdown | 29 ----------- help/about.markdown | 18 ------- help/index.markdown | 7 ++- 4 files changed, 5 insertions(+), 99 deletions(-) delete mode 100644 help/_posts/2024-09-23-welcome-to-jekyll.markdown delete mode 100644 help/about.markdown diff --git a/help/_config.yml b/help/_config.yml index 72bb4d942f9a..9135a372964e 100644 --- a/help/_config.yml +++ b/help/_config.yml @@ -1,53 +1,7 @@ -# Welcome to Jekyll! -# -# This config file is meant for settings that affect your whole blog, values -# which you are expected to set up once and rarely edit after that. If you find -# yourself editing this file very often, consider using Jekyll's data files -# feature for the data you need to update frequently. -# -# For technical reasons, this file is *NOT* reloaded automatically when you use -# 'bundle exec jekyll serve'. If you change this file, please restart the server process. -# -# If you need help with YAML syntax, here are some quick references for you: -# https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml -# https://learnxinyminutes.com/docs/yaml/ -# -# Site settings -# These are used to personalize your new site. If you look in the HTML files, -# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. -# You can create any custom variable you would like, and they will be accessible -# in the templates via {{ site.myvariable }}. - title: New Expensify Help email: concierge@expensify.com -description: >- # this means to ignore newlines until "baseurl:" - Comprehensive help documentation for New Expensify. -baseurl: "" # the subpath of your site, e.g. /blog -url: "https://newhelp.expensify.com" # the base hostname & protocol for your site, e.g. http://example.com +description: Comprehensive help documentation for New Expensify. +url: https://newhelp.expensify.com twitter_username: expensify github_username: expensify -# Build settings -theme: minima -plugins: - - jekyll-feed - -# Exclude from processing. -# The following items will not be processed, by default. -# Any item listed under the `exclude:` key here will be automatically added to -# the internal "default list". -# -# Excluded items can be processed by explicitly listing the directories or -# their entries' file path in the `include:` list. -# -# exclude: -# - .sass-cache/ -# - .jekyll-cache/ -# - gemfiles/ -# - Gemfile -# - Gemfile.lock -# - node_modules/ -# - vendor/bundle/ -# - vendor/cache/ -# - vendor/gems/ -# - vendor/ruby/ diff --git a/help/_posts/2024-09-23-welcome-to-jekyll.markdown b/help/_posts/2024-09-23-welcome-to-jekyll.markdown deleted file mode 100644 index c53c7643163e..000000000000 --- a/help/_posts/2024-09-23-welcome-to-jekyll.markdown +++ /dev/null @@ -1,29 +0,0 @@ ---- -layout: post -title: "Welcome to Jekyll!" -date: 2024-09-23 12:16:06 -0700 -categories: jekyll update ---- -You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated. - -Jekyll requires blog post files to be named according to the following format: - -`YEAR-MONTH-DAY-title.MARKUP` - -Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `MARKUP` is the file extension representing the format used in the file. After that, include the necessary front matter. Take a look at the source for this post to get an idea about how it works. - -Jekyll also offers powerful support for code snippets: - -{% highlight ruby %} -def print_hi(name) - puts "Hi, #{name}" -end -print_hi('Tom') -#=> prints 'Hi, Tom' to STDOUT. -{% endhighlight %} - -Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk]. - -[jekyll-docs]: https://jekyllrb.com/docs/home -[jekyll-gh]: https://github.com/jekyll/jekyll -[jekyll-talk]: https://talk.jekyllrb.com/ diff --git a/help/about.markdown b/help/about.markdown deleted file mode 100644 index 8b4e0b28c83e..000000000000 --- a/help/about.markdown +++ /dev/null @@ -1,18 +0,0 @@ ---- -layout: page -title: About -permalink: /about/ ---- - -This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](https://jekyllrb.com/) - -You can find the source code for Minima at GitHub: -[jekyll][jekyll-organization] / -[minima](https://github.com/jekyll/minima) - -You can find the source code for Jekyll at GitHub: -[jekyll][jekyll-organization] / -[jekyll](https://github.com/jekyll/jekyll) - - -[jekyll-organization]: https://github.com/jekyll diff --git a/help/index.markdown b/help/index.markdown index 06715078416f..e5d075402ecb 100644 --- a/help/index.markdown +++ b/help/index.markdown @@ -1,6 +1,5 @@ --- -# Feel free to add content and custom Front Matter to this file. -# To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults - -layout: home +title: New Expensify Help --- +Pages: +* [Expensify Superapp](/superapp.html) From 639335077bbf7f8ef7f517145457d99e63d335c0 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Thu, 26 Sep 2024 21:34:14 -0700 Subject: [PATCH 086/114] Still working on working directory --- .github/workflows/deployNewHelp.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 066845bbf49e..bcdea4190f6e 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -36,6 +36,7 @@ jobs: with: source: ./help/ destination: ./help/_site + working-directory: ./help - name: Deploy to Cloudflare Pages uses: cloudflare/pages-action@v1 # Using the latest stable version @@ -46,16 +47,19 @@ jobs: accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} projectName: newhelp directory: ./help/_site + working-directory: ./help - name: Setup Cloudflare CLI if: env.IS_PR_FROM_FORK != 'true' run: pip3 install cloudflare==2.19.0 + working-directory: ./help - name: Purge Cloudflare cache if: env.IS_PR_FROM_FORK != 'true' run: $HOME/.local/bin/cli4 --verbose --delete hosts=["help.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache env: CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} + working-directory: ./help - name: Leave a comment on the PR uses: actions-cool/maintain-one-comment@v3.2.0 # Using the latest stable version @@ -63,3 +67,4 @@ jobs: with: token: ${{ secrets.OS_BOTIFY_TOKEN }} body: ${{ format('A preview of your New Help changes have been deployed to {0} :zap:️', steps.deploy.outputs.alias) }} + working-directory: ./help From 29656b5383a08efd672296cc25206e0eb87b7089 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Thu, 26 Sep 2024 21:44:04 -0700 Subject: [PATCH 087/114] Still working on working directory --- .github/workflows/deployNewHelp.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index bcdea4190f6e..65d122a48a7a 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -36,7 +36,6 @@ jobs: with: source: ./help/ destination: ./help/_site - working-directory: ./help - name: Deploy to Cloudflare Pages uses: cloudflare/pages-action@v1 # Using the latest stable version @@ -67,4 +66,3 @@ jobs: with: token: ${{ secrets.OS_BOTIFY_TOKEN }} body: ${{ format('A preview of your New Help changes have been deployed to {0} :zap:️', steps.deploy.outputs.alias) }} - working-directory: ./help From f71e2072fbaae5322b72d46a1f1c8ace2fdc0981 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Thu, 26 Sep 2024 21:48:45 -0700 Subject: [PATCH 088/114] Still working on working directory --- .github/workflows/deployNewHelp.yml | 45 ++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 65d122a48a7a..f6d80f497f9a 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -6,7 +6,7 @@ on: paths: - 'help/**' - # Run on any pull request (except PRs against staging or production) that has changes to the docs directory + # Run on any pull request (except PRs against staging or production) that has changes to the help directory pull_request: types: [opened, synchronize] branches-ignore: [staging, production] @@ -16,8 +16,7 @@ on: # Run on any manual trigger workflow_dispatch: -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +# Allow only one concurrent deployment concurrency: group: "newhelp" cancel-in-progress: false @@ -27,42 +26,60 @@ jobs: env: IS_PR_FROM_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }} runs-on: ubuntu-latest + steps: - - name: Checkout + - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Build with Jekyll - uses: actions/jekyll-build-pages@v1.0.13 # Using the latest stable version + # Set up Ruby and run commands inside the /help directory + - name: Set up Ruby + uses: ruby/setup-ruby@v1 with: - source: ./help/ - destination: ./help/_site + ruby-version: '3.3.4' # Match your local Ruby version + bundler-cache: true + working-directory: ./help # Ensures Ruby setup is applied in /help + + - name: Install dependencies + run: | + bundle config set --local path 'vendor/bundle' + bundle install # Install all gems, including custom plugins + working-directory: ./help # Make sure dependencies are installed inside /help + + - name: Build Jekyll site + run: | + bundle exec jekyll build --source ./ --destination ./_site # Build within /help + working-directory: ./help # Ensure Jekyll is building the site in /help - name: Deploy to Cloudflare Pages - uses: cloudflare/pages-action@v1 # Using the latest stable version + uses: cloudflare/pages-action@v1 id: deploy if: env.IS_PR_FROM_FORK != 'true' with: apiToken: ${{ secrets.CLOUDFLARE_PAGES_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} projectName: newhelp - directory: ./help/_site - working-directory: ./help + directory: ./help/_site # Deploy the built site + working-directory: ./help # Ensure deployment is done from /help - name: Setup Cloudflare CLI if: env.IS_PR_FROM_FORK != 'true' run: pip3 install cloudflare==2.19.0 - working-directory: ./help + working-directory: ./help # Run CLI setup in /help - name: Purge Cloudflare cache if: env.IS_PR_FROM_FORK != 'true' run: $HOME/.local/bin/cli4 --verbose --delete hosts=["help.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache env: CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} - working-directory: ./help + working-directory: ./help # Ensure cache purging is executed in /help - name: Leave a comment on the PR - uses: actions-cool/maintain-one-comment@v3.2.0 # Using the latest stable version + uses: actions-cool/maintain-one-comment@v3.2.0 if: ${{ github.event_name == 'pull_request' && env.IS_PR_FROM_FORK != 'true' }} with: token: ${{ secrets.OS_BOTIFY_TOKEN }} body: ${{ format('A preview of your New Help changes have been deployed to {0} :zap:️', steps.deploy.outputs.alias) }} + working-directory: ./help # Leave comment from /help + From d158e6d67c881acd4d5992cb002dfe278f5f409d Mon Sep 17 00:00:00 2001 From: David Barrett Date: Thu, 26 Sep 2024 21:50:07 -0700 Subject: [PATCH 089/114] Still working on working directory --- .github/workflows/deployNewHelp.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index f6d80f497f9a..438fdb3c990e 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -39,7 +39,6 @@ jobs: with: ruby-version: '3.3.4' # Match your local Ruby version bundler-cache: true - working-directory: ./help # Ensures Ruby setup is applied in /help - name: Install dependencies run: | @@ -61,7 +60,6 @@ jobs: accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} projectName: newhelp directory: ./help/_site # Deploy the built site - working-directory: ./help # Ensure deployment is done from /help - name: Setup Cloudflare CLI if: env.IS_PR_FROM_FORK != 'true' @@ -81,5 +79,4 @@ jobs: with: token: ${{ secrets.OS_BOTIFY_TOKEN }} body: ${{ format('A preview of your New Help changes have been deployed to {0} :zap:️', steps.deploy.outputs.alias) }} - working-directory: ./help # Leave comment from /help From 1dde318e968fa62d0b8c20d4bae1c5112d550b97 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 27 Sep 2024 10:45:37 +0530 Subject: [PATCH 090/114] Merge conflicts --- src/components/ArchivedReportFooter.tsx | 37 ++++--------------- .../ReportActionItem/ReportPreview.tsx | 13 +++---- src/languages/en.ts | 2 +- src/languages/es.ts | 7 +--- src/languages/params.ts | 20 ---------- 5 files changed, 16 insertions(+), 63 deletions(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 37a686056781..af77a20b4caa 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -57,35 +57,14 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}} policyName = lodashEscape(policyName); } - let text: string; - switch (archiveReason) { - case CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED: - text = translate(`reportArchiveReasons.${archiveReason}`, { - displayName: `${displayName}`, - }); - break; - case CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED: - text = translate(`reportArchiveReasons.${archiveReason}`, { - displayName: `${displayName}`, - oldDisplayName: `${oldDisplayName}`, - }); - break; - case CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY: - text = translate(`reportArchiveReasons.${archiveReason}`, { - displayName: `${displayName}`, - policyName: `${policyName}`, - shouldUseYou: actorPersonalDetails?.accountID === getCurrentUserAccountID(), - }); - break; - case CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED: - text = translate(`reportArchiveReasons.${archiveReason}`, { - policyName: `${policyName}`, - }); - break; - default: - text = translate(`reportArchiveReasons.${archiveReason}`); - break; - } + const text = shouldRenderHTML + ? translate(`reportArchiveReasons.${archiveReason}`, { + displayName: `${displayName}`, + oldDisplayName: `${oldDisplayName}`, + policyName: `${policyName}`, + shouldUseYou: actorPersonalDetails?.accountID === getCurrentUserAccountID(), + }) + : translate(`reportArchiveReasons.${archiveReason}`); return ( `${count} UDDs added`, }), - mappingTitle: ({mappingName}: IntacctMappingTitleParams): string => { + mappingTitle: ({mappingName}: IntacctMappingTitleParams) => { switch (mappingName) { case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.DEPARTMENTS: return 'departments'; diff --git a/src/languages/es.ts b/src/languages/es.ts index 3f891ea81378..66c11d2e1728 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -6,7 +6,6 @@ import type { AddEmployeeParams, AddressLineParams, AdminCanceledRequestParams, - AgeParams, AlreadySignedInParams, ApprovalWorkflowErrorParams, ApprovedAmountParams, @@ -55,8 +54,6 @@ import type { DeleteActionParams, DeleteConfirmationParams, DidSplitAmountMessageParams, - DimensionsCountParams, - DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, EnterMagicCodeParams, @@ -2928,7 +2925,7 @@ const translations = { one: `1 UDD añadido`, other: (count: number) => `${count} UDDs añadido`, }), - mappingTitle: ({mappingName}: IntacctMappingTitleParams): string => { + mappingTitle: ({mappingName}: IntacctMappingTitleParams) => { switch (mappingName) { case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.DEPARTMENTS: return 'departamentos'; @@ -4070,7 +4067,7 @@ const translations = { } return { one: `te eliminó del flujo de trabajo de aprobaciones y del chat del espacio de trabajo de ${joinedNames}. Los informes enviados anteriormente seguirán estando disponibles para su aprobación en tu bandeja de entrada.`, - other: `te eliminó de los flujos de trabajo de aprobaciones y de los chats del espacio de trabajo de ${joinedNames}. Los informes enviados anteriormente seguirán estando disponibles para su aprobación en tu bandeja de entrada.` + other: `te eliminó de los flujos de trabajo de aprobaciones y de los chats del espacio de trabajo de ${joinedNames}. Los informes enviados anteriormente seguirán estando disponibles para su aprobación en tu bandeja de entrada.`, }; }, }, diff --git a/src/languages/params.ts b/src/languages/params.ts index e129705cf5c0..d51bb2d20e03 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -279,12 +279,8 @@ type LogSizeAndDateParams = {size: number; date: string}; type HeldRequestParams = {comment: string}; -type DistanceRateOperationsParams = {count: number}; - type ReimbursementRateParams = {unit: Unit}; -type ConfirmHoldExpenseParams = {transactionCount: number}; - type ChangeFieldParams = {oldValue?: string; newValue: string; fieldName: string}; type ChangePolicyParams = {fromPolicy: string; toPolicy: string}; @@ -331,10 +327,6 @@ type RemoveMemberPromptParams = { memberName: string; }; -type DeleteExpenseTranslationParams = { - count: number; -}; - type IssueVirtualCardParams = { assignee: string; link: string; @@ -384,8 +376,6 @@ type DisconnectTitleParams = {integration?: ConnectionName} | undefined; type AmountWithCurrencyParams = {amountWithCurrency: string}; -type SelectedNumberParams = {selectedNumber: number}; - type LowerUpperParams = {lower: string; upper: string}; type CategoryNameParams = {categoryName: string}; @@ -454,12 +444,8 @@ type RequiredFieldParams = {fieldName: string}; type ImportFieldParams = {importField: string}; -type DimensionsCountParams = {dimensionsCount: number}; - type IntacctMappingTitleParams = {mappingName: SageIntacctMappingName}; -type AgeParams = {age: number}; - type LastSyncAccountingParams = {relativeDate: string}; type SyncStageNameConnectionsParams = {stage: PolicyConnectionSyncStage}; @@ -570,9 +556,7 @@ export type { ReconciliationWorksParams, LastSyncAccountingParams, SyncStageNameConnectionsParams, - AgeParams, RequiredFieldParams, - DimensionsCountParams, IntacctMappingTitleParams, ImportFieldParams, AssigneeParams, @@ -608,7 +592,6 @@ export type { SecondaryLoginParams, TaxAmountParams, CategoryNameParams, - SelectedNumberParams, AmountWithCurrencyParams, LowerUpperParams, LogSizeAndDateParams, @@ -622,7 +605,6 @@ export type { BeginningOfChatHistoryDomainRoomPartOneParams, CanceledRequestParams, CharacterLimitParams, - ConfirmHoldExpenseParams, ConfirmThatParams, CompanyCardFeedNameParams, DateShouldBeAfterParams, @@ -630,7 +612,6 @@ export type { DeleteActionParams, DeleteConfirmationParams, DidSplitAmountMessageParams, - DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, EnterMagicCodeParams, @@ -732,7 +713,6 @@ export type { StripePaidParams, UnapprovedParams, RemoveMembersWarningPrompt, - DeleteExpenseTranslationParams, ApprovalWorkflowErrorParams, ConnectionNameParams, LastSyncDateParams, From ea0cfb583de2bd7aa7a9f23ce7a59784d5b11917 Mon Sep 17 00:00:00 2001 From: Roji Philip Date: Fri, 27 Sep 2024 11:48:27 +0530 Subject: [PATCH 091/114] fixed eslint error --- .../workspace/WorkspaceInviteMessagePage.tsx | 52 +++++-------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index 06f3962abbaa..c2e3e32799ec 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -2,8 +2,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; import lodashDebounce from 'lodash/debounce'; import React, {useEffect, useMemo, useState} from 'react'; import {Keyboard, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import type {GestureResponderEvent} from 'react-native/Libraries/Types/CoreEventTypes'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -35,36 +34,16 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/WorkspaceInviteMessageForm'; -import type {InvitedEmailsToAccountIDs, PersonalDetailsList} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import AccessOrNotFoundWrapper from './AccessOrNotFoundWrapper'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; -type WorkspaceInviteMessagePageOnyxProps = { - /** All of the personal details for everyone */ - allPersonalDetails: OnyxEntry; - - /** An object containing the accountID for every invited user email */ - invitedEmailsToAccountIDsDraft: OnyxEntry; - - /** Updated workspace invite message */ - workspaceInviteMessageDraft: OnyxEntry; -}; - type WorkspaceInviteMessagePageProps = WithPolicyAndFullscreenLoadingProps & WithCurrentUserPersonalDetailsProps & - WorkspaceInviteMessagePageOnyxProps & StackScreenProps; -function WorkspaceInviteMessagePage({ - workspaceInviteMessageDraft, - invitedEmailsToAccountIDsDraft, - policy, - route, - allPersonalDetails, - currentUserPersonalDetails, -}: WorkspaceInviteMessagePageProps) { +function WorkspaceInviteMessagePage({policy, route, currentUserPersonalDetails}: WorkspaceInviteMessagePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -72,6 +51,10 @@ function WorkspaceInviteMessagePage({ const {inputCallbackRef, inputRef} = useAutoFocusInput(); + const [invitedEmailsToAccountIDsDraft] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`); + const [workspaceInviteMessageDraft] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT}${route.params.policyID.toString()}`); + const [allPersonalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const welcomeNoteSubject = useMemo( () => `# ${currentUserPersonalDetails?.displayName ?? ''} invited you to ${policy?.name ?? 'a workspace'}`, [policy?.name, currentUserPersonalDetails?.displayName], @@ -100,6 +83,13 @@ function WorkspaceInviteMessagePage({ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, []); + useEffect(() => { + if (isEmptyObject(invitedEmailsToAccountIDsDraft)) { + return; + } + setWelcomeNote(getDefaultWelcomeNote()); + }, [invitedEmailsToAccountIDsDraft, workspaceInviteMessageDraft, route.params.policyID, policy]); + const debouncedSaveDraft = lodashDebounce((newDraft: string | null) => { Policy.setWorkspaceInviteMessageDraft(route.params.policyID, newDraft); }); @@ -221,18 +211,4 @@ function WorkspaceInviteMessagePage({ WorkspaceInviteMessagePage.displayName = 'WorkspaceInviteMessagePage'; -export default withPolicyAndFullscreenLoading( - withCurrentUserPersonalDetails( - withOnyx({ - allPersonalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - invitedEmailsToAccountIDsDraft: { - key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, - }, - workspaceInviteMessageDraft: { - key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT}${route.params.policyID.toString()}`, - }, - })(WorkspaceInviteMessagePage), - ), -); +export default withPolicyAndFullscreenLoading(withCurrentUserPersonalDetails(WorkspaceInviteMessagePage)); From 843e09949e6cd68685a736ee2e411a146fd8e399 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Thu, 26 Sep 2024 23:26:33 -0700 Subject: [PATCH 092/114] Fixing validation error --- .github/workflows/deployNewHelp.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 438fdb3c990e..5262e2468429 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -68,7 +68,8 @@ jobs: - name: Purge Cloudflare cache if: env.IS_PR_FROM_FORK != 'true' - run: $HOME/.local/bin/cli4 --verbose --delete hosts=["help.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache + run: "$HOME/.local/bin/cli4 --verbose --delete 'hosts=[\"newhelp.expensify.com\"]' /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache" + env: CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} working-directory: ./help # Ensure cache purging is executed in /help From a59988b89169539002ea930bb3b69d3e1cfa2fa1 Mon Sep 17 00:00:00 2001 From: Roji Philip Date: Fri, 27 Sep 2024 12:08:03 +0530 Subject: [PATCH 093/114] lint fix --- .../workspace/WorkspaceInviteMessagePage.tsx | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index c2e3e32799ec..3899656424ac 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import lodashDebounce from 'lodash/debounce'; -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {Keyboard, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import type {GestureResponderEvent} from 'react-native/Libraries/Types/CoreEventTypes'; @@ -60,16 +60,19 @@ function WorkspaceInviteMessagePage({policy, route, currentUserPersonalDetails}: [policy?.name, currentUserPersonalDetails?.displayName], ); - const getDefaultWelcomeNote = () => - // workspaceInviteMessageDraft can be an empty string - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - workspaceInviteMessageDraft || - // policy?.description can be an empty string - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - Parser.htmlToMarkdown(policy?.description ?? '') || - translate('workspace.common.welcomeNote', { - workspaceName: policy?.name ?? '', - }); + const getDefaultWelcomeNote = useCallback(() => { + return ( + // workspaceInviteMessageDraft can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + workspaceInviteMessageDraft || + // policy?.description can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + Parser.htmlToMarkdown(policy?.description ?? '') || + translate('workspace.common.welcomeNote', { + workspaceName: policy?.name ?? '', + }) + ); + }, [workspaceInviteMessageDraft, policy, translate]); useEffect(() => { if (!isEmptyObject(invitedEmailsToAccountIDsDraft)) { @@ -88,7 +91,7 @@ function WorkspaceInviteMessagePage({policy, route, currentUserPersonalDetails}: return; } setWelcomeNote(getDefaultWelcomeNote()); - }, [invitedEmailsToAccountIDsDraft, workspaceInviteMessageDraft, route.params.policyID, policy]); + }, [getDefaultWelcomeNote, invitedEmailsToAccountIDsDraft]); const debouncedSaveDraft = lodashDebounce((newDraft: string | null) => { Policy.setWorkspaceInviteMessageDraft(route.params.policyID, newDraft); From 405a695ac4d0a2879f8925c298bcaac4217f5634 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 27 Sep 2024 10:25:48 +0200 Subject: [PATCH 094/114] Simplify generating backTo param in Search --- src/components/Search/index.tsx | 4 ++-- src/components/SelectionList/Search/ReportListItem.tsx | 2 +- src/components/SelectionList/types.ts | 4 ---- src/libs/SearchUtils.ts | 7 +++---- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index d9c8f751ac70..b415d91b7ab4 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -227,7 +227,7 @@ function Search({queryJSON}: SearchProps) { } const ListItem = SearchUtils.getListItem(type, status); - const data = SearchUtils.getSections(type, status, searchResults.data, searchResults.search, queryJSON.inputQuery); + const data = SearchUtils.getSections(type, status, searchResults.data, searchResults.search); const sortedData = SearchUtils.getSortedSections(type, status, data, sortBy, sortOrder); const sortedSelectedData = sortedData.map((item) => mapToItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple)); @@ -294,7 +294,7 @@ function Search({queryJSON}: SearchProps) { SearchActions.createTransactionThread(hash, item.transactionID, reportID, item.moneyRequestReportActionID); } - const backTo = ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: queryJSON.inputQuery}); + const backTo = Navigation.getActiveRoute(); if (SearchUtils.isReportActionListItemType(item)) { const reportActionID = item.reportActionID; diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx index 63705bda90f1..2c23c3ede4c5 100644 --- a/src/components/SelectionList/Search/ReportListItem.tsx +++ b/src/components/SelectionList/Search/ReportListItem.tsx @@ -84,7 +84,7 @@ function ReportListItem({ }; const openReportInRHP = (transactionItem: TransactionListItemType) => { - const backTo = ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: reportItem.currentSearchQuery}); + const backTo = Navigation.getActiveRoute(); Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: transactionItem.transactionThreadReportID, backTo})); }; diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 3fa901073e0e..b0d657b202c6 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -1,6 +1,5 @@ import type {MutableRefObject, ReactElement, ReactNode} from 'react'; import type {GestureResponderEvent, InputModeOptions, LayoutChangeEvent, SectionListData, StyleProp, TextInput, TextStyle, ViewStyle} from 'react-native'; -import type {SearchQueryString} from '@components/Search/types'; import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; // eslint-disable-next-line no-restricted-imports import type CursorStyles from '@styles/utils/cursor/types'; @@ -236,9 +235,6 @@ type ReportListItemType = ListItem & /** List of transactions that belong to this report */ transactions: TransactionListItemType[]; - - /** The current search query that was used to display these report items */ - currentSearchQuery: SearchQueryString; }; type ListItemProps = CommonListItemProps & { diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index fa50fbc0ce47..703c4df5a29c 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -250,7 +250,7 @@ function getIOUReportName(data: OnyxTypes.SearchResults['data'], reportItem: Sea return reportItem.reportName; } -function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search'], currentSearchQuery: SearchQueryString): ReportListItemType[] { +function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']): ReportListItemType[] { const shouldShowMerchant = getShouldShowMerchant(data); const doesDataContainAPastYearTransaction = shouldShowYear(data); @@ -269,7 +269,6 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx from: data.personalDetailsList?.[reportItem.accountID ?? -1], to: reportItem.managerID ? data.personalDetailsList?.[reportItem.managerID] : emptyPersonalDetails, transactions, - currentSearchQuery, reportName: isIOUReport ? getIOUReportName(data, reportItem) : reportItem.reportName, }; } else if (isTransactionEntry(key)) { @@ -318,14 +317,14 @@ function getListItem(type: SearchDataTypes, status: SearchStatus): ListItemType< return ReportListItem; } -function getSections(type: SearchDataTypes, status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search'], currentSearchQuery: SearchQueryString) { +function getSections(type: SearchDataTypes, status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']) { if (type === CONST.SEARCH.DATA_TYPES.CHAT) { return getReportActionsSections(data); } if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) { return getTransactionsSections(data, metadata); } - return getReportSections(data, metadata, currentSearchQuery); + return getReportSections(data, metadata); } function getSortedSections(type: SearchDataTypes, status: SearchStatus, data: ListItemDataType, sortBy?: SearchColumnType, sortOrder?: SortOrder) { From 43c46764db5879dd331095cdf9885c55c2c3e97e Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 27 Sep 2024 10:32:01 +0200 Subject: [PATCH 095/114] Fix bug after merge conflict --- src/components/PromotedActionsBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PromotedActionsBar.tsx b/src/components/PromotedActionsBar.tsx index 85437a9ae3fd..e6ce3080ee0a 100644 --- a/src/components/PromotedActionsBar.tsx +++ b/src/components/PromotedActionsBar.tsx @@ -100,7 +100,7 @@ const PromotedActions = { return; } - ReportUtils.changeMoneyRequestHoldStatus(reportAction, ROUTES.SEARCH_REPORT.getRoute({reportID: targetedReportID})); + ReportUtils.changeMoneyRequestHoldStatus(reportAction, ROUTES.SEARCH_REPORT.getRoute({reportID: targetedReportID}), currentSearchHash); }, }), } satisfies PromotedActionsType; From eb4170625c4118ad438f9a1b8b0bff0138441335 Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 27 Sep 2024 16:23:19 +0700 Subject: [PATCH 096/114] fix replace value with const --- src/libs/CurrencyUtils.ts | 2 +- src/libs/PolicyDistanceRatesUtils.ts | 2 +- src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx | 2 +- .../workspace/distanceRates/PolicyDistanceRateEditPage.tsx | 5 ++--- .../PolicyDistanceRateTaxReclaimableEditPage.tsx | 5 +++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 9e54efa7a03d..7d11bd0d61ae 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -164,7 +164,7 @@ function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRE return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, - minimumFractionDigits: 4, + minimumFractionDigits: CONST.MAX_TAX_RATE_DECIMAL_PLACES, }); } diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts index 415fcc627b4e..8e4d68f78b4c 100644 --- a/src/libs/PolicyDistanceRatesUtils.ts +++ b/src/libs/PolicyDistanceRatesUtils.ts @@ -17,7 +17,7 @@ function validateRateValue(values: FormOnyxValues, currency: stri const decimalSeparator = toLocaleDigit('.'); // Allow one more decimal place for accuracy - const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{0,4})?$`, 'i'); + const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{0,${CONST.MAX_TAX_RATE_DECIMAL_PLACES}})?$`, 'i'); if (!rateValueRegex.test(parsedRate) || parsedRate === '') { errors.rate = Localize.translateLocal('common.error.invalidRateError'); } else if (NumberUtils.parseFloatAnyLocale(parsedRate) <= 0) { diff --git a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index 353bf42afe29..7566f458fcff 100644 --- a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -85,7 +85,7 @@ function CreateDistanceRatePage({route}: CreateDistanceRatePageProps) { ) => { if (currentRateValue === values.rate) { @@ -89,7 +88,7 @@ function PolicyDistanceRateEditPage({route}: PolicyDistanceRateEditPageProps) { ) => { if (values.taxClaimableValue === currentTaxReclaimableOnValue) { @@ -85,7 +86,7 @@ function PolicyDistanceRateTaxReclaimableEditPage({route, policy}: PolicyDistanc Date: Fri, 27 Sep 2024 13:57:33 +0200 Subject: [PATCH 097/114] Fix User not redirected to IOU report when logging in after deeplink --- src/libs/actions/Report.ts | 39 +++++++++++++++++-------------- src/libs/actions/Welcome/index.ts | 4 +++- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index e53cac804b90..0fe2bfbf8d47 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2689,28 +2689,31 @@ function openReportFromDeepLink(url: string) { return; } - // We need skip deeplinking if the user hasn't completed the guided setup flow. - Welcome.isOnboardingFlowCompleted({ - onNotCompleted: () => OnboardingFlow.startOnboardingFlow(), - onCompleted: () => { - const state = navigationRef.getRootState(); - const currentFocusedRoute = findFocusedRoute(state); + const handleDeeplinkNavigation = () => { + const state = navigationRef.getRootState(); + const currentFocusedRoute = findFocusedRoute(state); - if (isOnboardingFlowName(currentFocusedRoute?.name)) { - Welcome.setOnboardingErrorMessage(Localize.translateLocal('onboarding.purpose.errorBackButton')); - return; - } + if (isOnboardingFlowName(currentFocusedRoute?.name)) { + Welcome.setOnboardingErrorMessage(Localize.translateLocal('onboarding.purpose.errorBackButton')); + return; + } - if (shouldSkipDeepLinkNavigation(route)) { - return; - } + if (shouldSkipDeepLinkNavigation(route)) { + return; + } - if (isAuthenticated) { - return; - } + if (isAuthenticated) { + return; + } - Navigation.navigate(route as Route, CONST.NAVIGATION.ACTION_TYPE.PUSH); - }, + Navigation.navigate(route as Route, CONST.NAVIGATION.ACTION_TYPE.PUSH); + }; + + // We need skip deeplinking if the user hasn't completed the guided setup flow. + Welcome.isOnboardingFlowCompleted({ + onNotCompleted: OnboardingFlow.startOnboardingFlow, + onCompleted: handleDeeplinkNavigation, + onCanceled: handleDeeplinkNavigation, }); }); }, diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts index 75529a879104..fc921b16f4cf 100644 --- a/src/libs/actions/Welcome/index.ts +++ b/src/libs/actions/Welcome/index.ts @@ -23,6 +23,7 @@ let onboarding: OnboardingData; type HasCompletedOnboardingFlowProps = { onCompleted?: () => void; onNotCompleted?: () => void; + onCanceled?: () => void; }; type HasOpenedForTheFirstTimeFromHybridAppProps = { @@ -50,9 +51,10 @@ function onServerDataReady(): Promise { } let isOnboardingInProgress = false; -function isOnboardingFlowCompleted({onCompleted, onNotCompleted}: HasCompletedOnboardingFlowProps) { +function isOnboardingFlowCompleted({onCompleted, onNotCompleted, onCanceled}: HasCompletedOnboardingFlowProps) { isOnboardingFlowStatusKnownPromise.then(() => { if (Array.isArray(onboarding) || onboarding?.hasCompletedGuidedSetupFlow === undefined) { + onCanceled?.(); return; } From ebdeca565a4043acc328c4942625dbf5cbd771f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 27 Sep 2024 15:07:37 +0200 Subject: [PATCH 098/114] add comments to indicate platform specific logic should be avoided at all costs --- src/pages/ShareCodePage.tsx | 6 ++++++ src/pages/workspace/WorkspaceProfileSharePage.tsx | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index 0f8edf2c8d63..d2ef35d2daa8 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -84,6 +84,9 @@ function ShareCodePage({report, policy}: ShareCodePageProps) { }, [report, currentUserPersonalDetails, isReport]); const title = isReport ? ReportUtils.getReportName(report) : currentUserPersonalDetails.displayName ?? ''; + // We should remove this logic once https://github.com/Expensify/App/issues/19834 is done + // We shouldn't introduce platform specific code in our codebase + // This is a temporary solution while Web is not supported for the QR code download feature const platform = getPlatform(); const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); @@ -139,6 +142,9 @@ function ShareCodePage({report, policy}: ShareCodePageProps) { onPress={() => Clipboard.setString(url)} shouldLimitWidth={false} /> + {/* Remove this platform specific condition once https://github.com/Expensify/App/issues/19834 is done. + We shouldn't introduce platform specific code in our codebase. + This is a temporary solution while Web is not supported for the QR code download feature */} {isNative && ( + {/* Remove this once https://github.com/Expensify/App/issues/19834 is done. + We shouldn't introduce platform specific code in our codebase. + This is a temporary solution while Web is not supported for the QR code download feature */} {shouldAllowDownloadQRCode && ( Date: Fri, 27 Sep 2024 19:14:32 +0530 Subject: [PATCH 099/114] Fix backticks usage --- src/languages/en.ts | 26 +++++++++++++------------- src/languages/es.ts | 26 +++++++++++++------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 86ef7ed41fe7..6d579a2af2df 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -841,8 +841,8 @@ const translations = { receiptScanInProgress: 'Receipt scan in progress', receiptScanInProgressDescription: 'Receipt scan in progress. Check back later or enter the details now.', receiptIssuesFound: () => ({ - one: `Issue found`, - other: `Issues found`, + one: 'Issue found', + other: 'Issues found', }), fieldPending: 'Pending...', defaultRate: 'Default rate', @@ -874,12 +874,12 @@ const translations = { }; }, deleteExpense: () => ({ - one: `Delete expense`, - other: `Delete expenses`, + one: 'Delete expense', + other: 'Delete expenses', }), deleteConfirmation: () => ({ - one: `Are you sure that you want to delete this expense?`, - other: `Are you sure that you want to delete these expenses?`, + one: 'Are you sure that you want to delete this expense?', + other: 'Are you sure that you want to delete these expenses?', }), settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', @@ -982,14 +982,14 @@ const translations = { confirmApprove: 'Confirm approval amount', confirmApprovalAmount: 'Approve only compliant expenses, or approve the entire report.', confirmApprovalAllHoldAmount: () => ({ - one: `This expense is on hold. Do you want to approve anyway?`, - other: `These expenses are on hold. Do you want to approve anyway?`, + one: 'This expense is on hold. Do you want to approve anyway?', + other: 'These expenses are on hold. Do you want to approve anyway?', }), confirmPay: 'Confirm payment amount', confirmPayAmount: "Pay what's not on hold, or pay the entire report.", confirmPayAllHoldAmount: () => ({ - one: `This expense is on hold. Do you want to pay anyway?`, - other: `These expenses are on hold. Do you want to pay anyway?`, + one: 'This expense is on hold. Do you want to pay anyway?', + other: 'These expenses are on hold. Do you want to pay anyway?', }), payOnly: 'Pay only', approveOnly: 'Approve only', @@ -2283,7 +2283,7 @@ const translations = { issueAndManageCards: 'Issue and manage cards', reconcileCards: 'Reconcile cards', selected: () => ({ - one: `1 selected`, + one: '1 selected', other: (count: number) => `${count} selected`, }), settlementFrequency: 'Settlement frequency', @@ -2887,7 +2887,7 @@ const translations = { detailedInstructionsLink: 'View detailed instructions', detailedInstructionsRestOfSentence: ' on adding user-defined dimensions.', userDimensionsAdded: () => ({ - one: `1 UDD added`, + one: '1 UDD added', other: (count: number) => `${count} UDDs added`, }), mappingTitle: ({mappingName}: IntacctMappingTitleParams) => { @@ -3880,7 +3880,7 @@ const translations = { maxExpenseAge: 'Max expense age', maxExpenseAgeDescription: 'Flag spend older than a specific number of days.', maxExpenseAgeDays: () => ({ - one: `1 day`, + one: '1 day', other: (count: number) => `${count} days`, }), billableDefault: 'Billable default', diff --git a/src/languages/es.ts b/src/languages/es.ts index 66c11d2e1728..cb19b091b058 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -831,8 +831,8 @@ const translations = { markAsCash: 'Marcar como efectivo', routePending: 'Ruta pendiente...', receiptIssuesFound: () => ({ - one: `Problema encontrado`, - other: `Problemas encontrados`, + one: 'Problema encontrado', + other: 'Problemas encontrados', }), fieldPending: 'Pendiente...', receiptScanning: 'Escaneando recibo...', @@ -867,12 +867,12 @@ const translations = { }; }, deleteExpense: () => ({ - one: `Eliminar gasto`, - other: `Eliminar gastos`, + one: 'Eliminar gasto', + other: 'Eliminar gastos', }), deleteConfirmation: () => ({ - one: `¿Estás seguro de que quieres eliminar esta solicitud?`, - other: `¿Estás seguro de que quieres eliminar estas solicitudes?`, + one: '¿Estás seguro de que quieres eliminar esta solicitud?', + other: '¿Estás seguro de que quieres eliminar estas solicitudes?', }), settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', @@ -974,14 +974,14 @@ const translations = { confirmApprove: 'Confirmar importe a aprobar', confirmApprovalAmount: 'Aprueba sólo los gastos conformes, o aprueba todo el informe.', confirmApprovalAllHoldAmount: () => ({ - one: `Este gasto está bloqueado. ¿Quieres aprobarlo de todos modos?`, - other: `Estos gastos están bloqueados. ¿Quieres aprobarlos de todos modos?`, + one: 'Este gasto está bloqueado. ¿Quieres aprobarlo de todos modos?', + other: 'Estos gastos están bloqueados. ¿Quieres aprobarlos de todos modos?', }), confirmPay: 'Confirmar importe de pago', confirmPayAmount: 'Paga lo que no está bloqueado, o paga el informe completo.', confirmPayAllHoldAmount: () => ({ - one: `Este gasto está bloqueado. ¿Quieres pagarlo de todos modos?`, - other: `Estos gastos están bloqueados. ¿Quieres pagarlos de todos modos?`, + one: 'Este gasto está bloqueado. ¿Quieres pagarlo de todos modos?', + other: 'Estos gastos están bloqueados. ¿Quieres pagarlos de todos modos?', }), payOnly: 'Solo pagar', approveOnly: 'Solo aprobar', @@ -2303,7 +2303,7 @@ const translations = { issueAndManageCards: 'Emitir y gestionar tarjetas', reconcileCards: 'Reconciliar tarjetas', selected: () => ({ - one: `1 seleccionado`, + one: '1 seleccionado', other: (count: number) => `${count} seleccionados`, }), settlementFrequency: 'Frecuencia de liquidación', @@ -2922,7 +2922,7 @@ const translations = { detailedInstructionsLink: 'Ver instrucciones detalladas', detailedInstructionsRestOfSentence: ' para añadir dimensiones definidas por el usuario.', userDimensionsAdded: () => ({ - one: `1 UDD añadido`, + one: '1 UDD añadido', other: (count: number) => `${count} UDDs añadido`, }), mappingTitle: ({mappingName}: IntacctMappingTitleParams) => { @@ -3923,7 +3923,7 @@ const translations = { maxExpenseAge: 'Antigüedad máxima de los gastos', maxExpenseAgeDescription: 'Marca los gastos de más de un número determinado de días.', maxExpenseAgeDays: () => ({ - one: `1 día`, + one: '1 día', other: (count: number) => `${count} días`, }), billableDefault: 'Valor predeterminado facturable', From ff81529f0c26c8204fb6231f6c86d1cfcd4cc4ae Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 27 Sep 2024 16:31:13 +0000 Subject: [PATCH 100/114] Update version to 9.0.40-5 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 9153966e1d5d..d6e39817ecc8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009004004 - versionName "9.0.40-4" + versionCode 1009004005 + versionName "9.0.40-5" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index af696a13c998..ee277d55f828 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.40.4 + 9.0.40.5 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 0795209286ed..6ad146a70804 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.40.4 + 9.0.40.5 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index a545bd82c164..cfd49cb6a6a6 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.40 CFBundleVersion - 9.0.40.4 + 9.0.40.5 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index fa405b2ba182..9965f17d3b60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.40-4", + "version": "9.0.40-5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.40-4", + "version": "9.0.40-5", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 283b5a2d6a8c..c12b35334c76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.40-4", + "version": "9.0.40-5", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 78edc3f23c5b611bb7cb9525042e19599fa7cbfd Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 27 Sep 2024 16:44:25 +0000 Subject: [PATCH 101/114] Update version to 9.0.40-6 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index d6e39817ecc8..37584a89a7ad 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009004005 - versionName "9.0.40-5" + versionCode 1009004006 + versionName "9.0.40-6" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index ee277d55f828..b7df12311ccf 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.40.5 + 9.0.40.6 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 6ad146a70804..5bff4734e9dc 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.40.5 + 9.0.40.6 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index cfd49cb6a6a6..4f948fbeb4cc 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.40 CFBundleVersion - 9.0.40.5 + 9.0.40.6 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 9965f17d3b60..bb00bb068675 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.40-5", + "version": "9.0.40-6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.40-5", + "version": "9.0.40-6", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c12b35334c76..016135b94ec2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.40-5", + "version": "9.0.40-6", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 3be23a2e0c6b32a6b768bed7ac13c41aafa48c4e Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 27 Sep 2024 10:00:28 -0700 Subject: [PATCH 102/114] Add continue-on-error for non-critical steps --- .github/workflows/deploy.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 53afe03720f7..df3143368d2e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -507,11 +507,13 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: Rename web and desktop sourcemaps artifacts before assets upload in order to have unique ReleaseAsset.name + continue-on-error: true run: | mv ./desktop-staging-sourcemaps-artifact/merged-source-map.js.map ./desktop-staging-sourcemaps-artifact/desktop-staging-merged-source-map.js.map mv ./web-staging-sourcemaps-artifact/merged-source-map.js.map ./web-staging-sourcemaps-artifact/web-staging-merged-source-map.js.map - name: Upload artifacts to GitHub Release + continue-on-error: true run: | gh release upload ${{ needs.prep.outputs.APP_VERSION }} --repo ${{ github.repository }} --clobber \ ./android-sourcemaps-artifact/index.android.bundle.map#android-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ @@ -552,11 +554,6 @@ jobs: - name: Download all workflow run artifacts uses: actions/download-artifact@v4 - - name: Rename web and desktop sourcemaps artifacts before assets upload in order to have unique ReleaseAsset.name - run: | - mv ./desktop-sourcemaps-artifact/merged-source-map.js.map ./desktop-sourcemaps-artifact/desktop-merged-source-map.js.map - mv ./web-sourcemaps-artifact/merged-source-map.js.map ./web-sourcemaps-artifact/web-merged-source-map.js.map - - name: 🚀 Edit the release to be no longer a prerelease 🚀 run: | LATEST_RELEASE="$(gh release list --repo ${{ github.repository }} --exclude-pre-releases --json tagName,isLatest --jq '.[] | select(.isLatest) | .tagName')" @@ -565,7 +562,14 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} + - name: Rename web and desktop sourcemaps artifacts before assets upload in order to have unique ReleaseAsset.name + continue-on-error: true + run: | + mv ./desktop-sourcemaps-artifact/merged-source-map.js.map ./desktop-sourcemaps-artifact/desktop-merged-source-map.js.map + mv ./web-sourcemaps-artifact/merged-source-map.js.map ./web-sourcemaps-artifact/web-merged-source-map.js.map + - name: Upload artifacts to GitHub Release + continue-on-error: true run: | gh release upload ${{ needs.prep.outputs.APP_VERSION }} --repo ${{ github.repository }} --clobber \ ./desktop-sourcemaps-artifact/desktop-merged-source-map.js.map#desktop-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ From 244d751dc2ada8be9321ebe0f25b585acb068d84 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 27 Sep 2024 17:29:14 +0000 Subject: [PATCH 103/114] Update version to 9.0.41-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- ios/NotificationServiceExtension/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 37584a89a7ad..49f4b7931c38 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009004006 - versionName "9.0.40-6" + versionCode 1009004100 + versionName "9.0.41-0" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index b7df12311ccf..0ce3182823a1 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.40 + 9.0.41 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.40.6 + 9.0.41.0 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 5bff4734e9dc..d12e3a20a7b2 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.40 + 9.0.41 CFBundleSignature ???? CFBundleVersion - 9.0.40.6 + 9.0.41.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 4f948fbeb4cc..0b6ea2c597a2 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.40 + 9.0.41 CFBundleVersion - 9.0.40.6 + 9.0.41.0 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index bb00bb068675..f165c86c4356 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.40-6", + "version": "9.0.41-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.40-6", + "version": "9.0.41-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 016135b94ec2..ccbc2e977874 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.40-6", + "version": "9.0.41-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 77c50f3ecc002f6370c15e701d7e69f6f5e1c7a5 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 27 Sep 2024 17:56:18 +0000 Subject: [PATCH 104/114] Update version to 9.0.41-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 49f4b7931c38..f23f2d2242e5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009004100 - versionName "9.0.41-0" + versionCode 1009004101 + versionName "9.0.41-1" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 0ce3182823a1..7eda516ecebf 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.41.0 + 9.0.41.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index d12e3a20a7b2..16c45a1b5379 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.41.0 + 9.0.41.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 0b6ea2c597a2..2e420277ffff 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.41 CFBundleVersion - 9.0.41.0 + 9.0.41.1 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index f165c86c4356..4fd8c29f2da4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.41-0", + "version": "9.0.41-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.41-0", + "version": "9.0.41-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index ccbc2e977874..99a566b387dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.41-0", + "version": "9.0.41-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 689b355f8b58ccde4cce1eed34abc67f151b6a89 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Fri, 27 Sep 2024 11:58:38 -0700 Subject: [PATCH 105/114] With amazing updates from @rory --- .github/workflows/deployNewHelp.yml | 24 ++++++------------- .github/workflows/preDeploy.yml | 2 +- .../workflows/reassurePerformanceTests.yml | 2 +- .github/workflows/sendReassurePerfData.yml | 2 +- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 5262e2468429..917ce1b3c0ef 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -1,3 +1,5 @@ +name: Deploy New Help Site + on: # Run on any push to main that has changes to the help directory push: @@ -30,25 +32,16 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - # Set up Ruby and run commands inside the /help directory + # Set up Ruby and run bundle install inside the /help directory - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.3.4' # Match your local Ruby version bundler-cache: true - - - name: Install dependencies - run: | - bundle config set --local path 'vendor/bundle' - bundle install # Install all gems, including custom plugins - working-directory: ./help # Make sure dependencies are installed inside /help + working-directory: ./help - name: Build Jekyll site - run: | - bundle exec jekyll build --source ./ --destination ./_site # Build within /help + run: bundle exec jekyll build --source ./ --destination ./_site working-directory: ./help # Ensure Jekyll is building the site in /help - name: Deploy to Cloudflare Pages @@ -64,20 +57,17 @@ jobs: - name: Setup Cloudflare CLI if: env.IS_PR_FROM_FORK != 'true' run: pip3 install cloudflare==2.19.0 - working-directory: ./help # Run CLI setup in /help - name: Purge Cloudflare cache if: env.IS_PR_FROM_FORK != 'true' - run: "$HOME/.local/bin/cli4 --verbose --delete 'hosts=[\"newhelp.expensify.com\"]' /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache" - + run: /home/runner/.local/bin/cli4 --verbose --delete hosts=["newhelp.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache env: CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} - working-directory: ./help # Ensure cache purging is executed in /help - name: Leave a comment on the PR uses: actions-cool/maintain-one-comment@v3.2.0 if: ${{ github.event_name == 'pull_request' && env.IS_PR_FROM_FORK != 'true' }} with: - token: ${{ secrets.OS_BOTIFY_TOKEN }} + token: ${{ github.token }} body: ${{ format('A preview of your New Help changes have been deployed to {0} :zap:️', steps.deploy.outputs.alias) }} diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index 796468170275..bfe860e60224 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -4,7 +4,7 @@ name: Process new code merged to main on: push: branches: [main] - paths-ignore: [docs/**, contributingGuides/**, jest/**, tests/**] + paths-ignore: [docs/**, help/**, contributingGuides/**, jest/**, tests/**] jobs: typecheck: diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml index d4a25a63952b..fb7a34d6fa01 100644 --- a/.github/workflows/reassurePerformanceTests.yml +++ b/.github/workflows/reassurePerformanceTests.yml @@ -4,7 +4,7 @@ on: pull_request: types: [opened, synchronize] branches-ignore: [staging, production] - paths-ignore: [docs/**, .github/**, contributingGuides/**, tests/**, '**.md', '**.sh'] + paths-ignore: [docs/**, help/**, .github/**, contributingGuides/**, tests/**, '**.md', '**.sh'] jobs: perf-tests: diff --git a/.github/workflows/sendReassurePerfData.yml b/.github/workflows/sendReassurePerfData.yml index 42d946cece95..884182bfc896 100644 --- a/.github/workflows/sendReassurePerfData.yml +++ b/.github/workflows/sendReassurePerfData.yml @@ -3,7 +3,7 @@ name: Send Reassure Performance Tests to Graphite on: push: branches: [main] - paths-ignore: [docs/**, contributingGuides/**, jest/**] + paths-ignore: [docs/**, help/**, contributingGuides/**, jest/**] jobs: perf-tests: From a2598c498adb4497eb85eb1ecd320d8a92fc879c Mon Sep 17 00:00:00 2001 From: David Barrett Date: Fri, 27 Sep 2024 12:03:30 -0700 Subject: [PATCH 106/114] Whoops, missed ruby-version file --- .github/workflows/.ruby-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/workflows/.ruby-version diff --git a/.github/workflows/.ruby-version b/.github/workflows/.ruby-version new file mode 100644 index 000000000000..a0891f563f38 --- /dev/null +++ b/.github/workflows/.ruby-version @@ -0,0 +1 @@ +3.3.4 From cbaa36f85ab676be77f0a6157cf812c6916b8503 Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 27 Sep 2024 12:04:30 -0700 Subject: [PATCH 107/114] Use loop to verify web deploy --- .github/scripts/verifyDeploy.sh | 34 +++++++++++++++++++++++++++++++++ .github/workflows/deploy.yml | 16 ++-------------- 2 files changed, 36 insertions(+), 14 deletions(-) create mode 100755 .github/scripts/verifyDeploy.sh diff --git a/.github/scripts/verifyDeploy.sh b/.github/scripts/verifyDeploy.sh new file mode 100755 index 000000000000..51986979e188 --- /dev/null +++ b/.github/scripts/verifyDeploy.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +ENV="$1" +EXPECTED_VERSION="$2" + +BASE_URL="" +if [[ "$ENV" == 'staging' ]]; then + BASE_URL='https://staging.new.expensify.com' +else + BASE_URL='https://new.expensify.com' +fi + +sleep 5 +ATTEMPT=0 +MAX_ATTEMPTS=10 +while [[ $ATTEMPT -lt $MAX_ATTEMPTS ]]; do + ((ATTEMPT++)) + + echo "Attempt $ATTEMPT: Checking deployed version..." + DOWNLOADED_VERSION="$(wget -q -O /dev/stdout "$BASE_URL"/version.json | jq -r '.version')" + + if [[ "$EXPECTED_VERSION" == "$DOWNLOADED_VERSION" ]]; then + echo "Success: Deployed version matches local version: $DOWNLOADED_VERSION" + exit 0 + fi + + if [[ $ATTEMPT -lt $MAX_ATTEMPTS ]]; then + echo "Version mismatch. Retrying in 5 seconds..." + sleep 5 + fi +done + +echo "Error: Deployed version did not match local version after $MAX_ATTEMPTS attempts. Something went wrong..." +exit 1 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index df3143368d2e..99cd0c1dabc5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -386,23 +386,11 @@ jobs: - name: Verify staging deploy if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: | - sleep 5 - DOWNLOADED_VERSION="$(wget -q -O /dev/stdout https://staging.new.expensify.com/version.json | jq -r '.version')" - if [[ '${{ needs.prep.outputs.APP_VERSION }}' != "$DOWNLOADED_VERSION" ]]; then - echo "Error: deployed version $DOWNLOADED_VERSION does not match local version ${{ needs.prep.outputs.APP_VERSION }}. Something went wrong..." - exit 1 - fi + run: ./.github/scripts/verifyDeploy.sh staging ${{ needs.prep.outputs.APP_VERSION }} - name: Verify production deploy if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: | - sleep 5 - DOWNLOADED_VERSION="$(wget -q -O /dev/stdout https://new.expensify.com/version.json | jq -r '.version')" - if [[ '${{ needs.prep.outputs.APP_VERSION }}' != "$DOWNLOADED_VERSION" ]]; then - echo "Error: deployed version $DOWNLOADED_VERSION does not match local version ${{ needs.prep.outputs.APP_VERSION }}. Something went wrong..." - exit 1 - fi + run: ./.github/scripts/verifyDeploy.sh production ${{ needs.prep.outputs.APP_VERSION }} - name: Upload web sourcemaps artifact uses: actions/upload-artifact@v4 From 93385fcbadc331335830c47c0cf6032d9c7d0775 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Fri, 27 Sep 2024 12:09:01 -0700 Subject: [PATCH 108/114] Removing working directory from Ruby install so it finds the .ruby-version --- .github/workflows/deployNewHelp.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 917ce1b3c0ef..b0a535338e43 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -38,7 +38,6 @@ jobs: uses: ruby/setup-ruby@v1 with: bundler-cache: true - working-directory: ./help - name: Build Jekyll site run: bundle exec jekyll build --source ./ --destination ./_site From ec8e29e939299c7bf53e49011d9bc9366b3821ab Mon Sep 17 00:00:00 2001 From: David Barrett Date: Fri, 27 Sep 2024 12:12:42 -0700 Subject: [PATCH 109/114] Moving .ruby-version to /help to keep install all in there --- .github/workflows/deployNewHelp.yml | 1 + {.github/workflows => help}/.ruby-version | 0 2 files changed, 1 insertion(+) rename {.github/workflows => help}/.ruby-version (100%) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index b0a535338e43..917ce1b3c0ef 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -38,6 +38,7 @@ jobs: uses: ruby/setup-ruby@v1 with: bundler-cache: true + working-directory: ./help - name: Build Jekyll site run: bundle exec jekyll build --source ./ --destination ./_site diff --git a/.github/workflows/.ruby-version b/help/.ruby-version similarity index 100% rename from .github/workflows/.ruby-version rename to help/.ruby-version From 99beb83deeab79234338e8dcb7b5284aac44537e Mon Sep 17 00:00:00 2001 From: David Barrett Date: Fri, 27 Sep 2024 12:16:08 -0700 Subject: [PATCH 110/114] Removing main commit hook per @rory's suggestion --- .github/workflows/deployNewHelp.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 917ce1b3c0ef..4f90c39d1a50 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -2,11 +2,12 @@ name: Deploy New Help Site on: # Run on any push to main that has changes to the help directory - push: - branches: - - main - paths: - - 'help/**' +# TEST: Verify Cloudflare picks this up even if not run when merged to main +# push: +# branches: +# - main +# paths: +# - 'help/**' # Run on any pull request (except PRs against staging or production) that has changes to the help directory pull_request: @@ -38,7 +39,6 @@ jobs: uses: ruby/setup-ruby@v1 with: bundler-cache: true - working-directory: ./help - name: Build Jekyll site run: bundle exec jekyll build --source ./ --destination ./_site From 4c45ce32d8e0e7104ebd9384ca379ac68acfe2e3 Mon Sep 17 00:00:00 2001 From: David Barrett Date: Fri, 27 Sep 2024 12:28:42 -0700 Subject: [PATCH 111/114] Fixed working directory --- .github/workflows/deployNewHelp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 4f90c39d1a50..8c4d0fb0ae3b 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -39,6 +39,7 @@ jobs: uses: ruby/setup-ruby@v1 with: bundler-cache: true + working-directory: ./help - name: Build Jekyll site run: bundle exec jekyll build --source ./ --destination ./_site From a459ee8c20e8f12a89f9945c7b0d9fe40346b4e6 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 27 Sep 2024 19:28:54 +0000 Subject: [PATCH 112/114] Update version to 9.0.41-2 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f23f2d2242e5..2491cc21a400 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009004101 - versionName "9.0.41-1" + versionCode 1009004102 + versionName "9.0.41-2" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 7eda516ecebf..2de5297dd7fb 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.41.1 + 9.0.41.2 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 16c45a1b5379..31fc4454214c 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.41.1 + 9.0.41.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 2e420277ffff..0abd6fae99d5 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.41 CFBundleVersion - 9.0.41.1 + 9.0.41.2 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 4fd8c29f2da4..0ef9b9b19012 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.41-1", + "version": "9.0.41-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.41-1", + "version": "9.0.41-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 99a566b387dd..aed1cf9a2c3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.41-1", + "version": "9.0.41-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 2ccaacc3cbc8ba64115dfe13781df72d713c79b5 Mon Sep 17 00:00:00 2001 From: Rory Abraham <47436092+roryabraham@users.noreply.github.com> Date: Fri, 27 Sep 2024 13:17:59 -0700 Subject: [PATCH 113/114] Update .github/scripts/verifyDeploy.sh Co-authored-by: Andrew Gable --- .github/scripts/verifyDeploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/verifyDeploy.sh b/.github/scripts/verifyDeploy.sh index 51986979e188..0a8fd3c97bcf 100755 --- a/.github/scripts/verifyDeploy.sh +++ b/.github/scripts/verifyDeploy.sh @@ -25,7 +25,7 @@ while [[ $ATTEMPT -lt $MAX_ATTEMPTS ]]; do fi if [[ $ATTEMPT -lt $MAX_ATTEMPTS ]]; then - echo "Version mismatch. Retrying in 5 seconds..." + echo "Version mismatch, found $DOWNLOADED_VERSION. Retrying in 5 seconds..." sleep 5 fi done From e76359cf4ddc95152873b17007f626f1c35b095d Mon Sep 17 00:00:00 2001 From: rory Date: Sat, 28 Sep 2024 00:50:34 -0700 Subject: [PATCH 114/114] Use .md extension for consistency, fix .prettierignore --- .prettierignore | 1 + help/{index.markdown => index.md} | 0 2 files changed, 1 insertion(+) rename help/{index.markdown => index.md} (100%) diff --git a/.prettierignore b/.prettierignore index a9f7e1464529..98d06e8c5f71 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,6 +15,7 @@ package-lock.json *.css *.scss *.md +*.markdown # We need to modify the import here specifically, hence we disable prettier to get rid of the sorted imports src/libs/E2E/reactNativeLaunchingTest.ts # Temporary while we keep react-compiler in our repo diff --git a/help/index.markdown b/help/index.md similarity index 100% rename from help/index.markdown rename to help/index.md