Skip to content

Commit ed2765a

Browse files
committed
Refactor account-related views and tests
- Updated FormRegister.test.ts to use TypeScript and improved test structure. - Replaced PageEmailVerificationRequest.test.js with PageEmailVerificationRequest.test.ts, enhancing test coverage and structure. - Created PageForgotPassword.test.ts to replace the previous JavaScript test file, adding comprehensive tests for the forgot password flow. - Converted PageRegister.test.js to PageRegister.test.ts, ensuring TypeScript usage and improved test clarity. - Refactored PageEmailVerificationRequest.vue to streamline the email verification process and improve code readability. - Enhanced PageForgotPassword.vue to include a multi-step process for password recovery, improving user experience. - Updated PageLogin.vue to utilize router links instead of emitted events for navigation, simplifying the component logic. - Refactored PageRegister.vue to include a redirect if registration is disabled, improving user flow.
1 parent 4ea4efa commit ed2765a

File tree

66 files changed

+1912
-1422
lines changed

Some content is hidden

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

66 files changed

+1912
-1422
lines changed

packages/skeleton/app/assets/components/NavBar.vue

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
<script setup>
2-
import { useRouter } from 'vue-router'
32
import { useAuthStore } from '@userfrosting/sprinkle-account/stores'
43
import { useConfigStore } from '@userfrosting/sprinkle-core/stores'
5-
const router = useRouter()
64
const config = useConfigStore()
75
86
// Logout API variables
@@ -15,11 +13,8 @@ const auth = useAuthStore()
1513
<UFNavBarItem
1614
:to="{ name: 'account.register' }"
1715
:label="$t('REGISTER')"
18-
v-if="!auth.isAuthenticated" />
19-
<UFNavBarLogin
20-
v-if="!auth.isAuthenticated"
21-
@goto-login="router.push({ name: 'account.login' })"
22-
@goto-registration="router.push({ name: 'account.register' })" />
16+
v-if="!auth.isAuthenticated && useConfigStore().get('site.registration.enabled')" />
17+
<UFNavBarLogin v-if="!auth.isAuthenticated" />
2318
<UFNavBarUserCard
2419
v-if="auth.isAuthenticated"
2520
:username="auth.user.full_name"

packages/sprinkle-account/app/assets/composables/forgotPassword.ts

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
export { forgotPassword } from './forgotPassword'
21
export { useRegisterApi } from './useRegisterApi'
32
export { useUserProfileEditApi } from './useUserProfileEditApi'
43
export { useUserPasswordEditApi } from './useUserPasswordEditApi'
54
export { useUserEmailEditApi } from './useUserEmailEditApi'
65
export { useEmailVerificationApi } from './useEmailVerificationApi'
6+
export { useForgotPasswordApi } from './useForgotPasswordApi'
77
export { useAuthorizationManager } from './useAuthorizationManager'

