Skip to content

Commit c6af767

Browse files
mostafahassan04AhmedSobhy01
authored andcommitted
feat(forgot-password): forgot password - [CU-869aqfgqb] (#37)
1 parent 121f136 commit c6af767

45 files changed

Lines changed: 3825 additions & 521 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/components/auth/login/IdentifierStep.vue

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,44 @@
11
<script setup lang="ts">
2-
import { ref } from 'vue';
32
import * as yup from 'yup';
43
import { useForm } from 'vee-validate';
54
import { useLoginStore } from '~/stores/auth/login';
65
import { showToaster } from '@/utils/showToaster';
76
87
const loginStore = useLoginStore();
9-
const error = ref('');
108
119
const schema = yup.object({
12-
identifier: yup.string(),
10+
identifier: yup.string().trim().required($t('errors.IDENTIFIER_REQUIRED')),
1311
});
1412
15-
const { defineField, handleSubmit, resetForm } = useForm({
13+
const { defineField, handleSubmit, isSubmitting, meta } = useForm({
1614
validationSchema: schema,
1715
initialValues: {
1816
identifier: '',
1917
},
20-
validateOnMount: true,
18+
validateOnMount: false,
2119
});
2220
2321
const onSubmit = handleSubmit(async (values) => {
24-
error.value = '';
25-
2622
try {
27-
if (!values.identifier || values.identifier.trim() === '') {
28-
throw new Error($t('errors.IDENTIFIER_REQUIRED'));
29-
}
30-
const success = await loginStore.checkIdentifierExists(values.identifier);
23+
const success = await loginStore.checkUserExists(values.identifier.trim());
3124
if (!success) {
3225
throw new Error($t('errors.USER_NOT_FOUND'));
3326
}
34-
resetForm({ values: { identifier: '' } });
3527
} catch (err: unknown) {
36-
if (err instanceof Error) {
37-
error.value = err.message || $t('errors.GENERIC_ERROR');
38-
showToaster('error', error.value);
39-
} else {
40-
error.value = $t('errors.GENERIC_ERROR');
41-
showToaster('error', error.value);
42-
}
43-
console.error('Identifier check error:', error.value);
28+
showToaster('error', (err as Error).message || $t('errors.GENERIC_ERROR'));
4429
}
4530
});
4631
const [_identifier, identifierAttrs] = defineField('identifier');
4732
</script>
4833

4934
<template>
5035
<form @submit.prevent="onSubmit">
51-
<UiDialogHeader class="mt-3 px-8 py-4">
52-
<UiDialogTitle class="mx-auto w-75 text-3xl font-bold">{{
53-
$t('Sign in to Raven')
36+
<UiDialogHeader class="mt-1 px-8 py-4">
37+
<UiDialogTitle class="mx-auto w-75 text-start text-3xl font-bold">{{
38+
$t('login.identifier-step.title')
5439
}}</UiDialogTitle>
5540
</UiDialogHeader>
56-
<div class="mx-auto mt-7 w-75">
41+
<div class="mx-auto mt-5 w-75">
5742
<section class="flex flex-col gap-4">
5843
<UiButton
5944
class="bg-oauth dark:hover:bg-oauth/80 hover:bg-oauth/110 border-foreground mb-1 border text-black"
@@ -62,7 +47,7 @@ const [_identifier, identifierAttrs] = defineField('identifier');
6247
data-testid="google-button"
6348
>
6449
<Icon name="devicon:google" width="128" height="128"></Icon>
65-
{{ $t('root.auth.google-signin') }}</UiButton
50+
{{ $t('login.identifier-step.google-signin') }}</UiButton
6651
>
6752
<UiButton
6853
class="bg-oauth dark:hover:bg-oauth/80 hover:bg-oauth/110 border-foreground mb-1 border text-black"
@@ -71,23 +56,28 @@ const [_identifier, identifierAttrs] = defineField('identifier');
7156
data-testid="github-button"
7257
>
7358
<Icon name="devicon:github" width="128" height="128"></Icon>
74-
{{ $t('root.auth.github-signin') }}</UiButton
59+
{{ $t('login.identifier-step.github-signin') }}</UiButton
7560
>
7661
</section>
7762
<p class="py-2 text-center">{{ $t('root.auth.separator') }}</p>
7863
<section class="flex flex-col gap-4">
7964
<UiFormFieldInput
80-
class="mb-4"
81-
placeholder="email or username"
65+
:placeholder="$t('login.email-or-username')"
8266
type="text"
8367
name="identifier"
8468
v-bind="identifierAttrs"
8569
data-testid="identifier-input"
8670
></UiFormFieldInput>
8771
</section>
8872
</div>
89-
<UiDialogFooter>
90-
<UiButton class="mb-1 w-75" size="lg" type="submit" data-testid="submit-button">
73+
<UiDialogFooter class="absolute end-0 bottom-15 w-full">
74+
<UiButton
75+
class="mb-1 w-75"
76+
size="lg"
77+
type="submit"
78+
data-testid="submit-button"
79+
:disabled="!meta.valid || isSubmitting"
80+
>
9181
{{ $t('ui.next') }}
9282
</UiButton>
9383
<UiButton
@@ -96,18 +86,20 @@ const [_identifier, identifierAttrs] = defineField('identifier');
9686
variant="outline"
9787
type="button"
9888
data-testid="forgot-password-button"
89+
@click="loginStore.openForgotPasswordDialog"
9990
>
100-
{{ $t('root.auth.forget-password') }}
91+
{{ $t('login.forgot-password') }}
10192
</UiButton>
10293
<p class="mt-6">
103-
{{ $t('root.auth.dont-have-account') }}
104-
<NuxtLink to="/auth/signup" class="text-primary" data-testid="signup-link">
105-
{{ $t('root.auth.signup') }}
106-
</NuxtLink>
94+
{{ $t('login.dont-have-account') }}
95+
<span
96+
class="text-primary cursor-pointer hover:underline"
97+
data-testid="signup-link"
98+
@click="loginStore.openSignupDialog"
99+
>
100+
{{ $t('login.signup') }}
101+
</span>
107102
</p>
108-
<!-- <Transition name="fade"
109-
><p v-if="error" class="mt-3 text-red-500">{{ error }}</p></Transition
110-
> -->
111103
</UiDialogFooter>
112104
</form>
113105
</template>

app/components/auth/login/LoginDialog.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@ const handleDialogChange = (val: boolean) => {
1515
<UiDialog :open="loginStore.open" @update:open="handleDialogChange">
1616
<UiDialogContent class="pt-2">
1717
<UiSpinner v-if="loginStore.loading" class="mx-auto my-auto"></UiSpinner>
18-
<AuthLoginIdentifierStep v-if="loginStore.step === 0" id="identifier-step-test" />
19-
<AuthLoginPasswordStep v-if="loginStore.step === 1" id="password-step-test" />
18+
<AuthLoginIdentifierStep
19+
v-if="loginStore.step === 0 && !loginStore.loading"
20+
id="identifier-step-test"
21+
/>
22+
<AuthLoginPasswordStep
23+
v-if="loginStore.step === 1 && !loginStore.loading"
24+
id="password-step-test"
25+
/>
2026
</UiDialogContent>
2127
</UiDialog>
2228
</template>

app/components/auth/login/PasswordStep.vue

Lines changed: 40 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script setup lang="ts">
2-
import { ref } from 'vue';
32
import * as yup from 'yup';
43
import { useForm } from 'vee-validate';
54
import { storeToRefs } from 'pinia';
@@ -8,45 +7,28 @@ import { showToaster } from '@/utils/showToaster';
87
98
const loginStore = useLoginStore();
109
const { identifier } = storeToRefs(loginStore);
11-
const error = ref('');
1210
1311
const schema = yup.object({
14-
password: yup.string(),
12+
password: yup.string().trim().required($t('errors.PASSWORD_REQUIRED')),
1513
});
1614
17-
const { defineField, handleSubmit, resetForm } = useForm({
15+
const { defineField, handleSubmit, isSubmitting, meta } = useForm({
1816
validationSchema: schema,
1917
initialValues: {
2018
password: '',
2119
},
22-
validateOnMount: true,
20+
validateOnMount: false,
2321
});
2422
2523
const onSubmit = handleSubmit(async (values) => {
26-
error.value = '';
27-
2824
try {
29-
if (!values.password || values.password.trim() === '') {
30-
throw new Error($t('errors.PASSWORD_REQUIRED'));
31-
}
32-
3325
const submissionValues = {
3426
identifier: identifier.value,
35-
password: values.password,
27+
password: values.password.trim(),
3628
};
37-
3829
await loginStore.submitLogin(submissionValues);
39-
40-
resetForm({ values: { password: '' } });
4130
} catch (err: unknown) {
42-
if (err instanceof Error) {
43-
error.value = err.message || $t('errors.GENERIC_ERROR');
44-
showToaster('error', error.value);
45-
} else {
46-
error.value = $t('errors.GENERIC_ERROR');
47-
showToaster('error', error.value);
48-
}
49-
console.error('Login error:', error.value);
31+
showToaster('error', (err as Error).message || $t('errors.GENERIC_ERROR'));
5032
}
5133
});
5234
@@ -55,42 +37,49 @@ const [_password, passwordAttrs] = defineField('password');
5537

5638
<template>
5739
<form @submit.prevent="onSubmit">
58-
<UiDialogHeader class="mt-3 px-8 py-4">
59-
<UiDialogTitle class="mx-auto w-100 text-3xl font-bold">
60-
{{ $t('Enter your password') }}
40+
<UiDialogHeader class="mt-1 px-8 py-4">
41+
<UiDialogTitle class="mx-auto w-100 text-start text-3xl font-bold">
42+
{{ $t('login.password-step.title') }}
6143
</UiDialogTitle>
6244
</UiDialogHeader>
6345

6446
<div class="mx-auto mt-6 w-100">
65-
<UiFormFieldInput
66-
class="input-readonly mb-7"
67-
name="identifier"
68-
type="text"
69-
:model-value="identifier"
70-
:placeholder="loginStore.type ? loginStore.type : ''"
71-
readonly
72-
/>
73-
<UiFormFieldPassword name="password" v-bind="passwordAttrs" />
74-
<p class="text-primary ms-1 mt-1 block text-sm" data-testid="forgot-password-link">
75-
<NuxtLink to="/auth/forgot-password">
76-
{{ $t('root.auth.forget-password') }}
77-
</NuxtLink>
47+
<section class="flex flex-col gap-6">
48+
<UiFormFieldInput
49+
class="input-readonly"
50+
name="identifier"
51+
type="text"
52+
:model-value="identifier"
53+
:placeholder="
54+
loginStore.type ? $t(`login.${loginStore.type}`) : $t('login.email-or-username')
55+
"
56+
readonly
57+
/>
58+
<UiFormFieldPassword
59+
name="password"
60+
:placeholder="$t('login.password')"
61+
v-bind="passwordAttrs"
62+
/>
63+
</section>
64+
<p
65+
class="text-primary ms-1 mt-2 block w-fit cursor-pointer text-sm hover:underline"
66+
data-testid="forgot-password-link"
67+
@click="loginStore.openForgotPasswordDialog"
68+
>
69+
{{ $t('login.forgot-password') }}
7870
</p>
7971
</div>
8072

81-
<UiDialogFooter class="mt-40">
82-
<UiButton class="mb-1 w-100" size="lg" type="submit" data-testid="submit-button">
83-
{{ $t('root.auth.signin') }}
73+
<UiDialogFooter class="absolute end-0 bottom-15 w-full">
74+
<UiButton
75+
class="mb-1 w-100"
76+
size="lg"
77+
type="submit"
78+
data-testid="submit-button"
79+
:disabled="!meta.valid || isSubmitting"
80+
>
81+
{{ $t('login.password-step.signin') }}
8482
</UiButton>
85-
<p class="mt-4 w-fit">
86-
{{ $t('root.auth.dont-have-account') }}
87-
<NuxtLink to="/auth/signup" class="text-primary" data-testid="signup-link">
88-
{{ $t('root.auth.signup') }}
89-
</NuxtLink>
90-
</p>
91-
<!-- <Transition name="fade"
92-
><p v-if="error" class="mt-3 text-red-500">{{ error }}</p></Transition
93-
> -->
9483
</UiDialogFooter>
9584
</form>
9685
</template>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<script setup lang="ts">
2+
import * as yup from 'yup';
3+
import { useForm } from 'vee-validate';
4+
import { usePasswordStore } from '~/stores/auth/password';
5+
import { showToaster } from '@/utils/showToaster';
6+
import { createPasswordSchema } from '~/schemas/auth';
7+
8+
const passwordStore = usePasswordStore();
9+
const { t } = useI18n();
10+
11+
const schema = yup.object({
12+
newPassword: createPasswordSchema(t),
13+
confirmPassword: createPasswordSchema(t).oneOf(
14+
[yup.ref('newPassword')],
15+
t('errors.PASSWORD_MISMATCH'),
16+
),
17+
});
18+
19+
const { defineField, handleSubmit, isSubmitting, meta } = useForm({
20+
validationSchema: schema,
21+
initialValues: { newPassword: '', confirmPassword: '' },
22+
validateOnMount: false,
23+
});
24+
25+
const [_newPassword, newPasswordAttrs] = defineField('newPassword');
26+
const [_confirmPassword, confirmPasswordAttrs] = defineField('confirmPassword');
27+
28+
const onSubmit = handleSubmit(async (values) => {
29+
try {
30+
await passwordStore.resetPassword(values.newPassword.trim());
31+
} catch (err: unknown) {
32+
showToaster('error', (err as Error).message || t('errors.GENERIC_ERROR'));
33+
}
34+
});
35+
</script>
36+
37+
<template>
38+
<form @submit.prevent="onSubmit">
39+
<UiDialogHeader class="mt-3 w-fit px-8 py-4">
40+
<UiDialogTitle class="text-start text-3xl font-bold">
41+
{{ $t('forgot-password.new-password.title') }}
42+
</UiDialogTitle>
43+
<p class="text-muted-foreground mx-auto mt-2 text-start text-sm">
44+
{{ $t('forgot-password.new-password.description') }}
45+
</p>
46+
<p class="text-muted-foreground mx-auto mt-2 text-start text-sm">
47+
{{ $t('forgot-password.new-password.warning') }}
48+
</p>
49+
</UiDialogHeader>
50+
51+
<div class="mx-auto mt-2 px-8">
52+
<section class="flex flex-col gap-6">
53+
<UiFormFieldPassword
54+
:placeholder="$t('forgot-password.new-password.password.label')"
55+
name="newPassword"
56+
v-bind="newPasswordAttrs"
57+
data-testid="new-password-input"
58+
/>
59+
<UiFormFieldPassword
60+
:placeholder="$t('forgot-password.new-password.confirm-password.label')"
61+
name="confirmPassword"
62+
v-bind="confirmPasswordAttrs"
63+
data-testid="confirm-password-input"
64+
/>
65+
</section>
66+
</div>
67+
68+
<UiDialogFooter class="absolute end-0 bottom-8 w-full">
69+
<UiButton
70+
class="w-100"
71+
size="xl"
72+
type="submit"
73+
data-testid="submit-button"
74+
:disabled="!meta.valid || isSubmitting"
75+
>
76+
{{ $t('forgot-password.new-password.change-password-button') }}
77+
</UiButton>
78+
</UiDialogFooter>
79+
</form>
80+
</template>

0 commit comments

Comments
 (0)