Skip to content

Commit 67cd4fc

Browse files
Guillermo MachadoGuillermo Machado
authored andcommitted
feat: add forgot password screen
1 parent eb28cb9 commit 67cd4fc

File tree

7 files changed

+169
-1
lines changed

7 files changed

+169
-1
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { createMutation } from 'react-query-kit';
2+
3+
import { client } from '../common';
4+
5+
type Variables = {
6+
email: string;
7+
};
8+
9+
type Response = {
10+
message: string;
11+
};
12+
13+
const sendForgotPasswordInstructions = async (variables: Variables) => {
14+
const { data } = await client({
15+
url: 'auth/forgot-password', // Dummy endpoint for forgot password
16+
method: 'POST',
17+
data: {
18+
email: variables.email,
19+
},
20+
headers: {
21+
'Content-Type': 'application/json',
22+
},
23+
});
24+
return data;
25+
};
26+
27+
export const useForgotPassword = createMutation<Response, Variables>({
28+
mutationFn: (variables) => sendForgotPasswordInstructions(variables),
29+
});

src/app/_layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default function RootLayout() {
3434
<Stack.Screen name="(app)" options={{ headerShown: false }} />
3535
<Stack.Screen name="onboarding" options={{ headerShown: false }} />
3636
<Stack.Screen name="login" options={{ headerShown: false }} />
37+
<Stack.Screen name="forgot-password" />
3738
</Stack>
3839
</Providers>
3940
);

src/app/forgot-password.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { showMessage } from 'react-native-flash-message';
3+
4+
import { useForgotPassword } from '@/api/auth/use-forgot-password';
5+
import {
6+
ForgotPasswordForm,
7+
type FormType as ForgotPasswordFormType,
8+
} from '@/components/forgot-password-form';
9+
import { FocusAwareStatusBar } from '@/ui';
10+
11+
export default function ForgotPassword() {
12+
const { t } = useTranslation();
13+
14+
const { mutate: sendForgotPasswordInstructions } = useForgotPassword({
15+
onSuccess: () => {
16+
showMessage({
17+
message: t('forgotPassword.successMessage'),
18+
type: 'success',
19+
});
20+
},
21+
onError: () => {
22+
showMessage({
23+
message: t('forgotPassword.errorMessage'),
24+
type: 'danger',
25+
});
26+
},
27+
});
28+
29+
const onSubmit = (data: ForgotPasswordFormType) => {
30+
sendForgotPasswordInstructions(data);
31+
};
32+
return (
33+
<>
34+
<FocusAwareStatusBar />
35+
<ForgotPasswordForm onSubmit={onSubmit} />
36+
</>
37+
);
38+
}

src/app/login.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { useRouter } from 'expo-router';
2+
import { useTranslation } from 'react-i18next';
23
import { showMessage } from 'react-native-flash-message';
34

45
import { useLogin } from '@/api/auth/use-login';
56
import type { LoginFormProps } from '@/components/login-form';
67
import { LoginForm } from '@/components/login-form';
78
import { useAuth } from '@/core';
8-
import { FocusAwareStatusBar } from '@/ui';
9+
import { Button, FocusAwareStatusBar } from '@/ui';
910

1011
export default function Login() {
12+
const { t } = useTranslation();
1113
const router = useRouter();
1214
const signIn = useAuth.use.signIn();
1315
const { mutate: login } = useLogin({
@@ -21,10 +23,20 @@ export default function Login() {
2123
const onSubmit: LoginFormProps['onSubmit'] = (data) => {
2224
login(data);
2325
};
26+
27+
const navigateToForgotPasswordScreen = () => {
28+
router.push('/forgot-password');
29+
};
2430
return (
2531
<>
2632
<FocusAwareStatusBar />
2733
<LoginForm onSubmit={onSubmit} />
34+
<Button
35+
testID="login-button"
36+
variant="link"
37+
label={t('auth.sign-in.forgotPasswordButton')}
38+
onPress={navigateToForgotPasswordScreen}
39+
/>
2840
</>
2941
);
3042
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { zodResolver } from '@hookform/resolvers/zod';
2+
import { View } from 'moti';
3+
import { useForm } from 'react-hook-form';
4+
import { useTranslation } from 'react-i18next';
5+
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
6+
import { z } from 'zod';
7+
8+
import { translate } from '@/core';
9+
import { Button, ControlledInput, Text } from '@/ui';
10+
11+
const schema = z.object({
12+
email: z
13+
.string()
14+
.email(translate('forgotPassword.emailInvalidFormatFormError')),
15+
});
16+
17+
export type FormType = z.infer<typeof schema>;
18+
19+
export const ForgotPasswordForm = (props: {
20+
onSubmit: (data: FormType) => void;
21+
}) => {
22+
const { t } = useTranslation();
23+
const { handleSubmit, control } = useForm<{
24+
email: string;
25+
}>({
26+
resolver: zodResolver(schema),
27+
});
28+
29+
const onSubmit = (data: FormType) => {
30+
props.onSubmit(data);
31+
};
32+
33+
return (
34+
<KeyboardAvoidingView>
35+
<View className="gap-8 p-4">
36+
<View className="gap-2">
37+
<Text
38+
testID="forgot-password-form-title"
39+
className="text-center text-2xl"
40+
>
41+
{t('forgotPassword.title')}
42+
</Text>
43+
<Text className="text-center text-gray-600">
44+
{t('forgotPassword.description')}
45+
</Text>
46+
</View>
47+
<View className="gap-2">
48+
<ControlledInput
49+
testID="email-input"
50+
autoCapitalize="none"
51+
autoComplete="email"
52+
control={control}
53+
name="email"
54+
label={t('forgotPassword.emailLabel')}
55+
placeholder={t('forgotPassword.emailPlaceholder')}
56+
/>
57+
<Text className="text-sm text-gray-500">
58+
{t('forgotPassword.instructions')}
59+
</Text>
60+
<Button
61+
testID="send-email-button"
62+
label={t('forgotPassword.buttonLabel')}
63+
onPress={handleSubmit(onSubmit)}
64+
className=""
65+
/>
66+
</View>
67+
</View>
68+
</KeyboardAvoidingView>
69+
);
70+
};

src/components/login-form.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export const LoginForm = ({ onSubmit = () => {} }: LoginFormProps) => {
4242

4343
<ControlledInput
4444
testID="email-input"
45+
autoCapitalize="none"
46+
autoComplete="email"
4547
control={control}
4648
name="email"
4749
label="Email (optional)"

src/translations/en.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
11
{
2+
"auth": {
3+
"sign-in": {
4+
"forgotPasswordButton": "Forgot Password"
5+
}
6+
},
7+
"forgotPassword": {
8+
"buttonLabel": "Send Reset Instructions",
9+
"description": "Enter your email address below and we'll send you instructions to reset your password.",
10+
"emailInvalidFormatFormError": "Invalid email format",
11+
"emailLabel": "Email Address",
12+
"emailPlaceholder": "Enter your email",
13+
"errorMessage": "There was an error sending the instructions. Please try again.",
14+
"instructions": "You'll receive an email with a link to reset your password. Please check your inbox.",
15+
"successMessage": "Instructions to reset your password have been sent to your email.",
16+
"title": "Forgot your password?"
17+
},
218
"onboarding": {
319
"message": "Welcome to rootstrap app site"
420
},

0 commit comments

Comments
 (0)