Skip to content

Commit 0dfb10c

Browse files
authored
feat(forgot-password): setup ui and logic (#117)
* feat(forgot-password): setup ui and logic * apply review changes * feat: remove title in header
1 parent efd5fa4 commit 0dfb10c

File tree

6 files changed

+126
-7
lines changed

6 files changed

+126
-7
lines changed

mobile/app/(auth)/_layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const GuestLayout = () => {
2020
<Stack.Screen name="login" options={{ headerShown: false }} />
2121
<Stack.Screen name="register" options={{ headerShown: false }} />
2222
<Stack.Screen name="verify-email" options={{ headerShown: false }} />
23+
<Stack.Screen name="forgot-password" options={{ headerTitle: '', headerShown: true }} />
2324
</Stack>
2425
);
2526
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import ForgotPassword from '@/screens/auth/forgot-password';
2+
import React from 'react';
3+
4+
export default function ForgotPasswordScreen() {
5+
return <ForgotPassword />;
6+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
import { Text, View } from 'react-native';
3+
4+
export default function ResetPasswordScreen() {
5+
return (
6+
<View>
7+
<Text>ResetPasswordScreen</Text>
8+
</View>
9+
);
10+
}

mobile/features/auth/hook.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
33
import { AxiosError } from 'axios';
44
import { useRouter } from 'expo-router';
55
import { Alert } from 'react-native';
6-
import { login, logout, register, requestOtp, verifyEmail } from './api';
6+
import { forgotPassword, login, logout, register, requestOtp, verifyEmail } from './api';
77
import { useAuth } from './auth-context';
8-
import type { LoginSchema, LogoutSchema, RegisterSchema, VerifyEmailSchema } from './schema';
8+
import type { ForgotPasswordSchema, LoginSchema, LogoutSchema, RegisterSchema, VerifyEmailSchema } from './schema';
99
import { useAuthStore } from './store';
1010

1111
const useRegister = () => {
@@ -70,7 +70,6 @@ const useVerifyEmail = () => {
7070
mutationFn: (payload: VerifyEmailSchema) => verifyEmail(payload),
7171
onSuccess: (data) => {
7272
if (!credentials) {
73-
Alert.alert('Error', 'No credentials found for login');
7473
router.push('/login');
7574
return;
7675
}
@@ -104,6 +103,20 @@ const useRequestOtp = () => {
104103
});
105104
};
106105

106+
const useForgotPassword = () => {
107+
const router = useRouter();
108+
109+
return useMutation({
110+
mutationFn: (payload: ForgotPasswordSchema) => forgotPassword(payload),
111+
onSuccess: (data, variables) => {
112+
router.replace({ pathname: '/reset-password', params: { email: variables.email } });
113+
},
114+
onError: (data: AxiosError<ErrorResponse>) => {
115+
Alert.alert('Request Failed', data.response?.data?.message || data.message);
116+
},
117+
});
118+
};
119+
107120
const useLogout = () => {
108121
const queryClient = useQueryClient();
109122
const { logout: authLogout } = useAuth();
@@ -123,4 +136,4 @@ const useLogout = () => {
123136
});
124137
};
125138

126-
export { useLogin, useLogout, useRegister, useRequestOtp, useVerifyEmail };
139+
export { useForgotPassword, useLogin, useLogout, useRegister, useRequestOtp, useVerifyEmail };
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Button } from '@/components/reusables/ui/button';
2+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/reusables/ui/card';
3+
import { Input } from '@/components/reusables/ui/input';
4+
import { Label } from '@/components/reusables/ui/label';
5+
import GoldGradient from '@/components/ui/gold-gradient';
6+
import { useForgotPassword } from '@/features/auth/hook';
7+
import { forgotPasswordSchema, type ForgotPasswordSchema } from '@/features/auth/schema';
8+
import useForm from '@/hooks/use-app-form';
9+
import { useLocalSearchParams } from 'expo-router';
10+
import React from 'react';
11+
import {
12+
ActivityIndicator,
13+
KeyboardAvoidingView,
14+
Platform,
15+
ScrollView,
16+
Text,
17+
useColorScheme,
18+
View,
19+
} from 'react-native';
20+
21+
const ForgotPassword = () => {
22+
const colorScheme = useColorScheme();
23+
const { email } = useLocalSearchParams<{ email?: string }>();
24+
const { mutate: forgotPassword, isPending } = useForgotPassword();
25+
const { form, errors, handleChange, handleSubmit } = useForm<ForgotPasswordSchema>({
26+
data: {
27+
email: email ?? '',
28+
},
29+
schema: forgotPasswordSchema,
30+
onSubmit: (data) => forgotPassword(data),
31+
});
32+
33+
return (
34+
<KeyboardAvoidingView
35+
className="flex-1"
36+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
37+
keyboardVerticalOffset={Platform.OS === 'ios' ? 80 : 0}
38+
>
39+
<ScrollView
40+
keyboardShouldPersistTaps="handled"
41+
contentContainerClassName="sm:flex-1 items-center justify-center p-4 py-8 sm:py-4 sm:p-6 mt-safe"
42+
keyboardDismissMode="interactive"
43+
>
44+
<View className="w-full max-w-sm">
45+
<View className="gap-6">
46+
<Card className="bg-transparent border-0">
47+
<CardHeader>
48+
<CardTitle className="text-center text-gold-text text-xl sm:text-left">Forgot Password</CardTitle>
49+
<CardDescription className="text-center sm:text-left">
50+
Yeah! It happens. We&apos;ve got your back. Simply supply your registered email below and you&apos;ll
51+
be set to go.
52+
</CardDescription>
53+
</CardHeader>
54+
<CardContent className="gap-6">
55+
<View className="gap-6">
56+
<View className="gap-1.5">
57+
<Label htmlFor="email">Email</Label>
58+
<Input
59+
id="email"
60+
placeholder="m@example.com"
61+
keyboardType="email-address"
62+
autoComplete="email"
63+
autoCapitalize="none"
64+
editable={!isPending}
65+
value={form.email}
66+
onChangeText={(text) => handleChange('email', text)}
67+
returnKeyType="next"
68+
submitBehavior="submit"
69+
/>
70+
{errors?.email && <Text className="text-sm text-destructive">{errors?.email}</Text>}
71+
</View>
72+
<GoldGradient>
73+
<Button className="bg-transparent w-full" onPress={handleSubmit} disabled={isPending}>
74+
{isPending ? (
75+
<ActivityIndicator color={colorScheme === 'dark' ? '#000000' : '#ffffff'} />
76+
) : (
77+
<Text>Continue</Text>
78+
)}
79+
</Button>
80+
</GoldGradient>
81+
</View>
82+
</CardContent>
83+
</Card>
84+
</View>
85+
</View>
86+
</ScrollView>
87+
</KeyboardAvoidingView>
88+
);
89+
};
90+
91+
export default ForgotPassword;

mobile/screens/auth/login.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,7 @@ const Login = () => {
8383
variant="link"
8484
size="sm"
8585
className="ml-auto h-4 px-1 py-0 sm:h-4"
86-
onPress={() => {
87-
// TODO: Navigate to forgot password screen
88-
}}
86+
onPress={() => router.push({ pathname: '/forgot-password', params: { email: form.email } })}
8987
>
9088
<Text className="text-gold-text font-normal leading-4">Forgot your password?</Text>
9189
</Button>

0 commit comments

Comments
 (0)