Created
May 23, 2023 16:56
-
-
Save Jalson1982/ba4f596a7b775caaf40cd333cddad62f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | |
import { useFocusEffect, useRoute } from '@react-navigation/native'; | |
import { useV1OtpObtainSignUpWithEmail } from 'api/generated-hooks/otp/otp'; | |
import { | |
formatDateToSend, | |
formatSSNForBackend, | |
parseDateFromInput, | |
} from 'api/helpers'; | |
import { | |
useAuth, | |
useAutogenerateUsername, | |
useUnitApplicationActions, | |
useProfileActions, | |
useAlternateEmailActions, | |
useMe, | |
} from 'api/hooks'; | |
import { EmailOTPRequest } from 'api/model'; | |
import { FormSlide } from 'components/FormSlideGroup/types'; | |
import { | |
CustomError, | |
notEmpty, | |
useCustomForm, | |
formatPhoneToBackend, | |
useNavigation, | |
formatPhoneForDisplay, | |
useTimeout, | |
} from 'utils'; | |
import storage from 'utils/storage'; | |
import { | |
DEFAULT_VALUES, | |
getValidationSchemaFromSlides, | |
FieldName, | |
Values, | |
} from './constants'; | |
import { Props } from './types'; | |
const useConnect = () => { | |
const { navigate, replace } = useNavigation<Props['navigation']>(); | |
const { | |
params: { gift, isSignedId, id }, | |
} = useRoute<Props['route']>(); | |
const { me } = useMe(); | |
const { mutateAsync: mutateOtpObtainSignUpWithEmail } = | |
useV1OtpObtainSignUpWithEmail(); | |
type CreateEmailParams = { input: EmailOTPRequest }; | |
const inHowTo = useRef(false); | |
const { setTimeout } = useTimeout(); | |
const [loggedIn] = useState(!!storage.getObject('app.logged')); | |
const [isSecondaryEmail, setIsSecondaryEmail] = useState(false); | |
const slides1Ref = useRef<FormSlide[]>([]); | |
const recipientPhone = gift.recipientPhone; | |
const recipientEmail = gift.recipientEmail; | |
const deliveredFrom = gift.deliveredFrom; | |
const pendingGiftAmount = gift?.rewards | |
? gift?.rewards?.[0]?.amount | |
? Number(gift?.rewards?.[0]?.amount) | |
: 0 | |
: gift?.amount ?? 0; | |
const { createPhone, verifyPhone, createAccount } = useAuth(); | |
const { createAlternateEmail, otpSignUpEmail } = useAlternateEmailActions(); | |
const shouldCreateAlternateEmail = isSecondaryEmail && loggedIn; | |
const alternateEmailSecret = useRef<string>(); | |
const areSlidesInitialized = useRef(false); | |
const handleAlternateEmailCreation = useCallback(async () => { | |
if (recipientEmail) { | |
alternateEmailSecret.current = await createAlternateEmail({ | |
email: recipientEmail, | |
}); | |
} | |
}, [createAlternateEmail, recipientEmail]); | |
useEffect(() => { | |
if (shouldCreateAlternateEmail && !alternateEmailSecret.current) { | |
handleAlternateEmailCreation(); | |
} | |
}, [shouldCreateAlternateEmail, handleAlternateEmailCreation]); | |
const { updateProfile } = useProfileActions(); | |
const { createUnitApplication, loading: creatingUnitApplication } = | |
useUnitApplicationActions(); | |
const verifyingDetails = creatingUnitApplication; | |
const [showWelcome, setShowWelcome] = useState(false); | |
const wasVerifyingDetails = useRef(false); | |
useEffect(() => { | |
if (verifyingDetails) { | |
wasVerifyingDetails.current = true; | |
} else if (wasVerifyingDetails.current) { | |
setShowWelcome(true); | |
} | |
}, [verifyingDetails]); | |
const welcomeType = wasVerifyingDetails.current | |
? ('youre-all-set' as const) | |
: ('welcome-back' as const); | |
const { generateUsername } = useAutogenerateUsername(); | |
const clientSecret = useRef<string>(); | |
const emailClientSecret = useRef<string>(); | |
const [userExists, setUserExists] = useState(false); | |
const [wrongCodes, setWrongCodes] = useState<string[]>([]); | |
useFocusEffect( | |
useCallback(() => { | |
if (!inHowTo.current) { | |
clientSecret.current = undefined; | |
setUserExists(false); | |
setWrongCodes([]); | |
} | |
inHowTo.current = false; | |
}, []), | |
); | |
const handleEnd = useCallback(async () => { | |
replace('GiftDetail', { | |
id, | |
isSignedId, | |
canClose: true, | |
}); | |
return null; | |
}, [replace, id, isSignedId]); | |
useEffect(() => { | |
if (showWelcome) { | |
setTimeout(() => { | |
handleEnd(); | |
}, 3000); | |
} | |
}, [setTimeout, showWelcome, handleEnd]); | |
const handleSubmit = useCallback( | |
async (values: Values) => { | |
try { | |
if (!userExists) { | |
if (clientSecret.current) { | |
await createAccount({ | |
client_secret: clientSecret.current, | |
email: values.email, | |
otp: values.smsCode, | |
phone: formatPhoneToBackend(values.phone), | |
username: values.username, | |
}); | |
const [fname, lname] = values.fullName; | |
await updateProfile({ | |
fname, | |
lname, | |
username: values.username, | |
dob: formatDateToSend(parseDateFromInput(values.dob)), | |
}); | |
} | |
} else { | |
await updateProfile({ | |
dob: formatDateToSend(parseDateFromInput(values.dob)), | |
}); | |
} | |
if (values.ssn && values.street1) { | |
try { | |
await createUnitApplication({ | |
ssn: formatSSNForBackend(values.ssn), | |
address: { | |
street: values.street1, | |
street2: values.street2, | |
city: values.city, | |
postalCode: values.zipCode, | |
state: values.state, | |
country: 'US', | |
}, | |
}); | |
} catch (err) { | |
navigate('KYCFailed', { | |
pendingGiftAmount, | |
}); | |
} | |
} | |
return setShowWelcome(true); | |
} catch (err) { | |
// TODO: handle error | |
} finally { | |
return setShowWelcome(true); | |
} | |
}, | |
[ | |
createAccount, | |
updateProfile, | |
createUnitApplication, | |
navigate, | |
userExists, | |
pendingGiftAmount, | |
], | |
); | |
const triggerPhoneCreationFromSlides = useCallback(async () => { | |
let exists = false; | |
try { | |
clientSecret.current = await createPhone({ | |
input: { | |
phone: formatPhoneToBackend(recipientPhone ?? ''), | |
}, | |
type: 'login', | |
}); | |
exists = true; | |
setUserExists(exists); | |
if (!clientSecret.current) { | |
return false; | |
} | |
} catch (err) { | |
if (err instanceof CustomError && !exists) { | |
clientSecret.current = await createPhone({ | |
input: { | |
phone: formatPhoneToBackend(recipientPhone ?? ''), | |
}, | |
type: 'sign-up', | |
}); | |
if (!clientSecret.current) { | |
return false; | |
} | |
} | |
} | |
return true; | |
}, [recipientPhone, createPhone]); | |
const generateSlides = useCallback( | |
(slideArray: FormSlide[], shouldShift?: boolean) => { | |
let updatedSlides = [...slides1Ref.current]; | |
slideArray.forEach(({ name, type, step, totalSteps }) => { | |
if (!slides1Ref.current.find((slide) => slide.name === name)) { | |
const newSlide: FormSlide = { | |
name, | |
type, | |
}; | |
if (step !== undefined) newSlide.step = step; | |
if (totalSteps !== undefined) newSlide.totalSteps = totalSteps; | |
updatedSlides = updatedSlides.concat(newSlide); | |
} | |
}); | |
if (shouldShift) { | |
updatedSlides.shift(); | |
} | |
slides1Ref.current = updatedSlides; | |
return slides1Ref.current; | |
}, | |
[], | |
); | |
const slides: FormSlide[] = useMemo(() => { | |
if (showWelcome) { | |
return []; | |
} | |
const nonLoggedUserslides: FormSlide[] = [ | |
{ | |
name: 'phone', | |
type: 'phone', | |
}, | |
{ | |
name: 'smsCode', | |
type: 'phone-login-code', | |
}, | |
]; | |
const loggedUserSlides: FormSlide[] = []; | |
if (!areSlidesInitialized.current) { | |
slides1Ref.current = | |
me && !areSlidesInitialized.current | |
? loggedUserSlides | |
: nonLoggedUserslides; | |
} | |
let slides1 = slides1Ref.current; | |
areSlidesInitialized.current = true; | |
if ( | |
me && | |
slides1.length === 4 && | |
me.email !== recipientEmail && | |
me.alternateEmail !== recipientEmail && | |
deliveredFrom === 'email' | |
) { | |
const emailCodeSlide: FormSlide = { | |
name: 'emailCode', | |
type: 'email-verification', | |
}; | |
slides1 = nonLoggedUserslides.concat([emailCodeSlide]); | |
slides1Ref.current = slides1; | |
handleAlternateEmailCreation(); | |
return slides1; | |
} | |
if ( | |
me && | |
(me.email === recipientEmail || me.alternateEmail === recipientEmail) && | |
slides1?.length === 2 | |
) { | |
slides1Ref.current = slides1; | |
return slides1; | |
} | |
if (!me && deliveredFrom === 'email') { | |
slides1 = generateSlides([ | |
{ | |
name: 'fullName', | |
type: 'legal-name', | |
step: 0, | |
totalSteps: 2, | |
}, | |
{ | |
name: 'emailCode', | |
type: 'email-verification', | |
step: 1, | |
totalSteps: 2, | |
}, | |
]); | |
return slides1; | |
} | |
if (!me && deliveredFrom === 'sms') { | |
slides1 = generateSlides( | |
[ | |
{ | |
name: 'fullName', | |
type: 'legal-name', | |
step: 0, | |
totalSteps: 2, | |
}, | |
{ | |
name: 'email', | |
type: 'email', | |
step: 1, | |
totalSteps: 2, | |
}, | |
], | |
true, | |
); | |
triggerPhoneCreationFromSlides(); | |
return slides1; | |
} | |
if ( | |
me && | |
me.email !== recipientEmail && | |
me.alternateEmail !== recipientEmail && | |
deliveredFrom === 'email' | |
) { | |
const emailCodeSlide: FormSlide = { | |
name: 'emailCode', | |
type: 'email-verification', | |
}; | |
const existingSlide = slides1Ref.current.find( | |
(slide) => slide.name === 'emailCode', | |
); | |
if (!existingSlide) { | |
slides1 = generateSlides([emailCodeSlide]); | |
handleAlternateEmailCreation(); | |
return slides1; | |
} | |
} | |
if (me && deliveredFrom === 'sms') { | |
slides1 = generateSlides([ | |
{ | |
name: 'smsCode', | |
type: 'phone-login-code', | |
}, | |
]); | |
triggerPhoneCreationFromSlides(); | |
return slides1; | |
} | |
return slides1; | |
}, [ | |
deliveredFrom, | |
generateSlides, | |
handleAlternateEmailCreation, | |
me, | |
recipientEmail, | |
triggerPhoneCreationFromSlides, | |
showWelcome, | |
]); | |
const validationSchema = useMemo( | |
() => getValidationSchemaFromSlides(slides), | |
[slides], | |
); | |
const { | |
control, | |
formState: { errors }, | |
trigger, | |
watch, | |
submit, | |
getValues, | |
setValue, | |
} = useCustomForm({ | |
defaultValues: DEFAULT_VALUES, | |
validationSchema, | |
onSubmit: handleSubmit, | |
}); | |
const email = watch('email'); | |
const loggedInUserFirstName = watch('fullName')?.[0]; | |
const name = | |
(loggedInUserFirstName?.length | |
? loggedInUserFirstName | |
: gift?.toUser?.firstName) ?? ''; | |
const phone = watch('phone'); | |
useEffect(() => { | |
if (recipientPhone) { | |
setValue('phone', recipientPhone); | |
} | |
}, [setValue, recipientPhone]); | |
useEffect(() => { | |
if (recipientEmail) { | |
setValue('email', recipientEmail); | |
} | |
}, [setValue, recipientEmail]); | |
const extra = useMemo( | |
() => ({ | |
email, | |
name, | |
phone: formatPhoneForDisplay(phone), | |
}), | |
[email, name, phone], | |
); | |
const createEmail = useCallback( | |
async (params: CreateEmailParams) => { | |
try { | |
const { client_secret: cs } = await mutateOtpObtainSignUpWithEmail({ | |
data: { email: params.input.email }, | |
}); | |
emailClientSecret.current = cs; | |
return clientSecret; | |
} catch (err) { | |
const error = new CustomError(err); | |
if (error.statusCode !== 400) { | |
error.showSnackbar(); | |
} | |
throw error; | |
} | |
}, | |
[mutateOtpObtainSignUpWithEmail], | |
); | |
const triggerPhoneCreation = useCallback( | |
async (giftPhone?: string) => { | |
let exists = false; | |
try { | |
clientSecret.current = await createPhone({ | |
input: { | |
phone: formatPhoneToBackend(giftPhone || phone), | |
}, | |
type: 'login', | |
}); | |
exists = true; | |
setUserExists(exists); | |
if (!clientSecret.current) { | |
return false; | |
} | |
} catch (err) { | |
if (err instanceof CustomError && !exists) { | |
clientSecret.current = await createPhone({ | |
input: { | |
phone: formatPhoneToBackend(giftPhone || phone), | |
}, | |
type: 'sign-up', | |
}); | |
if (!clientSecret.current) { | |
return false; | |
} | |
} | |
} | |
return true; | |
}, | |
[phone, createPhone], | |
); | |
const processPhone = useCallback(async () => { | |
triggerPhoneCreation(); | |
return true; | |
}, [triggerPhoneCreation]); | |
const processFullName = useCallback( | |
async (value: [string, string]) => { | |
const [firstName, lastName] = value; | |
const res = await generateUsername({ | |
first_name: firstName, | |
last_name: lastName, | |
}); | |
const username = res.username; | |
if (username) { | |
setValue('username', username); | |
} | |
if (deliveredFrom === 'email') { | |
await createAccount({ | |
client_secret: clientSecret.current || '', | |
email: email, | |
otp: getValues().smsCode, | |
phone: formatPhoneToBackend(getValues().phone), | |
username: username, | |
}); | |
await createEmail({ | |
input: { | |
email: email, | |
}, | |
}); | |
return true; | |
} else return true; | |
}, | |
[ | |
generateUsername, | |
deliveredFrom, | |
setValue, | |
createAccount, | |
email, | |
getValues, | |
createEmail, | |
], | |
); | |
const processEmailCode = useCallback( | |
async (value: string) => { | |
if (!alternateEmailSecret.current && !emailClientSecret.current) { | |
return false; | |
} | |
const res = await otpSignUpEmail({ | |
client_secret: | |
emailClientSecret.current || alternateEmailSecret.current || '', | |
otp: value, | |
}); | |
if (res) { | |
setShowWelcome(true); | |
} | |
return res; | |
}, | |
[otpSignUpEmail], | |
); | |
const processSmsCode = useCallback( | |
async (value: string) => { | |
try { | |
const u = await verifyPhone( | |
{ | |
client_secret: clientSecret.current ?? '', | |
phone: formatPhoneToBackend(phone), | |
otp: value, | |
}, | |
userExists ? 'login' : 'sign-up', | |
); | |
const profile = u?.profile; | |
console.log('profile', profile); | |
if (profile) { | |
setValue('fullName', [profile.fname ?? '', profile.lname ?? '']); | |
} | |
if ( | |
profile && | |
(profile.alternate_email === recipientEmail || | |
me?.email === recipientEmail || | |
profile.phone === recipientPhone) | |
) { | |
setShowWelcome(true); | |
} | |
const secondary = | |
userExists && | |
recipientEmail === u?.profile?.alternate_email && | |
!u?.profile?.alternate_email_verified; | |
setIsSecondaryEmail(secondary); | |
} catch (err) { | |
setWrongCodes((codes) => [...codes, value]); | |
return false; | |
} | |
return true; | |
}, | |
[ | |
verifyPhone, | |
phone, | |
userExists, | |
recipientEmail, | |
me?.email, | |
recipientPhone, | |
setValue, | |
], | |
); | |
console.log('errors', slides); | |
const handlePressNext = useCallback( | |
async (n?: string) => { | |
if (!n) { | |
return true; | |
} | |
const fieldName = n as FieldName; | |
const value = getValues()?.[fieldName]; | |
try { | |
if (fieldName === 'phone' && typeof value === 'string') { | |
return await processPhone(); | |
} | |
if ( | |
fieldName === 'fullName' && | |
Array.isArray(value) && | |
value.length === 2 && | |
typeof value[0] === 'string' && | |
typeof value[1] === 'string' | |
) { | |
return await processFullName(value as [string, string]); | |
} | |
if (fieldName === 'emailCode' && typeof value === 'string') { | |
return await processEmailCode(value); | |
} | |
if ( | |
fieldName === 'smsCode' && | |
typeof value === 'string' && | |
clientSecret.current | |
) { | |
return await processSmsCode(value); | |
} | |
if (fieldName === 'email' && typeof value === 'string') { | |
const res = await createAccount({ | |
client_secret: clientSecret.current || '', | |
email: value, | |
otp: getValues().smsCode, | |
phone: formatPhoneToBackend(recipientPhone ?? ''), | |
username: getValues().username, | |
}); | |
const [fname, lname] = getValues().fullName; | |
await updateProfile({ | |
fname, | |
lname, | |
}); | |
if (res) { | |
setShowWelcome(true); | |
} | |
} | |
} catch (error) { | |
console.log('Error in handlePressNextNoAccountUs:', error); | |
return false; | |
} | |
return true; | |
}, | |
[ | |
getValues, | |
processPhone, | |
processFullName, | |
processEmailCode, | |
processSmsCode, | |
createAccount, | |
recipientPhone, | |
], | |
); | |
const handlePressSkip = useCallback(() => { | |
storage.setObject('app.skipLogin', true); | |
}, []); | |
const currentSlide = watch('currentSlide'); | |
const currentSlideType = slides?.[currentSlide]?.type; | |
const canNavigateBack = [ | |
'legal-name', | |
'ssn', | |
'phone-login-code', | |
'verify-unassociated-email', | |
].includes(currentSlideType); | |
const errorFieldNames = Object.keys(errors); | |
const isValid = useMemo(() => { | |
const pastSlidesNames = slides | |
.slice(0, currentSlide + 1) | |
.map((s) => s.name) | |
.filter(notEmpty); | |
return !errorFieldNames.some((field) => pastSlidesNames.includes(field)); | |
}, [slides, currentSlide, errorFieldNames]); | |
const handlePressResendSMS = useCallback(() => { | |
triggerPhoneCreation(); | |
}, [triggerPhoneCreation]); | |
const handlePressHowTo = useCallback(() => { | |
inHowTo.current = true; | |
navigate('HowYouKeepInfoSecure'); | |
}, [navigate]); | |
const showBack = | |
currentSlide > 0 && | |
!['welcome-user', 'welcome-user-verify'].includes(currentSlideType); | |
const isRewardGift = (gift?.rewards?.length ?? 0) > 0; | |
return { | |
control, | |
slides, | |
isValid, | |
trigger, | |
pendingGiftAmount, | |
submit, | |
handlePressNext, | |
extra, | |
handlePressResendSMS, | |
handlePressHowTo, | |
verifyingDetails, | |
wrongCodes, | |
handlePressSkip, | |
showBack, | |
canNavigateBack, | |
showWelcome, | |
welcomeType, | |
isRewardGift, | |
}; | |
}; | |
export default useConnect; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment