Переглянути джерело

Merge pull request #26 from MGT-Limited/20-create-form-component-forget-password

created forgetpassword page
MGTKenYCS 1 рік тому
батько
коміт
cdd644876c

+ 13 - 0
.prettierrc

@@ -0,0 +1,13 @@
+{
+    "singleQuote": true,
+    "printWidth": 80,
+    "editor.formatOnSave": true,
+    "proseWrap": "always",
+    "tabWidth": 4,
+    "requireConfig": false,
+    "useTabs": false,
+    "trailingComma": "none",
+    "bracketSpacing": true,
+    "jsxBracketSameLine": false,
+    "semi": true
+}

+ 14 - 9
app/(tabs)/search/index.tsx

@@ -1,11 +1,16 @@
-import { Text } from "react-native";
-import React from "react";
+import { Text, View } from 'react-native';
+import React from 'react';
+import ForgetPasswordMultiStepForm from '../../../component/forgetPasswordMultiStepForm/forgetPasswordForm';
 export default function Index() {
-  /**********************************狀態管理**********************************/
-  /**********************************狀態管理**********************************/
-  /**********************************組件初始化**********************************/
-  /**********************************組件初始化**********************************/
-  /**********************************異步函數**********************************/
-  /**********************************異步函數**********************************/
-  return <Text>index</Text>;
+    /**********************************狀態管理**********************************/
+    /**********************************狀態管理**********************************/
+    /**********************************組件初始化**********************************/
+    /**********************************組件初始化**********************************/
+    /**********************************異步函數**********************************/
+    /**********************************異步函數**********************************/
+    return (
+        <View style={{ flex: 1 }}>
+            <ForgetPasswordMultiStepForm />
+        </View>
+    );
 }

+ 1 - 1
app/(tabs)/settings/index.tsx

@@ -1,5 +1,5 @@
 import React from "react";
