diff --git a/src/CONST.ts b/src/CONST.ts index c316a2327cc8..fb481eb0724f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1926,6 +1926,15 @@ const CONST = { MONTHLY: 'monthly', FIXED: 'fixed', }, + STEP_NAMES: ['1', '2', '3', '4', '5', '6'], + STEP: { + ASSIGNEE: 'Assignee', + CARD_TYPE: 'CardType', + LIMIT_TYPE: 'LimitType', + LIMIT: 'Limit', + CARD_NAME: 'CardName', + CONFIRMATION: 'Confirmation', + }, }, AVATAR_ROW_SIZE: { DEFAULT: 4, diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index a15ae54cedea..dac39249044e 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -353,6 +353,9 @@ const ONYXKEYS = { /** Stores info during review duplicates flow */ REVIEW_DUPLICATES: 'reviewDuplicates', + /** Stores the information about the state of issuing a new card */ + ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -521,6 +524,8 @@ const ONYXKEYS = { NEW_CHAT_NAME_FORM_DRAFT: 'newChatNameFormDraft', SUBSCRIPTION_SIZE_FORM: 'subscriptionSizeForm', SUBSCRIPTION_SIZE_FORM_DRAFT: 'subscriptionSizeFormDraft', + ISSUE_NEW_EXPENSIFY_CARD_FORM: 'issueNewExpensifyCardForm', + ISSUE_NEW_EXPENSIFY_CARD_FORM_DRAFT: 'issueNewExpensifyCardFormDraft', }, } as const; @@ -581,6 +586,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm; [ONYXKEYS.FORMS.NEW_CHAT_NAME_FORM]: FormTypes.NewChatNameForm; [ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM]: FormTypes.SubscriptionSizeForm; + [ONYXKEYS.FORMS.ISSUE_NEW_EXPENSIFY_CARD_FORM]: FormTypes.IssueNewExpensifyCardForm; }; type OnyxFormDraftValuesMapping = { @@ -743,6 +749,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE]: OnyxTypes.QuickAction; [ONYXKEYS.NVP_TRAVEL_SETTINGS]: OnyxTypes.TravelSettings; [ONYXKEYS.REVIEW_DUPLICATES]: OnyxTypes.ReviewDuplicates; + [ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard; [ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: string; [ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL]: string; [ONYXKEYS.NVP_BILLING_FUND_ID]: number; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c4afee12952e..85792fcff97f 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -783,6 +783,17 @@ const ROUTES = { route: 'settings/workspaces/:policyID/reportFields', getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields` as const, }, + // TODO: uncomment after development is done + // WORKSPACE_EXPENSIFY_CARD: { + // route: 'settings/workspaces/:policyID/expensify-card', + // getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const, + // }, + // WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW: { + // route: 'settings/workspaces/:policyID/expensify-card/issues-new', + // getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/issue-new` as const, + // }, + // TODO: remove after development is done - this one is for testing purposes + WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW: 'settings/workspaces/expensify-card/issue-new', WORKSPACE_DISTANCE_RATES: { route: 'settings/workspaces/:policyID/distance-rates', getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 5ef7f2693512..e277e88b338e 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -281,6 +281,8 @@ const SCREENS = { RATE_AND_UNIT: 'Workspace_RateAndUnit', RATE_AND_UNIT_RATE: 'Workspace_RateAndUnit_Rate', RATE_AND_UNIT_UNIT: 'Workspace_RateAndUnit_Unit', + EXPENSIFY_CARD: 'Workspace_ExpensifyCard', + EXPENSIFY_CARD_ISSUE_NEW: 'Workspace_ExpensifyCard_New', BILLS: 'Workspace_Bills', INVOICES: 'Workspace_Invoices', TRAVEL: 'Workspace_Travel', diff --git a/src/languages/en.ts b/src/languages/en.ts index d72681701814..466e491bcefa 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2408,6 +2408,34 @@ export default { benefit4: 'Customizable limits and spend controls', addWorkEmail: 'Add work email address', checkingDomain: "Hang tight! We're still working on enabling your Expensify Cards. Check back here in a few minutes.", + issueCard: 'Issue card', + issueNewCard: { + whoNeedsCard: 'Who needs a card?', + findMember: 'Find member', + chooseCardType: 'Choose a card type', + physicalCard: 'Physical card', + physicalCardDescription: 'Great for the frequent spender', + virtualCard: 'Virtual card', + virtualCardDescription: 'Instant and flexible', + chooseLimitType: 'Choose a limit type', + smartLimit: 'Smart Limit', + smartLimitDescription: 'Spend up to a certain amount before requiring approval', + monthly: 'Monthly', + monthlyDescription: 'Spend up to a certain amount per month', + fixedAmount: 'Fixed amount', + fixedAmountDescription: 'Spend up to a certain amount once', + setLimit: 'Set a limit', + giveItName: 'Give it a name', + giveItNameInstruction: 'Make it unique enough to tell apart from the other. Specific use cases are even better!', + cardName: 'Card name', + letsDoubleCheck: 'Let’s double check that everything looks right.', + willBeReady: 'This card will be ready to use immediately.', + cardholder: 'Cardholder', + cardType: 'Card type', + limit: 'Limit', + limitType: 'Limit type', + name: 'Name', + }, }, reimburse: { captureReceipts: 'Capture receipts', diff --git a/src/languages/es.ts b/src/languages/es.ts index a53f773795b1..da9ff55eec5d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2603,6 +2603,34 @@ export default { benefit4: 'Límites personalizables', addWorkEmail: 'Añadir correo electrónico de trabajo', checkingDomain: '¡Un momento! Estamos todavía trabajando para habilitar tu Tarjeta Expensify. Vuelve aquí en unos minutos.', + issueCard: 'Emitir tarjeta', + issueNewCard: { + whoNeedsCard: '¿Quién necesita una tarjeta?', + findMember: 'Buscar miembro', + chooseCardType: 'Elegir un tipo de tarjeta', + physicalCard: 'Tarjeta física', + physicalCardDescription: 'Ideal para los consumidores habituales', + virtualCard: 'Tarjeta virtual', + virtualCardDescription: 'Instantáneo y flexible', + chooseLimitType: 'Elegir un tipo de límite', + smartLimit: 'Límite inteligente', + smartLimitDescription: 'Gasta hasta una determinada cantidad antes de requerir aprobación', + monthly: 'Mensual', + monthlyDescription: 'Gasta hasta una determinada cantidad al mes', + fixedAmount: 'Cantidad fija', + fixedAmountDescription: 'Gasta hasta una determinada cantidad una vez', + setLimit: 'Establecer un límite', + giveItName: 'Dale un nombre', + giveItNameInstruction: 'Hazlo lo suficientemente único como para distinguirlo de los demás. Los casos de uso específicos son aún mejores.', + cardName: 'Nombre de la tarjeta', + letsDoubleCheck: 'Vuelve a comprobar que todo parece correcto. ', + willBeReady: 'Esta tarjeta estará lista para su uso inmediato.', + cardholder: 'Titular de la tarjeta', + cardType: 'Tipo de tarjeta', + limit: 'Limite', + limitType: 'Tipo de limite', + name: 'Nombre', + }, }, reimburse: { captureReceipts: 'Captura recibos', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 6e9782defe4f..359da0a954d1 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -325,6 +325,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/taxes/NamePage').default, [SCREENS.WORKSPACE.TAX_VALUE]: () => require('../../../../pages/workspace/taxes/ValuePage').default, [SCREENS.WORKSPACE.TAX_CREATE]: () => require('../../../../pages/workspace/taxes/WorkspaceCreateTaxPage').default, + [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: () => require('../../../../pages/workspace/card/issueNew/IssueNewCardPage').default, [SCREENS.SETTINGS.SAVE_THE_WORLD]: () => require('../../../../pages/TeachersUnite/SaveTheWorldPage').default, [SCREENS.SETTINGS.SUBSCRIPTION.CHANGE_PAYMENT_CURRENCY]: () => require('../../../../pages/settings/PaymentCard/ChangeCurrency').default, [SCREENS.SETTINGS.SUBSCRIPTION.CHANGE_BILLING_CURRENCY]: () => require('../../../../pages/settings/Subscription/PaymentCard/ChangeBillingCurrency').default, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 49dcd6f8b246..bd02508e509f 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -366,6 +366,13 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.SHARE]: { path: ROUTES.WORKSPACE_PROFILE_SHARE.route, }, + // TODO: uncomment after development + // [SCREENS.WORKSPACE.EXPENSIFY_CARD]: { + // path: ROUTES.WORKSPACE_EXPENSIFY_CARD, + // }, + [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: { + path: ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW, + }, [SCREENS.WORKSPACE.RATE_AND_UNIT]: { path: ROUTES.WORKSPACE_RATE_AND_UNIT.route, }, diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index 9a011d88e582..aea952618071 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -5,7 +5,7 @@ import type {ActivatePhysicalExpensifyCardParams, ReportVirtualExpensifyCardFrau import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ExpensifyCardDetails} from '@src/types/onyx/Card'; +import type {ExpensifyCardDetails, IssueNewCardStep} from '@src/types/onyx/Card'; type ReplacementReason = 'damaged' | 'stolen'; @@ -44,7 +44,11 @@ function reportVirtualExpensifyCardFraud(cardID: number) { cardID, }; - API.write(WRITE_COMMANDS.REPORT_VIRTUAL_EXPENSIFY_CARD_FRAUD, parameters, {optimisticData, successData, failureData}); + API.write(WRITE_COMMANDS.REPORT_VIRTUAL_EXPENSIFY_CARD_FRAUD, parameters, { + optimisticData, + successData, + failureData, + }); } /** @@ -89,7 +93,11 @@ function requestReplacementExpensifyCard(cardID: number, reason: ReplacementReas reason, }; - API.write(WRITE_COMMANDS.REQUEST_REPLACEMENT_EXPENSIFY_CARD, parameters, {optimisticData, successData, failureData}); + API.write(WRITE_COMMANDS.REQUEST_REPLACEMENT_EXPENSIFY_CARD, parameters, { + optimisticData, + successData, + failureData, + }); } /** @@ -177,5 +185,9 @@ function revealVirtualCardDetails(cardID: number): Promise }); } -export {requestReplacementExpensifyCard, activatePhysicalExpensifyCard, clearCardListErrors, reportVirtualExpensifyCardFraud, revealVirtualCardDetails}; +function setIssueNewCardStep(step: IssueNewCardStep | null) { + Onyx.merge(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD, {currentStep: step}); +} + +export {requestReplacementExpensifyCard, activatePhysicalExpensifyCard, clearCardListErrors, reportVirtualExpensifyCardFraud, revealVirtualCardDetails, setIssueNewCardStep}; export type {ReplacementReason}; diff --git a/src/pages/workspace/card/issueNew/AssigneeStep.tsx b/src/pages/workspace/card/issueNew/AssigneeStep.tsx new file mode 100644 index 000000000000..5012ba294518 --- /dev/null +++ b/src/pages/workspace/card/issueNew/AssigneeStep.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@navigation/Navigation'; +import * as Card from '@userActions/Card'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function AssigneeStep() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const submit = () => { + // TODO: the logic will be created in https://github.com/Expensify/App/issues/44309 + Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.CARD_TYPE); + }; + + const handleBackButtonPress = () => { + Navigation.goBack(); + }; + + return ( + + + + + + {translate('workspace.card.issueNewCard.whoNeedsCard')} + + {/* TODO: the content will be created in https://github.com/Expensify/App/issues/44309 */} + + + + ); +} + +AssigneeStep.displayName = 'AssigneeStep'; + +export default AssigneeStep; diff --git a/src/pages/workspace/card/issueNew/CardNameStep.tsx b/src/pages/workspace/card/issueNew/CardNameStep.tsx new file mode 100644 index 000000000000..9b48d6417732 --- /dev/null +++ b/src/pages/workspace/card/issueNew/CardNameStep.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Card from '@userActions/Card'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function CardNameStep() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const submit = () => { + // TODO: the logic will be created in https://github.com/Expensify/App/issues/44309 + Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.CONFIRMATION); + }; + + const handleBackButtonPress = () => { + Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.LIMIT); + }; + + return ( + + + + + + {translate('workspace.card.issueNewCard.giveItName')} + + {/* TODO: the content will be created in https://github.com/Expensify/App/issues/44309 */} + + + + ); +} + +CardNameStep.displayName = 'CardNameStep'; + +export default CardNameStep; diff --git a/src/pages/workspace/card/issueNew/CardTypeStep.tsx b/src/pages/workspace/card/issueNew/CardTypeStep.tsx new file mode 100644 index 000000000000..93b99f51d239 --- /dev/null +++ b/src/pages/workspace/card/issueNew/CardTypeStep.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Card from '@userActions/Card'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function CardTypeStep() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const submit = () => { + // TODO: the logic will be created in https://github.com/Expensify/App/issues/44309 + Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE); + }; + + const handleBackButtonPress = () => { + Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.ASSIGNEE); + }; + + return ( + + + + + + {translate('workspace.card.issueNewCard.chooseCardType')} + + {/* TODO: the content will be created in https://github.com/Expensify/App/issues/44309 */} + + + + ); +} + +CardTypeStep.displayName = 'CardTypeStep'; + +export default CardTypeStep; diff --git a/src/pages/workspace/card/issueNew/ConfirmationStep.tsx b/src/pages/workspace/card/issueNew/ConfirmationStep.tsx new file mode 100644 index 000000000000..a64d6f463531 --- /dev/null +++ b/src/pages/workspace/card/issueNew/ConfirmationStep.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@navigation/Navigation'; +import * as Card from '@userActions/Card'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +function ConfirmationStep() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {isOffline} = useNetwork(); + + const submit = () => { + // TODO: the logic will be created in https://github.com/Expensify/App/issues/44309 + Navigation.navigate(ROUTES.SETTINGS); + }; + + const handleBackButtonPress = () => { + Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.CARD_NAME); + }; + + return ( + + + + + + {translate('workspace.card.issueNewCard.letsDoubleCheck')} + + {/* TODO: the content will be created in https://github.com/Expensify/App/issues/44309 */} +