From f9634e7af20de1691d0c589aa62e2dc6aeb544d5 Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Tue, 18 Jul 2023 09:00:10 +0100 Subject: [PATCH] wip --- app/graphql/generated.gql | 11 + app/graphql/generated.ts | 43 +++ app/i18n/en/index.ts | 12 + app/i18n/i18n-types.ts | 84 ++++++ app/i18n/raw-i18n/source/en.json | 12 + .../phone-auth-screen/phone-login-input.tsx | 19 +- .../phone-login-validation.tsx | 2 +- .../phone-registration-input.tsx | 278 ++++++++++++++++++ ...oneCode.ts => request-phone-code-login.ts} | 9 +- .../request-phone-code-registration.ts | 232 +++++++++++++++ 10 files changed, 680 insertions(+), 22 deletions(-) create mode 100644 app/screens/phone-auth-screen/phone-registration-input.tsx rename app/screens/phone-auth-screen/{useRequestPhoneCode.ts => request-phone-code-login.ts} (97%) create mode 100644 app/screens/phone-auth-screen/request-phone-code-registration.ts diff --git a/app/graphql/generated.gql b/app/graphql/generated.gql index 944073f538..3cbe02934c 100644 --- a/app/graphql/generated.gql +++ b/app/graphql/generated.gql @@ -488,6 +488,17 @@ mutation userPhoneDelete { } } +mutation userPhoneRegistrationInitiate($input: UserPhoneRegistrationInitiateInput!) { + userPhoneRegistrationInitiate(input: $input) { + errors { + message + __typename + } + success + __typename + } +} + mutation userUpdateLanguage($input: UserUpdateLanguageInput!) { userUpdateLanguage(input: $input) { errors { diff --git a/app/graphql/generated.ts b/app/graphql/generated.ts index 7a69da242f..642aee5ed4 100644 --- a/app/graphql/generated.ts +++ b/app/graphql/generated.ts @@ -1901,6 +1901,13 @@ export type SupportedCountriesQueryVariables = Exact<{ [key: string]: never; }>; export type SupportedCountriesQuery = { readonly __typename: 'Query', readonly globals?: { readonly __typename: 'Globals', readonly supportedCountries: ReadonlyArray<{ readonly __typename: 'Country', readonly id: string, readonly supportedAuthChannels: ReadonlyArray }> } | null }; +export type UserPhoneRegistrationInitiateMutationVariables = Exact<{ + input: UserPhoneRegistrationInitiateInput; +}>; + + +export type UserPhoneRegistrationInitiateMutation = { readonly __typename: 'Mutation', readonly userPhoneRegistrationInitiate: { readonly __typename: 'SuccessPayload', readonly success?: boolean | null, readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly message: string }> } }; + export type MyLnUpdatesSubscriptionVariables = Exact<{ [key: string]: never; }>; @@ -3665,6 +3672,42 @@ export function useSupportedCountriesLazyQuery(baseOptions?: Apollo.LazyQueryHoo export type SupportedCountriesQueryHookResult = ReturnType; export type SupportedCountriesLazyQueryHookResult = ReturnType; export type SupportedCountriesQueryResult = Apollo.QueryResult; +export const UserPhoneRegistrationInitiateDocument = gql` + mutation userPhoneRegistrationInitiate($input: UserPhoneRegistrationInitiateInput!) { + userPhoneRegistrationInitiate(input: $input) { + errors { + message + } + success + } +} + `; +export type UserPhoneRegistrationInitiateMutationFn = Apollo.MutationFunction; + +/** + * __useUserPhoneRegistrationInitiateMutation__ + * + * To run a mutation, you first call `useUserPhoneRegistrationInitiateMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUserPhoneRegistrationInitiateMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [userPhoneRegistrationInitiateMutation, { data, loading, error }] = useUserPhoneRegistrationInitiateMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useUserPhoneRegistrationInitiateMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UserPhoneRegistrationInitiateDocument, options); + } +export type UserPhoneRegistrationInitiateMutationHookResult = ReturnType; +export type UserPhoneRegistrationInitiateMutationResult = Apollo.MutationResult; +export type UserPhoneRegistrationInitiateMutationOptions = Apollo.BaseMutationOptions; export const MyLnUpdatesDocument = gql` subscription myLnUpdates { myUpdates { diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index a942172a78..53dfb1dd09 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -782,6 +782,18 @@ const en: BaseTranslation = { tryAgain: "Try Again", sendViaOtherChannel: "You selected to receive the code via {channel: string}. You can try receiving via {other: string} instead", }, + PhoneRegistrationSetScreen: { + title: "Phone set up", + header: "Enter your phone number, and we'll text you an access code.", + headerVerify: "Verify you are human", + errorRequestingCode: "Something went wrong requesting the phone code, please try again later.", + errorInvalidPhoneNumber: "Invalid phone number. Are you sure you entered the right number?", + errorUnsupportedCountry: "We are unable to support customers in your country.", + placeholder: "Phone Number", + verify: "Click to Verify", + sms: "Send via SMS", + whatsapp: "Send via WhatsApp", + }, EmailRegistrationInitiateScreen: { title: "Add your email", header: "Enter your email address, and we'll send you an access code.", diff --git a/app/i18n/i18n-types.ts b/app/i18n/i18n-types.ts index 8f51509e5a..cdbf45c762 100644 --- a/app/i18n/i18n-types.ts +++ b/app/i18n/i18n-types.ts @@ -2638,6 +2638,48 @@ type RootTranslation = { */ sendViaOtherChannel: RequiredParams<'channel' | 'other'> } + PhoneRegistrationSetScreen: { + /** + * P​h​o​n​e​ ​s​e​t​ ​u​p + */ + title: string + /** + * E​n​t​e​r​ ​y​o​u​r​ ​p​h​o​n​e​ ​n​u​m​b​e​r​,​ ​a​n​d​ ​w​e​'​l​l​ ​t​e​x​t​ ​y​o​u​ ​a​n​ ​a​c​c​e​s​s​ ​c​o​d​e​. + */ + header: string + /** + * V​e​r​i​f​y​ ​y​o​u​ ​a​r​e​ ​h​u​m​a​n + */ + headerVerify: string + /** + * S​o​m​e​t​h​i​n​g​ ​w​e​n​t​ ​w​r​o​n​g​ ​r​e​q​u​e​s​t​i​n​g​ ​t​h​e​ ​p​h​o​n​e​ ​c​o​d​e​,​ ​p​l​e​a​s​e​ ​t​r​y​ ​a​g​a​i​n​ ​l​a​t​e​r​. + */ + errorRequestingCode: string + /** + * I​n​v​a​l​i​d​ ​p​h​o​n​e​ ​n​u​m​b​e​r​.​ ​A​r​e​ ​y​o​u​ ​s​u​r​e​ ​y​o​u​ ​e​n​t​e​r​e​d​ ​t​h​e​ ​r​i​g​h​t​ ​n​u​m​b​e​r​? + */ + errorInvalidPhoneNumber: string + /** + * W​e​ ​a​r​e​ ​u​n​a​b​l​e​ ​t​o​ ​s​u​p​p​o​r​t​ ​c​u​s​t​o​m​e​r​s​ ​i​n​ ​y​o​u​r​ ​c​o​u​n​t​r​y​. + */ + errorUnsupportedCountry: string + /** + * P​h​o​n​e​ ​N​u​m​b​e​r + */ + placeholder: string + /** + * C​l​i​c​k​ ​t​o​ ​V​e​r​i​f​y + */ + verify: string + /** + * S​e​n​d​ ​v​i​a​ ​S​M​S + */ + sms: string + /** + * S​e​n​d​ ​v​i​a​ ​W​h​a​t​s​A​p​p + */ + whatsapp: string + } EmailRegistrationInitiateScreen: { /** * A​d​d​ ​y​o​u​r​ ​e​m​a​i​l @@ -5853,6 +5895,48 @@ export type TranslationFunctions = { */ sendViaOtherChannel: (arg: { channel: string, other: string }) => LocalizedString } + PhoneRegistrationSetScreen: { + /** + * Phone set up + */ + title: () => LocalizedString + /** + * Enter your phone number, and we'll text you an access code. + */ + header: () => LocalizedString + /** + * Verify you are human + */ + headerVerify: () => LocalizedString + /** + * Something went wrong requesting the phone code, please try again later. + */ + errorRequestingCode: () => LocalizedString + /** + * Invalid phone number. Are you sure you entered the right number? + */ + errorInvalidPhoneNumber: () => LocalizedString + /** + * We are unable to support customers in your country. + */ + errorUnsupportedCountry: () => LocalizedString + /** + * Phone Number + */ + placeholder: () => LocalizedString + /** + * Click to Verify + */ + verify: () => LocalizedString + /** + * Send via SMS + */ + sms: () => LocalizedString + /** + * Send via WhatsApp + */ + whatsapp: () => LocalizedString + } EmailRegistrationInitiateScreen: { /** * Add your email diff --git a/app/i18n/raw-i18n/source/en.json b/app/i18n/raw-i18n/source/en.json index bb634c7ab4..fc798f5f7c 100644 --- a/app/i18n/raw-i18n/source/en.json +++ b/app/i18n/raw-i18n/source/en.json @@ -768,6 +768,18 @@ "tryAgain": "Try Again", "sendViaOtherChannel": "You selected to receive the code via {channel: string}. You can try receiving via {other: string} instead" }, + "PhoneRegistrationSetScreen": { + "title": "Phone set up", + "header": "Enter your phone number, and we'll text you an access code.", + "headerVerify": "Verify you are human", + "errorRequestingCode": "Something went wrong requesting the phone code, please try again later.", + "errorInvalidPhoneNumber": "Invalid phone number. Are you sure you entered the right number?", + "errorUnsupportedCountry": "We are unable to support customers in your country.", + "placeholder": "Phone Number", + "verify": "Click to Verify", + "sms": "Send via SMS", + "whatsapp": "Send via WhatsApp" + }, "EmailRegistrationInitiateScreen": { "title": "Add your email", "header": "Enter your email address, and we'll send you an access code.", diff --git a/app/screens/phone-auth-screen/phone-login-input.tsx b/app/screens/phone-auth-screen/phone-login-input.tsx index e6d0a3fe82..e3961b2da0 100644 --- a/app/screens/phone-auth-screen/phone-login-input.tsx +++ b/app/screens/phone-auth-screen/phone-login-input.tsx @@ -17,13 +17,12 @@ import { ContactSupportButton } from "@app/components/contact-support-button/con import { useI18nContext } from "@app/i18n/i18n-react" import { makeStyles, useTheme, Text, Input } from "@rneui/themed" import { Screen } from "../../components/screen" -import { useAppConfig } from "../../hooks" import type { PhoneValidationStackParamList } from "../../navigation/stack-param-lists" import { ErrorType, RequestPhoneCodeStatus, - useRequestPhoneCode, -} from "./useRequestPhoneCode" + useRequestPhoneCodeLogin, +} from "./request-phone-code-login" import { GaloyPrimaryButton } from "@app/components/atomic/galoy-primary-button" import { GaloySecondaryButton } from "@app/components/atomic/galoy-secondary-button" import { GaloyErrorBox } from "@app/components/atomic/galoy-error-box" @@ -104,7 +103,7 @@ export const PhoneLoginSetScreen: React.FC = () => { const navigation = useNavigation>() - const { appConfig } = useAppConfig() + const { theme: { colors, mode: themeMode }, } = useTheme() @@ -124,9 +123,7 @@ export const PhoneLoginSetScreen: React.FC = () => { setCountryCode, supportedCountries, loadingSupportedCountries, - } = useRequestPhoneCode({ - skipRequestPhoneCode: appConfig.galoyInstance.name === "Local", - }) + } = useRequestPhoneCodeLogin() const { LL } = useI18nContext() @@ -150,8 +147,6 @@ export const PhoneLoginSetScreen: React.FC = () => { ) } - const showCaptcha = false - let errorMessage: string | undefined if (error) { switch (error) { @@ -225,11 +220,7 @@ export const PhoneLoginSetScreen: React.FC = () => { > - - {showCaptcha - ? LL.PhoneLoginSetScreen.headerVerify() - : LL.PhoneLoginSetScreen.header()} - + {LL.PhoneLoginSetScreen.header()} diff --git a/app/screens/phone-auth-screen/phone-login-validation.tsx b/app/screens/phone-auth-screen/phone-login-validation.tsx index a078e95a27..aedc3b487c 100644 --- a/app/screens/phone-auth-screen/phone-login-validation.tsx +++ b/app/screens/phone-auth-screen/phone-login-validation.tsx @@ -26,7 +26,7 @@ import { logUpgradeLoginSuccess, logValidateAuthCodeFailure, } from "@app/utils/analytics" -import { PhoneCodeChannelToFriendlyName } from "./useRequestPhoneCode" +import { PhoneCodeChannelToFriendlyName } from "./request-phone-code-login" import { AccountLevel, useLevel } from "@app/graphql/level-context" const useStyles = makeStyles(({ colors }) => ({ diff --git a/app/screens/phone-auth-screen/phone-registration-input.tsx b/app/screens/phone-auth-screen/phone-registration-input.tsx new file mode 100644 index 0000000000..0afa7b5b3b --- /dev/null +++ b/app/screens/phone-auth-screen/phone-registration-input.tsx @@ -0,0 +1,278 @@ +import { useNavigation } from "@react-navigation/native" +import { StackNavigationProp } from "@react-navigation/stack" +import * as React from "react" +import { useEffect } from "react" +import { ActivityIndicator, View } from "react-native" +import CountryPicker, { + CountryCode, + DARK_THEME, + DEFAULT_THEME, + Flag, +} from "react-native-country-picker-modal" +import { + CountryCode as PhoneNumberCountryCode, + getCountryCallingCode, +} from "libphonenumber-js/mobile" +import { ContactSupportButton } from "@app/components/contact-support-button/contact-support-button" +import { useI18nContext } from "@app/i18n/i18n-react" +import { makeStyles, useTheme, Text, Input } from "@rneui/themed" +import { Screen } from "../../components/screen" +import type { PhoneValidationStackParamList } from "../../navigation/stack-param-lists" +import { + ErrorType, + RequestPhoneCodeStatus, + useRequestPhoneCodeRegistration, +} from "./request-phone-code-registration" +import { GaloyPrimaryButton } from "@app/components/atomic/galoy-primary-button" +import { GaloySecondaryButton } from "@app/components/atomic/galoy-secondary-button" +import { GaloyErrorBox } from "@app/components/atomic/galoy-error-box" +import { PhoneCodeChannelType } from "@app/graphql/generated" +import { TouchableOpacity } from "react-native-gesture-handler" + +const DEFAULT_COUNTRY_CODE = "SV" +const PLACEHOLDER_PHONE_NUMBER = "123-456-7890" + +const useStyles = makeStyles(({ colors }) => ({ + screenStyle: { + padding: 20, + flexGrow: 1, + }, + buttonsContainer: { + flex: 1, + justifyContent: "flex-end", + }, + + inputContainer: { + marginBottom: 20, + flexDirection: "row", + alignItems: "stretch", + minHeight: 48, + }, + textContainer: { + marginBottom: 20, + }, + viewWrapper: { flex: 1 }, + + activityIndicator: { marginTop: 12 }, + + keyboardContainer: { + paddingHorizontal: 10, + }, + + codeTextStyle: {}, + countryPickerButtonStyle: { + minWidth: 110, + borderColor: colors.primary5, + borderWidth: 2, + borderRadius: 8, + paddingHorizontal: 10, + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + flex: 1, + }, + inputComponentContainerStyle: { + flex: 1, + marginLeft: 20, + paddingLeft: 0, + paddingRight: 0, + }, + inputContainerStyle: { + flex: 1, + borderWidth: 2, + borderBottomWidth: 2, + paddingHorizontal: 10, + borderColor: colors.primary5, + borderRadius: 8, + }, + errorContainer: { + marginBottom: 20, + }, + whatsAppButton: { + marginBottom: 20, + }, + contactSupportButton: { + marginTop: 10, + }, + + loadingView: { flex: 1, justifyContent: "center", alignItems: "center" }, +})) + +export const PhoneRegistrationSetScreen: React.FC = () => { + const styles = useStyles() + + const navigation = + useNavigation>() + + const { + theme: { colors, mode: themeMode }, + } = useTheme() + + const { + submitPhoneNumber, + status, + setPhoneNumber, + isSmsSupported, + isWhatsAppSupported, + phoneInputInfo, + phoneCodeChannel, + error, + validatedPhoneNumber, + setStatus, + setCountryCode, + supportedCountries, + loadingSupportedCountries, + } = useRequestPhoneCodeRegistration() + + const { LL } = useI18nContext() + + useEffect(() => { + if (status === RequestPhoneCodeStatus.SuccessRequestingCode) { + setStatus(RequestPhoneCodeStatus.InputtingPhoneNumber) + navigation.navigate("phoneLoginValidate", { + phone: validatedPhoneNumber || "", + channel: phoneCodeChannel, + }) + } + }, [status, phoneCodeChannel, validatedPhoneNumber, navigation, setStatus]) + + if (status === RequestPhoneCodeStatus.LoadingCountryCode || loadingSupportedCountries) { + return ( + + + + + + ) + } + + let errorMessage: string | undefined + if (error) { + switch (error) { + case ErrorType.RequestCodeError: + errorMessage = LL.PhoneRegistrationSetScreen.errorRequestingCode() + break + case ErrorType.TooManyAttemptsError: + errorMessage = LL.errors.tooManyRequestsPhoneCode() + break + case ErrorType.InvalidPhoneNumberError: + errorMessage = LL.PhoneRegistrationSetScreen.errorInvalidPhoneNumber() + break + case ErrorType.UnsupportedCountryError: + errorMessage = LL.PhoneRegistrationSetScreen.errorUnsupportedCountry() + break + } + } + if (!isSmsSupported && !isWhatsAppSupported) { + errorMessage = LL.PhoneRegistrationSetScreen.errorUnsupportedCountry() + } + + let PrimaryButton = undefined + let SecondaryButton = undefined + switch (true) { + case isSmsSupported && isWhatsAppSupported: + PrimaryButton = ( + submitPhoneNumber(PhoneCodeChannelType.Sms)} + /> + ) + SecondaryButton = ( + submitPhoneNumber(PhoneCodeChannelType.Whatsapp)} + /> + ) + break + case isSmsSupported && !isWhatsAppSupported: + PrimaryButton = ( + submitPhoneNumber(PhoneCodeChannelType.Sms)} + /> + ) + break + case !isSmsSupported && isWhatsAppSupported: + PrimaryButton = ( + submitPhoneNumber(PhoneCodeChannelType.Whatsapp)} + /> + ) + break + } + + return ( + + + + {LL.PhoneRegistrationSetScreen.header()} + + + + setCountryCode(country.cca2 as PhoneNumberCountryCode)} + renderFlagButton={({ countryCode, onOpen }) => { + return ( + countryCode && ( + + + + +{getCountryCallingCode(countryCode as PhoneNumberCountryCode)} + + + ) + ) + }} + withCallingCodeButton={true} + withFilter={true} + filterProps={{ + autoFocus: true, + }} + withCallingCode={true} + /> + + + {errorMessage && ( + + + + + )} + + + {SecondaryButton} + {PrimaryButton} + + + + ) +} diff --git a/app/screens/phone-auth-screen/useRequestPhoneCode.ts b/app/screens/phone-auth-screen/request-phone-code-login.ts similarity index 97% rename from app/screens/phone-auth-screen/useRequestPhoneCode.ts rename to app/screens/phone-auth-screen/request-phone-code-login.ts index e8cdec2545..3beff52ad4 100644 --- a/app/screens/phone-auth-screen/useRequestPhoneCode.ts +++ b/app/screens/phone-auth-screen/request-phone-code-login.ts @@ -44,10 +44,6 @@ type PhoneInputInfo = { rawPhoneNumber: string } -export type UseRequestPhoneCodeProps = { - skipRequestPhoneCode?: boolean -} - export type UseRequestPhoneCodeReturn = { submitPhoneNumber: (phoneCodeChannel?: PhoneCodeChannelType) => void setStatus: (status: RequestPhoneCodeStatus) => void @@ -93,9 +89,7 @@ gql` } ` -export const useRequestPhoneCode = ({ - skipRequestPhoneCode, -}: UseRequestPhoneCodeProps): UseRequestPhoneCodeReturn => { +export const useRequestPhoneCodeLogin = (): UseRequestPhoneCodeReturn => { const [status, setStatus] = useState( RequestPhoneCodeStatus.LoadingCountryCode, ) @@ -106,6 +100,7 @@ export const useRequestPhoneCode = ({ PhoneCodeChannelType.Sms, ) const { appConfig } = useAppConfig() + const skipRequestPhoneCode = appConfig.galoyInstance.name === "Local" const [error, setError] = useState() const [captchaRequestAuthCode] = useCaptchaRequestAuthCodeMutation() diff --git a/app/screens/phone-auth-screen/request-phone-code-registration.ts b/app/screens/phone-auth-screen/request-phone-code-registration.ts new file mode 100644 index 0000000000..11306a542d --- /dev/null +++ b/app/screens/phone-auth-screen/request-phone-code-registration.ts @@ -0,0 +1,232 @@ +import { useAppConfig } from "@app/hooks" +import { useEffect, useMemo, useState } from "react" +import parsePhoneNumber, { + AsYouType, + CountryCode, + getCountryCallingCode, +} from "libphonenumber-js/mobile" +import { gql } from "@apollo/client" +import { + PhoneCodeChannelType, + useCaptchaRequestAuthCodeMutation, + useSupportedCountriesQuery, +} from "@app/graphql/generated" + +export const RequestPhoneCodeStatus = { + LoadingCountryCode: "LoadingCountryCode", + InputtingPhoneNumber: "InputtingPhoneNumber", + CompletingCaptcha: "CompletingCaptcha", + RequestingCode: "RequestingCode", + SuccessRequestingCode: "SuccessRequestingCode", + Error: "Error", +} as const + +export const ErrorType = { + InvalidPhoneNumberError: "InvalidPhoneNumberError", + FailedCaptchaError: "FailedCaptchaError", + TooManyAttemptsError: "TooManyAttemptsError", + RequestCodeError: "RequestCodeError", + UnsupportedCountryError: "UnsupportedCountryError", +} as const + +import axios from "axios" + +type ErrorType = (typeof ErrorType)[keyof typeof ErrorType] + +export type RequestPhoneCodeStatus = + (typeof RequestPhoneCodeStatus)[keyof typeof RequestPhoneCodeStatus] + +type PhoneInputInfo = { + countryCode: CountryCode + countryCallingCode: string + formattedPhoneNumber: string + rawPhoneNumber: string +} + +export type UseRequestPhoneCodeReturn = { + submitPhoneNumber: (phoneCodeChannel?: PhoneCodeChannelType) => void + setStatus: (status: RequestPhoneCodeStatus) => void + status: RequestPhoneCodeStatus + phoneInputInfo?: PhoneInputInfo + validatedPhoneNumber?: string + isWhatsAppSupported: boolean + isSmsSupported: boolean + phoneCodeChannel: PhoneCodeChannelType + error?: ErrorType + setCountryCode: (countryCode: CountryCode) => void + setPhoneNumber: (number: string) => void + supportedCountries: CountryCode[] + loadingSupportedCountries: boolean +} + +export const PhoneCodeChannelToFriendlyName = { + [PhoneCodeChannelType.Sms]: "SMS", + [PhoneCodeChannelType.Whatsapp]: "WhatsApp", +} + +gql` + mutation userPhoneRegistrationInitiate($input: UserPhoneRegistrationInitiateInput!) { + userPhoneRegistrationInitiate(input: $input) { + errors { + message + } + success + } + } + + query supportedCountries { + globals { + supportedCountries { + id + supportedAuthChannels + } + } + } +` + +export const useRequestPhoneCodeRegistration = (): UseRequestPhoneCodeReturn => { + const [status, setStatus] = useState( + RequestPhoneCodeStatus.LoadingCountryCode, + ) + const [countryCode, setCountryCode] = useState() + const [rawPhoneNumber, setRawPhoneNumber] = useState("") + const [validatedPhoneNumber, setValidatedPhoneNumber] = useState() + const [phoneCodeChannel, setPhoneCodeChannel] = useState( + PhoneCodeChannelType.Sms, + ) + const { appConfig } = useAppConfig() + const skipRequestPhoneCode = appConfig.galoyInstance.name === "Local" + + const [error, setError] = useState() + const [captchaRequestAuthCode] = useCaptchaRequestAuthCodeMutation() + + const { data, loading: loadingSupportedCountries } = useSupportedCountriesQuery() + const { isWhatsAppSupported, isSmsSupported, allSupportedCountries } = useMemo(() => { + const currentCountry = data?.globals?.supportedCountries.find( + (country) => country.id === countryCode, + ) + + const allSupportedCountries = (data?.globals?.supportedCountries.map( + (country) => country.id, + ) || []) as CountryCode[] + + const isWhatsAppSupported = + currentCountry?.supportedAuthChannels.includes(PhoneCodeChannelType.Whatsapp) || + false + const isSmsSupported = + currentCountry?.supportedAuthChannels.includes(PhoneCodeChannelType.Sms) || false + + return { + isWhatsAppSupported, + isSmsSupported, + allSupportedCountries, + } + }, [data?.globals, countryCode]) + + useEffect(() => { + const getCountryCodeFromIP = async () => { + let defaultCountryCode = "SV" as CountryCode + try { + const response = await axios({ + method: "get", + url: "https://ipapi.co/json/", + timeout: 5000, + }) + const data = response.data + + if (data && data.country_code) { + const countryCode = data.country_code + defaultCountryCode = countryCode + } else { + console.warn("no data or country_code in response") + } + } catch (error) { + console.error(error) + } + + setCountryCode(defaultCountryCode) + setStatus(RequestPhoneCodeStatus.InputtingPhoneNumber) + } + + getCountryCodeFromIP() + }, []) + + const setPhoneNumber = (number: string) => { + if (status === RequestPhoneCodeStatus.RequestingCode) { + return + } + // handle paste + if (number.length - rawPhoneNumber.length > 1) { + const parsedPhoneNumber = parsePhoneNumber(number, countryCode) + + if (parsedPhoneNumber?.isValid()) { + parsedPhoneNumber.country && setCountryCode(parsedPhoneNumber.country) + } + } + + setRawPhoneNumber(number) + setError(undefined) + setStatus(RequestPhoneCodeStatus.InputtingPhoneNumber) + } + + const submitPhoneNumber = (phoneCodeChannel?: PhoneCodeChannelType) => { + if ( + status === RequestPhoneCodeStatus.LoadingCountryCode || + status === RequestPhoneCodeStatus.RequestingCode + ) { + return + } + + const parsedPhoneNumber = parsePhoneNumber(rawPhoneNumber, countryCode) + phoneCodeChannel && setPhoneCodeChannel(phoneCodeChannel) + if (parsedPhoneNumber?.isValid()) { + if ( + !parsedPhoneNumber.country || + (phoneCodeChannel === PhoneCodeChannelType.Sms && !isSmsSupported) || + (phoneCodeChannel === PhoneCodeChannelType.Whatsapp && !isWhatsAppSupported) + ) { + setStatus(RequestPhoneCodeStatus.Error) + setError(ErrorType.UnsupportedCountryError) + return + } + + setValidatedPhoneNumber(parsedPhoneNumber.number) + + if (skipRequestPhoneCode) { + setStatus(RequestPhoneCodeStatus.SuccessRequestingCode) + return + } + + setStatus(RequestPhoneCodeStatus.CompletingCaptcha) + } else { + setStatus(RequestPhoneCodeStatus.Error) + setError(ErrorType.InvalidPhoneNumberError) + } + } + + let phoneInputInfo: PhoneInputInfo | undefined = undefined + if (countryCode) { + phoneInputInfo = { + countryCode, + formattedPhoneNumber: new AsYouType(countryCode).input(rawPhoneNumber), + countryCallingCode: getCountryCallingCode(countryCode), + rawPhoneNumber, + } + } + + return { + status, + setStatus, + phoneInputInfo, + validatedPhoneNumber, + error, + submitPhoneNumber, + phoneCodeChannel, + isWhatsAppSupported, + isSmsSupported, + setCountryCode, + setPhoneNumber, + supportedCountries: allSupportedCountries, + loadingSupportedCountries, + } +}