packages/sprinkle-account/app/assets/composables/useEmailVerificationApi.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import axios from 'axios'
33
import { Severity } from '@userfrosting/sprinkle-core/interfaces'
44
import type { AlertInterface } from '@userfrosting/sprinkle-core/interfaces'
55
import type {
6+
ResendVerificationRequest,
67
ResendVerificationResponse,
78
ValidateCodeRequest,
89
ValidateCodeResponse
@@ -27,11 +28,15 @@ export function useEmailVerificationApi() {
2728
* @return {Promise} - The request success message given by the API. Throws an error
2829
* (AlertInterface) if the request failed.
2930
*/
30-
async function resendVerification(email: string): Promise<string> {
31+
async function requestVerificationCode(email: string): Promise<string> {
3132
apiLoading.value = true
3233
apiError.value = null
34+
const data: ResendVerificationRequest = {
35+
email: email
36+
}
37+
3338
return axios
34-
.post<ResendVerificationResponse>('/account/verify/request', { email: email })
39+
.post<ResendVerificationResponse>('/account/verify/request', data)
3540
.then((response): string => {
3641
return response.data.message
3742
})
@@ -97,5 +102,5 @@ export function useEmailVerificationApi() {
97102
})
98103
}
99104

100-
return { resendVerification, submitVerificationCode, apiLoading, apiError }
105+
return { requestVerificationCode, submitVerificationCode, apiLoading, apiError }
101106
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { ref } from 'vue'
2+
import axios from 'axios'
3+
import { Severity } from '@userfrosting/sprinkle-core/interfaces'
4+
import type { AlertInterface } from '@userfrosting/sprinkle-core/interfaces'
5+
import type {
6+
ForgotPasswordCodeRequest,
7+
ForgotPasswordCodeResponse,
8+
ForgotPasswordSetPasswordRequest,
9+
ForgotPasswordSetPasswordResponse
10+
} from '../interfaces'
11+
12+
/**
13+
* API Composable
14+
*/
15+
export function useForgotPasswordApi() {
16+
const apiLoading = ref<Boolean>(false)
17+
const apiError = ref<AlertInterface | null>(null)
18+
19+
/**
20+
* First step of the process. Ask the server to send a one time code to the
21+
* user by email.
22+
*
23+
* @param email The user email to send the one time code to.
24+
*
25+
* @return {Promise} - The request success message given by the API. Throws an error
26+
* (AlertInterface) if the request failed.
27+
*/
28+
async function requestCode(email: string): Promise<string> {
29+
apiLoading.value = true
30+
apiError.value = null
31+
const data: ForgotPasswordCodeRequest = {
32+
email: email
33+
}
34+
return axios
35+
.post<ForgotPasswordCodeResponse>('/account/forgot-password/request', data)
36+
.then((response): string => {
37+
return response.data.message
38+
})
39+
.catch((err) => {
40+
apiError.value = {
41+
...{
42+
description: 'An error as occurred',
43+
style: Severity.Danger,
44+
closeBtn: true
45+
},
46+
...err.response.data
47+
}
48+
49+
throw apiError.value
50+
})
51+
.finally(() => {
52+
apiLoading.value = false
53+
})
54+
}
55+
56+
/**
57+
* Second step of the password reset process. Ask the server to
58+
* verify the code entered by the user.
59+
*
60+
* @param email string - The email to validate.
61+
* @param code string - The verification code to validate.
62+
*
63+
* @return {Promise} - A success message returned by the API. Throws an error
64+
* (AlertInterface) if the request failed.
65+
*/
66+
async function setPassword(
67+
data: ForgotPasswordSetPasswordRequest
68+
): Promise<ForgotPasswordSetPasswordResponse> {
69+
apiLoading.value = true
70+
apiError.value = null
71+
72+
return axios
73+
.post<ForgotPasswordSetPasswordResponse>('/account/forgot-password/set-password', data)
74+
.then((response): ForgotPasswordSetPasswordResponse => {
75+
return {
76+
message: response.data.message
77+
}
78+
})
79+
.catch((err) => {
80+
apiError.value = {
81+
...{
82+
description: 'An error as occurred',
83+
style: Severity.Danger,
84+
closeBtn: true
85+
},
86+
...err.response.data
87+
}
88+
89+
throw apiError.value
90+
})
91+
.finally(() => {
92+
apiLoading.value = false
93+
})
94+
}
95+
96+
return { requestCode, setPassword, apiLoading, apiError }
97+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { ApiResponse } from '@userfrosting/sprinkle-core/interfaces'
2+
3+
/**
4+
* API Interfaces - Defines the expected requests and responses for the API.
5+
*
6+
* These interfaces are associated with the `ForgetPasswordRequestAction`
7+
* and `ForgetPasswordSetPasswordAction` APIs, which are accessed via the
8+
* POST `/account/forgot-password/request` and POST `/account/forgot-password/
9+
* set-password` endpoints.
10+
*/
11+
export interface ForgotPasswordCodeRequest {
12+
email: string
13+
}
14+
export interface ForgotPasswordSetPasswordRequest {
15+
email: string
16+
password: string
17+
passwordc: string
18+
code: string
19+
}
20+
export interface ForgotPasswordCodeResponse extends ApiResponse {}
21+
export interface ForgotPasswordSetPasswordResponse extends ApiResponse {}

packages/sprinkle-account/app/assets/interfaces/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ export type { PasswordEditRequest } from './PasswordEditApi'
99
export type { EmailEditRequest } from './EmailEditApi'
1010
export type { RegisterRequest, RegisterResponse } from './RegisterApi'
1111
export type { LoginRequest, LoginResponse } from './LoginApi'
12+
export type { UserDataInterface } from './UserDataInterface'
1213
export type {
1314
ResendVerificationRequest,
1415
ResendVerificationResponse,
1516
ValidateCodeRequest,
1617
ValidateCodeResponse
1718
} from './UserVerificationApi'
18-
export type { UserDataInterface } from './UserDataInterface'
19+
export type {
20+
ForgotPasswordCodeRequest,
21+
ForgotPasswordSetPasswordRequest,
22+
ForgotPasswordCodeResponse,
23+
ForgotPasswordSetPasswordResponse
24+
} from './ForgotPasswordApi'

packages/sprinkle-account/app/assets/routes/index.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ export default [
55
meta: {
66
guest: {
77
redirect: { name: 'home' }
8-
}
8+
},
9+
title: 'LOGIN',
10+
description: 'LOGIN.PAGE'
911
},
1012
component: () => import('../views/LoginView.vue')
1113
},
@@ -15,7 +17,9 @@ export default [
1517
meta: {
1618
guest: {
1719
redirect: { name: 'home' }
18-
}
20+
},
21+
title: 'REGISTER',
22+
description: 'REGISTER.PAGE'
1923
},
2024
component: () => import('../views/RegisterView.vue')
2125
},
@@ -25,17 +29,21 @@ export default [
2529
meta: {
2630
guest: {
2731
redirect: { name: 'home' }
28-
}
32+
},
33+
title: 'PASSWORD.FORGOT', // Get a new password / Obtenir un nouveau mot de passe
34+
description: 'PASSWORD.FORGOT.PAGE'
2935
},
30-
component: () => import('../views/ForgotPasswordView.vue')
36+
component: () => import('../views/ForgotPassword.vue')
3137
},
3238
{
3339
path: '/account/verification',
3440
name: 'account.verification',
3541
meta: {
3642
guest: {
3743
redirect: { name: 'home' }
38-
}
44+
},
45+
title: 'ACCOUNT.VERIFICATION',
46+
description: 'ACCOUNT.VERIFICATION.EXPLAIN'
3947
},
4048
component: () => import('../views/ResendVerificationView.vue')
4149
},

