From a7ba9164675f72d2477c74c28863e161bbcdecbe Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 30 Aug 2024 14:28:28 +0200 Subject: [PATCH 001/134] update forceClearInput for web --- src/libs/ComponentUtils/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/ComponentUtils/index.ts b/src/libs/ComponentUtils/index.ts index f7c48f87af5a..55a7252b3510 100644 --- a/src/libs/ComponentUtils/index.ts +++ b/src/libs/ComponentUtils/index.ts @@ -1,4 +1,3 @@ -import type {Component} from 'react'; import type {TextInput} from 'react-native'; import type {AnimatedRef} from 'react-native-reanimated'; import type {AccessibilityRoleForm, NewPasswordAutocompleteType, PasswordAutocompleteType} from './types'; @@ -10,10 +9,10 @@ const PASSWORD_AUTOCOMPLETE_TYPE: PasswordAutocompleteType = 'current-password'; const NEW_PASSWORD_AUTOCOMPLETE_TYPE: NewPasswordAutocompleteType = 'new-password'; const ACCESSIBILITY_ROLE_FORM: AccessibilityRoleForm = 'form'; -function forceClearInput(_: AnimatedRef, textInputRef: React.RefObject) { +function forceClearInput(animatedInputRef: AnimatedRef) { 'worklet'; - textInputRef.current?.clear(); + animatedInputRef.current?.clear(); } export {PASSWORD_AUTOCOMPLETE_TYPE, ACCESSIBILITY_ROLE_FORM, NEW_PASSWORD_AUTOCOMPLETE_TYPE, forceClearInput}; From cda29f7153d3c66618c7875190680d137682b098 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 30 Aug 2024 14:28:48 +0200 Subject: [PATCH 002/134] type --- src/libs/ComponentUtils/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libs/ComponentUtils/index.ts b/src/libs/ComponentUtils/index.ts index 55a7252b3510..518791b572c9 100644 --- a/src/libs/ComponentUtils/index.ts +++ b/src/libs/ComponentUtils/index.ts @@ -1,4 +1,4 @@ -import type {TextInput} from 'react-native'; +import type {Component} from 'react'; import type {AnimatedRef} from 'react-native-reanimated'; import type {AccessibilityRoleForm, NewPasswordAutocompleteType, PasswordAutocompleteType} from './types'; @@ -9,10 +9,13 @@ const PASSWORD_AUTOCOMPLETE_TYPE: PasswordAutocompleteType = 'current-password'; const NEW_PASSWORD_AUTOCOMPLETE_TYPE: NewPasswordAutocompleteType = 'new-password'; const ACCESSIBILITY_ROLE_FORM: AccessibilityRoleForm = 'form'; -function forceClearInput(animatedInputRef: AnimatedRef) { +function forceClearInput(animatedInputRef: AnimatedRef) { 'worklet'; - animatedInputRef.current?.clear(); + const input = animatedInputRef.current; + if (input && 'clear' in input && typeof input.clear === 'function') { + input.clear(); + } } export {PASSWORD_AUTOCOMPLETE_TYPE, ACCESSIBILITY_ROLE_FORM, NEW_PASSWORD_AUTOCOMPLETE_TYPE, forceClearInput}; From 3792093db7397a2bf9b569544b26211baf768b2b Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 30 Aug 2024 14:29:50 +0200 Subject: [PATCH 003/134] clean invocation forceClearInput --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 82482e1c0590..fc3765af81ce 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -607,7 +607,7 @@ function ComposerWithSuggestions( const clear = useCallback(() => { 'worklet'; - forceClearInput(animatedRef, textInputRef); + forceClearInput(animatedRef); }, [animatedRef]); const getCurrentText = useCallback(() => { From ac12ad37b928145bdf227ccadea19062901a1652 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 1 Sep 2024 05:40:47 +0200 Subject: [PATCH 004/134] Changing free trial text --- .../LHNOptionsList/LHNOptionsList.tsx | 18 +++++---- .../LHNOptionsList/OptionRowLHN.tsx | 17 +++++++-- src/components/LHNOptionsList/types.ts | 6 +++ src/libs/PolicyUtils.ts | 6 +++ src/libs/ReportUtils.ts | 1 + src/libs/SubscriptionUtils.ts | 37 +++++++++++++++---- src/pages/home/HeaderView.tsx | 8 ++-- src/pages/settings/InitialSettingsPage.tsx | 8 ++-- 8 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index a734890a1f38..d20d9dc1b196 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -19,6 +19,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as DraftCommentUtils from '@libs/DraftCommentUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -148,6 +149,8 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio } const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`]; + const freeTrialText = SubscriptionUtils.getFreeTrialText(policy); + return ( ); }, [ + reports, + reportActions, + policy, + transactions, draftComments, - onSelectRow, - optionMode, personalDetails, - policy, - preferredLocale, - reportActions, - reports, + optionMode, shouldDisableFocusOptions, - transactions, + onSelectRow, + preferredLocale, transactionViolations, onLayoutItem, ], diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index cee949133eb2..f0b50d3b48fc 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -26,14 +26,23 @@ import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportUtils from '@libs/ReportUtils'; -import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OptionRowLHNProps} from './types'; -function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}, hasDraftComment}: OptionRowLHNProps) { +function OptionRowLHN({ + reportID, + isFocused = false, + onSelectRow = () => {}, + optionItem, + viewMode = 'default', + style, + onLayout = () => {}, + hasDraftComment, + freeTrialText, +}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); const popoverAnchor = useRef(null); @@ -229,10 +238,10 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti ReportUtils.isSystemChat(report) } /> - {ReportUtils.isChatUsedForOnboarding(report) && SubscriptionUtils.isUserOnFreeTrial() && ( + {ReportUtils.isChatUsedForOnboarding(report) && freeTrialText && ( )} diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index f914b001aba6..9dc0c6702482 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -93,6 +93,9 @@ type OptionRowLHNDataProps = { /** Callback to execute when the OptionList lays out */ onLayout?: (event: LayoutChangeEvent) => void; + + /** The free trial banner text */ + freeTrialText?: string; }; type OptionRowLHNProps = { @@ -117,6 +120,9 @@ type OptionRowLHNProps = { /** Whether a report contains a draft */ hasDraftComment: boolean; + /** The free trial banner text */ + freeTrialText?: string; + onLayout?: (event: LayoutChangeEvent) => void; }; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 07dfe7a89b85..26046fb1321a 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -343,6 +343,11 @@ function isPaidGroupPolicy(policy: OnyxEntry): boolean { return policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE; } +function hasPaidPolicy(policies: OnyxCollection | null, currentUserAccountID: number): boolean { + const ownerPaidPolicy = Object.values(policies ?? {}).some((policy) => isPolicyOwner(policy, currentUserAccountID ?? -1) && isPaidGroupPolicy(policy)); + return ownerPaidPolicy; +} + function isControlPolicy(policy: OnyxEntry): boolean { return policy?.type === CONST.POLICY.TYPE.CORPORATE; } @@ -1010,6 +1015,7 @@ export { hasPolicyCategoriesError, hasPolicyError, hasPolicyErrorFields, + hasPaidPolicy, hasTaxRateError, isControlOnAdvancedApprovalMode, isExpensifyTeam, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a000d64a9adf..9ba354a61464 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -428,6 +428,7 @@ type OptionData = { hasDraftComment?: boolean | null; keyForList?: string; searchText?: string; + freeTrialText?: string; isIOUReportOwner?: boolean | null; isArchivedRoom?: boolean | null; shouldShowSubscript?: boolean | null; diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 6df0b2f2c205..a4054e2e221a 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -1,9 +1,11 @@ import {differenceInSeconds, fromUnixTime, isAfter, isBefore} from 'date-fns'; -import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; +import CardSectionUtils from '@src/pages/settings/Subscription/CardSection/utils'; import type {BillingGraceEndPeriod, BillingStatus, Fund, FundList, Policy, StripeCustomerID} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {translateLocal} from './Localize'; import * as PolicyUtils from './PolicyUtils'; const PAYMENT_STATUS = { @@ -369,6 +371,24 @@ function calculateRemainingFreeTrialDays(): number { return diffInDays < 0 ? 0 : diffInDays; } +function getFreeTrialText(policies: OnyxCollection | null): string | undefined { + if (!PolicyUtils.hasPaidPolicy(policies, currentUserAccountID)) { + return undefined; + } + + if (CardSectionUtils.shouldShowPreTrialBillingBanner()) { + return translateLocal('subscription.billingBanner.preTrial.title'); + } + if (isUserOnFreeTrial()) { + return translateLocal('subscription.billingBanner.trialStarted.title', {numOfDays: calculateRemainingFreeTrialDays()}); + } + if (hasUserFreeTrialEnded()) { + return translateLocal('subscription.billingBanner.trialEnded.title'); + } + + return undefined; +} + /** * Whether the workspace's owner is on its free trial period. */ @@ -449,16 +469,17 @@ function shouldRestrictUserBillableActions(policyID: string): boolean { export { calculateRemainingFreeTrialDays, doesUserHavePaymentCardAdded, - hasUserFreeTrialEnded, - isUserOnFreeTrial, - shouldRestrictUserBillableActions, - getSubscriptionStatus, - hasSubscriptionRedDotError, getAmountOwed, - getOverdueGracePeriodDate, getCardForSubscriptionBilling, + getFreeTrialText, + getOverdueGracePeriodDate, + getSubscriptionStatus, hasCardAuthenticatedError, - hasSubscriptionGreenDotInfo, hasRetryBillingError, + hasSubscriptionGreenDotInfo, + hasSubscriptionRedDotError, + hasUserFreeTrialEnded, + isUserOnFreeTrial, PAYMENT_STATUS, + shouldRestrictUserBillableActions, }; diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 2ca54d3a54d7..ecaa6e5225c0 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -1,4 +1,4 @@ -import React, {memo} from 'react'; +import React, {memo, useMemo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; @@ -59,6 +59,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || report?.reportID || '-1'}`); const policy = usePolicy(report?.policyID); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [policies] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}`); const {translate} = useLocalize(); const theme = useTheme(); @@ -131,6 +132,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report); const shouldUseGroupTitle = isGroupChat && (!!report?.reportName || !isMultipleParticipant); const isLoading = !report.reportID || !title; + const freeTrialText = SubscriptionUtils.getFreeTrialText(policies); return ( - {ReportUtils.isChatUsedForOnboarding(report) && SubscriptionUtils.isUserOnFreeTrial() && ( + {ReportUtils.isChatUsedForOnboarding(report) && freeTrialText && ( )} {isTaskReport && !shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && } diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index 20e42aba01d8..6548d4857f88 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -113,6 +113,8 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa const [shouldShowSignoutConfirmModal, setShouldShowSignoutConfirmModal] = useState(false); + const freeTrialText = SubscriptionUtils.getFreeTrialText(policies); + useEffect(() => { Wallet.openInitialSettingsPage(); }, []); @@ -211,8 +213,8 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa icon: Expensicons.CreditCard, routeName: ROUTES.SETTINGS_SUBSCRIPTION, brickRoadIndicator: !!privateSubscription?.errors || SubscriptionUtils.hasSubscriptionRedDotError() ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, - badgeText: SubscriptionUtils.isUserOnFreeTrial() ? translate('subscription.badge.freeTrial', {numOfDays: SubscriptionUtils.calculateRemainingFreeTrialDays()}) : undefined, - badgeStyle: SubscriptionUtils.isUserOnFreeTrial() ? styles.badgeSuccess : undefined, + badgeText: freeTrialText, + badgeStyle: freeTrialText ? styles.badgeSuccess : undefined, }); } @@ -221,7 +223,7 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa sectionTranslationKey: 'common.workspaces', items, }; - }, [policies, privateSubscription?.errors, styles.badgeSuccess, styles.workspaceSettingsSectionContainer, subscriptionPlan, translate]); + }, [freeTrialText, policies, privateSubscription?.errors, styles.badgeSuccess, styles.workspaceSettingsSectionContainer, subscriptionPlan]); /** * Retuns a list of menu items data for general section From b1822c07a13dc7132fc74544eb004261ba8a9a3e Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 3 Sep 2024 22:22:50 +0200 Subject: [PATCH 005/134] use getOwnedPaidPolicies instead of hasPaidPolicies --- src/hooks/useSubscriptionPlan.ts | 10 ++-------- src/libs/PolicyUtils.ts | 7 +++---- src/libs/SubscriptionUtils.ts | 4 +++- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/hooks/useSubscriptionPlan.ts b/src/hooks/useSubscriptionPlan.ts index 5ac5066ba965..75578511adda 100644 --- a/src/hooks/useSubscriptionPlan.ts +++ b/src/hooks/useSubscriptionPlan.ts @@ -1,6 +1,6 @@ import {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; -import {isPolicyOwner} from '@libs/PolicyUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -10,13 +10,7 @@ function useSubscriptionPlan() { const [session] = useOnyx(ONYXKEYS.SESSION); // Filter workspaces in which user is the owner and the type is either corporate (control) or team (collect) - const ownerPolicies = useMemo( - () => - Object.values(policies ?? {}).filter( - (policy) => isPolicyOwner(policy, session?.accountID ?? -1) && (CONST.POLICY.TYPE.CORPORATE === policy?.type || CONST.POLICY.TYPE.TEAM === policy?.type), - ), - [policies, session?.accountID], - ); + const ownerPolicies = useMemo(() => PolicyUtils.getOwnedPaidPolicies(policies, session?.accountID ?? -1), [policies, session?.accountID]); if (isEmptyObject(ownerPolicies)) { return null; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 26046fb1321a..9afe44c3f54d 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -343,9 +343,8 @@ function isPaidGroupPolicy(policy: OnyxEntry): boolean { return policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE; } -function hasPaidPolicy(policies: OnyxCollection | null, currentUserAccountID: number): boolean { - const ownerPaidPolicy = Object.values(policies ?? {}).some((policy) => isPolicyOwner(policy, currentUserAccountID ?? -1) && isPaidGroupPolicy(policy)); - return ownerPaidPolicy; +function getOwnedPaidPolicies(policies: OnyxCollection | null, currentUserAccountID: number): Policy[] { + return Object.values(policies ?? {}).filter((policy): policy is Policy => isPolicyOwner(policy, currentUserAccountID ?? -1) && isPaidGroupPolicy(policy)); } function isControlPolicy(policy: OnyxEntry): boolean { @@ -1015,7 +1014,6 @@ export { hasPolicyCategoriesError, hasPolicyError, hasPolicyErrorFields, - hasPaidPolicy, hasTaxRateError, isControlOnAdvancedApprovalMode, isExpensifyTeam, @@ -1034,6 +1032,7 @@ export { isTaxTrackingEnabled, shouldShowPolicy, getActiveAdminWorkspaces, + getOwnedPaidPolicies, canSendInvoiceFromWorkspace, canSendInvoice, hasDependentTags, diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index a4054e2e221a..c178462a4533 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -1,6 +1,7 @@ import {differenceInSeconds, fromUnixTime, isAfter, isBefore} from 'date-fns'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import useSubscriptionPlan from '@hooks/useSubscriptionPlan'; import ONYXKEYS from '@src/ONYXKEYS'; import CardSectionUtils from '@src/pages/settings/Subscription/CardSection/utils'; import type {BillingGraceEndPeriod, BillingStatus, Fund, FundList, Policy, StripeCustomerID} from '@src/types/onyx'; @@ -372,7 +373,8 @@ function calculateRemainingFreeTrialDays(): number { } function getFreeTrialText(policies: OnyxCollection | null): string | undefined { - if (!PolicyUtils.hasPaidPolicy(policies, currentUserAccountID)) { + const ownedPaidPolicies = PolicyUtils.getOwnedPaidPolicies(policies, currentUserAccountID); + if (isEmptyObject(ownedPaidPolicies)) { return undefined; } From b54212ec6d00383ba48dab9f2c3c45e7246135bb Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 3 Sep 2024 23:55:51 +0200 Subject: [PATCH 006/134] cleaning --- src/libs/SubscriptionUtils.ts | 1 - src/pages/home/HeaderView.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index c178462a4533..e111774061ca 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -1,7 +1,6 @@ import {differenceInSeconds, fromUnixTime, isAfter, isBefore} from 'date-fns'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import useSubscriptionPlan from '@hooks/useSubscriptionPlan'; import ONYXKEYS from '@src/ONYXKEYS'; import CardSectionUtils from '@src/pages/settings/Subscription/CardSection/utils'; import type {BillingGraceEndPeriod, BillingStatus, Fund, FundList, Policy, StripeCustomerID} from '@src/types/onyx'; diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index ecaa6e5225c0..28abacb1f959 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -1,4 +1,4 @@ -import React, {memo, useMemo} from 'react'; +import React, {memo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; From 6174fec92dae243947522491c01aee72bd7c1c11 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 3 Sep 2024 23:58:50 +0200 Subject: [PATCH 007/134] minor edit --- src/hooks/useSubscriptionPlan.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useSubscriptionPlan.ts b/src/hooks/useSubscriptionPlan.ts index 75578511adda..81d99d43bd0d 100644 --- a/src/hooks/useSubscriptionPlan.ts +++ b/src/hooks/useSubscriptionPlan.ts @@ -1,6 +1,6 @@ import {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; -import * as PolicyUtils from '@libs/PolicyUtils'; +import {getOwnedPaidPolicies} from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -10,7 +10,7 @@ function useSubscriptionPlan() { const [session] = useOnyx(ONYXKEYS.SESSION); // Filter workspaces in which user is the owner and the type is either corporate (control) or team (collect) - const ownerPolicies = useMemo(() => PolicyUtils.getOwnedPaidPolicies(policies, session?.accountID ?? -1), [policies, session?.accountID]); + const ownerPolicies = useMemo(() => getOwnedPaidPolicies(policies, session?.accountID ?? -1), [policies, session?.accountID]); if (isEmptyObject(ownerPolicies)) { return null; From 5be3131d3771a838fbaedba8565a95e7acebb8ee Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 4 Sep 2024 00:07:00 +0200 Subject: [PATCH 008/134] minor edit --- src/libs/SubscriptionUtils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index e111774061ca..e8107e0dfc66 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -371,6 +371,10 @@ function calculateRemainingFreeTrialDays(): number { return diffInDays < 0 ? 0 : diffInDays; } +/** + * @param policies - The policies collection. + * @returns The free trial badge text . + */ function getFreeTrialText(policies: OnyxCollection | null): string | undefined { const ownedPaidPolicies = PolicyUtils.getOwnedPaidPolicies(policies, currentUserAccountID); if (isEmptyObject(ownedPaidPolicies)) { @@ -466,7 +470,6 @@ function shouldRestrictUserBillableActions(policyID: string): boolean { return false; } - export { calculateRemainingFreeTrialDays, doesUserHavePaymentCardAdded, From 9221a03c30a1eed8f6ee41c94f58d05e7ce7705b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 4 Sep 2024 05:59:17 +0300 Subject: [PATCH 009/134] fix overflow name --- src/components/AccountSwitcher.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AccountSwitcher.tsx b/src/components/AccountSwitcher.tsx index ba30ea0062b9..be267a62c9f7 100644 --- a/src/components/AccountSwitcher.tsx +++ b/src/components/AccountSwitcher.tsx @@ -141,7 +141,7 @@ function AccountSwitcher() { {currentUserPersonalDetails?.displayName} From e2db20109319e78cbeb05b4d2b4c948c6d167714 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 4 Sep 2024 20:05:35 +0200 Subject: [PATCH 010/134] Fixing tests --- src/libs/SubscriptionUtils.ts | 7 ++++++- .../settings/Subscription/CardSection/CardSection.tsx | 2 +- src/pages/settings/Subscription/CardSection/utils.ts | 10 +--------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index e8107e0dfc66..d1a6051a188c 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -200,6 +200,9 @@ function hasInsufficientFundsError() { return billingStatus?.declineReason === 'insufficient_funds' && amountOwed !== 0; } +function shouldShowPreTrialBillingBanner(): boolean { + return !isUserOnFreeTrial() && !hasUserFreeTrialEnded(); +} /** * @returns The card to be used for subscription billing. */ @@ -381,7 +384,7 @@ function getFreeTrialText(policies: OnyxCollection | null): string | und return undefined; } - if (CardSectionUtils.shouldShowPreTrialBillingBanner()) { + if (shouldShowPreTrialBillingBanner()) { return translateLocal('subscription.billingBanner.preTrial.title'); } if (isUserOnFreeTrial()) { @@ -470,6 +473,7 @@ function shouldRestrictUserBillableActions(policyID: string): boolean { return false; } + export { calculateRemainingFreeTrialDays, doesUserHavePaymentCardAdded, @@ -477,6 +481,7 @@ export { getCardForSubscriptionBilling, getFreeTrialText, getOverdueGracePeriodDate, + shouldShowPreTrialBillingBanner, getSubscriptionStatus, hasCardAuthenticatedError, hasRetryBillingError, diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 797b28de4107..1d3bb3670637 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -90,7 +90,7 @@ function CardSection() { }; let BillingBanner: React.ReactNode | undefined; - if (CardSectionUtils.shouldShowPreTrialBillingBanner()) { + if (SubscriptionUtils.shouldShowPreTrialBillingBanner()) { BillingBanner = ; } else if (SubscriptionUtils.isUserOnFreeTrial()) { BillingBanner = ; diff --git a/src/pages/settings/Subscription/CardSection/utils.ts b/src/pages/settings/Subscription/CardSection/utils.ts index 48999f1bb7cb..81c7afbb8b7c 100644 --- a/src/pages/settings/Subscription/CardSection/utils.ts +++ b/src/pages/settings/Subscription/CardSection/utils.ts @@ -142,13 +142,5 @@ function getNextBillingDate(): string { return format(nextBillingDate, CONST.DATE.MONTH_DAY_YEAR_FORMAT); } -function shouldShowPreTrialBillingBanner(): boolean { - return !SubscriptionUtils.isUserOnFreeTrial() && !SubscriptionUtils.hasUserFreeTrialEnded(); -} - -export default { - getBillingStatus, - shouldShowPreTrialBillingBanner, - getNextBillingDate, -}; +export default {getBillingStatus, getNextBillingDate}; export type {BillingStatusResult}; From ce9e069b193482ef071cbc0102a2388e3c57ddbe Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 4 Sep 2024 20:42:39 +0200 Subject: [PATCH 011/134] fixing eslint --- src/libs/SubscriptionUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index d1a6051a188c..8f3fbc486464 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -2,7 +2,6 @@ import {differenceInSeconds, fromUnixTime, isAfter, isBefore} from 'date-fns'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; -import CardSectionUtils from '@src/pages/settings/Subscription/CardSection/utils'; import type {BillingGraceEndPeriod, BillingStatus, Fund, FundList, Policy, StripeCustomerID} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import {translateLocal} from './Localize'; @@ -481,7 +480,6 @@ export { getCardForSubscriptionBilling, getFreeTrialText, getOverdueGracePeriodDate, - shouldShowPreTrialBillingBanner, getSubscriptionStatus, hasCardAuthenticatedError, hasRetryBillingError, @@ -491,4 +489,5 @@ export { isUserOnFreeTrial, PAYMENT_STATUS, shouldRestrictUserBillableActions, + shouldShowPreTrialBillingBanner, }; From 3480cbb7bdb0a0e3a0f29e0cd74f782b8506c4b8 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 5 Sep 2024 08:22:57 +0530 Subject: [PATCH 012/134] fix: Track tax - Tax rate does not update when editing distance rate offline. Signed-off-by: krishna2323 --- src/libs/actions/IOU.ts | 4 ++++ src/pages/iou/request/step/IOURequestStepDistanceRate.tsx | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 29d481737790..4290bd08caa0 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3127,9 +3127,13 @@ function updateMoneyRequestDistanceRate( policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, + taxAmount?: number, + taxCode?: string, ) { const transactionChanges: TransactionChanges = { customUnitRateID: rateID, + ...(taxAmount ? {taxAmount} : {}), + ...(taxCode ? {taxCode} : {}), }; const allReports = ReportConnection.getAllReports(); const transactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 3b42b1d9a1be..7ac539522ce0 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -86,12 +86,14 @@ function IOURequestStepDistanceRate({ const initiallyFocusedOption = sections.find((item) => item.isSelected)?.keyForList; function selectDistanceRate(customUnitRateID: string) { + let taxAmount; + let taxRateExternalID; if (shouldShowTax) { const policyCustomUnitRate = getCustomUnitRate(policy, customUnitRateID); - const taxRateExternalID = policyCustomUnitRate?.attributes?.taxRateExternalID ?? '-1'; + taxRateExternalID = policyCustomUnitRate?.attributes?.taxRateExternalID ?? '-1'; const taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, TransactionUtils.getDistanceInMeters(transaction, unit)); const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxRateExternalID) ?? ''; - const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount, rates[customUnitRateID].currency ?? CONST.CURRENCY.USD)); + taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount, rates[customUnitRateID].currency ?? CONST.CURRENCY.USD)); IOU.setMoneyRequestTaxAmount(transactionID, taxAmount); IOU.setMoneyRequestTaxRate(transactionID, taxRateExternalID); } @@ -100,7 +102,7 @@ function IOURequestStepDistanceRate({ IOU.setMoneyRequestDistanceRate(transactionID, customUnitRateID, policy?.id ?? '-1', !isEditing); if (isEditing) { - IOU.updateMoneyRequestDistanceRate(transaction?.transactionID ?? '-1', reportID, customUnitRateID, policy, policyTags, policyCategories); + IOU.updateMoneyRequestDistanceRate(transaction?.transactionID ?? '-1', reportID, customUnitRateID, policy, policyTags, policyCategories, taxAmount, taxRateExternalID); } } From 2393f6e9bb2587a2eeea891000dfa04cb7817af6 Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 6 Sep 2024 17:31:36 +0700 Subject: [PATCH 013/134] feat: Subscribe guides to a new presence pusher channel --- src/components/ActiveGuidesEventListener.tsx | 36 +++++++++++++++++++ .../Navigation/AppNavigator/AuthScreens.tsx | 11 ++++-- src/libs/Pusher/pusher.ts | 4 +-- src/libs/actions/Report.ts | 12 +++++++ src/types/onyx/User.ts | 3 ++ 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 src/components/ActiveGuidesEventListener.tsx diff --git a/src/components/ActiveGuidesEventListener.tsx b/src/components/ActiveGuidesEventListener.tsx new file mode 100644 index 000000000000..56622c13ba73 --- /dev/null +++ b/src/components/ActiveGuidesEventListener.tsx @@ -0,0 +1,36 @@ +import {useEffect, useRef} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import * as Report from '@userActions/Report'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {User} from '@src/types/onyx'; + +type ActiveGuidesEventListenerOnyxProps = { + user: OnyxEntry; +}; + +type ActiveGuidesEventListenerProps = ActiveGuidesEventListenerOnyxProps; + +function ActiveGuidesEventListener({user}: ActiveGuidesEventListenerProps) { + const didSubscribeToActiveGuides = useRef(false); + useEffect( + () => () => { + if (didSubscribeToActiveGuides.current) { + return; + } + if (user?.isGuide) { + didSubscribeToActiveGuides.current = true; + Report.subscribeToActiveGuides(); + } + }, + + [user], + ); + return null; +} + +export default withOnyx({ + user: { + key: ONYXKEYS.USER, + }, +})(ActiveGuidesEventListener); diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 5ca9becf8986..0274edb25deb 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -1,8 +1,9 @@ -import React, {memo, useEffect, useMemo, useRef} from 'react'; +import React, {memo, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx, {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import ActiveGuidesEventListener from '@components/ActiveGuidesEventListener'; import ComposeProviders from '@components/ComposeProviders'; import OptionsListContextProvider from '@components/OptionListContextProvider'; import {SearchContextProvider} from '@components/Search/SearchContext'; @@ -108,7 +109,7 @@ function getCentralPaneScreenInitialParams(screenName: CentralPaneName, initialR } function initializePusher() { - Pusher.init({ + return Pusher.init({ appKey: CONFIG.PUSHER.APP_KEY, cluster: CONFIG.PUSHER.CLUSTER, authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/AuthenticatePusher?`, @@ -229,6 +230,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie [StyleUtils, isSmallScreenWidth, isMediumOrLargerScreenWidth, styles], ); const modal = useRef({}); + const [didPusherInit, setDidPusherInit] = useState(false); let initialReportID: string | undefined; const isInitialRender = useRef(true); @@ -266,7 +268,9 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie NetworkConnection.listenForReconnect(); NetworkConnection.onReconnect(handleNetworkReconnect); PusherConnectionManager.init(); - initializePusher(); + initializePusher().then(() => { + setDidPusherInit(true); + }); // If we are on this screen then we are "logged in", but the user might not have "just logged in". They could be reopening the app // or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp(). @@ -564,6 +568,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie })} + {didPusherInit && } ); } diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts index a3383dbadb8a..1a511eec391c 100644 --- a/src/libs/Pusher/pusher.ts +++ b/src/libs/Pusher/pusher.ts @@ -156,7 +156,7 @@ function getChannel(channelName: string): Channel | undefined { /** * Binds an event callback to a channel + eventName */ -function bindEventToChannel(channel: Channel | undefined, eventName: EventName, eventCallback: (data: EventData) => void = () => {}) { +function bindEventToChannel(channel: Channel | undefined, eventName?: EventName, eventCallback: (data: EventData) => void = () => {}) { if (!eventName || !channel) { return; } @@ -232,7 +232,7 @@ function bindEventToChannel(channel: Channel */ function subscribe( channelName: string, - eventName: EventName, + eventName?: EventName, eventCallback: (data: EventData) => void = () => {}, onResubscribe = () => {}, ): Promise { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index bd31d3832605..e5316d387767 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -4088,6 +4088,16 @@ function markAsManuallyExported(reportID: string, connectionName: ConnectionName API.write(WRITE_COMMANDS.MARK_AS_EXPORTED, params, {optimisticData, successData, failureData}); } +function subscribeToActiveGuides() { + Pusher.subscribe(`activeGuides`).catch((error: ReportError) => { + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChanelName: 'activeGuides'}); + }); +} + +function unsubscribeToActiveGuides() { + Pusher.unsubscribe(`activeGuides`); +} + export { searchInServer, addComment, @@ -4175,4 +4185,6 @@ export { exportToIntegration, markAsManuallyExported, handleReportChanged, + subscribeToActiveGuides, + unsubscribeToActiveGuides, }; diff --git a/src/types/onyx/User.ts b/src/types/onyx/User.ts index f30ca846ef43..3f12b35c4ebe 100644 --- a/src/types/onyx/User.ts +++ b/src/types/onyx/User.ts @@ -29,6 +29,9 @@ type User = { /** Whether the form is being submitted */ loading?: boolean; + + /** Whether the user is Expensify Guide */ + isGuide?: boolean; }; export default User; From 9c22426f2a5ce245e93fcf0669ae5d7b4bc32b40 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 8 Sep 2024 21:50:27 +0300 Subject: [PATCH 014/134] preserve isloading --- src/libs/actions/Delegate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index 4797506d1a3c..fe94a7cc6fea 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -18,7 +18,7 @@ Onyx.connect({ }, }); -const KEYS_TO_PRESERVE_DELEGATE_ACCESS = [ONYXKEYS.NVP_TRY_FOCUS_MODE, ONYXKEYS.PREFERRED_THEME, ONYXKEYS.NVP_PREFERRED_LOCALE, ONYXKEYS.SESSION]; +const KEYS_TO_PRESERVE_DELEGATE_ACCESS = [ONYXKEYS.NVP_TRY_FOCUS_MODE, ONYXKEYS.PREFERRED_THEME, ONYXKEYS.NVP_PREFERRED_LOCALE, ONYXKEYS.SESSION, ONYXKEYS.IS_LOADING_APP]; function connect(email: string) { if (!delegatedAccess?.delegators) { From 7f07f36f9ccbb030a1dccc246e0c939021a75168 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 8 Sep 2024 21:50:48 +0300 Subject: [PATCH 015/134] show loading screen for subscription page --- .../settings/Subscription/SubscriptionSettingsPage.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx b/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx index c6112ec19c8d..16fa481b16a6 100644 --- a/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx +++ b/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx @@ -1,5 +1,7 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Illustrations from '@components/Icon/Illustrations'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -11,6 +13,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import * as Subscription from '@userActions/Subscription'; +import ONYXKEYS from '@src/ONYXKEYS'; import CardSection from './CardSection/CardSection'; import ReducedFunctionalityMessage from './ReducedFunctionalityMessage'; import SubscriptionDetails from './SubscriptionDetails'; @@ -26,7 +29,11 @@ function SubscriptionSettingsPage() { useEffect(() => { Subscription.openSubscriptionPage(); }, []); + const [isAppLoading] = useOnyx(ONYXKEYS.IS_LOADING_APP); + if (!subscriptionPlan && isAppLoading) { + return ; + } if (!subscriptionPlan) { return ; } From f550dffff55ff8f7efede6a0d2316a9994bb7651 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 8 Sep 2024 21:57:25 +0300 Subject: [PATCH 016/134] disable feedback when cannot switch account --- src/components/AccountSwitcher.tsx | 1 + src/libs/actions/Delegate.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/AccountSwitcher.tsx b/src/components/AccountSwitcher.tsx index be267a62c9f7..705b18dfe673 100644 --- a/src/components/AccountSwitcher.tsx +++ b/src/components/AccountSwitcher.tsx @@ -127,6 +127,7 @@ function AccountSwitcher() { }} ref={buttonRef} interactive={canSwitchAccounts} + pressDimmingValue={canSwitchAccounts ? undefined : 1} wrapperStyle={[styles.flexGrow1, styles.flex1, styles.mnw0, styles.justifyContentCenter]} > diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index fe94a7cc6fea..c3e9fc5d1f4d 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -18,7 +18,14 @@ Onyx.connect({ }, }); -const KEYS_TO_PRESERVE_DELEGATE_ACCESS = [ONYXKEYS.NVP_TRY_FOCUS_MODE, ONYXKEYS.PREFERRED_THEME, ONYXKEYS.NVP_PREFERRED_LOCALE, ONYXKEYS.SESSION, ONYXKEYS.IS_LOADING_APP]; +const KEYS_TO_PRESERVE_DELEGATE_ACCESS = [ + ONYXKEYS.NVP_TRY_FOCUS_MODE, + ONYXKEYS.PREFERRED_THEME, + ONYXKEYS.NVP_PREFERRED_LOCALE, + ONYXKEYS.SESSION, + ONYXKEYS.IS_LOADING_APP, + ONYXKEYS.IS_LOADING_REPORT_DATA, +]; function connect(email: string) { if (!delegatedAccess?.delegators) { From 2b746dc90817eb6ba0ba7549a67bc9837df31d3d Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 8 Sep 2024 22:43:33 +0300 Subject: [PATCH 017/134] fix avatar position when upload error --- src/components/AvatarWithImagePicker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AvatarWithImagePicker.tsx b/src/components/AvatarWithImagePicker.tsx index 6b9995e77814..29eb819e043a 100644 --- a/src/components/AvatarWithImagePicker.tsx +++ b/src/components/AvatarWithImagePicker.tsx @@ -325,7 +325,7 @@ function AvatarWithImagePicker({ ); return ( - + From ad3bf76e0f3dbe4fab0cf0573502fd4500c49bfd Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 8 Sep 2024 22:43:52 +0300 Subject: [PATCH 018/134] show avatar skeleton when loading --- src/components/AvatarSkeleton.tsx | 13 ++++-- src/pages/settings/Profile/ProfilePage.tsx | 48 ++++++++++++---------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/components/AvatarSkeleton.tsx b/src/components/AvatarSkeleton.tsx index 273143f76098..a88304b15fc3 100644 --- a/src/components/AvatarSkeleton.tsx +++ b/src/components/AvatarSkeleton.tsx @@ -1,17 +1,22 @@ import React from 'react'; import {Circle} from 'react-native-svg'; +import type {ValueOf} from 'type-fest'; +import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; -import variables from '@styles/variables'; +import CONST from '@src/CONST'; import SkeletonViewContentLoader from './SkeletonViewContentLoader'; -function AvatarSkeleton() { +function AvatarSkeleton({size = CONST.AVATAR_SIZE.SMALL}: {size?: ValueOf}) { const theme = useTheme(); - const skeletonCircleRadius = variables.sidebarAvatarSize / 2; + + const StyleUtils = useStyleUtils(); + const avatarSize = StyleUtils.getAvatarSize(size); + const skeletonCircleRadius = avatarSize / 2; return ( diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index cc3d93c3db25..57bef1eb9190 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import AvatarSkeleton from '@components/AvatarSkeleton'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -34,6 +35,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {LoginList, PrivatePersonalDetails} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; type ProfilePageOnyxProps = { loginList: OnyxEntry; @@ -155,27 +157,31 @@ function ProfilePage({ titleStyles={styles.accountSettingsSectionTitle} > - - Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(String(accountID)))} - previewSource={UserUtils.getFullSizeAvatar(avatarURL, accountID)} - originalFileName={currentUserPersonalDetails.originalFileName} - headerTitle={translate('profilePage.profileAvatar')} - fallbackIcon={currentUserPersonalDetails?.fallbackIcon} - editIconStyle={styles.profilePageAvatar} - /> - + {isEmptyObject(currentUserPersonalDetails) || accountID === -1 || !avatarURL ? ( + + ) : ( + + Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(String(accountID)))} + previewSource={UserUtils.getFullSizeAvatar(avatarURL, accountID)} + originalFileName={currentUserPersonalDetails.originalFileName} + headerTitle={translate('profilePage.profileAvatar')} + fallbackIcon={currentUserPersonalDetails?.fallbackIcon} + editIconStyle={styles.profilePageAvatar} + /> + + )} {publicOptions.map((detail, index) => ( Date: Sun, 8 Sep 2024 22:44:07 +0300 Subject: [PATCH 019/134] rm IS_LOADING_REPORT_DATA --- src/libs/actions/Delegate.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index c3e9fc5d1f4d..fe94a7cc6fea 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -18,14 +18,7 @@ Onyx.connect({ }, }); -const KEYS_TO_PRESERVE_DELEGATE_ACCESS = [ - ONYXKEYS.NVP_TRY_FOCUS_MODE, - ONYXKEYS.PREFERRED_THEME, - ONYXKEYS.NVP_PREFERRED_LOCALE, - ONYXKEYS.SESSION, - ONYXKEYS.IS_LOADING_APP, - ONYXKEYS.IS_LOADING_REPORT_DATA, -]; +const KEYS_TO_PRESERVE_DELEGATE_ACCESS = [ONYXKEYS.NVP_TRY_FOCUS_MODE, ONYXKEYS.PREFERRED_THEME, ONYXKEYS.NVP_PREFERRED_LOCALE, ONYXKEYS.SESSION, ONYXKEYS.IS_LOADING_APP]; function connect(email: string) { if (!delegatedAccess?.delegators) { From 6ee983f54883ac03d47b5226f9f14eea6b6b0919 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 8 Sep 2024 22:54:42 +0300 Subject: [PATCH 020/134] reset error when source / avatar id changes --- src/components/AvatarWithImagePicker.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/AvatarWithImagePicker.tsx b/src/components/AvatarWithImagePicker.tsx index 29eb819e043a..738cff48c813 100644 --- a/src/components/AvatarWithImagePicker.tsx +++ b/src/components/AvatarWithImagePicker.tsx @@ -193,6 +193,10 @@ function AvatarWithImagePicker({ setError(null, {}); }, [isFocused]); + useEffect(() => { + setError(null, {}); + }, [source, avatarID]); + /** * Check if the attachment extension is allowed. */ From 71b3d2e4c3ac9c8c977bd1c5847b2f74e5342e4b Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 8 Sep 2024 23:41:44 +0200 Subject: [PATCH 021/134] Fix free trial text to update with Onyx data changes --- .../LHNOptionsList/LHNOptionsList.tsx | 18 +++--- .../LHNOptionsList/OptionRowLHN.tsx | 22 +------ src/libs/SubscriptionUtils.ts | 58 ++++++++++++------- src/pages/home/HeaderView.tsx | 12 +--- .../settings/Subscription/FreeTrailBadge.tsx | 28 +++++++++ 5 files changed, 76 insertions(+), 62 deletions(-) create mode 100644 src/pages/settings/Subscription/FreeTrailBadge.tsx diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index d20d9dc1b196..a734890a1f38 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -19,7 +19,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as DraftCommentUtils from '@libs/DraftCommentUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -149,8 +148,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio } const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`]; - const freeTrialText = SubscriptionUtils.getFreeTrialText(policy); - return ( ); }, [ - reports, - reportActions, - policy, - transactions, draftComments, - personalDetails, - optionMode, - shouldDisableFocusOptions, onSelectRow, + optionMode, + personalDetails, + policy, preferredLocale, + reportActions, + reports, + shouldDisableFocusOptions, + transactions, transactionViolations, onLayoutItem, ], diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index f0b50d3b48fc..4336b2b6a7cc 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -3,7 +3,6 @@ import React, {useCallback, useRef, useState} from 'react'; import type {GestureResponderEvent, ViewStyle} from 'react-native'; import {StyleSheet, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import Badge from '@components/Badge'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -27,22 +26,13 @@ import Performance from '@libs/Performance'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; +import FreeTrialBadge from '@pages/settings/Subscription/FreeTrailBadge'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OptionRowLHNProps} from './types'; -function OptionRowLHN({ - reportID, - isFocused = false, - onSelectRow = () => {}, - optionItem, - viewMode = 'default', - style, - onLayout = () => {}, - hasDraftComment, - freeTrialText, -}: OptionRowLHNProps) { +function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}, hasDraftComment}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); const popoverAnchor = useRef(null); @@ -238,13 +228,7 @@ function OptionRowLHN({ ReportUtils.isSystemChat(report) } /> - {ReportUtils.isChatUsedForOnboarding(report) && freeTrialText && ( - - )} + {ReportUtils.isChatUsedForOnboarding(report) && } {isStatusVisible && ( } [firstDay] - The start date of the free trial. + * @param {OnyxEntry} [lastDay] - The end date of the free trial. + * @returns {boolean} True if the pre-trial billing banner should be shown, false otherwise. + */ +function shouldShowPreTrialBillingBanner(firstDay: OnyxEntry = firstDayFreeTrial, lastDay: OnyxEntry = lastDayFreeTrial): boolean { + return !isUserOnFreeTrial(firstDay, lastDay) && !hasUserFreeTrialEnded(lastDay); } /** * @returns The card to be used for subscription billing. @@ -359,15 +365,17 @@ function hasSubscriptionGreenDotInfo(): boolean { /** * Calculates the remaining number of days of the workspace owner's free trial before it ends. + * @param {OnyxEntry} [lastDay] - The end date of the free trial. + * @returns {number} The remaining number of free trial days. */ -function calculateRemainingFreeTrialDays(): number { - if (!lastDayFreeTrial) { +function calculateRemainingFreeTrialDays(lastDay: OnyxEntry = lastDayFreeTrial): number { + if (!lastDay) { return 0; } const currentDate = new Date(); - const lastDayFreeTrialDate = new Date(`${lastDayFreeTrial}Z`); - const diffInSeconds = differenceInSeconds(lastDayFreeTrialDate, currentDate); + const lastDayDate = new Date(`${lastDay}Z`); + const diffInSeconds = differenceInSeconds(lastDayDate, currentDate); const diffInDays = Math.ceil(diffInSeconds / 86400); return diffInDays < 0 ? 0 : diffInDays; @@ -375,21 +383,22 @@ function calculateRemainingFreeTrialDays(): number { /** * @param policies - The policies collection. - * @returns The free trial badge text . + * @param {OnyxEntry} [firstDay] - The start date of the free trial. + * @param {OnyxEntry} [lastDay] - The end date of the free trial. + * @returns The free trial badge text. */ -function getFreeTrialText(policies: OnyxCollection | null): string | undefined { +function getFreeTrialText(policies: OnyxCollection | null, firstDay: OnyxEntry = firstDayFreeTrial, lastDay: OnyxEntry = lastDayFreeTrial): string | undefined { const ownedPaidPolicies = PolicyUtils.getOwnedPaidPolicies(policies, currentUserAccountID); if (isEmptyObject(ownedPaidPolicies)) { return undefined; } - - if (shouldShowPreTrialBillingBanner()) { + if (shouldShowPreTrialBillingBanner(firstDay, lastDay)) { return translateLocal('subscription.billingBanner.preTrial.title'); } - if (isUserOnFreeTrial()) { - return translateLocal('subscription.billingBanner.trialStarted.title', {numOfDays: calculateRemainingFreeTrialDays()}); + if (isUserOnFreeTrial(firstDay, lastDay)) { + return translateLocal('subscription.billingBanner.trialStarted.title', {numOfDays: calculateRemainingFreeTrialDays(lastDay)}); } - if (hasUserFreeTrialEnded()) { + if (hasUserFreeTrialEnded(lastDay)) { return translateLocal('subscription.billingBanner.trialEnded.title'); } @@ -398,33 +407,38 @@ function getFreeTrialText(policies: OnyxCollection | null): string | und /** * Whether the workspace's owner is on its free trial period. + * @param {OnyxEntry} [firstDay] - The start date of the free trial. + * @param {OnyxEntry} [lastDay] - The end date of the free trial. + * @returns {boolean} True if the user is on a free trial, false otherwise. */ -function isUserOnFreeTrial(): boolean { - if (!firstDayFreeTrial || !lastDayFreeTrial) { +function isUserOnFreeTrial(firstDay: OnyxEntry = firstDayFreeTrial, lastDay: OnyxEntry = lastDayFreeTrial): boolean { + if (!firstDay || !lastDay) { return false; } const currentDate = new Date(); // Free Trials are stored in UTC so the below code will convert the provided UTC datetime to local time - const firstDayFreeTrialDate = new Date(`${firstDayFreeTrial}Z`); - const lastDayFreeTrialDate = new Date(`${lastDayFreeTrial}Z`); + const firstDayDate = new Date(`${firstDay}Z`); + const lastDayDate = new Date(`${lastDay}Z`); - return isAfter(currentDate, firstDayFreeTrialDate) && isBefore(currentDate, lastDayFreeTrialDate); + return isAfter(currentDate, firstDayDate) && isBefore(currentDate, lastDayDate); } /** * Whether the workspace owner's free trial period has ended. + * @param {OnyxEntry} [lastDay] - The end date of the free trial. + * @returns {boolean} True if the free trial has ended, false otherwise. */ -function hasUserFreeTrialEnded(): boolean { - if (!lastDayFreeTrial) { +function hasUserFreeTrialEnded(lastDay: OnyxEntry = lastDayFreeTrial): boolean { + if (!lastDay) { return false; } const currentDate = new Date(); - const lastDayFreeTrialDate = new Date(`${lastDayFreeTrial}Z`); + const lastDayDate = new Date(`${lastDay}Z`); - return isAfter(currentDate, lastDayFreeTrialDate); + return isAfter(currentDate, lastDayDate); } /** diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 28abacb1f959..baca103af185 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -2,7 +2,6 @@ import React, {memo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; -import Badge from '@components/Badge'; import Button from '@components/Button'; import CaretWrapper from '@components/CaretWrapper'; import ConfirmModal from '@components/ConfirmModal'; @@ -25,7 +24,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import * as SubscriptionUtils from '@libs/SubscriptionUtils'; +import FreeTrialBadge from '@pages/settings/Subscription/FreeTrailBadge'; import * as Report from '@userActions/Report'; import * as Session from '@userActions/Session'; import * as Task from '@userActions/Task'; @@ -59,7 +58,6 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || report?.reportID || '-1'}`); const policy = usePolicy(report?.policyID); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const [policies] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}`); const {translate} = useLocalize(); const theme = useTheme(); @@ -132,7 +130,6 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report); const shouldUseGroupTitle = isGroupChat && (!!report?.reportName || !isMultipleParticipant); const isLoading = !report.reportID || !title; - const freeTrialText = SubscriptionUtils.getFreeTrialText(policies); return ( - {ReportUtils.isChatUsedForOnboarding(report) && freeTrialText && ( - - )} + {ReportUtils.isChatUsedForOnboarding(report) && } {isTaskReport && !shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && } {canJoin && !shouldUseNarrowLayout && joinButton} diff --git a/src/pages/settings/Subscription/FreeTrailBadge.tsx b/src/pages/settings/Subscription/FreeTrailBadge.tsx new file mode 100644 index 000000000000..78fda0b14e90 --- /dev/null +++ b/src/pages/settings/Subscription/FreeTrailBadge.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import {useOnyx} from 'react-native-onyx'; +import Badge from '@components/Badge'; +import * as SubscriptionUtils from '@libs/SubscriptionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function FreeTrialBadge() { + const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); + const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL); + const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL); + + const freeTrialText = SubscriptionUtils.getFreeTrialText(policies, firstDayFreeTrial, lastDayFreeTrial); + + if (!freeTrialText) { + return null; + } + + return ( + + ); +} + +FreeTrialBadge.displayName = 'FreeTrialBadge'; + +export default FreeTrialBadge; From 15f6dba6beebd68ce8911297c205a2d8ed40ecd8 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 8 Sep 2024 23:53:09 +0200 Subject: [PATCH 022/134] cleaning --- src/components/LHNOptionsList/types.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 9dc0c6702482..f914b001aba6 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -93,9 +93,6 @@ type OptionRowLHNDataProps = { /** Callback to execute when the OptionList lays out */ onLayout?: (event: LayoutChangeEvent) => void; - - /** The free trial banner text */ - freeTrialText?: string; }; type OptionRowLHNProps = { @@ -120,9 +117,6 @@ type OptionRowLHNProps = { /** Whether a report contains a draft */ hasDraftComment: boolean; - /** The free trial banner text */ - freeTrialText?: string; - onLayout?: (event: LayoutChangeEvent) => void; }; From fc76adf508a460e9e6613e928214a2a5ea859051 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 9 Sep 2024 00:09:13 +0200 Subject: [PATCH 023/134] passing badge styles --- src/components/LHNOptionsList/OptionRowLHN.tsx | 2 +- src/pages/settings/Subscription/FreeTrailBadge.tsx | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 4336b2b6a7cc..11a820209286 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -228,7 +228,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti ReportUtils.isSystemChat(report) } /> - {ReportUtils.isChatUsedForOnboarding(report) && } + {ReportUtils.isChatUsedForOnboarding(report) && } {isStatusVisible && ( ; +}; + +function FreeTrialBadge({badgeStyles}: FreeTrialBadgeProps) { const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL); const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL); @@ -19,6 +24,7 @@ function FreeTrialBadge() { ); } From d877cf481bf5280fe0ad2af5e505791d80051718 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 9 Sep 2024 00:13:04 +0200 Subject: [PATCH 024/134] fixing lint --- src/libs/SubscriptionUtils.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index c540ac6e7190..1fc39cc98e29 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -201,9 +201,9 @@ function hasInsufficientFundsError() { /** * Determines whether the pre-trial billing banner should be shown. - * @param {OnyxEntry} [firstDay] - The start date of the free trial. - * @param {OnyxEntry} [lastDay] - The end date of the free trial. - * @returns {boolean} True if the pre-trial billing banner should be shown, false otherwise. + * @param [firstDay] - The start date of the free trial. + * @param [lastDay] - The end date of the free trial. + * @returns True if the pre-trial billing banner should be shown, false otherwise. */ function shouldShowPreTrialBillingBanner(firstDay: OnyxEntry = firstDayFreeTrial, lastDay: OnyxEntry = lastDayFreeTrial): boolean { return !isUserOnFreeTrial(firstDay, lastDay) && !hasUserFreeTrialEnded(lastDay); @@ -365,8 +365,8 @@ function hasSubscriptionGreenDotInfo(): boolean { /** * Calculates the remaining number of days of the workspace owner's free trial before it ends. - * @param {OnyxEntry} [lastDay] - The end date of the free trial. - * @returns {number} The remaining number of free trial days. + * @param [lastDay] - The end date of the free trial. + * @returns The remaining number of free trial days. */ function calculateRemainingFreeTrialDays(lastDay: OnyxEntry = lastDayFreeTrial): number { if (!lastDay) { @@ -383,8 +383,8 @@ function calculateRemainingFreeTrialDays(lastDay: OnyxEntry = lastDayFre /** * @param policies - The policies collection. - * @param {OnyxEntry} [firstDay] - The start date of the free trial. - * @param {OnyxEntry} [lastDay] - The end date of the free trial. + * @param [firstDay] - The start date of the free trial. + * @param [lastDay] - The end date of the free trial. * @returns The free trial badge text. */ function getFreeTrialText(policies: OnyxCollection | null, firstDay: OnyxEntry = firstDayFreeTrial, lastDay: OnyxEntry = lastDayFreeTrial): string | undefined { @@ -407,9 +407,9 @@ function getFreeTrialText(policies: OnyxCollection | null, firstDay: Ony /** * Whether the workspace's owner is on its free trial period. - * @param {OnyxEntry} [firstDay] - The start date of the free trial. - * @param {OnyxEntry} [lastDay] - The end date of the free trial. - * @returns {boolean} True if the user is on a free trial, false otherwise. + * @param [firstDay] - The start date of the free trial. + * @param [lastDay] - The end date of the free trial. + * @returns True if the user is on a free trial, false otherwise. */ function isUserOnFreeTrial(firstDay: OnyxEntry = firstDayFreeTrial, lastDay: OnyxEntry = lastDayFreeTrial): boolean { if (!firstDay || !lastDay) { @@ -427,8 +427,8 @@ function isUserOnFreeTrial(firstDay: OnyxEntry = firstDayFreeTrial, last /** * Whether the workspace owner's free trial period has ended. - * @param {OnyxEntry} [lastDay] - The end date of the free trial. - * @returns {boolean} True if the free trial has ended, false otherwise. + * @param [lastDay] - The end date of the free trial. + * @returns True if the free trial has ended, false otherwise. */ function hasUserFreeTrialEnded(lastDay: OnyxEntry = lastDayFreeTrial): boolean { if (!lastDay) { From 7535793eb3a6fc11ba80cad450805accca452d61 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 9 Sep 2024 20:48:44 +0200 Subject: [PATCH 025/134] Cleanup --- src/components/LHNOptionsList/OptionRowLHN.tsx | 2 +- src/libs/ReportUtils.ts | 1 - src/pages/home/HeaderView.tsx | 2 +- .../Subscription/{FreeTrailBadge.tsx => FreeTrialBadge.tsx} | 0 4 files changed, 2 insertions(+), 3 deletions(-) rename src/pages/settings/Subscription/{FreeTrailBadge.tsx => FreeTrialBadge.tsx} (100%) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 11a820209286..e88b72b4edd6 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -26,7 +26,7 @@ import Performance from '@libs/Performance'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import FreeTrialBadge from '@pages/settings/Subscription/FreeTrailBadge'; +import FreeTrialBadge from '@pages/settings/Subscription/FreeTrialBadge'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f3ba8165c8c7..bdc536ac2ad3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -424,7 +424,6 @@ type OptionData = { hasDraftComment?: boolean | null; keyForList?: string; searchText?: string; - freeTrialText?: string; isIOUReportOwner?: boolean | null; isArchivedRoom?: boolean | null; shouldShowSubscript?: boolean | null; diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index baca103af185..470b0a32f897 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -24,7 +24,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import FreeTrialBadge from '@pages/settings/Subscription/FreeTrailBadge'; +import FreeTrialBadge from '@pages/settings/Subscription/FreeTrialBadge'; import * as Report from '@userActions/Report'; import * as Session from '@userActions/Session'; import * as Task from '@userActions/Task'; diff --git a/src/pages/settings/Subscription/FreeTrailBadge.tsx b/src/pages/settings/Subscription/FreeTrialBadge.tsx similarity index 100% rename from src/pages/settings/Subscription/FreeTrailBadge.tsx rename to src/pages/settings/Subscription/FreeTrialBadge.tsx From 348f7bb63b79c95badc8ba92633fd59d3e3b223a Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 11 Sep 2024 00:09:41 +0200 Subject: [PATCH 026/134] using debounced state --- src/libs/SubscriptionUtils.ts | 58 +++++++------------ .../settings/Subscription/FreeTrialBadge.tsx | 13 +++-- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 1fc39cc98e29..8f3fbc486464 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -199,14 +199,8 @@ function hasInsufficientFundsError() { return billingStatus?.declineReason === 'insufficient_funds' && amountOwed !== 0; } -/** - * Determines whether the pre-trial billing banner should be shown. - * @param [firstDay] - The start date of the free trial. - * @param [lastDay] - The end date of the free trial. - * @returns True if the pre-trial billing banner should be shown, false otherwise. - */ -function shouldShowPreTrialBillingBanner(firstDay: OnyxEntry = firstDayFreeTrial, lastDay: OnyxEntry = lastDayFreeTrial): boolean { - return !isUserOnFreeTrial(firstDay, lastDay) && !hasUserFreeTrialEnded(lastDay); +function shouldShowPreTrialBillingBanner(): boolean { + return !isUserOnFreeTrial() && !hasUserFreeTrialEnded(); } /** * @returns The card to be used for subscription billing. @@ -365,17 +359,15 @@ function hasSubscriptionGreenDotInfo(): boolean { /** * Calculates the remaining number of days of the workspace owner's free trial before it ends. - * @param [lastDay] - The end date of the free trial. - * @returns The remaining number of free trial days. */ -function calculateRemainingFreeTrialDays(lastDay: OnyxEntry = lastDayFreeTrial): number { - if (!lastDay) { +function calculateRemainingFreeTrialDays(): number { + if (!lastDayFreeTrial) { return 0; } const currentDate = new Date(); - const lastDayDate = new Date(`${lastDay}Z`); - const diffInSeconds = differenceInSeconds(lastDayDate, currentDate); + const lastDayFreeTrialDate = new Date(`${lastDayFreeTrial}Z`); + const diffInSeconds = differenceInSeconds(lastDayFreeTrialDate, currentDate); const diffInDays = Math.ceil(diffInSeconds / 86400); return diffInDays < 0 ? 0 : diffInDays; @@ -383,22 +375,21 @@ function calculateRemainingFreeTrialDays(lastDay: OnyxEntry = lastDayFre /** * @param policies - The policies collection. - * @param [firstDay] - The start date of the free trial. - * @param [lastDay] - The end date of the free trial. - * @returns The free trial badge text. + * @returns The free trial badge text . */ -function getFreeTrialText(policies: OnyxCollection | null, firstDay: OnyxEntry = firstDayFreeTrial, lastDay: OnyxEntry = lastDayFreeTrial): string | undefined { +function getFreeTrialText(policies: OnyxCollection | null): string | undefined { const ownedPaidPolicies = PolicyUtils.getOwnedPaidPolicies(policies, currentUserAccountID); if (isEmptyObject(ownedPaidPolicies)) { return undefined; } - if (shouldShowPreTrialBillingBanner(firstDay, lastDay)) { + + if (shouldShowPreTrialBillingBanner()) { return translateLocal('subscription.billingBanner.preTrial.title'); } - if (isUserOnFreeTrial(firstDay, lastDay)) { - return translateLocal('subscription.billingBanner.trialStarted.title', {numOfDays: calculateRemainingFreeTrialDays(lastDay)}); + if (isUserOnFreeTrial()) { + return translateLocal('subscription.billingBanner.trialStarted.title', {numOfDays: calculateRemainingFreeTrialDays()}); } - if (hasUserFreeTrialEnded(lastDay)) { + if (hasUserFreeTrialEnded()) { return translateLocal('subscription.billingBanner.trialEnded.title'); } @@ -407,38 +398,33 @@ function getFreeTrialText(policies: OnyxCollection | null, firstDay: Ony /** * Whether the workspace's owner is on its free trial period. - * @param [firstDay] - The start date of the free trial. - * @param [lastDay] - The end date of the free trial. - * @returns True if the user is on a free trial, false otherwise. */ -function isUserOnFreeTrial(firstDay: OnyxEntry = firstDayFreeTrial, lastDay: OnyxEntry = lastDayFreeTrial): boolean { - if (!firstDay || !lastDay) { +function isUserOnFreeTrial(): boolean { + if (!firstDayFreeTrial || !lastDayFreeTrial) { return false; } const currentDate = new Date(); // Free Trials are stored in UTC so the below code will convert the provided UTC datetime to local time - const firstDayDate = new Date(`${firstDay}Z`); - const lastDayDate = new Date(`${lastDay}Z`); + const firstDayFreeTrialDate = new Date(`${firstDayFreeTrial}Z`); + const lastDayFreeTrialDate = new Date(`${lastDayFreeTrial}Z`); - return isAfter(currentDate, firstDayDate) && isBefore(currentDate, lastDayDate); + return isAfter(currentDate, firstDayFreeTrialDate) && isBefore(currentDate, lastDayFreeTrialDate); } /** * Whether the workspace owner's free trial period has ended. - * @param [lastDay] - The end date of the free trial. - * @returns True if the free trial has ended, false otherwise. */ -function hasUserFreeTrialEnded(lastDay: OnyxEntry = lastDayFreeTrial): boolean { - if (!lastDay) { +function hasUserFreeTrialEnded(): boolean { + if (!lastDayFreeTrial) { return false; } const currentDate = new Date(); - const lastDayDate = new Date(`${lastDay}Z`); + const lastDayFreeTrialDate = new Date(`${lastDayFreeTrial}Z`); - return isAfter(currentDate, lastDayDate); + return isAfter(currentDate, lastDayFreeTrialDate); } /** diff --git a/src/pages/settings/Subscription/FreeTrialBadge.tsx b/src/pages/settings/Subscription/FreeTrialBadge.tsx index 6adeda42b123..bf5f06b165d4 100644 --- a/src/pages/settings/Subscription/FreeTrialBadge.tsx +++ b/src/pages/settings/Subscription/FreeTrialBadge.tsx @@ -1,7 +1,8 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Badge from '@components/Badge'; +import useDebouncedState from '@hooks/useDebouncedState'; import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -13,17 +14,21 @@ function FreeTrialBadge({badgeStyles}: FreeTrialBadgeProps) { const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL); const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL); + /* eslint-disable @typescript-eslint/no-unused-vars */ + const [freeTrialText, debouncedFreeTrialText, setFreeTrialText] = useDebouncedState(undefined); - const freeTrialText = SubscriptionUtils.getFreeTrialText(policies, firstDayFreeTrial, lastDayFreeTrial); + useEffect(() => { + setFreeTrialText(SubscriptionUtils.getFreeTrialText(policies)); + }, [policies, firstDayFreeTrial, lastDayFreeTrial, setFreeTrialText]); - if (!freeTrialText) { + if (!debouncedFreeTrialText) { return null; } return ( ); From 13e070f208f82711851ba36e00448540042d159e Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Wed, 11 Sep 2024 06:46:13 +0700 Subject: [PATCH 027/134] move the shouldSkipDeepLinkNavigation if check Signed-off-by: Tsaqif --- src/libs/actions/Report.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 147b07f650b2..2298c78f4ffc 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2684,10 +2684,6 @@ function openReportFromDeepLink(url: string) { return; } - if (shouldSkipDeepLinkNavigation(route)) { - return; - } - const state = navigationRef.getRootState(); const currentFocusedRoute = findFocusedRoute(state); const hasCompletedGuidedSetupFlow = hasCompletedGuidedSetupFlowSelector(onboarding); @@ -2705,6 +2701,10 @@ function openReportFromDeepLink(url: string) { return; } + if (shouldSkipDeepLinkNavigation(route)) { + return; + } + if (isAuthenticated) { return; } From babc3c90edb3090ee24ac2b48f0d16680f82ac04 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 11 Sep 2024 17:21:11 +0200 Subject: [PATCH 028/134] Pop home screen when going back to Search RHP --- .../CustomRouter.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts index 5b3cefb63a2d..f5713c6657f8 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts @@ -131,6 +131,31 @@ function CustomRouter(options: ResponsiveStackNavigatorRouterOptions) { if (shouldPreventReset(state, action)) { return state; } + + /** + * When we go back from the chat opened in the Chats section to the chat opened in the Search RHP we have to pop the Home screen from the Bottom tab navigator to display correctly Search page under RHP on native platform. + * It fixes this issue: https://github.com/Expensify/App/issues/48882 + */ + if (action.type === 'GO_BACK' || action.type === 'POP') { + const shouldPopHome = + state.routes.length >= 3 && + state.routes.at(-1)?.name === SCREENS.REPORT && + state.routes.at(-2)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR && + getTopmostBottomTabRoute(state as State)?.name === SCREENS.HOME; + + if (!shouldPopHome) { + return stackRouter.getStateForAction(state, action, configOptions); + } + + const bottomTabState = state.routes.at(0)?.state; + const newBottomTabState = {...bottomTabState, routes: bottomTabState?.routes?.slice(0, -1), index: (bottomTabState?.index ?? 0) - 1}; + + const newState = {...state}; + newState.routes[0].state = newBottomTabState as State; + + return stackRouter.getStateForAction(state, action, configOptions); + } + return stackRouter.getStateForAction(state, action, configOptions); }, }; From 5a4fe7c0fc3a257d4a93dffa83083cb8d43d302b Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 11 Sep 2024 19:35:26 +0200 Subject: [PATCH 029/134] removing stateDebounce --- .../settings/Subscription/FreeTrialBadge.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/pages/settings/Subscription/FreeTrialBadge.tsx b/src/pages/settings/Subscription/FreeTrialBadge.tsx index bf5f06b165d4..3bc95f87d527 100644 --- a/src/pages/settings/Subscription/FreeTrialBadge.tsx +++ b/src/pages/settings/Subscription/FreeTrialBadge.tsx @@ -1,8 +1,7 @@ -import React, {useEffect} from 'react'; +import React, {useEffect, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Badge from '@components/Badge'; -import useDebouncedState from '@hooks/useDebouncedState'; import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -14,21 +13,25 @@ function FreeTrialBadge({badgeStyles}: FreeTrialBadgeProps) { const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL); const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL); - /* eslint-disable @typescript-eslint/no-unused-vars */ - const [freeTrialText, debouncedFreeTrialText, setFreeTrialText] = useDebouncedState(undefined); + const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION); + + const [freeTrialText, setFreeTrialText] = useState(undefined); useEffect(() => { + if (!privateSubscription) { + return; + } setFreeTrialText(SubscriptionUtils.getFreeTrialText(policies)); - }, [policies, firstDayFreeTrial, lastDayFreeTrial, setFreeTrialText]); + }, [privateSubscription, policies, firstDayFreeTrial, lastDayFreeTrial]); - if (!debouncedFreeTrialText) { + if (!freeTrialText) { return null; } return ( ); From bee4bece7990553b714a47989f40f13b312711c2 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Thu, 12 Sep 2024 08:43:50 +0200 Subject: [PATCH 030/134] removing the`Your trial has ended` case --- src/libs/SubscriptionUtils.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 8f3fbc486464..f2ceef9069fa 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -389,9 +389,6 @@ function getFreeTrialText(policies: OnyxCollection | null): string | und if (isUserOnFreeTrial()) { return translateLocal('subscription.billingBanner.trialStarted.title', {numOfDays: calculateRemainingFreeTrialDays()}); } - if (hasUserFreeTrialEnded()) { - return translateLocal('subscription.billingBanner.trialEnded.title'); - } return undefined; } From af8bf2dca35761d69e5bee2e57a81e5bf8e9435f Mon Sep 17 00:00:00 2001 From: war-in Date: Thu, 12 Sep 2024 18:25:50 +0200 Subject: [PATCH 031/134] send completeOnboarding to OD only on finished request --- .../HybridAppMiddleware/index.ios.tsx | 33 ----------------- src/components/HybridAppMiddleware/index.tsx | 37 ------------------- src/libs/API/types.ts | 4 +- src/libs/actions/Welcome/index.ts | 25 ++++++------- 4 files changed, 13 insertions(+), 86 deletions(-) diff --git a/src/components/HybridAppMiddleware/index.ios.tsx b/src/components/HybridAppMiddleware/index.ios.tsx index 0fb61fb9ad7a..5b7b95f0aab1 100644 --- a/src/components/HybridAppMiddleware/index.ios.tsx +++ b/src/components/HybridAppMiddleware/index.ios.tsx @@ -2,29 +2,15 @@ import type React from 'react'; import {useEffect} from 'react'; import {NativeEventEmitter, NativeModules} from 'react-native'; import type {NativeModule} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; import Log from '@libs/Log'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import {useSplashScreenStateContext} from '@src/SplashScreenStateContext'; -import type {TryNewDot} from '@src/types/onyx'; type HybridAppMiddlewareProps = { authenticated: boolean; children: React.ReactNode; }; -const onboardingStatusSelector = (tryNewDot: OnyxEntry) => { - let completedHybridAppOnboarding = tryNewDot?.classicRedirect?.completedHybridAppOnboarding; - - if (typeof completedHybridAppOnboarding === 'string') { - completedHybridAppOnboarding = completedHybridAppOnboarding === 'true'; - } - - return completedHybridAppOnboarding; -}; - /* * HybridAppMiddleware is responsible for handling BootSplash visibility correctly. * It is crucial to make transitions between OldDot and NewDot look smooth. @@ -32,25 +18,6 @@ const onboardingStatusSelector = (tryNewDot: OnyxEntry) => { */ function HybridAppMiddleware({children}: HybridAppMiddlewareProps) { const {setSplashScreenState} = useSplashScreenStateContext(); - const [completedHybridAppOnboarding] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT, {selector: onboardingStatusSelector}); - - /** - * This useEffect tracks changes of `nvp_tryNewDot` value. - * We propagate it from OldDot to NewDot with native method due to limitations of old app. - */ - useEffect(() => { - if (completedHybridAppOnboarding === undefined) { - return; - } - - if (!NativeModules.HybridAppModule) { - Log.hmmm(`[HybridApp] Onboarding status has changed, but the HybridAppModule is not defined`); - return; - } - - Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding}); - NativeModules.HybridAppModule.completeOnboarding(completedHybridAppOnboarding); - }, [completedHybridAppOnboarding]); // In iOS, the HybridApp defines the `onReturnToOldDot` event. // If we frequently transition from OldDot to NewDot during a single app lifecycle, diff --git a/src/components/HybridAppMiddleware/index.tsx b/src/components/HybridAppMiddleware/index.tsx index 1ebe1347df8e..74e018bcfa5a 100644 --- a/src/components/HybridAppMiddleware/index.tsx +++ b/src/components/HybridAppMiddleware/index.tsx @@ -1,47 +1,10 @@ import type React from 'react'; -import {useEffect} from 'react'; -import {NativeModules} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; -import Log from '@libs/Log'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {TryNewDot} from '@src/types/onyx'; type HybridAppMiddlewareProps = { children: React.ReactNode; }; -const onboardingStatusSelector = (tryNewDot: OnyxEntry) => { - let completedHybridAppOnboarding = tryNewDot?.classicRedirect?.completedHybridAppOnboarding; - - if (typeof completedHybridAppOnboarding === 'string') { - completedHybridAppOnboarding = completedHybridAppOnboarding === 'true'; - } - - return completedHybridAppOnboarding; -}; - -/* - * HybridAppMiddleware is responsible for handling BootSplash visibility correctly. - * It is crucial to make transitions between OldDot and NewDot look smooth. - * The middleware assumes that the entry point for HybridApp is the /transition route. - */ function HybridAppMiddleware({children}: HybridAppMiddlewareProps) { - const [completedHybridAppOnboarding] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT, {selector: onboardingStatusSelector}); - - /** - * This useEffect tracks changes of `nvp_tryNewDot` value. - * We propagate it from OldDot to NewDot with native method due to limitations of old app. - */ - useEffect(() => { - if (completedHybridAppOnboarding === undefined || !NativeModules.HybridAppModule) { - return; - } - - Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding}); - NativeModules.HybridAppModule.completeOnboarding(completedHybridAppOnboarding); - }, [completedHybridAppOnboarding]); - return children; } diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 2db83a89bbe4..86066925270d 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -164,7 +164,6 @@ const WRITE_COMMANDS = { REOPEN_TASK: 'ReopenTask', COMPLETE_TASK: 'CompleteTask', COMPLETE_GUIDED_SETUP: 'CompleteGuidedSetup', - COMPLETE_HYBRID_APP_ONBOARDING: 'CompleteHybridAppOnboarding', SET_NAME_VALUE_PAIR: 'SetNameValuePair', SET_REPORT_FIELD: 'Report_SetFields', DELETE_REPORT_FIELD: 'RemoveReportField', @@ -527,7 +526,6 @@ type WriteCommandParameters = { [WRITE_COMMANDS.REOPEN_TASK]: Parameters.ReopenTaskParams; [WRITE_COMMANDS.COMPLETE_TASK]: Parameters.CompleteTaskParams; [WRITE_COMMANDS.COMPLETE_GUIDED_SETUP]: Parameters.CompleteGuidedSetupParams; - [WRITE_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING]: EmptyObject; [WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams; [WRITE_COMMANDS.SET_REPORT_FIELD]: Parameters.SetReportFieldParams; [WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams; @@ -896,6 +894,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = { TWO_FACTOR_AUTH_VALIDATE: 'TwoFactorAuth_Validate', CONNECT_AS_DELEGATE: 'ConnectAsDelegate', DISCONNECT_AS_DELEGATE: 'DisconnectAsDelegate', + COMPLETE_HYBRID_APP_ONBOARDING: 'CompleteHybridAppOnboarding', } as const; type SideEffectRequestCommand = ValueOf; @@ -914,6 +913,7 @@ type SideEffectRequestCommandParameters = { [SIDE_EFFECT_REQUEST_COMMANDS.TWO_FACTOR_AUTH_VALIDATE]: Parameters.ValidateTwoFactorAuthParams; [SIDE_EFFECT_REQUEST_COMMANDS.CONNECT_AS_DELEGATE]: Parameters.ConnectAsDelegateParams; [SIDE_EFFECT_REQUEST_COMMANDS.DISCONNECT_AS_DELEGATE]: EmptyObject; + [SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING]: EmptyObject; }; type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters; diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts index f5995aa1e2a9..58c42fcd100e 100644 --- a/src/libs/actions/Welcome/index.ts +++ b/src/libs/actions/Welcome/index.ts @@ -2,7 +2,8 @@ import {NativeModules} from 'react-native'; import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import {WRITE_COMMANDS} from '@libs/API/types'; +import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types'; +import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; import type {OnboardingPurposeType} from '@src/CONST'; @@ -161,7 +162,7 @@ function updateOnboardingLastVisitedPath(path: string) { } function completeHybridAppOnboarding() { - const optimisticData: OnyxUpdate[] = [ + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.NVP_TRYNEWDOT, @@ -173,19 +174,15 @@ function completeHybridAppOnboarding() { }, ]; - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.NVP_TRYNEWDOT, - value: { - classicRedirect: { - completedHybridAppOnboarding: false, - }, - }, - }, - ]; + // eslint-disable-next-line rulesdir/no-api-side-effects-method + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING, {}, {successData}).then((response) => { + if (!response) { + return; + } - API.write(WRITE_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING, {}, {optimisticData, failureData}); + Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding: response?.jsonCode === 200}); + NativeModules.HybridAppModule.completeOnboarding(response?.jsonCode === 200); + }); } Onyx.connect({ From 4d5ce8e91962175956dc0b3f1e35d4cbb69331a1 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Thu, 12 Sep 2024 22:04:35 +0200 Subject: [PATCH 032/134] Enable calling getFreeTrialText if user is offline --- src/pages/settings/Subscription/FreeTrialBadge.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Subscription/FreeTrialBadge.tsx b/src/pages/settings/Subscription/FreeTrialBadge.tsx index 3bc95f87d527..dc078519e86d 100644 --- a/src/pages/settings/Subscription/FreeTrialBadge.tsx +++ b/src/pages/settings/Subscription/FreeTrialBadge.tsx @@ -1,7 +1,8 @@ -import React, {useEffect, useState} from 'react'; +import {useEffect, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Badge from '@components/Badge'; +import useNetwork from '@hooks/useNetwork'; import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -16,13 +17,14 @@ function FreeTrialBadge({badgeStyles}: FreeTrialBadgeProps) { const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION); const [freeTrialText, setFreeTrialText] = useState(undefined); + const {isOffline} = useNetwork(); useEffect(() => { - if (!privateSubscription) { + if (!privateSubscription && !isOffline) { return; } setFreeTrialText(SubscriptionUtils.getFreeTrialText(policies)); - }, [privateSubscription, policies, firstDayFreeTrial, lastDayFreeTrial]); + }, [isOffline, privateSubscription, policies, firstDayFreeTrial, lastDayFreeTrial]); if (!freeTrialText) { return null; From d61c06ff45a9d7e4e1bec9aab0bca933e4e1d549 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Fri, 13 Sep 2024 10:27:23 -0700 Subject: [PATCH 033/134] create pinned status --- src/CONST.ts | 1 + src/components/Search/SearchStatusBar.tsx | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index c57e622b1311..c8f7f66d1cce 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5486,6 +5486,7 @@ const CONST = { SENT: 'sent', ATTACHMENTS: 'attachments', LINKS: 'links', + PINNED: 'pinned', }, }, TABLE_COLUMNS: { diff --git a/src/components/Search/SearchStatusBar.tsx b/src/components/Search/SearchStatusBar.tsx index ccc35feb7ced..ea19ef5f4e99 100644 --- a/src/components/Search/SearchStatusBar.tsx +++ b/src/components/Search/SearchStatusBar.tsx @@ -129,6 +129,12 @@ const chatOptions: Array<{key: ChatSearchStatus; icon: IconAsset; text: Translat text: 'common.links', query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.LINKS), }, + { + key: CONST.SEARCH.STATUS.CHAT.PINNED, + icon: Expensicons.Pin, + text: 'search.filters.pinned', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.PINNED), + }, ]; function getOptions(type: SearchDataTypes) { From c8d3f2e353b1e20da959475334e3e7ac3ed0ff45 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 13 Sep 2024 21:19:50 +0300 Subject: [PATCH 034/134] disable delete transaction for archived room --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7001dedea2f1..08dee490b1b0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1666,7 +1666,7 @@ function getChildReportNotificationPreference(reportAction: OnyxInputOrEntry): boolean { - if (!isMoneyRequestReport(moneyRequestReport)) { + if (!isMoneyRequestReport(moneyRequestReport) || isArchivedRoom(moneyRequestReport)) { return false; } From 687f54a70c8dd4fa4ddaadf1ceacc9b781292e38 Mon Sep 17 00:00:00 2001 From: daledah Date: Sat, 14 Sep 2024 09:33:58 +0700 Subject: [PATCH 035/134] fix: update activeGuides channel name --- src/CONST.ts | 1 + src/components/ActiveGuidesEventListener.tsx | 22 ++++++++------------ src/libs/actions/Report.ts | 7 +------ 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index cf3facb0d1d8..14b0d2da5b34 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1254,6 +1254,7 @@ const CONST = { PUSHER: { PRIVATE_USER_CHANNEL_PREFIX: 'private-encrypted-user-accountID-', PRIVATE_REPORT_CHANNEL_PREFIX: 'private-report-reportID-', + PRESENT_ACTIVE_GUIDES: 'presence-activeGuides', }, EMOJI_SPACER: 'SPACER', diff --git a/src/components/ActiveGuidesEventListener.tsx b/src/components/ActiveGuidesEventListener.tsx index 56622c13ba73..b2733388313f 100644 --- a/src/components/ActiveGuidesEventListener.tsx +++ b/src/components/ActiveGuidesEventListener.tsx @@ -13,19 +13,15 @@ type ActiveGuidesEventListenerProps = ActiveGuidesEventListenerOnyxProps; function ActiveGuidesEventListener({user}: ActiveGuidesEventListenerProps) { const didSubscribeToActiveGuides = useRef(false); - useEffect( - () => () => { - if (didSubscribeToActiveGuides.current) { - return; - } - if (user?.isGuide) { - didSubscribeToActiveGuides.current = true; - Report.subscribeToActiveGuides(); - } - }, - - [user], - ); + useEffect(() => { + if (didSubscribeToActiveGuides.current) { + return; + } + if (user?.isGuide) { + didSubscribeToActiveGuides.current = true; + Report.subscribeToActiveGuides(); + } + }, [user]); return null; } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 84a633c59ad0..26953b0aa4db 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -4088,15 +4088,11 @@ function markAsManuallyExported(reportID: string, connectionName: ConnectionName } function subscribeToActiveGuides() { - Pusher.subscribe(`activeGuides`).catch((error: ReportError) => { + Pusher.subscribe(`${CONST.PUSHER.PRESENT_ACTIVE_GUIDES}${CONFIG.PUSHER.SUFFIX}`).catch((error: ReportError) => { Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChanelName: 'activeGuides'}); }); } -function unsubscribeToActiveGuides() { - Pusher.unsubscribe(`activeGuides`); -} - export type {Video}; export { @@ -4186,5 +4182,4 @@ export { markAsManuallyExported, handleReportChanged, subscribeToActiveGuides, - unsubscribeToActiveGuides, }; From 205f47a8a3590ace026a1edd986aa36b866f9813 Mon Sep 17 00:00:00 2001 From: gijoe0295 Date: Sun, 15 Sep 2024 13:24:24 +0700 Subject: [PATCH 036/134] fix: unique key warning --- src/components/MoneyRequestConfirmationListFooter.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index 93b80f5732d2..e36412b3f44a 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -300,7 +300,10 @@ function MoneyRequestConfirmationListFooter({ }, { item: ( - + + Date: Mon, 16 Sep 2024 01:47:07 +0700 Subject: [PATCH 037/134] fix: move subscribe to User.ts --- src/CONST.ts | 2 +- src/components/ActiveGuidesEventListener.tsx | 4 ++-- src/libs/actions/Report.ts | 7 ------- src/libs/actions/User.ts | 8 ++++++++ 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 14b0d2da5b34..fe09087b866d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1254,7 +1254,7 @@ const CONST = { PUSHER: { PRIVATE_USER_CHANNEL_PREFIX: 'private-encrypted-user-accountID-', PRIVATE_REPORT_CHANNEL_PREFIX: 'private-report-reportID-', - PRESENT_ACTIVE_GUIDES: 'presence-activeGuides', + PRESENCE_ACTIVE_GUIDES: 'presence-activeGuides', }, EMOJI_SPACER: 'SPACER', diff --git a/src/components/ActiveGuidesEventListener.tsx b/src/components/ActiveGuidesEventListener.tsx index b2733388313f..1aab40eae7d0 100644 --- a/src/components/ActiveGuidesEventListener.tsx +++ b/src/components/ActiveGuidesEventListener.tsx @@ -1,7 +1,7 @@ import {useEffect, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import * as Report from '@userActions/Report'; +import {subscribeToActiveGuides} from '@userActions/User'; import ONYXKEYS from '@src/ONYXKEYS'; import type {User} from '@src/types/onyx'; @@ -19,7 +19,7 @@ function ActiveGuidesEventListener({user}: ActiveGuidesEventListenerProps) { } if (user?.isGuide) { didSubscribeToActiveGuides.current = true; - Report.subscribeToActiveGuides(); + subscribeToActiveGuides(); } }, [user]); return null; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 26953b0aa4db..147b07f650b2 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -4087,12 +4087,6 @@ function markAsManuallyExported(reportID: string, connectionName: ConnectionName API.write(WRITE_COMMANDS.MARK_AS_EXPORTED, params, {optimisticData, successData, failureData}); } -function subscribeToActiveGuides() { - Pusher.subscribe(`${CONST.PUSHER.PRESENT_ACTIVE_GUIDES}${CONFIG.PUSHER.SUFFIX}`).catch((error: ReportError) => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChanelName: 'activeGuides'}); - }); -} - export type {Video}; export { @@ -4181,5 +4175,4 @@ export { exportToIntegration, markAsManuallyExported, handleReportChanged, - subscribeToActiveGuides, }; diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 355dcf93338e..ada6b0fda1b4 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -33,6 +33,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import playSoundExcludingMobile from '@libs/Sound/playSoundExcludingMobile'; import Visibility from '@libs/Visibility'; +import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -1311,6 +1312,12 @@ function requestRefund() { API.write(WRITE_COMMANDS.REQUEST_REFUND, null); } +function subscribeToActiveGuides() { + Pusher.subscribe(`${CONST.PUSHER.PRESENCE_ACTIVE_GUIDES}${CONFIG.PUSHER.SUFFIX}`).catch(() => { + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {pusherChanelName: 'activeGuides'}); + }); +} + export { clearFocusModeNotification, closeAccount, @@ -1349,4 +1356,5 @@ export { requestValidateCodeAction, addPendingContactMethod, clearValidateCodeActionError, + subscribeToActiveGuides, }; From f92c53fa10ad97125cef8f688b3eb41732fe408e Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 16 Sep 2024 12:13:53 +0700 Subject: [PATCH 038/134] fix: define channel name --- src/libs/actions/User.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 3c135150d7c7..8a030e16a9e8 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -1317,8 +1317,9 @@ function requestRefund() { } function subscribeToActiveGuides() { - Pusher.subscribe(`${CONST.PUSHER.PRESENCE_ACTIVE_GUIDES}${CONFIG.PUSHER.SUFFIX}`).catch(() => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {pusherChanelName: 'activeGuides'}); + const pusherChannelName = `${CONST.PUSHER.PRESENCE_ACTIVE_GUIDES}${CONFIG.PUSHER.SUFFIX}`; + Pusher.subscribe(pusherChannelName).catch(() => { + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {pusherChannelName}); }); } From 06c0425facbe06ce08443c122acb0345df1e4ab1 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 16 Sep 2024 14:41:46 +0900 Subject: [PATCH 039/134] open expense report on single transaction report --- src/components/SelectionList/Search/ReportListItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx index 88fbb1f45314..a0b96547bcd8 100644 --- a/src/components/SelectionList/Search/ReportListItem.tsx +++ b/src/components/SelectionList/Search/ReportListItem.tsx @@ -110,7 +110,7 @@ function ReportListItem({ isDisabled={isDisabled} canSelectMultiple={canSelectMultiple} onCheckboxPress={() => onCheckboxPress?.(transactionItem as unknown as TItem)} - onSelectRow={() => openReportInRHP(transactionItem)} + onSelectRow={onSelectRow} onDismissError={onDismissError} onFocus={onFocus} onLongPressRow={onLongPressRow} From 0618191576b53ac038e76c3a8bd25af6c3827262 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 12:01:45 +0300 Subject: [PATCH 040/134] add center style avatar --- src/components/AvatarWithImagePicker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AvatarWithImagePicker.tsx b/src/components/AvatarWithImagePicker.tsx index 738cff48c813..011b7f510275 100644 --- a/src/components/AvatarWithImagePicker.tsx +++ b/src/components/AvatarWithImagePicker.tsx @@ -376,7 +376,7 @@ function AvatarWithImagePicker({ accessibilityLabel={translate('avatarWithImagePicker.editImage')} disabled={isAvatarCropModalOpen || (disabled && !enablePreview)} disabledStyle={disabledStyle} - style={[styles.pRelative, avatarStyle]} + style={[styles.pRelative, type === CONST.ICON_TYPE_AVATAR && styles.alignSelfCenter, avatarStyle]} ref={anchorRef} > From ca0e5d48763f001d11e4fafd3502892e2dfffb53 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 12:13:03 +0300 Subject: [PATCH 041/134] fix #48965 --- .../settings/Security/AddDelegate/SelectDelegateRolePage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx index e295a9a7ece7..60353b90dfba 100644 --- a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx +++ b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx @@ -24,9 +24,9 @@ function SelectDelegateRolePage({route}: SelectDelegateRolePageProps) { const roleOptions = Object.values(CONST.DELEGATE_ROLE).map((role) => ({ value: role, text: translate('delegate.role', role), - keyForList: role, alternateText: translate('delegate.roleDescription', role), isSelected: role === route.params.role, + keyForList: role, })); return ( @@ -41,6 +41,7 @@ function SelectDelegateRolePage({route}: SelectDelegateRolePageProps) { <> From b1365f4b6ee8e26850f2c5cce87ed5d51614fcf7 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 12:15:07 +0300 Subject: [PATCH 042/134] add fallback avatar. fix #48966 --- src/pages/settings/Security/SecuritySettingsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 1de5cb58bf38..07e611b0022c 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -119,7 +119,7 @@ function SecuritySettingsPage() { description: personalDetail?.displayName ? formattedEmail : '', badgeText: translate('delegate.role', role), avatarID: personalDetail?.accountID ?? -1, - icon: personalDetail?.avatar ?? '', + icon: personalDetail?.avatar ?? FallbackAvatar, iconType: CONST.ICON_TYPE_AVATAR, numberOfLinesDescription: 1, wrapperStyle: [styles.sectionMenuItemTopDescription], From ae039e1f91f2bfd3c8676d247d234eae607e42b9 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 12:25:12 +0300 Subject: [PATCH 043/134] fix #48967 show no results found --- src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx index eae54fa88c2a..ade900f365a7 100644 --- a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx +++ b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx @@ -48,7 +48,7 @@ function useOptions() { 0, ); - const headerMessage = OptionsListUtils.getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0 || !!currentUserOption, !!userToInvite, ''); + const headerMessage = OptionsListUtils.getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0, !!userToInvite, ''); if (isLoading) { setIsLoading(false); @@ -72,7 +72,7 @@ function useOptions() { maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); const headerMessage = OptionsListUtils.getHeaderMessage( - (filteredOptions.recentReports?.length || 0) + (filteredOptions.personalDetails?.length || 0) !== 0 || !!filteredOptions.currentUserOption, + (filteredOptions.recentReports?.length || 0) + (filteredOptions.personalDetails?.length || 0) !== 0, !!filteredOptions.userToInvite, debouncedSearchValue, ); From e45328e0656e6a8dd8fc94fbf2554cd558d88525 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 16 Sep 2024 11:31:36 +0200 Subject: [PATCH 044/134] Adding React to the FreeTrialBadge imports --- src/pages/settings/Subscription/FreeTrialBadge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Subscription/FreeTrialBadge.tsx b/src/pages/settings/Subscription/FreeTrialBadge.tsx index dc078519e86d..5379d5a888b7 100644 --- a/src/pages/settings/Subscription/FreeTrialBadge.tsx +++ b/src/pages/settings/Subscription/FreeTrialBadge.tsx @@ -1,4 +1,4 @@ -import {useEffect, useState} from 'react'; +import React, {useEffect, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Badge from '@components/Badge'; From 412544c13e544cdc9d982b6eec07c962f31817c2 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 12:33:20 +0300 Subject: [PATCH 045/134] fix #48972 --- src/pages/settings/Security/SecuritySettingsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 07e611b0022c..6403b0ac64e2 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -163,7 +163,7 @@ function SecuritySettingsPage() {
( - + {translate('delegate.copilotDelegatedAccessDescription')} {translate('common.learnMore')} - + )} isCentralPane subtitleMuted From 3cff6034c47d3aebc889d71efb7ff27fd875aa21 Mon Sep 17 00:00:00 2001 From: war-in Date: Mon, 16 Sep 2024 15:41:08 +0200 Subject: [PATCH 046/134] revert unnecessary removals --- src/libs/actions/Welcome/index.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts index 58c42fcd100e..df218dfa818d 100644 --- a/src/libs/actions/Welcome/index.ts +++ b/src/libs/actions/Welcome/index.ts @@ -162,7 +162,7 @@ function updateOnboardingLastVisitedPath(path: string) { } function completeHybridAppOnboarding() { - const successData: OnyxUpdate[] = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.NVP_TRYNEWDOT, @@ -174,12 +174,25 @@ function completeHybridAppOnboarding() { }, ]; + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_TRYNEWDOT, + value: { + classicRedirect: { + completedHybridAppOnboarding: false, + }, + }, + }, + ]; + // eslint-disable-next-line rulesdir/no-api-side-effects-method - API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING, {}, {successData}).then((response) => { + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING, {}, {optimisticData, failureData}).then((response) => { if (!response) { return; } + // if the call succeeded HybridApp onboarding is finished, otherwise it's not Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding: response?.jsonCode === 200}); NativeModules.HybridAppModule.completeOnboarding(response?.jsonCode === 200); }); From 964363e8b51e2621ffff444f31745a4fb9723cd0 Mon Sep 17 00:00:00 2001 From: war-in Date: Mon, 16 Sep 2024 15:42:14 +0200 Subject: [PATCH 047/134] remove outdated comment --- src/components/HybridAppMiddleware/index.ios.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/HybridAppMiddleware/index.ios.tsx b/src/components/HybridAppMiddleware/index.ios.tsx index 5b7b95f0aab1..6982487983c4 100644 --- a/src/components/HybridAppMiddleware/index.ios.tsx +++ b/src/components/HybridAppMiddleware/index.ios.tsx @@ -11,18 +11,13 @@ type HybridAppMiddlewareProps = { children: React.ReactNode; }; -/* - * HybridAppMiddleware is responsible for handling BootSplash visibility correctly. - * It is crucial to make transitions between OldDot and NewDot look smooth. - * The middleware assumes that the entry point for HybridApp is the /transition route. - */ function HybridAppMiddleware({children}: HybridAppMiddlewareProps) { const {setSplashScreenState} = useSplashScreenStateContext(); // In iOS, the HybridApp defines the `onReturnToOldDot` event. // If we frequently transition from OldDot to NewDot during a single app lifecycle, // we need to artificially display the bootsplash since the app is booted only once. - // Therefore, isSplashHidden needs to be updated at the appropriate time. + // Therefore, splashScreenState needs to be updated at the appropriate time. useEffect(() => { if (!NativeModules.HybridAppModule) { return; From cdbf52f0e89dc0b910aa362697a97dc61d1d5f4f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 19:29:11 +0300 Subject: [PATCH 048/134] fix center avatar on center --- src/pages/settings/Profile/ProfilePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index 57bef1eb9190..c7a585b724ef 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -168,7 +168,7 @@ function ProfilePage({ onImageSelected={PersonalDetails.updateAvatar} onImageRemoved={PersonalDetails.deleteAvatar} size={CONST.AVATAR_SIZE.XLARGE} - avatarStyle={styles.avatarXLarge} + avatarStyle={[styles.avatarXLarge, styles.alignSelfStart]} pendingAction={currentUserPersonalDetails?.pendingFields?.avatar ?? undefined} errors={currentUserPersonalDetails?.errorFields?.avatar ?? null} errorRowStyles={styles.mt6} From 186dfde697f28f23f0d6460f5697945d207af506 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 20:22:49 +0300 Subject: [PATCH 049/134] Update src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx Co-authored-by: Gandalf --- .../settings/Security/AddDelegate/SelectDelegateRolePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx index 60353b90dfba..121b2a6e1c4f 100644 --- a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx +++ b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx @@ -41,7 +41,7 @@ function SelectDelegateRolePage({route}: SelectDelegateRolePageProps) { role.value === route.params.role)?.value} headerContent={ <> From 81e49dd5f2ca02301c2ab729dd108fbc4088284a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 21:05:14 +0300 Subject: [PATCH 050/134] undo --- .../settings/Security/AddDelegate/SelectDelegateRolePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx index 121b2a6e1c4f..60353b90dfba 100644 --- a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx +++ b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx @@ -41,7 +41,7 @@ function SelectDelegateRolePage({route}: SelectDelegateRolePageProps) { role.value === route.params.role)?.value} + initiallyFocusedOptionKey={route.params.role} headerContent={ <> From ef398851d6f29097ba1e0432a47eec34773d5775 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 21:20:53 +0300 Subject: [PATCH 051/134] migrate to useOnyx --- src/pages/settings/Profile/ProfilePage.tsx | 58 ++++------------------ 1 file changed, 10 insertions(+), 48 deletions(-) diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index c7a585b724ef..83fcdefb3529 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -1,7 +1,6 @@ import React 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 AvatarSkeleton from '@components/AvatarSkeleton'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; @@ -17,8 +16,7 @@ import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; -import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -34,45 +32,21 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {LoginList, PrivatePersonalDetails} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -type ProfilePageOnyxProps = { - loginList: OnyxEntry; - /** User's private personal details */ - privatePersonalDetails: OnyxEntry; - /** Whether app is loading */ - isLoadingApp: OnyxEntry; -}; - -type ProfilePageProps = ProfilePageOnyxProps & WithCurrentUserPersonalDetailsProps; - -function ProfilePage({ - loginList, - privatePersonalDetails = { - legalFirstName: '', - legalLastName: '', - dob: '', - addresses: [ - { - street: '', - street2: '', - city: '', - state: '', - zip: '', - country: '', - }, - ], - }, - currentUserPersonalDetails, - isLoadingApp, -}: ProfilePageProps) { +function ProfilePage() { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); + const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + + const isLoadingApp = useOnyx(ONYXKEYS.IS_LOADING_APP); + const getPronouns = (): string => { const pronounsKey = currentUserPersonalDetails?.pronouns?.replace(CONST.PRONOUNS.PREFIX, '') ?? ''; return pronounsKey ? translate(`pronouns.${pronounsKey}` as TranslationPaths) : translate('profilePage.selectYourPronouns'); @@ -250,16 +224,4 @@ function ProfilePage({ ProfilePage.displayName = 'ProfilePage'; -export default withCurrentUserPersonalDetails( - withOnyx({ - loginList: { - key: ONYXKEYS.LOGIN_LIST, - }, - privatePersonalDetails: { - key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, - }, - isLoadingApp: { - key: ONYXKEYS.IS_LOADING_APP, - }, - })(ProfilePage), -); +export default ProfilePage; From ef72f6e8f30fb1e1c780b1a0cf1709b71da0302a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 22:30:18 +0300 Subject: [PATCH 052/134] Update src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx Co-authored-by: Gandalf --- .../settings/Security/AddDelegate/SelectDelegateRolePage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx index 60353b90dfba..4ecc4916c358 100644 --- a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx +++ b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx @@ -41,7 +41,8 @@ function SelectDelegateRolePage({route}: SelectDelegateRolePageProps) { role.isSelected)?.keyForList} + shouldUpdateFocusedIndex={true} headerContent={ <> From 747372d480adc07403d1c8e7200fae9c2e369558 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 16 Sep 2024 22:31:44 +0300 Subject: [PATCH 053/134] fix lint --- .../settings/Security/AddDelegate/SelectDelegateRolePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx index 4ecc4916c358..551f5c2c223c 100644 --- a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx +++ b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx @@ -42,7 +42,7 @@ function SelectDelegateRolePage({route}: SelectDelegateRolePageProps) { isAlternateTextMultilineSupported alternateTextNumberOfLines={4} initiallyFocusedOptionKey={roleOptions.find((role) => role.isSelected)?.keyForList} - shouldUpdateFocusedIndex={true} + shouldUpdateFocusedIndex headerContent={ <> From 0b862d77e22dd20b5fa8c804b751f7988743a314 Mon Sep 17 00:00:00 2001 From: daledah Date: Tue, 17 Sep 2024 11:33:26 +0700 Subject: [PATCH 054/134] refactor: change verbose message --- src/libs/Permissions.ts | 2 +- src/libs/actions/User.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 8c47100e465b..2a548354c679 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return !!betas?.includes(CONST.BETAS.ALL); + return true || !!betas?.includes(CONST.BETAS.ALL); } function canUseDefaultRooms(betas: OnyxEntry): boolean { diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 8a030e16a9e8..6d7e67fa02f9 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -1319,7 +1319,7 @@ function requestRefund() { function subscribeToActiveGuides() { const pusherChannelName = `${CONST.PUSHER.PRESENCE_ACTIVE_GUIDES}${CONFIG.PUSHER.SUFFIX}`; Pusher.subscribe(pusherChannelName).catch(() => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {pusherChannelName}); + Log.hmmm('[User] Failed to initially subscribe to Pusher channel', {pusherChannelName}); }); } From 2f95fd963fd457a5e75a097df1457de6ef577567 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 17 Sep 2024 18:38:37 +0200 Subject: [PATCH 055/134] Fix bug with Payment option is not selected --- src/components/MoneyReportHeader.tsx | 35 ++++++++-------- .../ReportActionItem/ReportPreview.tsx | 41 ++++++++++--------- src/components/SettlementButton/index.tsx | 1 + 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 21646789052a..4fc92d619e68 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -145,22 +145,25 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const {isDelegateAccessRestricted, delegatorEmail} = useDelegateUserDetails(); const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); - const confirmPayment = (type?: PaymentMethodType | undefined, payAsBusiness?: boolean) => { - if (!type || !chatReport) { - return; - } - setPaymentType(type); - setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); - if (isDelegateAccessRestricted) { - setIsNoDelegateAccessMenuVisible(true); - } else if (isAnyTransactionOnHold) { - setIsHoldMenuVisible(true); - } else if (ReportUtils.isInvoiceReport(moneyRequestReport)) { - IOU.payInvoice(type, chatReport, moneyRequestReport, payAsBusiness); - } else { - IOU.payMoneyRequest(type, chatReport, moneyRequestReport, true); - } - }; + const confirmPayment = useCallback( + (type?: PaymentMethodType | undefined, payAsBusiness?: boolean) => { + if (!type || !chatReport) { + return; + } + setPaymentType(type); + setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); + if (isDelegateAccessRestricted) { + setIsNoDelegateAccessMenuVisible(true); + } else if (isAnyTransactionOnHold) { + setIsHoldMenuVisible(true); + } else if (ReportUtils.isInvoiceReport(moneyRequestReport)) { + IOU.payInvoice(type, chatReport, moneyRequestReport, payAsBusiness); + } else { + IOU.payMoneyRequest(type, chatReport, moneyRequestReport, true); + } + }, + [chatReport, isAnyTransactionOnHold, isDelegateAccessRestricted, moneyRequestReport], + ); const confirmApproval = () => { setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 367defc4ff43..1b4d51088019 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -199,26 +199,29 @@ function ReportPreview({ const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); const stopAnimation = useCallback(() => setIsPaidAnimationRunning(false), []); - const confirmPayment = (type: PaymentMethodType | undefined, payAsBusiness?: boolean) => { - if (!type) { - return; - } - setPaymentType(type); - setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); - if (isDelegateAccessRestricted) { - setIsNoDelegateAccessMenuVisible(true); - } else if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { - setIsHoldMenuVisible(true); - } else if (chatReport && iouReport) { - setIsPaidAnimationRunning(true); - HapticFeedback.longPress(); - if (ReportUtils.isInvoiceReport(iouReport)) { - IOU.payInvoice(type, chatReport, iouReport, payAsBusiness); - } else { - IOU.payMoneyRequest(type, chatReport, iouReport); + const confirmPayment = useCallback( + (type: PaymentMethodType | undefined, payAsBusiness?: boolean) => { + if (!type) { + return; } - } - }; + setPaymentType(type); + setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); + if (isDelegateAccessRestricted) { + setIsNoDelegateAccessMenuVisible(true); + } else if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { + setIsHoldMenuVisible(true); + } else if (chatReport && iouReport) { + setIsPaidAnimationRunning(true); + HapticFeedback.longPress(); + if (ReportUtils.isInvoiceReport(iouReport)) { + IOU.payInvoice(type, chatReport, iouReport, payAsBusiness); + } else { + IOU.payMoneyRequest(type, chatReport, iouReport); + } + } + }, + [chatReport, iouReport, isDelegateAccessRestricted], + ); const confirmApproval = () => { setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 2d24f9102aab..ae6dabf2fab7 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -155,6 +155,7 @@ function SettlementButton({ } return buttonOptions; // We don't want to reorder the options when the preferred payment method changes while the button is still visible + // To be sure that onPress should be wrapped in a useCallback to prevent unnecessary updates // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ iouReport, From 60d0752e839927f537528e507da1fe9ec00f8748 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 17 Sep 2024 18:40:34 +0200 Subject: [PATCH 056/134] Update comment --- src/components/SettlementButton/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index ae6dabf2fab7..8d185d497a53 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -155,7 +155,7 @@ function SettlementButton({ } return buttonOptions; // We don't want to reorder the options when the preferred payment method changes while the button is still visible - // To be sure that onPress should be wrapped in a useCallback to prevent unnecessary updates + // Be sure that onPress should be wrapped in a useCallback to prevent unnecessary updates // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ iouReport, From 5a90681ac9f39ee0201330aa6cd8b59772442d7d Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 17 Sep 2024 19:50:59 +0200 Subject: [PATCH 057/134] Update comment --- src/components/SettlementButton/index.tsx | 2 +- src/libs/actions/IOU.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 8d185d497a53..0118875193d8 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -155,7 +155,7 @@ function SettlementButton({ } return buttonOptions; // We don't want to reorder the options when the preferred payment method changes while the button is still visible - // Be sure that onPress should be wrapped in a useCallback to prevent unnecessary updates + // We need to be sure that onPress should be wrapped in an useCallback to prevent unnecessary updates // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ iouReport, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index b0d025e0768f..c899a204a0cc 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3113,9 +3113,13 @@ function updateMoneyRequestDistance({ const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport?.parentReportID}`] ?? null; let data: UpdateMoneyRequestData; if (ReportUtils.isTrackExpenseReport(transactionThreadReport) && ReportUtils.isSelfDM(parentReport)) { + console.log(1, '---------------------'); data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true, policy); } else { + console.log(2, '---------------------'); data = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories, true); + + console.log(data); } const {params, onyxData} = data; API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DISTANCE, params, onyxData); @@ -3134,6 +3138,7 @@ function updateMoneyRequestCategory( category, }; const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories, true); + console.log(onyxData); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_CATEGORY, params, onyxData); } From a56d546f3dbddc4d4f1a5097ff7599884f7230e0 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 17 Sep 2024 19:52:02 +0200 Subject: [PATCH 058/134] Revert come shanges --- src/libs/actions/IOU.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index c899a204a0cc..b0d025e0768f 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3113,13 +3113,9 @@ function updateMoneyRequestDistance({ const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport?.parentReportID}`] ?? null; let data: UpdateMoneyRequestData; if (ReportUtils.isTrackExpenseReport(transactionThreadReport) && ReportUtils.isSelfDM(parentReport)) { - console.log(1, '---------------------'); data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true, policy); } else { - console.log(2, '---------------------'); data = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories, true); - - console.log(data); } const {params, onyxData} = data; API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DISTANCE, params, onyxData); @@ -3138,7 +3134,6 @@ function updateMoneyRequestCategory( category, }; const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories, true); - console.log(onyxData); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_CATEGORY, params, onyxData); } From 3fcea03fc63a9f1c5ffe6466d4b4067ab333cf0f Mon Sep 17 00:00:00 2001 From: gijoe0295 Date: Wed, 18 Sep 2024 01:12:21 +0700 Subject: [PATCH 059/134] fix: invalid prop inbetweenCompo of type boolean --- src/components/AddressSearch/index.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx index c7753dccadd7..9f63f81a4c1f 100644 --- a/src/components/AddressSearch/index.tsx +++ b/src/components/AddressSearch/index.tsx @@ -462,15 +462,14 @@ function AddressSearch( }} inbetweenCompo={ // We want to show the current location button even if there are no recent destinations - predefinedPlaces?.length === 0 && - shouldShowCurrentLocationButton && ( + predefinedPlaces?.length === 0 && shouldShowCurrentLocationButton ? ( - ) + ) : undefined } placeholder="" listViewDisplayed From 0eee5faf00d7bc0913391cf9a60e7fd85467b923 Mon Sep 17 00:00:00 2001 From: gijoe0295 Date: Wed, 18 Sep 2024 01:17:01 +0700 Subject: [PATCH 060/134] fix: invalid prop listEmptyComponent and listLoaderComponent of type function --- src/components/AddressSearch/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx index 9f63f81a4c1f..5a3bb6f6cd5b 100644 --- a/src/components/AddressSearch/index.tsx +++ b/src/components/AddressSearch/index.tsx @@ -1,4 +1,4 @@ -import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {forwardRef, useEffect, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {ActivityIndicator, Keyboard, LogBox, View} from 'react-native'; import type {LayoutChangeEvent} from 'react-native'; @@ -329,12 +329,12 @@ function AddressSearch( return predefinedPlaces?.filter((predefinedPlace) => isPlaceMatchForSearch(searchValue, predefinedPlace)) ?? []; }, [predefinedPlaces, searchValue]); - const listEmptyComponent = useCallback( - () => (!isTyping ? null : {translate('common.noResultsFound')}), + const listEmptyComponent = useMemo( + () => (!isTyping ? undefined : {translate('common.noResultsFound')}), [isTyping, styles, translate], ); - const listLoader = useCallback( + const listLoader = useMemo( () => ( Date: Wed, 18 Sep 2024 01:23:42 +0700 Subject: [PATCH 061/134] remove redundant changes --- src/components/AddressSearch/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx index 5a3bb6f6cd5b..a06c76e4f869 100644 --- a/src/components/AddressSearch/index.tsx +++ b/src/components/AddressSearch/index.tsx @@ -345,7 +345,6 @@ function AddressSearch( ), [styles.pv4, theme.spinner], ); - ``; return ( /* From e9ef0dc86d18f7dbcfbc7f1dd5007155a1c632d5 Mon Sep 17 00:00:00 2001 From: gijoe0295 Date: Wed, 18 Sep 2024 01:38:26 +0700 Subject: [PATCH 062/134] fix lint: jsx deprecation --- src/components/MoneyRequestConfirmationListFooter.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index e36412b3f44a..b77e3b993b8d 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -527,8 +527,8 @@ function MoneyRequestConfirmationListFooter({ }, ]; - const primaryFields: JSX.Element[] = []; - const supplementaryFields: JSX.Element[] = []; + const primaryFields: React.JSX.Element[] = []; + const supplementaryFields: React.JSX.Element[] = []; classifiedFields.forEach((field) => { if (field.shouldShow && !field.isSupplementary) { From 49d1ceaa7cf357574899a99b9b89d6a8f5dab70b Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 18 Sep 2024 09:42:19 +0700 Subject: [PATCH 063/134] fix: remove unnecessary change --- src/libs/Permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 2a548354c679..8c47100e465b 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return true || !!betas?.includes(CONST.BETAS.ALL); + return !!betas?.includes(CONST.BETAS.ALL); } function canUseDefaultRooms(betas: OnyxEntry): boolean { From c965330ba2d538af3abc9c5a746679e0764d88bd Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 18 Sep 2024 08:21:11 +0200 Subject: [PATCH 064/134] Update dependencies for useMemo --- src/components/SettlementButton/index.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 0118875193d8..f5556b089778 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React, {useMemo, useRef} from 'react'; import type {GestureResponderEvent} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; @@ -7,6 +7,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import KYCWall from '@components/KYCWall'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import usePrevious from '@hooks/usePrevious'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; @@ -63,6 +64,7 @@ function SettlementButton({ // The app would crash due to subscribing to the entire report collection if chatReportID is an empty string. So we should have a fallback ID here. const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || -1}`); const [lastPaymentMethod = '-1'] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {selector: (paymentMethod) => paymentMethod?.[policyID]}); + const lastPaymentMethodIsInitialized = lastPaymentMethod !== '-1'; const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); const isInvoiceReport = (!isEmptyObject(iouReport) && ReportUtils.isInvoiceReport(iouReport)) || false; const shouldShowPaywithExpensifyOption = !shouldHidePaymentOptions && policy?.reimbursementChoice !== CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL; @@ -154,10 +156,11 @@ function SettlementButton({ return buttonOptions.sort((method) => (method.value === lastPaymentMethod ? -1 : 0)); } return buttonOptions; - // We don't want to reorder the options when the preferred payment method changes while the button is still visible + // We don't want to reorder the options when the preferred payment method changes while the button is still visible except for component initialization when the last payment method is not set yet. // We need to be sure that onPress should be wrapped in an useCallback to prevent unnecessary updates // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ + lastPaymentMethodIsInitialized, iouReport, translate, formattedAmount, From f567e7fc23837aeb1ac4a2661a55777438e2dd57 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 18 Sep 2024 08:23:16 +0200 Subject: [PATCH 065/134] Remove redundant code --- src/components/SettlementButton/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index f5556b089778..8236233a8e6c 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useRef} from 'react'; +import React, {useMemo} from 'react'; import type {GestureResponderEvent} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; @@ -7,7 +7,6 @@ import * as Expensicons from '@components/Icon/Expensicons'; import KYCWall from '@components/KYCWall'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import usePrevious from '@hooks/usePrevious'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; From 4dc6731c670ac947bd218dc2d8f099291e0aca24 Mon Sep 17 00:00:00 2001 From: war-in Date: Wed, 18 Sep 2024 08:23:52 +0200 Subject: [PATCH 066/134] use CONST.JSON_CODE.SUCCESS --- src/libs/actions/Welcome/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts index df218dfa818d..75529a879104 100644 --- a/src/libs/actions/Welcome/index.ts +++ b/src/libs/actions/Welcome/index.ts @@ -7,6 +7,7 @@ import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; import type {OnboardingPurposeType} from '@src/CONST'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type Onboarding from '@src/types/onyx/Onboarding'; @@ -193,8 +194,8 @@ function completeHybridAppOnboarding() { } // if the call succeeded HybridApp onboarding is finished, otherwise it's not - Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding: response?.jsonCode === 200}); - NativeModules.HybridAppModule.completeOnboarding(response?.jsonCode === 200); + Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding: response?.jsonCode === CONST.JSON_CODE.SUCCESS}); + NativeModules.HybridAppModule.completeOnboarding(response?.jsonCode === CONST.JSON_CODE.SUCCESS); }); } From c07cd003e0acf40cc0c3f16a20b339ec9bee2a2b Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 18 Sep 2024 08:24:40 +0200 Subject: [PATCH 067/134] Update comment --- src/components/SettlementButton/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 8236233a8e6c..223148b30bf3 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -155,7 +155,7 @@ function SettlementButton({ return buttonOptions.sort((method) => (method.value === lastPaymentMethod ? -1 : 0)); } return buttonOptions; - // We don't want to reorder the options when the preferred payment method changes while the button is still visible except for component initialization when the last payment method is not set yet. + // We don't want to reorder the options when the preferred payment method changes while the button is still visible except for component initialization when the last payment method is not initialized yet. // We need to be sure that onPress should be wrapped in an useCallback to prevent unnecessary updates // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ From 2e8005d54fc4edf1aaae30544a5a674a2353dd53 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 18 Sep 2024 08:33:26 +0200 Subject: [PATCH 068/134] Update comment x2 --- src/components/SettlementButton/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 223148b30bf3..baa2d02eb971 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -155,7 +155,7 @@ function SettlementButton({ return buttonOptions.sort((method) => (method.value === lastPaymentMethod ? -1 : 0)); } return buttonOptions; - // We don't want to reorder the options when the preferred payment method changes while the button is still visible except for component initialization when the last payment method is not initialized yet. + // We don't want to reorder the options when the preferred payment method changes while the button is still visible except when the last payment method is not initialized yet. // We need to be sure that onPress should be wrapped in an useCallback to prevent unnecessary updates // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ From 8b1fd4fe3db202acf010e75c087eb00c3746bc57 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 18 Sep 2024 09:04:41 +0200 Subject: [PATCH 069/134] Update code to prevent potencial issues --- src/components/SettlementButton/index.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index baa2d02eb971..47a742f94b3b 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -62,13 +62,14 @@ function SettlementButton({ const {isOffline} = useNetwork(); // The app would crash due to subscribing to the entire report collection if chatReportID is an empty string. So we should have a fallback ID here. const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || -1}`); - const [lastPaymentMethod = '-1'] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {selector: (paymentMethod) => paymentMethod?.[policyID]}); - const lastPaymentMethodIsInitialized = lastPaymentMethod !== '-1'; + const [nvpLastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD); + const nvpLastPaymentMethodIsInitialized = nvpLastPaymentMethod !== undefined; const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); const isInvoiceReport = (!isEmptyObject(iouReport) && ReportUtils.isInvoiceReport(iouReport)) || false; const shouldShowPaywithExpensifyOption = !shouldHidePaymentOptions && policy?.reimbursementChoice !== CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL; const shouldShowPayElsewhereOption = !shouldHidePaymentOptions && !isInvoiceReport; const paymentButtonOptions = useMemo(() => { + const lastPaymentMethod = nvpLastPaymentMethod?.[policyID] ?? '-1'; const buttonOptions = []; const isExpenseReport = ReportUtils.isExpenseReport(iouReport); const paymentMethods = { @@ -155,11 +156,12 @@ function SettlementButton({ return buttonOptions.sort((method) => (method.value === lastPaymentMethod ? -1 : 0)); } return buttonOptions; - // We don't want to reorder the options when the preferred payment method changes while the button is still visible except when the last payment method is not initialized yet. + // We don't want to reorder the options when the preferred payment method changes while the button is still visible except for component initialization when the last payment method is not initialized yet. // We need to be sure that onPress should be wrapped in an useCallback to prevent unnecessary updates // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ - lastPaymentMethodIsInitialized, + policyID, + nvpLastPaymentMethodIsInitialized, iouReport, translate, formattedAmount, From 3d543d6188119aab3337453ea7e8e8c19c197632 Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 18 Sep 2024 14:07:44 +0700 Subject: [PATCH 070/134] refactor: use useOnyx --- src/components/ActiveGuidesEventListener.tsx | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/components/ActiveGuidesEventListener.tsx b/src/components/ActiveGuidesEventListener.tsx index 1aab40eae7d0..2599576c9c23 100644 --- a/src/components/ActiveGuidesEventListener.tsx +++ b/src/components/ActiveGuidesEventListener.tsx @@ -1,17 +1,10 @@ import {useEffect, useRef} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import {subscribeToActiveGuides} from '@userActions/User'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {User} from '@src/types/onyx'; -type ActiveGuidesEventListenerOnyxProps = { - user: OnyxEntry; -}; - -type ActiveGuidesEventListenerProps = ActiveGuidesEventListenerOnyxProps; - -function ActiveGuidesEventListener({user}: ActiveGuidesEventListenerProps) { +function ActiveGuidesEventListener() { + const [user] = useOnyx(ONYXKEYS.USER); const didSubscribeToActiveGuides = useRef(false); useEffect(() => { if (didSubscribeToActiveGuides.current) { @@ -25,8 +18,4 @@ function ActiveGuidesEventListener({user}: ActiveGuidesEventListenerProps) { return null; } -export default withOnyx({ - user: { - key: ONYXKEYS.USER, - }, -})(ActiveGuidesEventListener); +export default ActiveGuidesEventListener; From f496a175d57d6195cad7f1007e03469e2ff8fbe8 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 16 Jul 2024 14:24:20 +0100 Subject: [PATCH 071/134] feat: debug mode for report and report actions --- src/CONST.ts | 8 + src/ROUTES.ts | 32 ++++ src/SCREENS.ts | 6 + src/components/AccountSwitcher.tsx | 10 ++ src/components/TabSelector/TabSelector.tsx | 8 + src/components/TestToolMenu.tsx | 9 ++ src/languages/en.ts | 7 + src/languages/es.ts | 1 + .../ModalStackNavigators/index.tsx | 8 + .../Navigators/RightModalNavigator.tsx | 4 + src/libs/Navigation/linkingConfig/config.ts | 40 +++++ src/libs/Navigation/types.ts | 15 ++ src/libs/actions/User.ts | 5 + src/pages/Debug/DebugDetails.tsx | 102 +++++++++++++ src/pages/Debug/DebugJSON.tsx | 20 +++ .../Report/DebugReportActionCreatePage.tsx | 140 ++++++++++++++++++ .../Debug/Report/DebugReportActionPage.tsx | 73 +++++++++ .../Debug/Report/DebugReportActionPreview.tsx | 35 +++++ src/pages/Debug/Report/DebugReportActions.tsx | 59 ++++++++ src/pages/Debug/Report/DebugReportPage.tsx | 66 +++++++++ src/pages/ProfilePage.tsx | 8 + .../BaseReportActionContextMenu.tsx | 1 + src/types/onyx/User.ts | 3 + 23 files changed, 660 insertions(+) create mode 100644 src/pages/Debug/DebugDetails.tsx create mode 100644 src/pages/Debug/DebugJSON.tsx create mode 100644 src/pages/Debug/Report/DebugReportActionCreatePage.tsx create mode 100644 src/pages/Debug/Report/DebugReportActionPage.tsx create mode 100644 src/pages/Debug/Report/DebugReportActionPreview.tsx create mode 100644 src/pages/Debug/Report/DebugReportActions.tsx create mode 100644 src/pages/Debug/Report/DebugReportPage.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 78b01d67b78e..554560b014d7 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4140,6 +4140,7 @@ const CONST = { CARD_AUTHENTICATION_REQUIRED: 'authentication_required', }, TAB: { + DEBUG_TAB_ID: 'DebugTab', NEW_CHAT_TAB_ID: 'NewChatTab', NEW_CHAT: 'chat', NEW_ROOM: 'room', @@ -5754,6 +5755,13 @@ const CONST = { CATEGORIES_ARTICLE_LINK: 'https://help.expensify.com/articles/expensify-classic/workspaces/Create-categories#import-custom-categories', TAGS_ARTICLE_LINK: 'https://help.expensify.com/articles/expensify-classic/workspaces/Create-tags#import-a-spreadsheet-1', }, + + DEBUG: { + DETAILS: 'details', + JSON: 'json', + REPORT_ACTIONS: 'actions', + REPORT_ACTION_PREVIEW: 'preview', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 04cc8a125fbb..d169e98e7a28 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1483,6 +1483,38 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/sage-intacct/advanced/payment-account', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/advanced/payment-account` as const, }, + DEBUG_REPORT: { + route: 'debug/report/:reportID', + getRoute: (reportID: string) => `debug/report/${reportID}`, + }, + DEBUG_REPORT_TAB_DETAILS: { + route: 'debug/report/:reportID/details', + getRoute: (reportID: string) => `debug/report/${reportID}/details`, + }, + DEBUG_REPORT_TAB_JSON: { + route: 'debug/report/:reportID/json', + getRoute: (reportID: string) => `debug/report/${reportID}/json`, + }, + DEBUG_REPORT_TAB_ACTIONS: { + route: 'debug/report/:reportID/actions', + getRoute: (reportID: string) => `debug/report/${reportID}/actions`, + }, + DEBUG_REPORT_ACTION: { + route: 'debug/report/:reportID/actions/:reportActionID', + getRoute: (reportID: string, reportActionID: string) => `debug/report/${reportID}/actions/${reportActionID}`, + }, + DEBUG_REPORT_ACTION_CREATE: { + route: 'debug/report/:reportID/actions/create', + getRoute: (reportID: string) => `debug/report/${reportID}/actions/create`, + }, + DEBUG_REPORT_ACTION_TAB_DETAILS: { + route: 'debug/report/:reportID/actions/:reportActionID/details', + getRoute: (reportID: string, reportActionID: string) => `debug/report/${reportID}/actions/${reportActionID}/details`, + }, + DEBUG_REPORT_ACTION_TAB_JSON: { + route: 'debug/report/:reportID/actions/:reportActionID/json', + getRoute: (reportID: string, reportActionID: string) => `debug/report/${reportID}/actions/${reportActionID}/json`, + }, } as const; /** diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 2369e231f519..418ae00280c8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -178,6 +178,7 @@ const SCREENS = { RESTRICTED_ACTION: 'RestrictedAction', REPORT_EXPORT: 'Report_Export', MISSING_PERSONAL_DETAILS: 'MissingPersonalDetails', + DEBUG: 'Debug', }, ONBOARDING_MODAL: { ONBOARDING: 'Onboarding', @@ -551,6 +552,11 @@ const SCREENS = { FEATURE_TRAINING_ROOT: 'FeatureTraining_Root', RESTRICTED_ACTION_ROOT: 'RestrictedAction_Root', MISSING_PERSONAL_DETAILS_ROOT: 'MissingPersonalDetails_Root', + DEBUG: { + REPORT: 'Debug_Report', + REPORT_ACTION: 'Debug_Report_Action', + REPORT_ACTION_CREATE: 'Debug_Report_Action_Create', + }, } as const; type Screen = DeepValueOf; diff --git a/src/components/AccountSwitcher.tsx b/src/components/AccountSwitcher.tsx index a9e223e56632..7e5f4dc156be 100644 --- a/src/components/AccountSwitcher.tsx +++ b/src/components/AccountSwitcher.tsx @@ -38,6 +38,8 @@ function AccountSwitcher() { const {canUseNewDotCopilot} = usePermissions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const [session] = useOnyx(ONYXKEYS.SESSION); + const [user] = useOnyx(ONYXKEYS.USER); const buttonRef = useRef(null); const [shouldShowDelegatorMenu, setShouldShowDelegatorMenu] = useState(false); @@ -166,6 +168,14 @@ function AccountSwitcher() { > {Str.removeSMSDomain(currentUserPersonalDetails?.login ?? '')} + {!!user?.isDebugModeEnabled && ( + + AccountID: {session?.accountID} + + )} diff --git a/src/components/TabSelector/TabSelector.tsx b/src/components/TabSelector/TabSelector.tsx index 7fca66b5f8c7..1bf753cd4aa4 100644 --- a/src/components/TabSelector/TabSelector.tsx +++ b/src/components/TabSelector/TabSelector.tsx @@ -27,6 +27,14 @@ type IconAndTitle = { function getIconAndTitle(route: string, translate: LocaleContextProps['translate']): IconAndTitle { switch (route) { + case CONST.DEBUG.DETAILS: + return {icon: Expensicons.Info, title: translate('debug.details')}; + case CONST.DEBUG.JSON: + return {icon: Expensicons.Eye, title: translate('debug.JSON')}; + case CONST.DEBUG.REPORT_ACTIONS: + return {icon: Expensicons.Document, title: translate('debug.reportActions')}; + case CONST.DEBUG.REPORT_ACTION_PREVIEW: + return {icon: Expensicons.Document, title: translate('debug.reportActionPreview')}; case CONST.TAB_REQUEST.MANUAL: return {icon: Expensicons.Pencil, title: translate('tabSelector.manual')}; case CONST.TAB_REQUEST.SCAN: diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 2c553386aff0..ce044b4c6198 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -41,6 +41,15 @@ function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) { > {translate('initialSettingsPage.troubleshoot.testingPreferences')} + {/* When toggled the app will be put into debug mode. */} + + User.setIsDebugModeEnabled(!user.isDebugModeEnabled)} + /> + + {/* Option to switch between staging and default api endpoints. This enables QA, internal testers and external devs to take advantage of sandbox environments for 3rd party services like Plaid and Onfido. This toggle is not rendered for internal devs as they make environment changes directly to the .env file. */} diff --git a/src/languages/en.ts b/src/languages/en.ts index a7ddea880161..bdd2990b299b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1102,6 +1102,7 @@ export default { maskExportOnyxStateData: 'Mask fragile user data while exporting Onyx state', exportOnyxState: 'Export Onyx state', testCrash: 'Test crash', + debugMode: 'Debug mode', }, debugConsole: { saveLog: 'Save log', @@ -4797,4 +4798,10 @@ export default { notAllowedMessageHyperLinked: ' limited access', notAllowedMessageEnd: ' copilot', }, + debug: { + details: 'Details', + JSON: 'JSON', + reportActions: 'Actions', + reportActionPreview: 'Preview', + }, } satisfies TranslationBase; diff --git a/src/languages/es.ts b/src/languages/es.ts index a8481ec305fc..6e3a7bb23a63 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1105,6 +1105,7 @@ export default { maskExportOnyxStateData: 'Enmascare los datos frágiles del usuario mientras exporta el estado Onyx', exportOnyxState: 'Exportar estado Onyx', testCrash: 'Prueba de fallo', + debugMode: 'Modo depuración', }, debugConsole: { saveLog: 'Guardar registro', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index b4b676bbdec5..8635adde6456 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -4,6 +4,7 @@ import {createStackNavigator} from '@react-navigation/stack'; import React from 'react'; import type { AddPersonalBankAccountNavigatorParamList, + DebugParamList, EditRequestNavigatorParamList, EnablePaymentsNavigatorParamList, FlagCommentNavigatorParamList, @@ -572,6 +573,12 @@ const MissingPersonalDetailsModalStackNavigator = createModalStackNavigator require('../../../../pages/MissingPersonalDetails').default, }); +const DebugModalStackNavigator = createModalStackNavigator({ + [SCREENS.DEBUG.REPORT]: () => require('../../../../pages/debug/Report/DebugReportPage').default, + [SCREENS.DEBUG.REPORT_ACTION]: () => require('../../../../pages/debug/Report/DebugReportActionPage').default, + [SCREENS.DEBUG.REPORT_ACTION_CREATE]: () => require('../../../../pages/debug/Report/DebugReportActionCreatePage').default, +}); + export { AddPersonalBankAccountModalStackNavigator, EditRequestStackNavigator, @@ -604,4 +611,5 @@ export { SearchAdvancedFiltersModalStackNavigator, SearchSavedSearchModalStackNavigator, MissingPersonalDetailsModalStackNavigator, + DebugModalStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index 481df0b1c9ad..cd6a93165990 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -83,6 +83,10 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { name={SCREENS.RIGHT_MODAL.PROFILE} component={ModalStackNavigators.ProfileModalStackNavigator} /> + ['config'] = { [SCREENS.RIGHT_MODAL.MISSING_PERSONAL_DETAILS]: { screens: { [SCREENS.MISSING_PERSONAL_DETAILS_ROOT]: ROUTES.MISSING_PERSONAL_DETAILS.route, + }, + }, + [SCREENS.RIGHT_MODAL.DEBUG]: { + screens: { + [SCREENS.DEBUG.REPORT]: { + path: ROUTES.DEBUG_REPORT.route, + exact: true, + screens: { + details: { + path: ROUTES.DEBUG_REPORT_TAB_DETAILS.route, + exact: true, + }, + json: { + path: ROUTES.DEBUG_REPORT_TAB_JSON.route, + exact: true, + }, + actions: { + path: ROUTES.DEBUG_REPORT_TAB_ACTIONS.route, + exact: true, + }, + }, + }, + [SCREENS.DEBUG.REPORT_ACTION]: { + path: ROUTES.DEBUG_REPORT_ACTION.route, + exact: true, + screens: { + details: { + path: ROUTES.DEBUG_REPORT_ACTION_TAB_DETAILS.route, + exact: true, + }, + json: { + path: ROUTES.DEBUG_REPORT_ACTION_TAB_JSON.route, + exact: true, + }, + }, + }, + [SCREENS.DEBUG.REPORT_ACTION_CREATE]: { + path: ROUTES.DEBUG_REPORT_ACTION_CREATE.route, + exact: true, + }, }, }, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 9f76814740d3..4bc524bc4b90 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1190,6 +1190,7 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.SEARCH_ADVANCED_FILTERS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.SEARCH_SAVED_SEARCH]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.MISSING_PERSONAL_DETAILS]: NavigatorScreenParams; + [SCREENS.RIGHT_MODAL.DEBUG]: NavigatorScreenParams; }; type TravelNavigatorParamList = { @@ -1429,6 +1430,19 @@ type MissingPersonalDetailsParamList = { }; }; +type DebugParamList = { + [SCREENS.DEBUG.REPORT]: { + reportID: string; + }; + [SCREENS.DEBUG.REPORT_ACTION]: { + reportID: string; + reportActionID: string; + }; + [SCREENS.DEBUG.REPORT_ACTION_CREATE]: { + reportID: string; + }; +}; + type RootStackParamList = PublicScreensParamList & AuthScreensParamList & LeftModalNavigatorParamList; type BottomTabName = keyof BottomTabNavigatorParamList; @@ -1502,4 +1516,5 @@ export type { SearchSavedSearchParamList, RestrictedActionParamList, MissingPersonalDetailsParamList, + DebugParamList, }; diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 4f85bbdcd139..eb78da49cdda 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -1327,6 +1327,10 @@ function requestRefund() { API.write(WRITE_COMMANDS.REQUEST_REFUND, null); } +function setIsDebugModeEnabled(isDebugModeEnabled: boolean) { + Onyx.merge(ONYXKEYS.USER, {isDebugModeEnabled}); +} + export { clearFocusModeNotification, closeAccount, @@ -1366,4 +1370,5 @@ export { addPendingContactMethod, clearValidateCodeActionError, dismissGBRTooltip, + setIsDebugModeEnabled, }; diff --git a/src/pages/Debug/DebugDetails.tsx b/src/pages/Debug/DebugDetails.tsx new file mode 100644 index 000000000000..ab786acc5b6c --- /dev/null +++ b/src/pages/Debug/DebugDetails.tsx @@ -0,0 +1,102 @@ +import React, {useState} from 'react'; +import type {OnyxEntry, OnyxKey} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import Button from '@components/Button'; +import ScrollView from '@components/ScrollView'; +import TextInput from '@components/TextInput'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {OnyxValues, OnyxKey as OnyxValuesKey} from '@src/ONYXKEYS'; + +type DebugDetailsProps = { + data: OnyxEntry; + onyxKey: OnyxKey; + isCollection?: boolean; + idSelector?: (data: OnyxEntry>) => string; +}; + +function DebugDetails({data, onyxKey, isCollection = false, idSelector = () => ''}: DebugDetailsProps) { + const styles = useThemeStyles(); + const [draftData, setDraftData] = useState>(data); + const [errors, setErrors] = useState>({}); + const [hasChanges, setHasChanges] = useState(false); + return ( + + {Object.entries(data ?? {}).map(([key, value]) => ( + { + setDraftData((currentDraftData) => { + if (typeof currentDraftData === 'object') { + return {...currentDraftData, [key]: updatedValue}; + } + return updatedValue; + }); + setHasChanges(true); + }} + /> + ))} +