Skip to content

Commit c323f86

Browse files
Merge pull request #120 from rootstrap/feat/update-password-screen
feat: add update password screen
2 parents d3a5689 + cf2e414 commit c323f86

File tree

5 files changed

+177
-0
lines changed

5 files changed

+177
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { createMutation } from 'react-query-kit';
2+
3+
import { client } from '../common';
4+
5+
type Variables = {
6+
password: string;
7+
passwordConfirmation: string;
8+
};
9+
10+
type ResponseData = {
11+
email: string;
12+
provider: string;
13+
uid: string;
14+
id: number;
15+
allow_password_change: boolean;
16+
name: string;
17+
nickname: string;
18+
image: string | null;
19+
created_at: string;
20+
updated_at: string;
21+
birthday: string | null;
22+
};
23+
24+
type Response = {
25+
success: boolean;
26+
message: string;
27+
data?: ResponseData;
28+
};
29+
30+
const updatePasswordRequest = async (variables: Variables) => {
31+
const { data } = await client({
32+
url: '/v1/users/password',
33+
method: 'PUT',
34+
data: { ...variables },
35+
headers: {
36+
'Content-Type': 'application/json',
37+
},
38+
});
39+
return data;
40+
};
41+
42+
export const useUpdatePassword = createMutation<Response, Variables>({
43+
mutationFn: (variables) => updatePasswordRequest(variables),
44+
});

src/app/(app)/settings.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ export default function Settings() {
5050
text={'settings.account.email'}
5151
value={userData?.email ?? ''}
5252
/>
53+
<Link
54+
asChild
55+
href={{
56+
pathname: '/update-password',
57+
}}
58+
>
59+
<Item text="settings.account.password" />
60+
</Link>
5361
</ItemsContainer>
5462
<ItemsContainer title="settings.generale">
5563
<LanguageItem />

src/app/_layout.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
55
import { ThemeProvider } from '@react-navigation/native';
66
import { SplashScreen, Stack } from 'expo-router';
77
import React from 'react';
8+
import { useTranslation } from 'react-i18next';
89
import { StyleSheet } from 'react-native';
910
import FlashMessage from 'react-native-flash-message';
1011
import { GestureHandlerRootView } from 'react-native-gesture-handler';
@@ -29,12 +30,19 @@ interceptors();
2930
SplashScreen.preventAutoHideAsync();
3031

3132
export default function RootLayout() {
33+
const { t } = useTranslation();
3234
return (
3335
<Providers>
3436
<Stack>
3537
<Stack.Screen name="(app)" options={{ headerShown: false }} />
3638
<Stack.Screen name="onboarding" options={{ headerShown: false }} />
3739
<Stack.Screen name="forgot-password" />
40+
<Stack.Screen
41+
name="update-password"
42+
options={{
43+
title: t('updatePassword.title'),
44+
}}
45+
/>
3846
<Stack.Screen name="sign-in" options={{ headerShown: false }} />
3947
<Stack.Screen
4048
name="sign-up"

src/app/update-password.tsx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { zodResolver } from '@hookform/resolvers/zod';
2+
import { useNavigation } from 'expo-router';
3+
import React from 'react';
4+
import { useForm } from 'react-hook-form';
5+
import { useTranslation } from 'react-i18next';
6+
import { showMessage } from 'react-native-flash-message';
7+
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
8+
import { z } from 'zod';
9+
10+
import { useUpdatePassword } from '@/api/auth/use-update-password';
11+
import { translate } from '@/core';
12+
import { Button, ControlledInput, FocusAwareStatusBar, Text, View } from '@/ui';
13+
14+
type FormValues = { password: string; passwordConfirmation: string };
15+
const MIN_CHARS = 6;
16+
17+
const schema = z
18+
.object({
19+
password: z.string().min(
20+
MIN_CHARS,
21+
translate('updatePassword.error.shortPassword', {
22+
minChars: MIN_CHARS,
23+
}),
24+
),
25+
passwordConfirmation: z.string(),
26+
})
27+
.refine((data) => data.password === data.passwordConfirmation, {
28+
message: translate('updatePassword.error.passwordsMustMatch'),
29+
path: ['passwordConfirmation'],
30+
});
31+
32+
export default function UpdatePassword() {
33+
const { t } = useTranslation();
34+
const navigate = useNavigation();
35+
36+
const { mutateAsync: updatePassword } = useUpdatePassword({
37+
onSuccess: () => {
38+
showMessage({
39+
message: t('updatePassword.successMessage'),
40+
type: 'success',
41+
});
42+
navigate.goBack();
43+
},
44+
onError: () => {
45+
showMessage({
46+
message: t('updatePassword.errorMessage'),
47+
type: 'danger',
48+
});
49+
},
50+
});
51+
52+
const { handleSubmit, control } = useForm<FormValues>({
53+
resolver: zodResolver(schema),
54+
});
55+
const onSubmit = async (data: FormValues) => {
56+
await updatePassword(data);
57+
};
58+
59+
return (
60+
<>
61+
<FocusAwareStatusBar />
62+
<KeyboardAvoidingView>
63+
<View className="gap-8 p-4">
64+
<View className="gap-2">
65+
<Text className="text-center text-2xl">
66+
{t('updatePassword.title')}
67+
</Text>
68+
<Text className="text-center text-gray-600">
69+
{t('updatePassword.description')}
70+
</Text>
71+
</View>
72+
<View className="gap-2">
73+
<ControlledInput
74+
testID="password-input"
75+
autoCapitalize="none"
76+
secureTextEntry
77+
control={control}
78+
name="password"
79+
label={t('updatePassword.passwordLabel')}
80+
placeholder={t('updatePassword.passwordPlaceholder')}
81+
/>
82+
<ControlledInput
83+
testID="confirm-password-input"
84+
autoCapitalize="none"
85+
secureTextEntry
86+
control={control}
87+
name="passwordConfirmation"
88+
label={t('updatePassword.confirmPasswordLabel')}
89+
placeholder={t('updatePassword.confirmPasswordPlaceholder')}
90+
/>
91+
<Button
92+
testID="update-password-button"
93+
label={t('updatePassword.buttonLabel')}
94+
onPress={handleSubmit(onSubmit)}
95+
/>
96+
</View>
97+
</View>
98+
</KeyboardAvoidingView>
99+
</>
100+
);
101+
}

src/translations/en.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"account": {
6969
"email": "Email",
7070
"name": "Name",
71+
"password": "Password",
7172
"title": "Account"
7273
},
7374
"app_name": "App Name",
@@ -102,6 +103,21 @@
102103
"version": "Version",
103104
"website": "Website"
104105
},
106+
"updatePassword": {
107+
"buttonLabel": "Update Password",
108+
"confirmPasswordLabel": "Confirm Password",
109+
"confirmPasswordPlaceholder": "Re-enter new password",
110+
"description": "Enter a new password for your account.",
111+
"error": {
112+
"passwordsMustMatch": "Passwords must match",
113+
"shortPassword": "Password must be at least {{minChars}} characters"
114+
},
115+
"errorMessage": "An error occurred while updating your password. Please try again.",
116+
"passwordLabel": "New Password",
117+
"passwordPlaceholder": "Enter new password",
118+
"successMessage": "Your password has been successfully updated.",
119+
"title": "Update Password"
120+
},
105121
"welcome": "Welcome to rootstrap app site",
106122
"www": {
107123
"invalidUrl": "Invalid Url"

0 commit comments

Comments
 (0)