diff --git a/src/CONST.ts b/src/CONST.ts index aa3ade14b040..32c500962d8a 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -574,6 +574,7 @@ const CONST = { ADD_SECONDARY_LOGIN_URL: encodeURI('settings?param={"section":"account","openModal":"secondaryLogin"}'), MANAGE_CARDS_URL: 'domain_companycards', FEES_URL: `${USE_EXPENSIFY_URL}/fees`, + SAVE_WITH_EXPENSIFY_URL: `${USE_EXPENSIFY_URL}/savings-calculator`, CFPB_PREPAID_URL: 'https://cfpb.gov/prepaid', STAGING_NEW_EXPENSIFY_URL: 'https://staging.new.expensify.com', NEWHELP_URL: 'https://help.expensify.com', @@ -1897,6 +1898,12 @@ const CONST = { COMPACT: 'compact', DEFAULT: 'default', }, + SUBSCRIPTION: { + TYPE: { + ANNUAL: 'yearly2018', + PAYPERUSE: 'monthly2018', + }, + }, REGEX: { SPECIAL_CHARS_WITHOUT_NEWLINE: /((?!\n)[()-\s\t])/g, DIGITS_AND_PLUS: /^\+?[0-9]*$/, diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 145be07fae0c..f8600ed9c9ee 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -154,6 +154,9 @@ const ONYXKEYS = { /** Whether the user has been shown the hold educational interstitial yet */ NVP_HOLD_USE_EXPLAINED: 'holdUseExplained', + /** Store the state of the subscription */ + NVP_PRIVATE_SUBSCRIPTION: 'nvp_private_subscription', + /** Store preferred skintone for emoji */ PREFERRED_EMOJI_SKIN_TONE: 'nvp_expensify_preferredEmojiSkinTone', @@ -643,6 +646,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_ACTIVE_POLICY_ID]: string; [ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS]: OnyxTypes.DismissedReferralBanners; [ONYXKEYS.NVP_HAS_SEEN_TRACK_TRAINING]: boolean; + [ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION]: OnyxTypes.PrivateSubscription; [ONYXKEYS.USER_WALLET]: OnyxTypes.UserWallet; [ONYXKEYS.WALLET_ONFIDO]: OnyxTypes.WalletOnfido; [ONYXKEYS.WALLET_ADDITIONAL_DETAILS]: OnyxTypes.WalletAdditionalDetails; diff --git a/src/hooks/useSubscriptionPlan.ts b/src/hooks/useSubscriptionPlan.ts new file mode 100644 index 000000000000..5ac5066ba965 --- /dev/null +++ b/src/hooks/useSubscriptionPlan.ts @@ -0,0 +1,32 @@ +import {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; +import {isPolicyOwner} from '@libs/PolicyUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +function useSubscriptionPlan() { + const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); + 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], + ); + + if (isEmptyObject(ownerPolicies)) { + return null; + } + + // Check if user has corporate (control) workspace + const hasControlWorkspace = ownerPolicies.some((policy) => policy?.type === CONST.POLICY.TYPE.CORPORATE); + + // Corporate (control) workspace is supposed to be the higher priority + return hasControlWorkspace ? CONST.POLICY.TYPE.CORPORATE : CONST.POLICY.TYPE.TEAM; +} + +export default useSubscriptionPlan; diff --git a/src/languages/en.ts b/src/languages/en.ts index fbdaaeab9903..c69531a7ab13 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3162,6 +3162,35 @@ export default { mergedWithCashTransaction: 'matched a receipt to this transaction.', }, subscription: { + yourPlan: { + title: 'Your plan', + collect: { + title: 'Collect', + priceAnnual: 'From $5/active member with the Expensify Card, $10/active member without the Expensify Card.', + pricePayPerUse: 'From $10/active member with the Expensify Card, $20/active member without the Expensify Card.', + benefit1: 'Unlimited SmartScans and distance tracking', + benefit2: 'Expensify Cards with Smart Limits', + benefit3: 'Bill pay and invoicing', + benefit4: 'Expense approvals', + benefit5: 'ACH reimbursement', + benefit6: 'QuickBooks and Xero integrations', + benefit7: 'Custom insights and reporting', + }, + control: { + title: 'Control', + priceAnnual: 'From $9/active member with the Expensify Card, $18/active member without the Expensify Card.', + pricePayPerUse: 'From $18/active member with the Expensify Card, $36/active member without the Expensify Card.', + benefit1: 'Everything in Collect, plus:', + benefit2: 'NetSuite and Sage Intacct integrations', + benefit3: 'Certinia and Workday sync', + benefit4: 'Multiple expense approvers', + benefit5: 'SAML/SSO', + benefit6: 'Budgeting', + }, + saveWithExpensifyTitle: 'Save with the Expensify Card', + saveWithExpensifyDescription: 'Use our savings calculator to see how cash back from the Expensify Card can reduce your Expensify bill.', + saveWithExpensifyButton: 'Learn more', + }, subscriptionSize: { title: 'Subscription size', yourSize: 'Your subscription size is the number of open seats that can be filled by any active member in a given month.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 033291d5bba2..3e8a4a00d4f2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3668,6 +3668,35 @@ export default { mergedWithCashTransaction: 'encontró un recibo para esta transacción.', }, subscription: { + yourPlan: { + title: 'Tu plan', + collect: { + title: 'Recolectar', + priceAnnual: 'Desde $5/miembro activo con la Tarjeta Expensify, $10/miembro activo sin la Tarjeta Expensify.', + pricePayPerUse: 'Desde $10/miembro activo con la Tarjeta Expensify, $20/miembro activo sin la Tarjeta Expensify.', + benefit1: 'SmartScans ilimitados y seguimiento de la distancia', + benefit2: 'Tarjetas Expensify con Límites Inteligentes', + benefit3: 'Pago de facturas y facturación', + benefit4: 'Aprobación de gastos', + benefit5: 'Reembolso ACH', + benefit6: 'Integraciones con QuickBooks y Xero', + benefit7: 'Reportes e informes personalizados', + }, + control: { + title: 'Control', + priceAnnual: 'Desde $9/miembro activo con la Tarjeta Expensify, $18/miembro activo sin la Tarjeta Expensify.', + pricePayPerUse: 'Desde $18/miembro activo con la Tarjeta Expensify, $36/miembro activo sin la Tarjeta Expensify.', + benefit1: 'Todo en Recolectar, más:', + benefit2: 'Integraciones con NetSuite y Sage Intacct', + benefit3: 'Sincronización de Certinia y Workday', + benefit4: 'Varios aprobadores de gastos', + benefit5: 'SAML/SSO', + benefit6: 'Presupuestos', + }, + saveWithExpensifyTitle: 'Ahorra con la Tarjeta Expensify', + saveWithExpensifyDescription: 'Utiliza nuestra calculadora de ahorro para ver cómo el reembolso en efectivo de la Tarjeta Expensify puede reducir tu factura de Expensify', + saveWithExpensifyButton: 'Más información', + }, subscriptionSize: { title: 'Tamaño de suscripción', yourSize: 'El tamaño de tu suscripción es el número de plazas abiertas que puede ocupar cualquier miembro activo en un mes determinado.', diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 9b77a9b74aa4..c91ea4475403 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -481,6 +481,7 @@ const READ_COMMANDS = { OPEN_POLICY_MORE_FEATURES_PAGE: 'OpenPolicyMoreFeaturesPage', OPEN_POLICY_ACCOUNTING_PAGE: 'OpenPolicyAccountingPage', SEARCH: 'Search', + OPEN_SUBSCRIPTION_PAGE: 'OpenSubscriptionPage', } as const; type ReadCommand = ValueOf; @@ -528,6 +529,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE]: Parameters.OpenPolicyMoreFeaturesPageParams; [READ_COMMANDS.OPEN_POLICY_ACCOUNTING_PAGE]: Parameters.OpenPolicyAccountingPageParams; [READ_COMMANDS.SEARCH]: Parameters.SearchParams; + [READ_COMMANDS.OPEN_SUBSCRIPTION_PAGE]: EmptyObject; }; const SIDE_EFFECT_REQUEST_COMMANDS = { diff --git a/src/libs/actions/Subscription.ts b/src/libs/actions/Subscription.ts new file mode 100644 index 000000000000..f28c395f1b40 --- /dev/null +++ b/src/libs/actions/Subscription.ts @@ -0,0 +1,14 @@ +import * as API from '@libs/API'; +import {READ_COMMANDS} from '@libs/API/types'; + +/** + * Fetches data when the user opens the SubscriptionSettingsPage + */ +function openSubscriptionPage() { + API.read(READ_COMMANDS.OPEN_SUBSCRIPTION_PAGE, {}); +} + +export { + // eslint-disable-next-line import/prefer-default-export + openSubscriptionPage, +}; diff --git a/src/pages/settings/Subscription/SaveWithExpensifyButton/index.native.tsx b/src/pages/settings/Subscription/SaveWithExpensifyButton/index.native.tsx new file mode 100644 index 000000000000..be12d66a4f9a --- /dev/null +++ b/src/pages/settings/Subscription/SaveWithExpensifyButton/index.native.tsx @@ -0,0 +1,6 @@ +/** Return null because this button is not supposed to be rendered in the native apps */ +function SaveWithExpensifyButton() { + return null; +} + +export default SaveWithExpensifyButton; diff --git a/src/pages/settings/Subscription/SaveWithExpensifyButton/index.tsx b/src/pages/settings/Subscription/SaveWithExpensifyButton/index.tsx new file mode 100644 index 000000000000..f6aff02c801c --- /dev/null +++ b/src/pages/settings/Subscription/SaveWithExpensifyButton/index.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {Linking} from 'react-native'; +import Button from '@components/Button'; +import useLocalize from '@hooks/useLocalize'; +import CONST from '@src/CONST'; + +function SaveWithExpensifyButton() { + const {translate} = useLocalize(); + + const onLinkPress = () => { + Linking.openURL(CONST.SAVE_WITH_EXPENSIFY_URL); + }; + + return ( +