packages/sprinkle-account/app/assets/tests/composables/forgotPassword.test.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

packages/sprinkle-account/app/assets/tests/composables/useEmailVerificationApi.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ describe('useEmailVerificationApi', () => {
1010
})
1111

1212
test('should successfully resend verification email', async () => {
13-
const { resendVerification, apiLoading, apiError } = useEmailVerificationApi()
13+
const { requestVerificationCode, apiLoading, apiError } = useEmailVerificationApi()
1414
const email = 'test@example.com'
1515
const successMessage = 'Verification email sent successfully'
1616

1717
// Set axios mock
1818
vi.spyOn(axios, 'post').mockResolvedValueOnce({ data: { message: successMessage } })
1919

2020
// Act
21-
const result = await resendVerification(email)
21+
const result = await requestVerificationCode(email)
2222

2323
// Assert
2424
expect(result).toBe(successMessage)
@@ -28,7 +28,7 @@ describe('useEmailVerificationApi', () => {
2828
})
2929

3030
test('should handle error when resending verification email fails', async () => {
31-
const { resendVerification, apiLoading, apiError } = useEmailVerificationApi()
31+
const { requestVerificationCode, apiLoading, apiError } = useEmailVerificationApi()
3232
const email = 'test@example.com'
3333
const errorResponse = {
3434
response: {
@@ -52,7 +52,7 @@ describe('useEmailVerificationApi', () => {
5252
vi.spyOn(axios, 'post').mockRejectedValueOnce(errorResponse)
5353

5454
// Act & Assert
55-
await expect(resendVerification(email)).rejects.toEqual(expectedError)
55+
await expect(requestVerificationCode(email)).rejects.toEqual(expectedError)
5656
expect(apiLoading.value).toBe(false)
5757
expect(apiError.value).toEqual(expectedError)
5858
})

0 commit comments

Comments
 (0)