diff --git a/amplify/team-provider-info.json b/amplify/team-provider-info.json index 08a9c4ebabc..cb29ced583f 100644 --- a/amplify/team-provider-info.json +++ b/amplify/team-provider-info.json @@ -1,4 +1,3 @@ { - "dev": { - } + "dev": {} } \ No newline at end of file diff --git a/src/components/frame/LoadingTemplate/LoadingTemplate.module.css b/src/components/frame/LoadingTemplate/LoadingTemplate.module.css index 700156cc877..058c55579f5 100644 --- a/src/components/frame/LoadingTemplate/LoadingTemplate.module.css +++ b/src/components/frame/LoadingTemplate/LoadingTemplate.module.css @@ -2,6 +2,7 @@ composes: stretchVertical stretchHorizontal from '~styles/layout.css'; position: absolute; top: 0; + left: 0; overflow: auto; background-color: var(--color-base-white); } diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/AutomaticDeposits/AutomaticDeposits.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/AutomaticDeposits/AutomaticDeposits.tsx index d9886030daf..0734871eab4 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/AutomaticDeposits/AutomaticDeposits.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/AutomaticDeposits/AutomaticDeposits.tsx @@ -21,7 +21,7 @@ const BodyDescription = () => ( {'. '} {formatText({ id: 'navigation.learnMore' })} diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/AutomaticDeposits/consts.ts b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/AutomaticDeposits/consts.ts index c1b971c237d..72629c114a3 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/AutomaticDeposits/consts.ts +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/AutomaticDeposits/consts.ts @@ -81,7 +81,7 @@ export const HEADING_MSG = defineMessages({ }, headingAccessory: { id: `${displayName}.headingAccessory`, - defaultMessage: 'optional', + defaultMessage: 'Optional', }, }); diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetails/partials/BankDetailsDescriptionComponent/BankDetailsDescriptionComponent.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetails/partials/BankDetailsDescriptionComponent/BankDetailsDescriptionComponent.tsx index 8a3493dc34b..008bd87535b 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetails/partials/BankDetailsDescriptionComponent/BankDetailsDescriptionComponent.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetails/partials/BankDetailsDescriptionComponent/BankDetailsDescriptionComponent.tsx @@ -2,9 +2,11 @@ import React from 'react'; import { defineMessages } from 'react-intl'; import LoadingSkeleton from '~common/LoadingSkeleton/LoadingSkeleton.tsx'; -import { type CheckKycStatusMutation } from '~gql'; +import { type SupportedCurrencies, type CheckKycStatusMutation } from '~gql'; import { formatMessage } from '~utils/yup/tests/helpers.ts'; +import { CurrencyLabel } from '../../../CurrencyLabel.tsx'; + import { TABLE_TD_LOADER_STYLES } from './consts.ts'; const displayName = @@ -15,10 +17,14 @@ const MSG = defineMessages({ id: `${displayName}.componentTitle`, defaultMessage: 'Bank, address and currency information', }, - tableTitle: { - id: `${displayName}.tableTitle`, + tableBankDetailsTitle: { + id: `${displayName}.tableBankDetailsTitle`, defaultMessage: 'Bank details', }, + tableCurrencyTitle: { + id: `${displayName}.tableCurrencyTitle`, + defaultMessage: 'Currency', + }, columnHeadingBankName: { id: `${displayName}.columnHeadingBankName`, defaultMessage: 'Bank name', @@ -50,64 +56,80 @@ const BankDetailsDescriptionComponent = ({ }: BankDetailsDescriptionComponentProps) => { return (
-

{formatMessage(MSG.componentTitle)}

-

{formatMessage(MSG.tableTitle)}

- - - - - - - - - - - - - - - - - -
- {formatMessage(MSG.columnHeadingBankName)} - - {formatMessage(MSG.columnHeadingAccountNumber)} - {formatMessage(MSG.columnHeadingBic)} - {formatMessage(MSG.columnHeadingPayoutCurrency)} -
- - {bankAccount?.bankName ?? '-'} - - - - {bankAccount?.usAccount?.last4 ?? - bankAccount?.iban?.last4 ?? - '-'} - - - - {bankAccount?.usAccount?.routingNumber ?? - bankAccount?.iban?.bic ?? - '-'} - - - - {bankAccount?.currency ?? ''} - -
+

+ {formatMessage(MSG.componentTitle)} +

+
+
+ {formatMessage(MSG.tableBankDetailsTitle)} +
+
+ {formatMessage(MSG.tableCurrencyTitle)} +
+ +
+ {formatMessage(MSG.columnHeadingBankName)} +
+ +
+ {formatMessage(MSG.columnHeadingAccountNumber)} +
+ +
+ {formatMessage(MSG.columnHeadingBic)} +
+ +
+ {formatMessage(MSG.columnHeadingPayoutCurrency)} +
+ +
+ + {bankAccount?.bankName ?? '-'} + +
+ +
+ + {bankAccount?.usAccount?.last4 ?? bankAccount?.iban?.last4 ?? '-'} + +
+ +
+ + {bankAccount?.usAccount?.routingNumber ?? + bankAccount?.iban?.bic ?? + '-'} + +
+ +
+ + {bankAccount?.currency ? ( + + ) : ( + '-' + )} + +
+
); }; diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/AccountDetailsInputs.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/AccountDetailsInputs.tsx index 773865b89fb..9036c3d6817 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/AccountDetailsInputs.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/AccountDetailsInputs.tsx @@ -3,12 +3,16 @@ import { useFormContext } from 'react-hook-form'; import { SupportedCurrencies } from '~gql'; import { getCountries } from '~utils/countries.ts'; +import { formatText } from '~utils/intl.ts'; import { CURRENCY_VALUES } from '../../constants.ts'; import { FormInput } from '../FormInput.tsx'; import { FormRow } from '../FormRow.tsx'; import { FormSelect } from '../FormSelect.tsx'; +import { BANK_DETAILS_FORM_MSG } from './constants.ts'; +import { BankDetailsFields, type BankDetailsFormSchema } from './validation.ts'; + const displayName = 'v5.pages.UserCryptoToFiatPage.partials.BankDetailsForm.AccountDetailsInputs'; @@ -28,13 +32,26 @@ const AccountDetailsInputs = () => { {currency === CURRENCY_VALUES[SupportedCurrencies.Eur] && ( <> - + + name={BankDetailsFields.IBAN} + label={formatText(BANK_DETAILS_FORM_MSG.ibanLabel)} + placeholder={formatText(BANK_DETAILS_FORM_MSG.ibanLabel)} + /> - + + name={BankDetailsFields.SWIFT} + label={formatText(BANK_DETAILS_FORM_MSG.swiftLabel)} + placeholder={formatText(BANK_DETAILS_FORM_MSG.swiftLabel)} + /> - + + name={BankDetailsFields.COUNTRY} + options={countriesOptions} + labelMessage={formatText(BANK_DETAILS_FORM_MSG.countryLabel)} + placeholder={formatText(BANK_DETAILS_FORM_MSG.countryPlaceholder)} + /> )} @@ -42,17 +59,17 @@ const AccountDetailsInputs = () => { {currency === CURRENCY_VALUES[SupportedCurrencies.Usd] && ( <> - + name={BankDetailsFields.ACCOUNT_NUMBER} + label={formatText(BANK_DETAILS_FORM_MSG.accountNumberLabel)} + placeholder={formatText(BANK_DETAILS_FORM_MSG.accountNumberLabel)} /> - + name={BankDetailsFields.ROUTING_NUMBER} + label={formatText(BANK_DETAILS_FORM_MSG.routingNumberLabel)} + placeholder={formatText(BANK_DETAILS_FORM_MSG.routingNumberLabel)} /> diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/BankDetailsForm.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/BankDetailsForm.tsx index e9728f4f6e8..43355766477 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/BankDetailsForm.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/BankDetailsForm.tsx @@ -1,5 +1,4 @@ import React, { type FC } from 'react'; -import { defineMessages } from 'react-intl'; import { Form } from '~shared/Fields/index.ts'; import { formatText } from '~utils/intl.ts'; @@ -13,108 +12,76 @@ import ModalFormCTAButtons from '../ModalFormCTAButtons/ModalFormCTAButtons.tsx' import ModalHeading from '../ModalHeading/ModalHeading.tsx'; import { AccountDetailsInputs } from './AccountDetailsInputs.tsx'; -import { validationSchema } from './validation.ts'; +import { BANK_DETAILS_FORM_MSG } from './constants.ts'; +import { CurrencyFormattedOptionLabel } from './CurrencyFormattedOptionLabel.tsx'; +import { + BankDetailsFields, + type BankDetailsFormSchema, + validationSchema, +} from './validation.ts'; interface BankDetailsFormProps { onSubmit: (values: any) => void; onClose: () => void; defaultValues: BankDetailsFormValues; + isLoading?: boolean; } -const displayName = 'v5.pages.UserCryptoToFiatPage.partials.BankDetailsForm'; - -const MSG = defineMessages({ - title: { - id: `${displayName}.title`, - defaultMessage: 'Bank details', - }, - subtitle: { - id: `${displayName}.subtitle`, - defaultMessage: - 'Complete your bank, and currency information to receive USDC payments to your bank account.', - }, - cancelButtonTitle: { - id: `${displayName}.cancelButtonTitle`, - defaultMessage: 'Cancel', - }, - proceedButtonTitle: { - id: `${displayName}.proceedButtonTitle`, - defaultMessage: 'Submit details', - }, - accountOwnerNameLabel: { - id: `${displayName}.accountOwnerNameLabel`, - defaultMessage: 'Account owner name', - }, - accountOwnerNamePlaceholder: { - id: `${displayName}.accountOwnerNamePlaceholder`, - defaultMessage: 'Full name', - }, - bankNameabel: { - id: `${displayName}.bankNameabel`, - defaultMessage: 'Bank name', - }, - bankNamePlaceholder: { - id: `${displayName}.bankNamePlaceholder`, - defaultMessage: 'Bank name', - }, - payoutCurrencyLabel: { - id: `${displayName}.payoutCurrencyLabel`, - defaultMessage: 'Payout currency', - }, - payoutCurrencyPlaceholder: { - id: `${displayName}.payoutCurrencyPlaceholder`, - defaultMessage: 'Payout currency', - }, - countryLabel: { - id: `${displayName}.countryLabel`, - defaultMessage: 'Country', - }, -}); - const BankDetailsForm: FC = ({ onSubmit, onClose, defaultValues, + isLoading, }) => { return (
- +
- + name={BankDetailsFields.ACCOUNT_OWNER} shouldFocus - label={formatText(MSG.accountOwnerNameLabel)} - placeholder={formatText(MSG.accountOwnerNamePlaceholder)} + label={formatText(BANK_DETAILS_FORM_MSG.accountOwnerNameLabel)} + placeholder={formatText( + BANK_DETAILS_FORM_MSG.accountOwnerNamePlaceholder, + )} /> - + name={BankDetailsFields.BANK_NAME} + label={formatText(BANK_DETAILS_FORM_MSG.bankNameLabel)} + placeholder={formatText(BANK_DETAILS_FORM_MSG.bankNamePlaceholder)} /> - + name={BankDetailsFields.CURRENCY} + labelMessage={formatText(BANK_DETAILS_FORM_MSG.payoutCurrencyLabel)} options={CURRENCIES} + formatOptionLabel={CurrencyFormattedOptionLabel} />
diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/CurrencyFormattedOptionLabel.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/CurrencyFormattedOptionLabel.tsx new file mode 100644 index 00000000000..38f6aecf67a --- /dev/null +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/CurrencyFormattedOptionLabel.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +import { type SupportedCurrencies } from '~gql'; +import { type SelectOption } from '~v5/common/Fields/Select/types.ts'; + +import { CurrencyLabel } from '../CurrencyLabel.tsx'; + +export const CurrencyFormattedOptionLabel = ({ label }: SelectOption) => ( + +); diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/constants.ts b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/constants.ts index 96c282a7a40..2eda4bcc7d1 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/constants.ts +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/constants.ts @@ -1,6 +1,93 @@ +import { defineMessages } from 'react-intl'; + export const IBAN_REGEX = /^([A-Z]{2}[ +\\-]?[0-9]{2})(?=(?:[ +\\-]?[A-Z0-9]){9,30}$)((?:[ +\\-]?[A-Z0-9]{3,5}){2,7})([ +\\-]?[A-Z0-9]{1,3})?$/; export const BIC_REGEX = // eslint-disable-next-line max-len /^([a-zA-Z]{4})([a-zA-Z]{2})(([2-9a-zA-Z]{1})([0-9a-np-zA-NP-Z]{1}))((([0-9a-wy-zA-WY-Z]{1})([0-9a-zA-Z]{2}))|([xX]{3})?)$/; + +export const displayName = + 'v5.pages.UserCryptoToFiatPage.partials.BankDetailsForm'; + +export const BANK_DETAILS_FORM_MSG = defineMessages({ + title: { + id: `${displayName}.title`, + defaultMessage: 'Bank details', + }, + subtitle: { + id: `${displayName}.subtitle`, + defaultMessage: + 'Complete your bank, and currency information to receive USDC payments to your bank account.', + }, + cancelButtonTitle: { + id: `${displayName}.cancelButtonTitle`, + defaultMessage: 'Cancel', + }, + proceedButtonTitle: { + id: `${displayName}.proceedButtonTitle`, + defaultMessage: 'Submit details', + }, + accountOwnerNameLabel: { + id: `${displayName}.accountOwnerNameLabel`, + defaultMessage: 'Account owner name', + }, + accountOwnerNamePlaceholder: { + id: `${displayName}.accountOwnerNamePlaceholder`, + defaultMessage: 'Full name', + }, + bankAccountLabel: { + id: `${displayName}.bankAccountLabel`, + defaultMessage: 'Bank account', + }, + bankNameLabel: { + id: `${displayName}.bankNameLabel`, + defaultMessage: 'Bank name', + }, + bankNamePlaceholder: { + id: `${displayName}.bankNamePlaceholder`, + defaultMessage: 'Bank name', + }, + payoutCurrencyLabel: { + id: `${displayName}.payoutCurrencyLabel`, + defaultMessage: 'Payout currency', + }, + payoutCurrencyPlaceholder: { + id: `${displayName}.payoutCurrencyPlaceholder`, + defaultMessage: 'Select payout currency', + }, + countryLabel: { + id: `${displayName}.countryLabel`, + defaultMessage: 'Country', + }, + countryPlaceholder: { + id: `${displayName}.countryPlaceholder`, + defaultMessage: 'Select country', + }, + ibanLabel: { + id: `${displayName}.ibanLabel`, + defaultMessage: 'IBAN', + }, + swiftLabel: { + id: `${displayName}.swiftLabel`, + defaultMessage: 'SWIFT/BIC', + }, + accountNumberLabel: { + id: `${displayName}.accountNumberLabel`, + defaultMessage: 'Account number', + }, + routingNumberLabel: { + id: `${displayName}.routingNumberLabel`, + defaultMessage: 'Routing number', + }, +}); + +export const BANK_DETAILS_FORM_FIELD_VALUE_LENGTHS = { + accountNumber: { + min: 8, + max: 17, + }, + routingNumber: { + length: 9, + }, +}; diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/validation.ts b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/validation.ts index 8e969447395..123be6e84d9 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/validation.ts +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsForm/validation.ts @@ -1,58 +1,148 @@ import { type InferType, object, string } from 'yup'; import { SupportedCurrencies } from '~gql'; -import { intl } from '~utils/intl.ts'; +import { formErrorMessage, formatText } from '~utils/intl.ts'; +import { capitalizeFirstLetter } from '~utils/strings.ts'; import { CURRENCY_VALUES } from '../../constants.ts'; -import { BIC_REGEX, IBAN_REGEX } from './constants.ts'; +import { + BANK_DETAILS_FORM_FIELD_VALUE_LENGTHS, + BANK_DETAILS_FORM_MSG, + BIC_REGEX, + IBAN_REGEX, +} from './constants.ts'; -const { formatMessage } = intl({ - 'error.iban': 'Invalid IBAN format', - 'error.bic': 'Invalid SWIFT/BIC format', -}); +export enum BankDetailsFields { + ACCOUNT_OWNER = 'accountOwner', + BANK_NAME = 'bankName', + CURRENCY = 'currency', + COUNTRY = 'country', + IBAN = 'iban', + SWIFT = 'swift', + ACCOUNT_NUMBER = 'accountNumber', + ROUTING_NUMBER = 'routingNumber', +} + +const { accountNumber, routingNumber } = BANK_DETAILS_FORM_FIELD_VALUE_LENGTHS; export const validationSchema = object({ - accountOwner: string().required(), - bankName: string().required(), - currency: string().required(), - country: string().when('currency', { + [BankDetailsFields.ACCOUNT_OWNER]: string().required( + formatText({ id: 'cryptoToFiat.forms.error.bankAccount.accountOwner' }), + ), + [BankDetailsFields.BANK_NAME]: string().required( + formatText({ id: 'cryptoToFiat.forms.error.bankAccount.bankName' }), + ), + [BankDetailsFields.CURRENCY]: string().required( + formatText({ id: 'cryptoToFiat.forms.error.bankAccount.currency' }), + ), + [BankDetailsFields.COUNTRY]: string().when(BankDetailsFields.CURRENCY, { is: CURRENCY_VALUES[SupportedCurrencies.Eur], - then: string().required(), + then: string().required( + formatText({ + id: 'cryptoToFiat.forms.error.bankAccount.country', + }), + ), otherwise: string().notRequired(), }), - iban: string().when('currency', { + [BankDetailsFields.IBAN]: string().when(BankDetailsFields.CURRENCY, { is: CURRENCY_VALUES[SupportedCurrencies.Eur], then: string() - .matches(IBAN_REGEX, formatMessage({ id: 'error.iban' })) - .required(), + .required( + formatText({ + id: 'cryptoToFiat.forms.error.bankAccount.iban', + }), + ) + .matches( + IBAN_REGEX, + formErrorMessage(BANK_DETAILS_FORM_MSG.ibanLabel, 'invalid'), + ), otherwise: string().notRequired(), }), - swift: string().when('currency', { + [BankDetailsFields.SWIFT]: string().when(BankDetailsFields.CURRENCY, { is: CURRENCY_VALUES[SupportedCurrencies.Eur], then: string() - .matches(BIC_REGEX, formatMessage({ id: 'error.bic' })) - .required(), - otherwise: string().notRequired(), - }), - accountNumber: string().when('currency', { - is: CURRENCY_VALUES[SupportedCurrencies.Usd], - then: string() - .required() - .matches(/^[0-9]+$/) - .min(8) - .max(17), - otherwise: string().notRequired(), - }), - routingNumber: string().when('currency', { - is: CURRENCY_VALUES[SupportedCurrencies.Usd], - then: string() - .required() - .matches(/^[0-9]+$/) - .min(9) - .max(9), + .required( + formatText({ + id: 'cryptoToFiat.forms.error.bankAccount.swift', + }), + ) + .matches( + BIC_REGEX, + formErrorMessage(BANK_DETAILS_FORM_MSG.swiftLabel, 'invalid'), + ), otherwise: string().notRequired(), }), + [BankDetailsFields.ACCOUNT_NUMBER]: string().when( + BankDetailsFields.CURRENCY, + { + is: CURRENCY_VALUES[SupportedCurrencies.Usd], + then: string() + .required( + formatText({ + id: 'cryptoToFiat.forms.error.bankAccount.accountNumber', + }), + ) + .matches( + /^[0-9]+$/, + capitalizeFirstLetter( + formErrorMessage( + BANK_DETAILS_FORM_MSG.accountNumberLabel, + 'invalid', + ), + { lowerCaseRemainingLetters: true }, + ), + ) + .min( + accountNumber.min, + formErrorMessage( + BANK_DETAILS_FORM_MSG.accountNumberLabel, + 'min', + accountNumber.min, + ), + ) + .max( + accountNumber.max, + formErrorMessage( + BANK_DETAILS_FORM_MSG.accountNumberLabel, + 'max', + accountNumber.max, + ), + ), + otherwise: string().notRequired(), + }, + ), + [BankDetailsFields.ROUTING_NUMBER]: string().when( + BankDetailsFields.CURRENCY, + { + is: CURRENCY_VALUES[SupportedCurrencies.Usd], + then: string() + .required( + formatText({ + id: 'cryptoToFiat.forms.error.bankAccount.routingNumber', + }), + ) + .matches( + /^[0-9]+$/, + capitalizeFirstLetter( + formErrorMessage( + BANK_DETAILS_FORM_MSG.routingNumberLabel, + 'invalid', + ), + { lowerCaseRemainingLetters: true }, + ), + ) + .length( + routingNumber.length, + formErrorMessage( + BANK_DETAILS_FORM_MSG.routingNumberLabel, + 'length', + routingNumber.length, + ), + ), + otherwise: string().notRequired(), + }, + ), }).defined(); -export type FormValues = InferType; +export type BankDetailsFormSchema = InferType; diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsModal/BankDetailsModal.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsModal/BankDetailsModal.tsx index e43a4803e5d..b859649aea3 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsModal/BankDetailsModal.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsModal/BankDetailsModal.tsx @@ -1,16 +1,12 @@ -import React, { useState, type FC } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; +import React, { type FC } from 'react'; +import { useIntl } from 'react-intl'; -import { SupportedCurrencies } from '~gql'; import { type BridgeBankAccount } from '~types/graphql.ts'; -import { formatText } from '~utils/intl.ts'; import { CloseButton } from '~v5/shared/Button/index.ts'; import ModalBase from '~v5/shared/Modal/ModalBase.tsx'; -import { CURRENCY_VALUES } from '../../constants.ts'; import BankDetailsForm from '../BankDetailsForm/index.ts'; import ContactDetailsForm from '../ContactDetailsForm/index.ts'; -import Stepper from '../Stepper.tsx'; import { useBankDetailsFields } from './useBankDetailsFields.tsx'; @@ -20,73 +16,31 @@ interface BankDetailsModalProps { data?: BridgeBankAccount | null; } -enum TabId { - BankDetails = 1, - ContactDetails = 2, -} - const displayName = 'v5.pages.UserCryptoToFiatPage.partials.BankDetailsModal'; -const MSG = defineMessages({ - bankDetailsLabel: { - id: `${displayName}.bankDetailsLabel`, - defaultMessage: 'Bank details', - }, - addressDetailsLabel: { - id: `${displayName}.addressDetailsLabel`, - defaultMessage: 'Address details', - }, -}); - const BankDetailsModal: FC = ({ isOpened, onClose, data, }) => { const { formatMessage } = useIntl(); - const [activeTab, setActiveTab] = useState(TabId.BankDetails); - - const redirectToSecondTab = () => setActiveTab(TabId.ContactDetails); - - const { bankDetailsFields, handleSubmitFirstStep, handleSubmitSecondStep } = - useBankDetailsFields({ - onClose, - redirectToSecondTab, - data, - }); - const stepItems = [ - { - key: TabId.BankDetails, - heading: { label: formatText(MSG.bankDetailsLabel) }, - content: ( - - ), - }, - { - key: TabId.ContactDetails, - heading: { label: formatText(MSG.addressDetailsLabel) }, - isHidden: - bankDetailsFields.currency !== CURRENCY_VALUES[SupportedCurrencies.Usd], - content: ( - - ), - }, - ]; + const { + isLoading, + bankDetailsFields, + showContactDetailsForm, + handleSubmitFirstStep, + handleSubmitSecondStep, + } = useBankDetailsFields({ + onClose, + data, + }); return ( = ({ onClick={onClose} className="absolute right-4 top-4 text-gray-400 hover:text-gray-600" /> -
- +
+ {!showContactDetailsForm ? ( + + ) : ( + + )}
); diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsModal/useBankDetailsFields.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsModal/useBankDetailsFields.tsx index 24cc511e436..350f52bf8f5 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsModal/useBankDetailsFields.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/BankDetailsModal/useBankDetailsFields.tsx @@ -22,28 +22,36 @@ const MSG = defineMessages({ id: `${displayName}.bankDetailsConfirmed`, defaultMessage: 'Bank details confirmed', }, - bankInfoAddeddSuccessfully: { - id: `${displayName}.bankInfoAddeddSuccessfully`, + bankDetailsTitleSuccess: { + id: `${displayName}.bankDetailsTitleSuccess`, + defaultMessage: 'Bank details confirmed', + }, + bankDetailsDescriptionSuccess: { + id: `${displayName}.bankDetailsDescriptionSuccess`, defaultMessage: 'Your information has been added successfully', }, - bankDetailsError: { - id: `${displayName}.bankDetailsError`, - defaultMessage: 'Something went wrong :(', + bankDetailsTitleError: { + id: `${displayName}.bankDetailsTitleError`, + defaultMessage: 'Something went wrong', + }, + bankDetailsDescriptionError: { + id: `${displayName}.bankDetailsDescriptionError`, + defaultMessage: 'Your bank details could not be updated at this time', }, }); interface UseBankDetailsParams { data?: BridgeBankAccount | null; onClose: () => void; - redirectToSecondTab: () => void; } export const useBankDetailsFields = ({ onClose, - redirectToSecondTab, data, }: UseBankDetailsParams) => { const [createBankAccount] = useCreateBankAccountMutation(); const [updateBankAccount] = useUpdateBankAccountMutation(); + const [isLoading, setIsLoading] = useState(false); + const [showContactDetailsForm, setShowContactDetailsForm] = useState(false); const [bankDetailsFields, setBankDetailsFields] = useState({ @@ -63,6 +71,7 @@ export const useBankDetailsFields = ({ }); const handleSubmitForm = async (values: BankDetailsFormValues) => { + setIsLoading(true); const { bankName, accountNumber, @@ -119,37 +128,46 @@ export const useBankDetailsFields = ({ let isSuccess; - if (!data) { - const result = await createBankAccount({ - variables: { input: accountInput }, - }); - isSuccess = !!result.data?.bridgeCreateBankAccount?.success; - } else { - const result = await updateBankAccount({ - variables: { - input: { - id: data.id, - account: accountInput, + try { + if (!data) { + const result = await createBankAccount({ + variables: { input: accountInput }, + }); + isSuccess = !!result.data?.bridgeCreateBankAccount?.success; + } else { + const result = await updateBankAccount({ + variables: { + input: { + id: data.id, + account: accountInput, + }, }, - }, - }); - isSuccess = !!result.data?.bridgeUpdateBankAccount?.success; - } + }); + isSuccess = !!result.data?.bridgeUpdateBankAccount?.success; + } + + if (!isSuccess) throw new Error(); - if (isSuccess) { toast.success( , ); onClose(); - } else { + setShowContactDetailsForm(false); + } catch (e) { toast.error( - , + , ); + } finally { + setIsLoading(false); } }; @@ -158,7 +176,7 @@ export const useBankDetailsFields = ({ handleSubmitForm(values); } else { setBankDetailsFields({ ...values }); - redirectToSecondTab(); + setShowContactDetailsForm(true); } }; @@ -166,5 +184,11 @@ export const useBankDetailsFields = ({ handleSubmitForm({ ...bankDetailsFields, ...values }); }; - return { bankDetailsFields, handleSubmitFirstStep, handleSubmitSecondStep }; + return { + isLoading, + bankDetailsFields, + showContactDetailsForm, + handleSubmitFirstStep, + handleSubmitSecondStep, + }; }; diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/ContactDetailsForm.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/ContactDetailsForm.tsx index 80598811ba6..f57270de0a6 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/ContactDetailsForm.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/ContactDetailsForm.tsx @@ -1,6 +1,4 @@ -/* eslint-disable jsx-a11y/label-has-associated-control */ import React, { type FC } from 'react'; -import { defineMessages } from 'react-intl'; import { Form } from '~shared/Fields/index.ts'; import { formatText } from '~utils/intl.ts'; @@ -10,128 +8,85 @@ import { FormRow } from '../FormRow.tsx'; import ModalFormCTAButtons from '../ModalFormCTAButtons/ModalFormCTAButtons.tsx'; import ModalHeading from '../ModalHeading/ModalHeading.tsx'; +import { CONTACT_DETAILS_FORM_MSGS } from './consts.ts'; import { CountrySelect } from './CountrySelect.tsx'; import { SubdivisionSelect } from './SubdivisionSelect.tsx'; -import { addressValidationSchema } from './validation.ts'; +import { + AddressFields, + addressValidationSchema, + type ContactDetailsFormSchema, +} from './validation.ts'; interface ContactDetailsFormProps { onSubmit: (values: any) => void; onClose: () => void; + isLoading?: boolean; } -const displayName = 'v5.pages.UserCryptoToFiatpage.partials.ContactDetailsForm'; - -const MSG = defineMessages({ - title: { - id: `${displayName}.title`, - defaultMessage: 'Contact details', - }, - subtitle: { - id: `${displayName}.subtitle`, - defaultMessage: - 'The address details provided should match your bank account details. This information is only provided to Bridge and not stored by Colony', - }, - cancelButtonTitle: { - id: `${displayName}.cancelButtonTitle`, - defaultMessage: 'Cancel', - }, - proceedButtonTitle: { - id: `${displayName}.proceedButtonTitle`, - defaultMessage: 'Submit', - }, - addressLabel: { - id: `${displayName}.addressLabel`, - defaultMessage: 'Address', - }, - address1Placeholder: { - id: `${displayName}.address1Placeholder`, - defaultMessage: 'Address line 1', - }, - address2Placeholder: { - id: `${displayName}.address2Placeholder`, - defaultMessage: 'Address line 2', - }, - cityPlaceholder: { - id: `${displayName}.cityPlaceholder`, - defaultMessage: 'City', - }, - postcodePlaceholder: { - id: `${displayName}.postcodePlaceholder`, - defaultMessage: 'Postcode', - }, - dobLabel: { - id: `${displayName}.dobLabel`, - defaultMessage: 'Date of birth', - }, - dobPlaceholder: { - id: `${displayName}.dobPlaceholder`, - defaultMessage: 'YYYY-MM-DD', - }, - taxLabel: { - id: `${displayName}.taxLabel`, - defaultMessage: - 'Tax identification number (eg. social security number or EIN)', - }, - taxPlaceholder: { - id: `${displayName}.taxPlaceholder`, - defaultMessage: 'Tax identification number', - }, -}); - const ContactDetailsForm: FC = ({ onSubmit, onClose, + isLoading, }) => { return (
- +
{/* */} - - - + name={AddressFields.ADDRESS1} + label={formatText(CONTACT_DETAILS_FORM_MSGS.address1Label)} + placeholder={formatText( + CONTACT_DETAILS_FORM_MSGS.address1Placeholder, + )} /> - + name={AddressFields.ADDRESS2} + label={formatText(CONTACT_DETAILS_FORM_MSGS.address2Label)} + placeholder={formatText( + CONTACT_DETAILS_FORM_MSGS.address2Placeholder, + )} />
- + name={AddressFields.CITY} + label={formatText(CONTACT_DETAILS_FORM_MSGS.cityLabel)} + placeholder={formatText( + CONTACT_DETAILS_FORM_MSGS.cityPlaceholder, + )} />
@@ -139,15 +94,25 @@ const ContactDetailsForm: FC = ({ - + name={AddressFields.POSTCODE} + label={formatText(CONTACT_DETAILS_FORM_MSGS.postcodeLabel)} + placeholder={formatText( + CONTACT_DETAILS_FORM_MSGS.postcodePlaceholder, + )} />
diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/CountrySelect.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/CountrySelect.tsx index 62bd4211de5..be0a7e3adc9 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/CountrySelect.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/CountrySelect.tsx @@ -1,21 +1,13 @@ import React from 'react'; import { useFormContext } from 'react-hook-form'; -import { defineMessages } from 'react-intl'; import { getCountries } from '~utils/countries.ts'; import { formatText } from '~utils/intl.ts'; import { FormSelect } from '../FormSelect.tsx'; -const displayName = - 'v5.pages.UserCryptoToFiatpage.partials.ContactDetailsForm.CountrySelect'; - -const MSG = defineMessages({ - countryLabel: { - id: `${displayName}.countryLabel`, - defaultMessage: 'Country', - }, -}); +import { CONTACT_DETAILS_FORM_MSGS } from './consts.ts'; +import { AddressFields, type ContactDetailsFormSchema } from './validation.ts'; export const CountrySelect = () => { const countries = getCountries(); @@ -33,20 +25,24 @@ export const CountrySelect = () => { const handleSelect = () => { // if country changed user should choose state of new country - setValue('state', ''); + setValue(AddressFields.STATE, ''); // If no attempt to submit the form has been made yet, do not trigger state validation if (!isSubmitted) { return; } - trigger('state'); + trigger(AddressFields.STATE); + trigger(AddressFields.CITY); + // if country changed postcode should be revalidated + trigger(AddressFields.POSTCODE); }; return ( - + name={AddressFields.COUNTRY} options={countriesOptions} - placeholder={formatText(MSG.countryLabel)} + labelMessage={formatText(CONTACT_DETAILS_FORM_MSGS.countryLabel)} + placeholder={formatText(CONTACT_DETAILS_FORM_MSGS.countryPlaceholder)} handleChange={handleSelect} /> ); diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/SubdivisionSelect.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/SubdivisionSelect.tsx index f14c18a4584..eb900ec0760 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/SubdivisionSelect.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/SubdivisionSelect.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useWatch } from 'react-hook-form'; +import { formatText } from '~utils/intl.ts'; import { type SubdivisionData, getSubdivisionsByCountryCode, @@ -8,9 +9,12 @@ import { import { FormSelect } from '../FormSelect.tsx'; +import { CONTACT_DETAILS_FORM_MSGS } from './consts.ts'; +import { AddressFields, type ContactDetailsFormSchema } from './validation.ts'; + export const SubdivisionSelect = () => { const countryCode = useWatch({ - name: 'country', + name: AddressFields.COUNTRY, }); const [subdivisions, setSubdivisions] = useState([]); @@ -29,8 +33,10 @@ export const SubdivisionSelect = () => { return (
- + name={AddressFields.STATE} + labelMessage={formatText(CONTACT_DETAILS_FORM_MSGS.stateLabel)} + placeholder={formatText(CONTACT_DETAILS_FORM_MSGS.statePlaceholder)} options={subdivisions.map((item) => ({ value: item.code, label: item.name, diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/consts.ts b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/consts.ts new file mode 100644 index 00000000000..f2b5095fe2c --- /dev/null +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/consts.ts @@ -0,0 +1,92 @@ +import { defineMessages } from 'react-intl'; + +const displayName = 'v5.pages.UserCryptoToFiatpage.partials.ContactDetailsForm'; + +export const CONTACT_DETAILS_FORM_MSGS = defineMessages({ + title: { + id: `${displayName}.title`, + defaultMessage: 'Contact details', + }, + subtitle: { + id: `${displayName}.subtitle`, + defaultMessage: + 'The address details provided should match your bank account details. This information is only provided to Bridge and not stored by Colony', + }, + cancelButtonTitle: { + id: `${displayName}.cancelButtonTitle`, + defaultMessage: 'Cancel', + }, + proceedButtonTitle: { + id: `${displayName}.proceedButtonTitle`, + defaultMessage: 'Submit details', + }, + addressLabel: { + id: `${displayName}.addressLabel`, + defaultMessage: 'Address', + }, + address1Label: { + id: `${displayName}.address1Placeholder`, + defaultMessage: 'Address', + }, + address1Placeholder: { + id: `${displayName}.address1Placeholder`, + defaultMessage: 'Street line one', + }, + address2Label: { + id: `${displayName}.address2Placeholder`, + defaultMessage: 'Address 2', + }, + address2Placeholder: { + id: `${displayName}.address2Placeholder`, + defaultMessage: 'Street line two', + }, + cityLabel: { + id: `${displayName}.cityLabel`, + defaultMessage: 'City', + }, + cityPlaceholder: { + id: `${displayName}.cityPlaceholder`, + defaultMessage: 'City', + }, + postcodeLabel: { + id: `${displayName}.postcodeLabel`, + defaultMessage: 'Postcode', + }, + postcodePlaceholder: { + id: `${displayName}.postcodePlaceholder`, + defaultMessage: 'Postcode', + }, + dobLabel: { + id: `${displayName}.dobLabel`, + defaultMessage: 'Date of birth', + }, + dobPlaceholder: { + id: `${displayName}.dobPlaceholder`, + defaultMessage: 'YYYY-MM-DD', + }, + taxLabel: { + id: `${displayName}.taxLabel`, + defaultMessage: + 'Tax identification number (eg. social security number or EIN)', + }, + taxPlaceholder: { + id: `${displayName}.taxPlaceholder`, + defaultMessage: 'Tax identification number', + }, + countryLabel: { + id: `${displayName}.country`, + defaultMessage: 'Country', + }, + countryPlaceholder: { + id: `${displayName}.countryPlaceholder`, + defaultMessage: 'Select country', + }, + stateLabel: { + id: `${displayName}.stateLabel`, + defaultMessage: 'State', + }, + statePlaceholder: { + id: `${displayName}.stateLabel`, + defaultMessage: 'State', + }, +}); diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/validation.ts b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/validation.ts index b8010d445e5..090ffdc1a7d 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/validation.ts +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ContactDetailsForm/validation.ts @@ -1,16 +1,74 @@ import * as Yup from 'yup'; -import { COUNTRIES_WITHOUT_STATES } from '~utils/countries.ts'; +import { postalCodesRegex } from '~constants/postalCodesRegex.ts'; +import { + getCountryByCode, + COUNTRIES_WITHOUT_STATES, +} from '~utils/countries.ts'; +import { formErrorMessage, formatText } from '~utils/intl.ts'; +import { capitalizeFirstLetter } from '~utils/strings.ts'; + +import { CONTACT_DETAILS_FORM_MSGS } from './consts.ts'; + +export enum AddressFields { + ADDRESS1 = 'address1', + ADDRESS2 = 'address2', + COUNTRY = 'country', + CITY = 'city', + STATE = 'state', + POSTCODE = 'postcode', +} export const addressValidationSchema = Yup.object({ - country: Yup.string().required(), - address1: Yup.string().required(), - address2: Yup.string().notRequired(), - city: Yup.string().required(), - state: Yup.string().when('country', { - is: (country) => COUNTRIES_WITHOUT_STATES.includes(country), + [AddressFields.ADDRESS1]: Yup.string().required( + formatText({ id: 'cryptoToFiat.forms.error.address.address1' }), + ), + [AddressFields.ADDRESS2]: Yup.string().notRequired(), + [AddressFields.COUNTRY]: Yup.string().required( + formatText({ id: 'cryptoToFiat.forms.error.address.country' }), + ), + [AddressFields.CITY]: Yup.string().when(AddressFields.COUNTRY, { + is: (country) => !country || COUNTRIES_WITHOUT_STATES.includes(country), then: Yup.string().notRequired(), - otherwise: Yup.string().required(), + otherwise: Yup.string().required( + formatText({ id: 'cryptoToFiat.forms.error.address.city' }), + ), }), - postcode: Yup.string().required(), + [AddressFields.STATE]: Yup.string().when(AddressFields.COUNTRY, { + is: (country) => !country || COUNTRIES_WITHOUT_STATES.includes(country), + then: Yup.string().notRequired(), + otherwise: Yup.string().required( + formatText({ id: 'cryptoToFiat.forms.error.address.state' }), + ), + }), + [AddressFields.POSTCODE]: Yup.string().when( + AddressFields.COUNTRY, + (countryCode, schema) => { + const country = getCountryByCode(countryCode); + const postalCodeRegex = country?.alpha2 + ? postalCodesRegex[country.alpha2] + : null; + if (postalCodeRegex) { + return schema + .required( + formatText({ id: 'cryptoToFiat.forms.error.address.postcode' }), + ) + .matches( + postalCodeRegex, + capitalizeFirstLetter( + formErrorMessage( + CONTACT_DETAILS_FORM_MSGS.postcodeLabel, + 'invalid', + ), + { lowerCaseRemainingLetters: true }, + ), + ); + } + return schema; + }, + ), }).defined(); + +export type ContactDetailsFormSchema = Yup.InferType< + typeof addressValidationSchema +>; diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/CurrencyLabel.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/CurrencyLabel.tsx new file mode 100644 index 00000000000..ae9091c805b --- /dev/null +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/CurrencyLabel.tsx @@ -0,0 +1,25 @@ +import clsx from 'clsx'; +import React, { type FC } from 'react'; + +import { type SupportedCurrencies } from '~gql'; + +import { currencyIcons } from '../../../../../common/Extensions/UserNavigation/partials/UserMenu/consts.ts'; + +interface CurrencyLabelProps { + currency: SupportedCurrencies; + labelClassName?: string; +} + +export const CurrencyLabel: FC = ({ + currency, + labelClassName, +}) => { + const CurrencyIcon = currencyIcons[currency]; + + return ( +
+ +

{currency}

+
+ ); +}; diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormInput.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormInput.tsx index 9af60699b0a..396477b20fd 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormInput.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormInput.tsx @@ -1,23 +1,22 @@ -import React, { type FC } from 'react'; -import { useFormContext } from 'react-hook-form'; +import React from 'react'; +import { type Message, useFormContext } from 'react-hook-form'; -import { type Message } from '~types/index.ts'; import { formatText } from '~utils/intl.ts'; import { get } from '~utils/lodash.ts'; import Input from '~v5/common/Fields/Input/index.ts'; -interface FormInputProps { - name: string; +interface FormInputProps { + name: Extract; label?: string; placeholder?: string; shouldFocus?: boolean; } -export const FormInput: FC = ({ +export const FormInput = ({ name, label, shouldFocus, placeholder, -}) => { +}: FormInputProps) => { const { register, formState: { isSubmitting, errors }, @@ -35,6 +34,7 @@ export const FormInput: FC = ({ placeholder={placeholder} isError={!!error} customErrorMessage={error ? formatText(error) : ''} + allowLayoutShift /> ); }; diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormRow.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormRow.tsx index 1c89d104af3..a383db05bbf 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormRow.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormRow.tsx @@ -1,5 +1,5 @@ import React, { type FC, type PropsWithChildren } from 'react'; export const FormRow: FC = ({ children }) => { - return
{children}
; + return
{children}
; }; diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormSelect.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormSelect.tsx index a83344a5d21..6fe63542648 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormSelect.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FormSelect.tsx @@ -1,33 +1,33 @@ -import React, { type FC } from 'react'; -import { Controller, useFormContext } from 'react-hook-form'; +import React from 'react'; +import { Controller, type Message, useFormContext } from 'react-hook-form'; -import { type Message } from '~types/index.ts'; import { formatText } from '~utils/intl.ts'; import { get } from '~utils/lodash.ts'; import Select from '~v5/common/Fields/Select/Select.tsx'; import { type SelectOption } from '~v5/common/Fields/Select/types.ts'; import FormError from '~v5/shared/FormError/index.ts'; -interface FormSelectProps { - name: string; +interface FormSelectProps { + name: Extract; labelMessage?: string; options: SelectOption[]; handleChange?: any; placeholder?: string; + formatOptionLabel?: (option: SelectOption) => JSX.Element; } -export const FormSelect: FC = ({ +export const FormSelect = ({ name, options, labelMessage, placeholder, handleChange, -}) => { + formatOptionLabel, +}: FormSelectProps) => { const { control, formState: { errors }, } = useFormContext(); - const error = get(errors, name)?.message as Message | undefined; return ( @@ -49,7 +49,9 @@ export const FormSelect: FC = ({ options={options} isError={!!error} isSearchable + isDisabled={!options || !options.length} placeholder={placeholder} + formatOptionLabel={formatOptionLabel} onChange={(val) => { handleChange?.(val); field.onChange(val?.value); diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ModalFormCTAButtons/ModalFormCTAButtons.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ModalFormCTAButtons/ModalFormCTAButtons.tsx index 504b2aa2e5e..79a7e15d03e 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ModalFormCTAButtons/ModalFormCTAButtons.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ModalFormCTAButtons/ModalFormCTAButtons.tsx @@ -1,8 +1,9 @@ import clsx from 'clsx'; import React from 'react'; -import Button from '~v5/shared/Button/index.ts'; +import Button from '~v5/shared/Button/Button.tsx'; +import { ProceedButton } from './ProceedButton.tsx'; import { type ModalFormCTAButtonsProps } from './types.ts'; const displayName = @@ -12,18 +13,21 @@ const ModalFormCTAButtons: React.FC = ({ cancelButton, proceedButton, className, -}) => ( -
-
-); + isLoading, +}) => { + return ( +
+
+ ); +}; ModalFormCTAButtons.displayName = displayName; diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ModalFormCTAButtons/ProceedButton.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ModalFormCTAButtons/ProceedButton.tsx new file mode 100644 index 00000000000..1c16e5ce805 --- /dev/null +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/ModalFormCTAButtons/ProceedButton.tsx @@ -0,0 +1,40 @@ +import { SpinnerGap } from '@phosphor-icons/react'; +import clsx from 'clsx'; +import React, { type FC } from 'react'; +import { type MessageDescriptor } from 'react-intl'; + +import { useMobile } from '~hooks'; +import { formatText } from '~utils/intl.ts'; +import Button from '~v5/shared/Button/Button.tsx'; +import IconButton from '~v5/shared/Button/IconButton.tsx'; + +interface ProceedButtonProps { + text: MessageDescriptor; + isLoading?: boolean; +} + +export const ProceedButton: FC = ({ text, isLoading }) => { + const isMobile = useMobile(); + + return isLoading ? ( + + + + } + > + {formatText(text)} + + ) : ( +