-import MultiStepForm from "../../../component/multiStepForm/multi_step_form";
+import MultiStepForm from "../../../component/registrationMultiStepForm/multi_step_form";
 export default function Index() {
 	/**********************************狀態管理**********************************/
 	/**********************************狀態管理**********************************/

BIN
assets/successLogo.png


+ 41 - 0
component/forgetPasswordMultiStepForm/forgetPasswordForm.tsx

@@ -0,0 +1,41 @@
+import { Text, View, StyleSheet } from 'react-native';
+import { StatusBar } from 'expo-status-bar';
+import { useEffect, useState } from 'react';
+import ForgetPasswordPage from './formComponent/form';
+import { forgetPasswordFormData } from '../../types/signup';
+
+const ForgetPasswordMultiStepForm: React.FC = () => {
+    const [forgetPasswordFormData, setForgetPasswordFormData] =
+        useState<forgetPasswordFormData>({
+            phone: '',
+            phoneVerificationStatus: false,
+            otp: '',
+            otpSent: false,
+            otpAuthCompleted: false,
+            newPassword: '',
+            confirmedNewPassword: ''
+        });
+
+    useEffect(() => {
+        console.log(forgetPasswordFormData);
+    }, [forgetPasswordFormData]);
+
+    return (
+        <View style={styles.container}>
+            <ForgetPasswordPage
+                forgetPasswordFormData={forgetPasswordFormData}
+                setForgetPasswordFormData={setForgetPasswordFormData}
+            />
+            <StatusBar style="auto" />
+        </View>
+    );
+};
+
+const styles = StyleSheet.create({
+    container: {
+        flex: 1,
+        backgroundColor: '#FFFFFF'
+    }
+});
+
+export default ForgetPasswordMultiStepForm;

+ 92 - 0
component/forgetPasswordMultiStepForm/formComponent/form.tsx

@@ -0,0 +1,92 @@
+import { useState } from 'react';
+import {
+    View,
+    Text,
+    StyleSheet,
+    TouchableWithoutFeedback,
+    Keyboard
+} from 'react-native';
+
+import ResetSuccessful from './formPages/resetSuccessful';
+import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
+import ForgetPasswordPage from './formPages/forgetPasswordPage';
+import { forgetPasswordFormData } from '../../../types/signup';
+
+type ForgetPasswordFormProps = {
+    forgetPasswordFormData: forgetPasswordFormData;
+    setForgetPasswordFormData: React.Dispatch<
+        React.SetStateAction<forgetPasswordFormData>
+    >;
+};
+const ForgetPasswordForm: React.FC<ForgetPasswordFormProps> = ({
+    forgetPasswordFormData,
+    setForgetPasswordFormData
+}) => {
+    const [screen, setScreen] = useState<number>(0);
+    const FormTitle = ['忘記密碼 - 電話驗證'];
+
+    const ScreenDisplay = () => {
+        switch (screen) {
+            case 0:
+                return (
+                    <ForgetPasswordPage
+                        forgetPasswordFormData={forgetPasswordFormData}
+                        setForgetPasswordFormData={setForgetPasswordFormData}
+                        setScreen={setScreen}
+                    />
+                );
+            case 1:
+                return <ResetSuccessful />;
+
+            default:
+                return <></>;
+        }
+    };
+    return (
+        <>
+            <KeyboardAwareScrollView
+                enableOnAndroid={true}
+                resetScrollToCoords={{ x: 0, y: 0 }}
+                style={{
+                    height: "100%"
+                }}
+            >
+                {screen === 0 && (
+                    <View style={styles.topContainer}>
+                        <Text style={styles.text}>{FormTitle[screen]}</Text>
+                        <View style={styles.breakline} />
+                    </View>
+                )}
+                <View style={styles.bottomContainer}>{ScreenDisplay()}</View>
+            </KeyboardAwareScrollView>
+        </>
+    );
+};
+
+const styles = StyleSheet.create({
+    topContainer: {
+        flex: 1,
+        alignItems: 'center',
+        justifyContent: 'center',
+        paddingBottom: '25%',
+        paddingTop: '15%'
+    },
+    previouspageAndPaginationWrapper: {
+        display: 'flex',
+        width: '100%',
+        flexDirection: 'row',
+        justifyContent: 'space-between',
+        alignItems: 'center',
+        paddingHorizontal: 25
+    },
+    bottomContainer: { flex: 1 },
+    breakline: {
+        width: 24,
+        height: 1,
+        backgroundColor: '#000000',
+        marginVertical: 17
+    },
+    text: { fontSize: 24, fontWeight: '300' }
+});
+
+export default ForgetPasswordForm;

+ 218 - 0
component/forgetPasswordMultiStepForm/formComponent/formPages/forgetPasswordPage.tsx

@@ -0,0 +1,218 @@
+import { View, Text, StyleSheet, Pressable } from "react-native";
+import { useState } from "react";
+import { forgetPasswordFormData, HandleForgetPasswordFormDataChange } from "../../../type";
+import PhoneInput from "../../../global/phone_input";
+import NumberInput from "../../../global/number_input";
+import NormalButton from "../../../global/normal_button";
+import NormalInput from "../../../global/normal_input";
+
+type ForgetPasswordPageProps = {
+	forgetPasswordFormData: forgetPasswordFormData;
+	setForgetPasswordFormData: React.Dispatch<React.SetStateAction<forgetPasswordFormData>>;
+	setScreen: React.Dispatch<React.SetStateAction<number>>;
+};
+
+const ForgetPasswordPage: React.FC<ForgetPasswordPageProps> = ({
+	forgetPasswordFormData,
+	setForgetPasswordFormData,
+	setScreen,
+}) => {
+	const [authError, setAuthError] = useState("");
+	const [error, setError] = useState("");
+	const [canSendOtp, setCanSendOtp] = useState(true);
+	const [lockPhoneInput, setLockPhoneInput] = useState(false);
+
+	const handleVerification = () => {
+		if (!forgetPasswordFormData.otp && !forgetPasswordFormData.phone) {
+			setAuthError("請確保所有資料都已填寫");
+		} else if (!forgetPasswordFormData.otp) {
+			setAuthError("請輸入OTP驗證碼");
+		} else if (!forgetPasswordFormData.phone) {
+			setAuthError("請輸入電話號碼");
+		} else {
+			setAuthError("");
+			setForgetPasswordFormData((prevFormData) => ({
+				...prevFormData,
+				otpAuthCompleted: true,
+			}));
+		}
+	};
+
+	const handleFormDataChange: HandleForgetPasswordFormDataChange = (field, value) => {
+		setForgetPasswordFormData((prevFormData) => ({
+			...prevFormData,
+			[field]: value,
+		}));
+	};
+
+	const handleSubmitOtp = () => {
+		if (forgetPasswordFormData.phoneVerificationStatus) {
+			if (canSendOtp) {
+				setCanSendOtp(false);
+				setLockPhoneInput(true);
+				console.log(lockPhoneInput);
+				//can only request otp every 60 seconds
+				setTimeout(() => {
+					setCanSendOtp(true);
+					setLockPhoneInput(false);
+				}, 60000);
+				setAuthError("");
+			} else {
+				setAuthError("請等待一分鐘後再重新發送。");
+			}
+		} else {
+			setAuthError("請確保所有資料都已填寫。");
+		}
+	};
+
+	const handleFinishResetPassword = () => {
+		if (forgetPasswordFormData.newPassword !== forgetPasswordFormData.confirmedNewPassword) {
+			setError("請確保新密碼和確認密碼相同");
+		} else {
+			setError("");
+			setScreen(1);
+		}
+	};
+	return (
+		<>
+			<View style={styles.container}>
+				<View style={styles.bottomContainer}>
+					<Text style={styles.text}>驗證電話號碼後即可重置密碼</Text>
+					<PhoneInput
+						placeholder="輸入電話號碼"
+						handleForgetPasswordFormDataChange={handleFormDataChange}
+						editable={!lockPhoneInput}
+						extendedStyle={{ opacity: !lockPhoneInput ? 1 : 0.5 }}
+					/>
+					<View
+						style={{
+							display: "flex",
+							flexDirection: "row",
+							paddingVertical: 10,
+							gap: 10,
+						}}
+					>
+						<NumberInput
+							placeholder="OTP驗證碼"
+							onChangeText={(t) => handleFormDataChange("otp", t)}
+							editable={!forgetPasswordFormData.otpAuthCompleted}
+							extendedStyle={{ flex: 1, opacity: !forgetPasswordFormData.otpAuthCompleted ? 1 : 0.5 }}
+						/>
+						<NormalButton
+							title={<Text style={{ color: "#fff" }}>{lockPhoneInput ? "已發送" : "驗證"}</Text>}
+							onPress={handleSubmitOtp}
+							extendedStyle={{ flex: 1 / 2 }}
+						/>
+					</View>
+					<NormalButton
+						title={
+							<Text style={{ color: "#fff" }}>
+								{forgetPasswordFormData.otpAuthCompleted == true ? "已驗證" : "驗證"}
+							</Text>
+						}
+						onPress={handleVerification}
+						extendedStyle={
+							forgetPasswordFormData.otpAuthCompleted == true ? { backgroundColor: "#70787C" } : {}
+						}
+					/>
+
+					{authError && <Text style={styles.errorMessage}>{authError}</Text>}
+					{lockPhoneInput && (
+						<Pressable
+							disabled={forgetPasswordFormData.otpAuthCompleted}
+							onPress={() => setLockPhoneInput(false)}
+						>
+							<Text style={[styles.footer, forgetPasswordFormData.otpAuthCompleted && { opacity: 0.5 }]}>
+								修改電話號碼
+							</Text>
+						</Pressable>
+					)}
+
+					{forgetPasswordFormData.otpAuthCompleted && (
+						<View
+							style={[
+								styles.hiddenPasswordFields,
+								forgetPasswordFormData.otpAuthCompleted ? styles.opacityFull : styles.opacityZero,
+							]}
+						>
+							<NormalInput
+								placeholder="新密碼"
+								onChangeText={(t) => handleFormDataChange("newPassword", t)}
+								secureTextEntry={true}
+								textContentType={"oneTimeCode"}
+							/>
+							<NormalInput
+								placeholder="確認密碼"
+								onChangeText={(t) => handleFormDataChange("confirmedNewPassword", t)}
+								secureTextEntry={true}
+								textContentType={"oneTimeCode"}
+							/>
+							<NormalButton
+								title={<Text style={{ color: "#fff" }}>重置</Text>}
+								onPress={handleFinishResetPassword}
+								extendedStyle={{}}
+							/>
+						</View>
+					)}
+
+					{error && <Text style={styles.errorMessage}>{error}</Text>}
+				</View>
+			</View>
+		</>
+	);
+};
+
+const styles = StyleSheet.create({
+	container: {
+		flex: 1,
+		marginHorizontal: 20,
+	},
+
+	titleText: {
+		fontSize: 24,
+		fontWeight: "300",
+	},
+	bottomContainer: {
+		flex: 3,
+		paddingBottom: 100,
+	},
+	breakline: {
+		width: 24,
+		height: 1,
+		backgroundColor: "#000000",
+		marginVertical: 17,
+	},
+	text: {
+		fontSize: 18,
+		paddingBottom: 10,
+	},
+	hiddenPasswordFields: {
+		gap: 10,
+		paddingTop: 10,
+	},
+	opacityZero: {
+		opacity: 0,
+	},
+	opacityFull: {
+		opacity: 1,
+	},
+	errorMessage: {
+		fontSize: 14,
+		color: "#ff0033",
+		fontWeight: "400",
+		marginLeft: 10,
+		marginTop: 10,
+	},
+	footer: { color: "#02677D", fontSize: 16, paddingVertical: 10 },
+});
+
+export default ForgetPasswordPage;
+
+// const handleVerification = () => {
+// 	if (formData.phone === "" || otp === "") {
+// 		setError("請輸入電話號碼和OTP驗證碼");
+// 	} else {
+// 		setError("");
+// 		setScreen((currentScreenNumber) => currentScreenNumber + 1);
+// 	}
+// };

+ 51 - 0
component/forgetPasswordMultiStepForm/formComponent/formPages/resetSuccessful.tsx

@@ -0,0 +1,51 @@
+import { View, Text, StyleSheet, Image } from 'react-native';
+import NormalButton from '../../../global/normal_button';
+
+const successLogo = require('../../../../assets/successLogo.png');
+
+const ResetSuccessful = () => {
+    const imageSize = 200;
+    return (
+        <>
+            <View style={styles.container}>
+                <View style={styles.textContainer}>
+                    <Text style={styles.titleText}>重置密碼成功</Text>
+                    <Text style={styles.text}>歡迎使用 Crazy Charge</Text>
+                    <Text style={styles.text}>祝你一路順“瘋“</Text>
+                </View>
+                <Image
+                    style={{
+                        width: imageSize,
+                        height: imageSize,
+                        alignSelf: 'center',
+						marginVertical: 9
+                    }}
+                    source={successLogo}
+                />
+                <NormalButton
+                    title={<Text style={{ color: '#fff' }}>下一步</Text>}
+                    onPress={() => console.log('clicked')}
+                />
+            </View>
+        </>
+    );
+};
+const styles = StyleSheet.create({
+    container: {
+        flex: 1,
+        paddingHorizontal: 20,
+        height: '100%'
+    },
+    textContainer: {
+        justifyContent: 'flex-start',
+        alignItems: 'center',
+        // backgroundColor: 'black',
+        paddingTop: '10%',
+        gap: 15
+    },
+    titleText: { fontSize: 28 },
+    text: {
+        fontSize: 16
+    }
+});
+export default ResetSuccessful;

+ 3 - 2
component/global/normal_input.tsx

@@ -8,6 +8,7 @@ interface NormalInputProps {
 	type?: KeyboardTypeOptions;
 	secureTextEntry?: boolean;
 	value?: string;
+	textContentType?: "oneTimeCode";
 }
 
 const NormalInput: React.FC<NormalInputProps> = ({
@@ -17,6 +18,7 @@ const NormalInput: React.FC<NormalInputProps> = ({
 	onChangeText,
 	secureTextEntry = false,
 	value,
+	textContentType,
 }) => {
 	return (
 		<TextInput
@@ -25,10 +27,9 @@ const NormalInput: React.FC<NormalInputProps> = ({
 			placeholderTextColor={"#888888"}
 			secureTextEntry={secureTextEntry}
 			keyboardType={type ? type : "default"}
-			// onChangeText={(t) => console.log(t)}
-			// onChangeText={onChangeText}
 			value={value}
 			onChangeText={(value) => onChangeText(value)}
+			textContentType={textContentType}
 		/>
 	);
 };

+ 16 - 2
component/global/phone_input.tsx

@@ -1,24 +1,38 @@
 import React, { useState } from "react";
 import { View, Text, TextInput, StyleSheet, ViewStyle, StyleProp } from "react-native";
-import { HandleSignUpFormDataChange } from "../../types/signup";
+import { HandleForgetPasswordFormDataChange, HandleSignUpFormDataChange } from "../../types/signup";
 interface PhoneInputProps {
 	placeholder: string;
 	extendedStyle?: StyleProp<ViewStyle>;
 	handleFormDataChange?: HandleSignUpFormDataChange;
+	handleForgetPasswordFormDataChange?: HandleForgetPasswordFormDataChange;
 	editable?: boolean;
 }
 
-const PhoneInput: React.FC<PhoneInputProps> = ({ placeholder, extendedStyle, handleFormDataChange, editable }) => {
+const PhoneInput: React.FC<PhoneInputProps> = ({
+	placeholder,
+	extendedStyle,
+	handleFormDataChange,
+	handleForgetPasswordFormDataChange,
+	editable,
+}) => {
 	const [error, setError] = useState("");
+	//by calling <PhoneInput handleFormDataChange={}... />, we use Registration Form Type.
+	//by calling <PhoneInput handleForgetPasswordFormDataChange={}... />, we use Forget-Password Form Type.
+
 	const handleTextChange = (text: string) => {
 		if (text.length >= 8) {
 			setError("");
 			handleFormDataChange?.("phone", text);
 			handleFormDataChange?.("phoneVerificationStatus", true);
+			handleForgetPasswordFormDataChange?.("phone", text);
+			handleForgetPasswordFormDataChange?.("phoneVerificationStatus", true);
 		} else {
 			setError("Please enter at least 8 digits");
 			handleFormDataChange?.("phone", text);
 			handleFormDataChange?.("phoneVerificationStatus", false);
+			handleForgetPasswordFormDataChange?.("phone", text);
+			handleForgetPasswordFormDataChange?.("phoneVerificationStatus", false);
 		}
 	};
 

+ 0 - 0
component/multiStepForm/formComponent/form.tsx → component/registrationMultiStepForm/formComponent/form.tsx


+ 0 - 0
component/multiStepForm/formComponent/formPages/basicInformation.tsx → component/registrationMultiStepForm/formComponent/formPages/basicInformation.tsx


+ 0 - 0
component/multiStepForm/formComponent/formPages/carInformation.tsx → component/registrationMultiStepForm/formComponent/formPages/carInformation.tsx


+ 0 - 0
component/multiStepForm/formComponent/formPages/createWallet.tsx → component/registrationMultiStepForm/formComponent/formPages/createWallet.tsx


+ 0 - 0
component/multiStepForm/formComponent/formPages/finishSignUp.tsx → component/registrationMultiStepForm/formComponent/formPages/finishSignUp.tsx


+ 0 - 0
component/multiStepForm/formComponent/formPages/loginPage.tsx → component/registrationMultiStepForm/formComponent/formPages/loginPage.tsx


+ 0 - 0
component/multiStepForm/formComponent/formPages/uberDriver.tsx → component/registrationMultiStepForm/formComponent/formPages/uberDriver.tsx


+ 0 - 0
component/multiStepForm/formComponent/formPages/verification.tsx → component/registrationMultiStepForm/formComponent/formPages/verification.tsx


+ 0 - 0
component/multiStepForm/multi_step_form.tsx → component/registrationMultiStepForm/multi_step_form.tsx


+ 17 - 0
types/signup.d.ts

@@ -16,3 +16,20 @@ export type SignUpFormData = {
 export type SignUpFormDataKey = keyof SignUpFormData;
 
 export type HandleSignUpFormDataChange = <K extends SignUpFormDataKey>(field: K, value: SignUpFormData[K]) => void;
+
+export type forgetPasswordFormData = {
+	phone: string;
+	phoneVerificationStatus: boolean;
+	otp: string;
+	otpSent: boolean;
+	otpAuthCompleted: boolean;
+	newPassword: string;
+	confirmedNewPassword: string;
+};
+
+export type forgetPasswordFormDataKey = keyof forgetPasswordFormData;
+
+export type HandleForgetPasswordFormDataChange = <K extends forgetPasswordFormDataKey>(
+	field: K,
+	value: forgetPasswordFormData[K]
+) => void;