diff options
author | Ashm Walia <40498934+ashmgarv@users.noreply.github.com> | 2020-12-08 20:19:32 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-08 23:19:32 -0500 |
commit | db575615046544e83759a3615f37540305aa9742 (patch) | |
tree | f30a29f47420990872c9baede4978582cea0b607 | |
parent | 0cb19c5b173d4cf6ba67378cbffd61abac7f18c3 (diff) |
[TMA-308] Forgot password logic [Frontend] (#131)
* Done with changes
* Submit on enter
* Fixed StrongPassword issue
* Clean and modular Verification.tsx
* small fix
-rw-r--r-- | src/constants/api.ts | 1 | ||||
-rw-r--r-- | src/constants/constants.ts | 2 | ||||
-rw-r--r-- | src/constants/regex.ts | 6 | ||||
-rw-r--r-- | src/routes/onboarding/Onboarding.tsx | 16 | ||||
-rw-r--r-- | src/routes/onboarding/OnboardingStack.tsx | 7 | ||||
-rw-r--r-- | src/screens/onboarding/Login.tsx | 13 | ||||
-rw-r--r-- | src/screens/onboarding/PasswordReset.tsx | 246 | ||||
-rw-r--r-- | src/screens/onboarding/PasswordResetRequest.tsx | 198 | ||||
-rw-r--r-- | src/screens/onboarding/RegistrationOne.tsx | 4 | ||||
-rw-r--r-- | src/screens/onboarding/RegistrationThree.tsx | 4 | ||||
-rw-r--r-- | src/screens/onboarding/Verification.tsx | 133 | ||||
-rw-r--r-- | src/screens/onboarding/index.ts | 2 | ||||
-rw-r--r-- | src/services/UserProfileService.ts | 191 | ||||
-rw-r--r-- | src/types/types.ts | 9 |
14 files changed, 767 insertions, 65 deletions
diff --git a/src/constants/api.ts b/src/constants/api.ts index d3047e55..5a752e7b 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -24,6 +24,7 @@ export const FOLLOWING_ENDPOINT: string = API_URL + 'following/'; export const ALL_USERS_ENDPOINT: string = API_URL + 'users/'; export const REPORT_ISSUE_ENDPOINT: string = API_URL + 'report/'; export const BLOCK_USER_ENDPOINT: string = API_URL + 'block/'; +export const PASSWORD_RESET_ENDPOINT: string = API_URL + 'password-reset/'; // Register Social Link (Non-integrated) export const LINK_SNAPCHAT_ENDPOINT: string = API_URL + 'link-sc/'; diff --git a/src/constants/constants.ts b/src/constants/constants.ts index c14068c1..8832cec3 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -97,7 +97,9 @@ export const defaultMoments: Array<string> = [ 'Activity', ]; +export const TAGG_CUSTOMER_SUPPORT: string = 'support@tagg.id'; export const BROWSABLE_SOCIAL_URLS: Record<string, string> = { Instagram: 'https://instagram.com/', Twitter: 'https://twitter.com/', }; + diff --git a/src/constants/regex.ts b/src/constants/regex.ts index 01fd9a2d..6ab045bd 100644 --- a/src/constants/regex.ts +++ b/src/constants/regex.ts @@ -56,3 +56,9 @@ export const genderRegex: RegExp = /^$|^[A-Za-z\- ]{2,20}$/; * */ export const phoneRegex: RegExp = /([0-9]{10})/; + +/** + * The code regex has the following constraints + * - must be 6 digits + */ +export const codeRegex: RegExp = /([0-9]{6})/; diff --git a/src/routes/onboarding/Onboarding.tsx b/src/routes/onboarding/Onboarding.tsx index 4ebc281c..63a75934 100644 --- a/src/routes/onboarding/Onboarding.tsx +++ b/src/routes/onboarding/Onboarding.tsx @@ -10,6 +10,8 @@ import { ProfileOnboarding, Checkpoint, SocialMedia, + PasswordResetRequest, + PasswordReset, } from '../../screens'; import {StackCardInterpolationProps} from '@react-navigation/stack'; @@ -33,6 +35,20 @@ const Onboarding: React.FC = () => { }} /> <OnboardingStack.Screen + name="PasswordResetRequest" + component={PasswordResetRequest} + options={{ + gestureEnabled: false, + }} + /> + <OnboardingStack.Screen + name="PasswordReset" + component={PasswordReset} + options={{ + gestureEnabled: false, + }} + /> + <OnboardingStack.Screen name="InvitationCodeVerification" component={InvitationCodeVerification} /> diff --git a/src/routes/onboarding/OnboardingStack.tsx b/src/routes/onboarding/OnboardingStack.tsx index ccf9be47..33ff51ea 100644 --- a/src/routes/onboarding/OnboardingStack.tsx +++ b/src/routes/onboarding/OnboardingStack.tsx @@ -1,7 +1,12 @@ import {createStackNavigator} from '@react-navigation/stack'; +import {VerificationScreenType} from '../../types'; export type OnboardingStackParams = { Login: undefined; + PasswordResetRequest: undefined; + PasswordReset: { + value: string; + }; InvitationCodeVerification: undefined; RegistrationOne: undefined; RegistrationTwo: {phone: string}; @@ -12,7 +17,7 @@ export type OnboardingStackParams = { email: string; }; Checkpoint: {username: string; userId: string}; - Verification: {phone: string}; + Verification: {id: string; screenType: VerificationScreenType}; ProfileOnboarding: {username: string; userId: string}; SocialMedia: {username: string; userId: string}; }; diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx index 2738d6ca..cb550ef8 100644 --- a/src/screens/onboarding/Login.tsx +++ b/src/screens/onboarding/Login.tsx @@ -127,10 +127,10 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { }; /** - * Handler for the Let's Start button or the Go button on the keyboard. - Makes a POST request to the Django login API and presents Alerts based on the status codes that the backend returns. - * Stores token received in the response, into client's AsynStorage - */ + * Handler for the Let's Start button or the Go button on the keyboard. + Makes a POST request to the Django login API and presents Alerts based on the status codes that the backend returns. + * Stores token received in the response, into client's AsynStorage + */ const handleLogin = async () => { if (!form.attemptedSubmit) { setForm({ @@ -207,7 +207,7 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { accessibilityLabel="Forgot password button" accessibilityHint="Select this if you forgot your tagg password" style={styles.forgotPassword} - onPress={() => Alert.alert("tagg! You're it!")}> + onPress={() => navigation.navigate('PasswordResetRequest')}> <Text style={styles.forgotPasswordText}>Forgot password</Text> </TouchableOpacity> ); @@ -293,8 +293,7 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { attemptedSubmit={form.attemptedSubmit} ref={inputRef} /> - {/* Commenting this line because the Forgot Password has not been implemented for Alpha */} - {/* <ForgotPassword /> */} + <ForgotPassword /> <LoginButton /> </KeyboardAvoidingView> <RegistrationPrompt /> diff --git a/src/screens/onboarding/PasswordReset.tsx b/src/screens/onboarding/PasswordReset.tsx new file mode 100644 index 00000000..25991d6e --- /dev/null +++ b/src/screens/onboarding/PasswordReset.tsx @@ -0,0 +1,246 @@ +import React, {useState, useRef} from 'react'; +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import { + View, + Text, + StyleSheet, + StatusBar, + Alert, + Platform, + KeyboardAvoidingView, +} from 'react-native'; + +import {OnboardingStackParams} from '../../routes'; + +import { + ArrowButton, + TaggInput, + Background, + LoadingIndicator, + SubmitButton, +} from '../../components'; + +import {trackPromise} from 'react-promise-tracker'; +import {passwordRegex} from '../../constants'; +import {handlePasswordReset} from '../../services'; + +type PasswordResetRequestRouteProp = RouteProp< + OnboardingStackParams, + 'PasswordReset' +>; +type PasswordResetRequestNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'PasswordReset' +>; +interface PasswordResetRequestProps { + route: PasswordResetRequestRouteProp; + navigation: PasswordResetRequestNavigationProp; +} +/** + * Password reset page + * @param navigation react-navigation navigation object + */ +const PasswordResetRequest: React.FC<PasswordResetRequestProps> = ({ + route, + navigation, +}) => { + const passwordRef = useRef(); + const confirmRef = useRef(); + const {value} = route.params; + + /** + * Handles focus change to the next input field. + * @param field key for field to move focus onto + */ + const handleFocusChange = (): void => { + const confirmField: any = confirmRef.current; + confirmField.focus(); + }; + + const [form, setForm] = useState({ + password: '', + confirm: '', + isValidPassword: false, + passwordsMatch: false, + attemptedSubmit: false, + }); + + const handlePasswordUpdate = (password: string) => { + let isValidPassword: boolean = passwordRegex.test(password); + let passwordsMatch: boolean = form.password === form.confirm; + setForm({ + ...form, + password, + isValidPassword, + passwordsMatch, + }); + }; + + const handleConfirmUpdate = (confirm: string) => { + let passwordsMatch: boolean = form.password === confirm; + setForm({ + ...form, + confirm, + passwordsMatch, + }); + }; + + const handleSubmit = async () => { + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + try { + if (form.isValidPassword && form.passwordsMatch) { + const success = await trackPromise( + handlePasswordReset(value, form.password), + ); + if (success) { + navigation.navigate('Login'); + } + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + } + } catch (error) { + Alert.alert( + 'Looks like our servers are down. 😓', + "Try again in a couple minutes. We're sorry for the inconvenience.", + ); + return { + name: 'Verify Password error', + description: error, + }; + } + }; + + /** + * Reset Password Button. + */ + const ResetPasswordButton = () => ( + <SubmitButton + text="Reset Password" + color="#fff" + style={styles.button} + accessibilityLabel="Reset Password" + accessibilityHint="Select this after entering teh code and new password" + onPress={handleSubmit} + /> + ); + + const Footer = () => ( + <View style={styles.footer}> + <ArrowButton + direction="backward" + onPress={() => navigation.navigate('Login')} + /> + </View> + ); + + return ( + <Background style={styles.container}> + <StatusBar barStyle="light-content" /> + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={styles.container}> + <View> + <Text style={styles.description}>Enter your new password</Text> + </View> + <TaggInput + accessibilityHint="Enter a password." + accessibilityLabel="Password input field." + placeholder="Password" + autoCompleteType="password" + textContentType="oneTimeCode" + returnKeyType="next" + onChangeText={handlePasswordUpdate} + onSubmitEditing={() => handleFocusChange} + blurOnSubmit={false} + secureTextEntry + ref={passwordRef} + valid={form.isValidPassword} + invalidWarning={ + 'Password must be at least 8 characters & contain at least one of a-z, A-Z, 0-9, and a special character.' + } + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + <TaggInput + accessibilityHint={'Re-enter your password.'} + accessibilityLabel={'Password confirmation input field.'} + placeholder={'Confirm Password'} + autoCompleteType="password" + textContentType="oneTimeCode" + returnKeyType={'go'} + onChangeText={handleConfirmUpdate} + onSubmitEditing={handleSubmit} + secureTextEntry + ref={confirmRef} + valid={form.passwordsMatch} + invalidWarning={'Passwords must match.'} + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + <ResetPasswordButton /> + <LoadingIndicator /> + </KeyboardAvoidingView> + <Footer /> + </Background> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + header: { + ...Platform.select({ + ios: { + top: 50, + }, + android: { + bottom: 40, + }, + }), + }, + formHeader: { + color: '#fff', + fontSize: 20, + fontWeight: 'bold', + alignSelf: 'flex-start', + marginBottom: '6%', + marginHorizontal: '10%', + }, + load: { + top: '5%', + }, + description: { + color: '#fff', + fontWeight: '600', + fontSize: 17, + marginHorizontal: '10%', + }, + footer: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-around', + ...Platform.select({ + ios: { + bottom: '20%', + }, + android: { + bottom: '10%', + }, + }), + }, + button: { + marginVertical: '10%', + }, +}); + +export default PasswordResetRequest; diff --git a/src/screens/onboarding/PasswordResetRequest.tsx b/src/screens/onboarding/PasswordResetRequest.tsx new file mode 100644 index 00000000..5f67eb19 --- /dev/null +++ b/src/screens/onboarding/PasswordResetRequest.tsx @@ -0,0 +1,198 @@ +import React, {useState, useRef} from 'react'; +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import { + View, + Text, + StyleSheet, + StatusBar, + Alert, + Platform, + TouchableOpacity, + KeyboardAvoidingView, +} from 'react-native'; + +import {OnboardingStackParams} from '../../routes'; + +import { + ArrowButton, + TaggInput, + Background, + LoadingIndicator, +} from '../../components'; + +import {trackPromise} from 'react-promise-tracker'; +import {emailRegex, usernameRegex} from '../../constants'; +import {handlePasswordResetRequest} from '../../services'; +import {VerificationScreenType} from '../../types'; + +type PasswordResetRequestRouteProp = RouteProp< + OnboardingStackParams, + 'PasswordResetRequest' +>; +type PasswordResetRequestNavigationProp = StackNavigationProp< + OnboardingStackParams, + 'PasswordResetRequest' +>; +interface PasswordResetRequestProps { + route: PasswordResetRequestRouteProp; + navigation: PasswordResetRequestNavigationProp; +} +/** + * Password reset request page for getting username / email + * @param navigation react-navigation navigation object + */ +const PasswordResetRequest: React.FC<PasswordResetRequestProps> = ({ + navigation, +}) => { + const [form, setForm] = useState({ + value: '', + isValid: false, + attemptedSubmit: false, + }); + + const handleValueUpdate = (value: string) => { + value = value.trim(); + + //Entered field should either be a valid username or a valid email + let isValid: boolean = emailRegex.test(value) || usernameRegex.test(value); + + setForm({ + ...form, + value, + isValid, + }); + }; + + const goToPasswordCodeVerification = async () => { + if (!form.attemptedSubmit) { + setForm({ + ...form, + attemptedSubmit: true, + }); + } + try { + if (form.isValid) { + const success = await trackPromise( + handlePasswordResetRequest(form.value), + ); + if (success) { + navigation.navigate('Verification', { + id: form.value, + screenType: VerificationScreenType.Password, + }); + } + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + } + } catch (error) { + Alert.alert( + 'Looks like our servers are down. 😓', + "Try again in a couple minutes. We're sorry for the inconvenience.", + ); + return { + name: 'Send OTP error', + description: error, + }; + } + }; + + const Footer = () => ( + <View style={styles.footer}> + <ArrowButton + direction="backward" + onPress={() => navigation.navigate('Login')} + /> + <TouchableOpacity onPress={goToPasswordCodeVerification}> + <ArrowButton + direction="forward" + disabled={!form.isValid} + onPress={goToPasswordCodeVerification} + /> + </TouchableOpacity> + </View> + ); + + return ( + <Background style={styles.container}> + <StatusBar barStyle="light-content" /> + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={styles.container}> + <View> + <Text style={styles.description}> + Enter your registered username / email + </Text> + </View> + <TaggInput + accessibilityHint="Enter a username / email" + accessibilityLabel="Input field." + placeholder="Username / Email" + autoCompleteType="username" + textContentType="username" + autoCapitalize="none" + returnKeyType="go" + onSubmitEditing={goToPasswordCodeVerification} + onChangeText={handleValueUpdate} + valid={form.isValid} + invalidWarning={'You must enter a valid username / email'} + attemptedSubmit={form.attemptedSubmit} + width={280} + /> + <LoadingIndicator /> + </KeyboardAvoidingView> + <Footer /> + </Background> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + header: { + ...Platform.select({ + ios: { + top: 50, + }, + android: { + bottom: 40, + }, + }), + }, + formHeader: { + color: '#fff', + fontSize: 20, + fontWeight: 'bold', + alignSelf: 'flex-start', + marginBottom: '6%', + marginHorizontal: '10%', + }, + load: { + top: '5%', + }, + description: { + color: '#fff', + fontWeight: '600', + fontSize: 17, + marginHorizontal: '10%', + }, + footer: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-around', + ...Platform.select({ + ios: { + bottom: '20%', + }, + android: { + bottom: '10%', + }, + }), + }, +}); + +export default PasswordResetRequest; diff --git a/src/screens/onboarding/RegistrationOne.tsx b/src/screens/onboarding/RegistrationOne.tsx index 277b3510..3373b903 100644 --- a/src/screens/onboarding/RegistrationOne.tsx +++ b/src/screens/onboarding/RegistrationOne.tsx @@ -27,6 +27,7 @@ import {trackPromise} from 'react-promise-tracker'; import {SEND_OTP_ENDPOINT} from '../../constants'; import {phoneRegex} from '../../constants'; +import {VerificationScreenType} from '../../types'; type RegistrationScreenOneRouteProp = RouteProp< OnboardingStackParams, @@ -91,7 +92,8 @@ const RegistrationOne: React.FC<RegistrationOneProps> = ({navigation}) => { let otpStatusCode = sendOtpResponse.status; if (otpStatusCode === 200) { navigation.navigate('Verification', { - phone: form.phone_number, + id: form.phone_number, + screenType: VerificationScreenType.Phone, }); } else if (otpStatusCode === 409) { Alert.alert( diff --git a/src/screens/onboarding/RegistrationThree.tsx b/src/screens/onboarding/RegistrationThree.tsx index 1e1b48d4..614795ca 100644 --- a/src/screens/onboarding/RegistrationThree.tsx +++ b/src/screens/onboarding/RegistrationThree.tsx @@ -274,7 +274,7 @@ const RegistrationThree: React.FC<RegistrationThreeProps> = ({ accessibilityLabel="Password input field." placeholder="Password" autoCompleteType="password" - textContentType="newPassword" + textContentType="oneTimeCode" returnKeyType="next" onChangeText={handlePasswordUpdate} onSubmitEditing={() => handleFocusChange('confirm')} @@ -293,7 +293,7 @@ const RegistrationThree: React.FC<RegistrationThreeProps> = ({ accessibilityLabel={'Password confirmation input field.'} placeholder={'Confirm Password'} autoCompleteType="password" - textContentType="password" + textContentType="oneTimeCode" returnKeyType={form.tcAccepted ? 'go' : 'default'} onChangeText={handleConfirmUpdate} onSubmitEditing={handleRegister} diff --git a/src/screens/onboarding/Verification.tsx b/src/screens/onboarding/Verification.tsx index ad012c92..9fa1c12f 100644 --- a/src/screens/onboarding/Verification.tsx +++ b/src/screens/onboarding/Verification.tsx @@ -10,7 +10,7 @@ import { ArrowButton, LoadingIndicator, } from '../../components'; -import {VERIFY_OTP_ENDPOINT, SEND_OTP_ENDPOINT} from '../../constants'; + import {Text} from 'react-native-animatable'; import { CodeField, @@ -27,6 +27,13 @@ import { Platform, } from 'react-native'; import {trackPromise} from 'react-promise-tracker'; +import {VerificationScreenType} from '../../types'; +import { + handlePasswordCodeVerification, + sendOtp, + verifyOtp, + handlePasswordResetRequest, +} from '../../services'; type VerificationScreenRouteProp = RouteProp< OnboardingStackParams, @@ -41,6 +48,8 @@ interface VerificationProps { navigation: VerificationScreenNavigationProp; } +import {codeRegex} from '../../constants'; + const Verification: React.FC<VerificationProps> = ({route, navigation}) => { const [value, setValue] = React.useState(''); const ref = useBlurOnFulfill({value, cellCount: 6}); @@ -48,41 +57,46 @@ const Verification: React.FC<VerificationProps> = ({route, navigation}) => { value, setValue, }); - const {phone} = route.params; + const {id, screenType} = route.params; + const isPhoneVerification = screenType === VerificationScreenType.Phone; + + const handlePhoneVerification = async () => { + const success = await trackPromise(verifyOtp(id, value)); + if (success) { + navigation.navigate('RegistrationTwo', {phone: id}); + } + }; + + const handlePasswordVerification = async () => { + const success = await trackPromise( + handlePasswordCodeVerification(id, value), + ); + if (success) { + navigation.navigate('PasswordReset', {value: id}); + } + }; /** * Sends the verify_otp request upon tapping the Verify button. - * If successful, it navigates to the Profile page. + * If successful, it navigates to the respected page. */ const handleVerification = async () => { - try { - let verifyOtpResponse = await fetch(VERIFY_OTP_ENDPOINT, { - method: 'POST', - body: JSON.stringify({ - phone_number: '+1' + phone, - otp: value, - }), - }); - let statusCode = verifyOtpResponse.status; - if (statusCode === 200) { - navigation.navigate('RegistrationTwo', { - phone: phone, - }); - } else { - Alert.alert( - 'Invalid verification code 🤔', - 'Try again. Tap the resend code button if you need a new code.', - ); + if (codeRegex.test(value)) { + try { + switch (screenType) { + case VerificationScreenType.Phone: + handlePhoneVerification(); + break; + case VerificationScreenType.Password: + handlePasswordVerification(); + break; + } + } catch (error) { + console.log(error); + Alert.alert('Something went wrong'); } - } catch (error) { - Alert.alert( - 'Verifiation failed 😓', - 'Please double-check your network connection and retry.', - ); - return { - name: 'Verification error', - description: error, - }; + } else { + Alert.alert('Please enter a valid 6 digit code'); } }; @@ -91,42 +105,49 @@ const Verification: React.FC<VerificationProps> = ({route, navigation}) => { */ const handleResend = async () => { try { - let sendOtpResponse = await trackPromise( - fetch(SEND_OTP_ENDPOINT, { - method: 'POST', - body: JSON.stringify({ - phone: phone, - }), - }), - ); - let sendOtpStatusCode = sendOtpResponse.status; - if (sendOtpStatusCode === 200) { - setValue(''); - Alert.alert( - 'New verification code sent!', - 'Check your phone messages for your code.', - ); - } else { - Alert.alert('Something went wrong!'); + switch (screenType) { + case VerificationScreenType.Phone: + trackPromise(sendOtp(id)); + break; + case VerificationScreenType.Password: + trackPromise(handlePasswordResetRequest(id)); + break; } } catch (error) { console.log(error); + Alert.alert('Something went wrong'); + } + }; + + const handleGoBack = () => { + switch (screenType) { + case VerificationScreenType.Phone: + navigation.navigate('RegistrationOne'); + break; + case VerificationScreenType.Password: + navigation.navigate('PasswordResetRequest'); + break; } }; const Footer = () => ( <View style={styles.footer}> - <ArrowButton - direction="backward" - onPress={() => navigation.navigate('RegistrationOne')} - /> + <ArrowButton direction="backward" onPress={() => handleGoBack()} /> </View> ); return ( <Background centered style={styles.container}> - <RegistrationWizard style={styles.wizard} step="three" /> - <KeyboardAvoidingView behavior="padding" style={styles.form}> + {isPhoneVerification ? ( + <RegistrationWizard style={styles.wizard} step="three" /> + ) : ( + <React.Fragment /> + )} + <KeyboardAvoidingView + behavior="padding" + style={ + isPhoneVerification ? styles.form : styles.formPasswordVerification + }> <Text style={styles.formHeader}>Enter 6 digit code</Text> <Text style={styles.description}> We sent a 6 digit verification code to the phone number you provided. @@ -185,6 +206,12 @@ const styles = StyleSheet.create({ justifyContent: 'flex-start', flex: 3, }, + formPasswordVerification: { + alignItems: 'center', + justifyContent: 'flex-start', + flex: 3, + top: '35%', + }, formHeader: { color: '#fff', fontSize: 20, diff --git a/src/screens/onboarding/index.ts b/src/screens/onboarding/index.ts index e116bf2c..2411a7e7 100644 --- a/src/screens/onboarding/index.ts +++ b/src/screens/onboarding/index.ts @@ -7,3 +7,5 @@ export {default as Checkpoint} from './Checkpoint'; export {default as ProfileOnboarding} from './ProfileOnboarding'; export {default as InvitationCodeVerification} from './InvitationCodeVerification'; export {default as SocialMedia} from './SocialMedia'; +export {default as PasswordResetRequest} from './PasswordResetRequest'; +export {default as PasswordReset} from './PasswordReset'; diff --git a/src/services/UserProfileService.ts b/src/services/UserProfileService.ts index c8dbcdd1..18bcfb5a 100644 --- a/src/services/UserProfileService.ts +++ b/src/services/UserProfileService.ts @@ -10,6 +10,10 @@ import { GET_IG_POSTS_ENDPOINT, GET_TWITTER_POSTS_ENDPOINT, PROFILE_INFO_ENDPOINT, + PASSWORD_RESET_ENDPOINT, + TAGG_CUSTOMER_SUPPORT, + VERIFY_OTP_ENDPOINT, + SEND_OTP_ENDPOINT, } from '../constants'; export const loadProfileInfo = async (token: string, userId: string) => { @@ -116,3 +120,190 @@ export const loadRecentlySearchedUsers = async () => { console.log(e); } }; + +export const handlePasswordResetRequest = async (value: string) => { + try { + const token = await AsyncStorage.getItem('token'); + const response = await fetch(PASSWORD_RESET_ENDPOINT + 'request/', { + method: 'POST', + headers: { + Authorization: 'Token ' + token, + }, + body: JSON.stringify({ + value, + }), + }); + const status = response.status; + if (status === 200) { + Alert.alert( + 'A code was sent to your registered phone number, please use the same to reset your password', + ); + return true; + } else { + if (status == 404) { + Alert.alert( + `Please make sure that the email / username entered is registered with us. You may contact our customer support at ${TAGG_CUSTOMER_SUPPORT}`, + ); + } else { + Alert.alert( + 'Something went wrong! ðŸ˜', + "Would you believe me if I told you that I don't know what happened?", + ); + } + + console.log(response); + return false; + } + } catch (error) { + console.log(error); + Alert.alert('Something went wrong! ðŸ˜', 'Looks like our servers are down'); + return false; + } +}; + +export const handlePasswordCodeVerification = async ( + value: string, + otp: string, +) => { + try { + const token = await AsyncStorage.getItem('token'); + const response = await fetch(PASSWORD_RESET_ENDPOINT + 'verify/', { + method: 'POST', + headers: { + Authorization: 'Token ' + token, + }, + body: JSON.stringify({ + value, + otp, + }), + }); + const status = response.status; + if (status === 200) { + return true; + } else { + if (status == 404) { + Alert.alert( + `Please make sure that the email / username entered is registered with us. You may contact our customer support at ${TAGG_CUSTOMER_SUPPORT}`, + ); + } else if (status === 401) { + Alert.alert('Looks like you have entered the wrong code'); + } else { + Alert.alert( + 'Something went wrong! ðŸ˜', + "Would you believe me if I told you that I don't know what happened?", + ); + } + + console.log(response); + return false; + } + } catch (error) { + console.log(error); + Alert.alert('Something went wrong! ðŸ˜', 'Looks like our servers are down'); + return false; + } +}; + +export const handlePasswordReset = async (value: string, password: string) => { + try { + const token = await AsyncStorage.getItem('token'); + const response = await fetch(PASSWORD_RESET_ENDPOINT + `reset/`, { + method: 'POST', + headers: { + Authorization: 'Token ' + token, + }, + body: JSON.stringify({ + value, + password, + }), + }); + const status = response.status; + if (status === 200) { + Alert.alert('Your password was reset successfully'); + return true; + } else { + if (status == 404) { + Alert.alert( + `Please make sure that the email / username entered is registered with us. You may contact our customer support at ${TAGG_CUSTOMER_SUPPORT}`, + ); + } else if (status == 406) { + Alert.alert('You may not use an already used password'); + } else { + Alert.alert( + 'Something went wrong! ðŸ˜', + "Would you believe me if I told you that I don't know what happened?", + ); + } + console.log(response); + return false; + } + } catch (error) { + console.log(error); + Alert.alert('Something went wrong! ðŸ˜', 'Looks like our servers are down'); + return false; + } +}; + +export const verifyOtp = async (phone: string, otp: string) => { + try { + let response = await fetch(VERIFY_OTP_ENDPOINT, { + method: 'POST', + body: JSON.stringify({ + phone_number: '+1' + phone, + otp, + }), + }); + let statusCode = response.status; + if (statusCode === 200) { + return true; + } else { + if (statusCode === 401) { + Alert.alert( + 'Invalid verification code 🤔', + 'Try again. Tap the resend code button if you need a new code.', + ); + } else { + Alert.alert( + 'Something went wrong! ðŸ˜', + "Would you believe me if I told you that I don't know what happened?", + ); + } + } + } catch (error) { + Alert.alert( + 'Verifiation failed 😓', + 'Please double-check your network connection and retry.', + ); + return { + name: 'Verification error', + description: error, + }; + } +}; + +export const sendOtp = async (phone: string) => { + try { + console.log(phone); + let response = await fetch(SEND_OTP_ENDPOINT, { + method: 'POST', + body: JSON.stringify({ + phone_number: '+1' + phone, + }), + }); + + let status = response.status; + if (status === 200) { + Alert.alert( + 'New verification code sent!', + 'Check your phone messages for your code.', + ); + return true; + } else { + Alert.alert('Something went wrong!'); + return false; + } + } catch (error) { + console.log(error); + return false; + } +}; diff --git a/src/types/types.ts b/src/types/types.ts index 79f15ae9..bfd0cd93 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -95,7 +95,6 @@ export type PreviewType = | 'Discover Users' | 'Follow'; - export enum ScreenType { Profile, Search, @@ -118,3 +117,11 @@ export interface UserXType { avatar: string; cover: string; } + +/** + * We have two verification screen types, this enum is used to display proper content based on the type + */ +export enum VerificationScreenType { + Phone, + Password, +} |