Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Jalson1982/ba4f596a7b775caaf40cd333cddad62f to your computer and use it in GitHub Desktop.
Save Jalson1982/ba4f596a7b775caaf40cd333cddad62f to your computer and use it in GitHub Desktop.
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