Skip to content

Commit be37c8c

Browse files
committed
fix(account login) : password field
1 parent 471d5b9 commit be37c8c

File tree

3 files changed

+110
-139
lines changed

3 files changed

+110
-139
lines changed

components/account/AccountLogin.vue

+85-123
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,72 @@
11
<template>
2-
<div
3-
v-if="auth?.type=='credentials'"
4-
class="account-login">
2+
<div v-if="auth?.type == 'credentials'" class="account-login">
53
<slot name="head">
64
<div class="account-login__heading">
75
<h2 class="heading__title">
8-
{{ $t('account.login.welcome_back') }}
6+
{{ t('account.login.welcome_back') }}
97
</h2>
108
<p class="heading__description">
11-
{{ $t('account.login.description') }}
9+
{{ t('account.login.description') }}
1210
</p>
1311
</div>
1412
</slot>
1513
<form @submit.prevent="submit" class="account-login__form">
1614
<label class="form__control control-login">
1715
<div class="label">
1816
<span class="label-text">
19-
{{ $t('account.address.email') }}
17+
{{ t('account.address.email') }}
2018
</span>
2119
</div>
22-
<input type="email" v-model="login" class="control__input" @keyup="checkValidity('login', $event)" :placeholder="$t('account.address.email')" />
23-
<div class="label">
24-
<span v-if="error.login" class="label-text-alt text-error">{{ $t('error.login.email') }}</span>
25-
</div>
20+
<input
21+
type="email"
22+
v-model="login"
23+
class="control__input"
24+
required
25+
:placeholder="t('account.address.email')"
26+
/>
2627
</label>
27-
<label class="form__control control-password">
28-
<div class="label">
29-
<span class="label-text">
30-
{{ $t('account.login.password') }}
31-
</span>
32-
</div>
33-
<label class="control__input">
34-
<input
35-
v-model="password"
36-
:type="passwordView ? 'text' : 'password'"
37-
:disabled="loading"
38-
required
39-
class="grow"
40-
minlength="6"
41-
:placeholder="passwordView ? '' : '*************'"
42-
@keyup="checkValidity('password', $event)"
43-
/>
44-
<button type="button" @click="passwordView = !passwordView" class="cursor-pointer text-lg">
45-
<icon
46-
class="view-icon"
47-
:name="passwordView ? 'view': 'hide'"
48-
/>
49-
</button>
50-
</label>
51-
<div class="label">
52-
<span v-if="error.password" class="label-text-alt text-error">{{ $t('error.login.password') }}</span>
53-
</div>
54-
<div class="label">
55-
<NuxtLink
28+
<input-password
29+
v-model="password"
30+
:required="true"
31+
placeholder="***********"
32+
:disabled="loading"
33+
pattern=".{6,}"
34+
>
35+
<template #label>
36+
{{ t('account.login.password') }}
37+
</template>
38+
<template #error><span></span></template>
39+
<template #label-bottom>
40+
<nuxt-link
5641
class="label-text-alt underline"
5742
:to="{
5843
path: localePath('/account/password-reset'),
5944
query: { login }
6045
}"
6146
>
62-
{{ $t('account.login.forgot_password') }}
63-
</NuxtLink>
64-
</div>
65-
</label>
47+
{{ t('account.login.forgot_password') }}
48+
</nuxt-link>
49+
</template>
50+
</input-password>
6651
<div class="form__submit">
67-
<div v-if="error.auth" class="submit__error" >
68-
<icon class="text-xl" name="error" />
69-
{{ error.auth }}
70-
</div>
71-
<button type="submit" class="submit__btn " :disabled="loading">
52+
<button type="submit" class="submit__btn" :disabled="loading">
7253
<span v-if="loading" class="loading loading-spinner"></span>
73-
{{ $t('account.login.sign_in') }}
54+
{{ t('account.login.sign_in') }}
7455
</button>
56+
<div v-if="error" class="submit__error">
57+
<icon class="w-12" name="error" />
58+
{{ error }}
59+
</div>
7560
</div>
7661
</form>
7762
<div class="account-login__footer">
7863
<slot name="footer">
7964
<p class="footer-content">
8065
<span class="footer-content__text">
81-
{{ $t('account.login.not_yet_account') }}
66+
{{ t('account.login.not_yet_account') }}
8267
</span>
83-
<nuxt-link
84-
class="footer-content__link"
85-
:to="localePath('/account/register')"
86-
>
87-
{{ $t('account.login.create_account') }}
68+
<nuxt-link class="footer-content__link" :to="localePath('/account/register')">
69+
{{ t('account.login.create_account') }}
8870
</nuxt-link>
8971
</p>
9072
</slot>
@@ -96,98 +78,78 @@
9678
</div>
9779
</div>
9880
</template>
99-
<script lang="ts">
81+
<script lang="ts" setup>
10082
import type { AuthCredentialService } from '#services'
101-
import LogoVue from '../global/Logo.vue'
83+
import { erpApiGetErrorMessagesAsStr } from '~/utils/ErpApiHelper'
10284
103-
export default defineNuxtComponent({
104-
name: 'AccountLogin',
105-
components: {
106-
logo: LogoVue
107-
},
108-
data() {
109-
return {
110-
login: '' as string | null,
111-
password: '' as string | null,
112-
passwordView: false as boolean,
113-
loading: false as boolean,
114-
error: {
115-
auth: null as string | null,
116-
login: null as string | null,
117-
password: null as string | null,
118-
message: null as string | null
119-
}
120-
}
121-
},
122-
setup() {
123-
const localePath = useLocalePath()
124-
const auth = useShopinvaderService('auth') as AuthCredentialService | null
85+
const localePath = useLocalePath()
86+
const auth = useShopinvaderService('auth') as AuthCredentialService | null
12587
126-
onMounted(async () => {
127-
if (!auth?.getUser()?.value && auth?.type == 'oidc') {
128-
const url = useRequestURL()
129-
await auth?.loginRedirect(url?.href)
130-
}
131-
})
132-
return {
133-
localePath,
134-
auth
88+
const emit = defineEmits(['success'])
89+
const { t } = useI18n()
90+
const login = ref<string>('')
91+
const password = ref<string>('')
92+
const loading = ref<boolean>(false)
93+
const error = ref<string | null>(null)
94+
95+
onMounted(async () => {
96+
if (!auth?.getUser()?.value && auth?.type == 'oidc') {
97+
const url = useRequestURL()
98+
await auth?.loginRedirect(url?.href)
99+
}
100+
})
101+
102+
/*
103+
* Submit the login form
104+
*/
105+
const submit = async (_e: Event) => {
106+
loading.value = true
107+
error.value = null
108+
109+
try {
110+
login.value = login.value?.trim()
111+
if (!login.value || !password.value) {
112+
throw new Error(t('error.login.unable_to_login'))
135113
}
136-
},
137-
methods: {
138-
checkValidity(input: "login" | "password", e: Event) {
139-
const target = e.target as HTMLInputElement
140-
const validity = target?.checkValidity()
141-
this.error = {
142-
...this.error,
143-
[input] : !validity || target?.value === ''
144-
}
145-
},
146-
async submit(_e: Event) {
147-
const auth = this.auth
148-
this.loading = true
149-
if (this?.login && this?.password) {
150-
try {
151-
await auth?.login(this.login, this.password)
152-
this.$emit('success')
153-
} catch (e) {
154-
this.error.auth = this.$t('error.login.unable_to_login')
155-
} finally {
156-
this.loading = false
157-
}
158-
}
114+
const user = await auth?.login(login.value, password.value)
115+
if (!user) {
116+
// Null user means login failed
117+
error.value = t('error.login.unable_to_login')
118+
} else {
119+
emit('success')
159120
}
121+
} catch (e) {
122+
// Display the error message
123+
error.value = erpApiGetErrorMessagesAsStr(e) || t('error.generic')
124+
} finally {
125+
loading.value = false
160126
}
161-
})
127+
}
162128
</script>
163129
<style lang="scss">
164130
.account-login {
165-
@apply flex flex-col max-w-sm;
131+
@apply flex max-w-sm flex-col;
166132
&__form {
167-
168133
@apply card card-body card-bordered rounded-b-none bg-white;
169134
.form {
170135
&__control {
171-
172136
@apply form-control w-full max-w-xs;
173137
.control {
174138
&__input {
175139
@apply input input-bordered w-full max-w-xs;
176140
}
177-
178141
}
179142
&.control-password {
180-
.control__input{
143+
.control__input {
181144
@apply flex items-center gap-2;
182145
}
183146
}
184147
}
185148
.form__control {
186149
&.control-password {
187-
188150
.control {
189151
&__input {
190-
@apply items-center gap-2;
152+
@apply items-center gap-2;
191153
}
192154
}
193155
}
@@ -196,7 +158,7 @@ export default defineNuxtComponent({
196158
@apply flex flex-col justify-center;
197159
.submit {
198160
&__error {
199-
@apply flex items-center justify-center text-error py-4 gap-1;
161+
@apply flex items-start justify-center gap-1 py-4 text-error;
200162
}
201163
&__btn {
202164
@apply btn btn-primary btn-block;
@@ -217,14 +179,14 @@ export default defineNuxtComponent({
217179
}
218180
}
219181
&__footer {
220-
@apply card card-body bg-primary-800 text-primary-content rounded-t-none;
182+
@apply card card-body rounded-t-none bg-primary-800 text-primary-content;
221183
.footer-content {
222-
@apply flex flex-col justify-center items-center gap-2;
184+
@apply flex flex-col items-center justify-center gap-2;
223185
&__text {
224186
@apply text-lg;
225187
}
226188
&__link {
227-
@apply btn btn-sm bg-white;
189+
@apply btn btn-sm bg-white;
228190
}
229191
}
230192
}

locales/fr-FR.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@
342342
"login": {
343343
"email": "Format d'adresse e-mail invalide.",
344344
"password": "Le mot de passe doit contenir au moins 8 caractères et contenir au moins une majuscule, un chiffre ainsi qu'un caractère spécial {'@#$%^&+=!?'}.",
345-
"unable_to_login": "Impossible de se connecter"
345+
"unable_to_login": "Mot de passe incorrect. Réessayez ou cliquez sur \"Mot de passe oublié\" pour le réinitialiser."
346346
},
347347
"reset": {
348348
"pasword_missmatch": "Les deux mot de passe ne corréspondent pas",

services/auth/AuthCredentialService.ts

+24-15
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ export interface AuthAPIConfig {
1313
}
1414

1515
/**
16-
* AuthCredentialService
16+
* AuthCredentialService : manage user authentication via login/logout request
17+
* directly to the ERP API.
18+
*
1719
* @description AuthCredentialService is a service to manage the user authentication
1820
* @extends AuthService
1921
*/
@@ -34,9 +36,12 @@ export class AuthCredentialService extends AuthService {
3436

3537
override async init(services: ShopinvaderServiceList) {
3638
await super.init(services)
39+
/* check if the user is already logged in localStorage */
3740
if (this.getSession()) {
41+
// fetch the user profile
3842
this.profile()
3943
} else {
44+
// No session in localStorage, logout the user
4045
this.setUser(null)
4146
}
4247
this.logoutRedirect = this.logoutRedirect.bind(this)
@@ -96,32 +101,36 @@ export class AuthCredentialService extends AuthService {
96101
}
97102

98103
/**
99-
* login user
104+
* login user via credentials API (directly with login and password to the API)
100105
* @param login
101106
* @param password
107+
* @returns User | null return the user if the login is successful or null if the login failed
102108
*/
103-
async login(login: string, password: string) {
104-
try {
105-
const data = await this.ofetch(this.urlEndpointAuth + '/login', {
106-
method: 'POST',
107-
body: { login, password }
108-
})
109-
if (data?.login) {
110-
await this.profile()
111-
}
112-
} catch (error) {
113-
console.error(error)
114-
throw error
109+
async login(login: string, password: string): Promise<User | null> {
110+
// user connection via raw request to avoid error rejection in case of 403 response code (wrong username or password)
111+
const response = await this.ofetch.raw(this.urlEndpointAuth + '/login', {
112+
method: 'POST',
113+
body: { login, password },
114+
ignoreResponseError: true
115+
})
116+
if (response.ok) {
117+
// user found, fetch user data
118+
return await this.profile()
119+
} else if (response.status !== 403) {
120+
// 403 (password error) is a valid response, we don't want to throw an error
121+
throw new Error(await response.json())
115122
}
123+
return null
116124
}
117125

118126
/**
119-
* logout user
127+
* logout user via credentials API
120128
*/
121129
async logout(): Promise<any> {
122130
await this.ofetch(this.urlEndpointAuth + '/logout', {
123131
method: 'POST'
124132
})
133+
// remove the user from the store
125134
await this.setUser(null)
126135
}
127136

0 commit comments

Comments
 (0)