nuxt-auth-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +307 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +5 -0
- package/dist/module.d.ts +5 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +92 -0
- package/dist/runtime/components/auth/AuthLayout.vue +117 -0
- package/dist/runtime/components/auth/ForgotPasswordForm.vue +110 -0
- package/dist/runtime/components/auth/LoginForm.vue +209 -0
- package/dist/runtime/components/auth/RegisterForm.vue +182 -0
- package/dist/runtime/components/auth/ResetPasswordForm.vue +134 -0
- package/dist/runtime/components/profile/UpdatePasswordForm.vue +115 -0
- package/dist/runtime/components/profile/UpdateProfileForm.vue +157 -0
- package/dist/runtime/composables/useAuth.d.ts +58 -0
- package/dist/runtime/composables/useAuth.js +214 -0
- package/dist/runtime/middleware/auth.d.ts +2 -0
- package/dist/runtime/middleware/auth.js +14 -0
- package/dist/runtime/middleware/guest.d.ts +2 -0
- package/dist/runtime/middleware/guest.js +11 -0
- package/dist/runtime/middleware/role.d.ts +2 -0
- package/dist/runtime/middleware/role.js +18 -0
- package/dist/runtime/plugins/auth.d.ts +2 -0
- package/dist/runtime/plugins/auth.js +20 -0
- package/dist/runtime/stores/auth.d.ts +38 -0
- package/dist/runtime/stores/auth.js +47 -0
- package/dist/runtime/types/index.d.ts +85 -0
- package/dist/runtime/types/index.js +0 -0
- package/dist/types.d.mts +1 -0
- package/dist/types.d.ts +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="auth-login">
|
|
3
|
+
<h1 class="text-3xl font-bold text-[#1a2e1a] mb-2">{{ title }}</h1>
|
|
4
|
+
<p class="text-[#6b7c6b] mb-8">{{ subtitle }}</p>
|
|
5
|
+
|
|
6
|
+
<!-- Role Switcher -->
|
|
7
|
+
<div v-if="roles && roles.length > 0" class="flex gap-6 mb-8">
|
|
8
|
+
<label
|
|
9
|
+
v-for="r in roles"
|
|
10
|
+
:key="r.value"
|
|
11
|
+
class="flex items-center gap-2 cursor-pointer"
|
|
12
|
+
>
|
|
13
|
+
<input
|
|
14
|
+
type="radio"
|
|
15
|
+
:value="r.value"
|
|
16
|
+
v-model="selectedRole"
|
|
17
|
+
class="accent-[#1B4332] w-4 h-4"
|
|
18
|
+
/>
|
|
19
|
+
<span class="text-sm text-[#1a2e1a]">{{ r.label }}</span>
|
|
20
|
+
</label>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<!-- Social Login -->
|
|
24
|
+
<div v-if="showSocial" class="space-y-3 mb-6">
|
|
25
|
+
<button
|
|
26
|
+
type="button"
|
|
27
|
+
@click="$emit('google-login')"
|
|
28
|
+
class="w-full flex items-center justify-center gap-3 bg-white border border-[#e0e0d8] rounded-2xl py-3 px-4 text-[#1a2e1a] font-medium hover:bg-[#f5f5ed] transition-colors"
|
|
29
|
+
>
|
|
30
|
+
<svg class="w-5 h-5" viewBox="0 0 24 24">
|
|
31
|
+
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
|
|
32
|
+
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
|
|
33
|
+
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
|
|
34
|
+
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
|
|
35
|
+
</svg>
|
|
36
|
+
Se connecter avec Google
|
|
37
|
+
</button>
|
|
38
|
+
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
@click="$emit('apple-login')"
|
|
42
|
+
class="w-full flex items-center justify-center gap-3 bg-white border border-[#e0e0d8] rounded-2xl py-3 px-4 text-[#1a2e1a] font-medium hover:bg-[#f5f5ed] transition-colors"
|
|
43
|
+
>
|
|
44
|
+
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
|
|
45
|
+
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/>
|
|
46
|
+
</svg>
|
|
47
|
+
Se connecter avec Apple
|
|
48
|
+
</button>
|
|
49
|
+
|
|
50
|
+
<div class="flex items-center gap-3 my-2">
|
|
51
|
+
<div class="flex-1 h-px bg-[#d8d8cc]"></div>
|
|
52
|
+
<span class="text-sm text-[#8a9a8a]">OU</span>
|
|
53
|
+
<div class="flex-1 h-px bg-[#d8d8cc]"></div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Form -->
|
|
58
|
+
<form @submit.prevent="handleSubmit" class="space-y-4">
|
|
59
|
+
<!-- Error alert -->
|
|
60
|
+
<div v-if="error" class="bg-red-50 border border-red-200 text-red-700 rounded-xl px-4 py-3 text-sm">
|
|
61
|
+
{{ error }}
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Email -->
|
|
65
|
+
<div>
|
|
66
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">
|
|
67
|
+
Email <span class="text-red-500">*</span>
|
|
68
|
+
</label>
|
|
69
|
+
<div class="relative">
|
|
70
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
71
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
72
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
73
|
+
</svg>
|
|
74
|
+
</span>
|
|
75
|
+
<input
|
|
76
|
+
v-model="form.email"
|
|
77
|
+
type="email"
|
|
78
|
+
placeholder="hello@example.com"
|
|
79
|
+
required
|
|
80
|
+
autocomplete="email"
|
|
81
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-4 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition"
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
<p v-if="fieldErrors.email" class="text-red-500 text-xs mt-1">{{ fieldErrors.email }}</p>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Password -->
|
|
88
|
+
<div>
|
|
89
|
+
<div class="flex justify-between items-center mb-1.5">
|
|
90
|
+
<label class="text-sm font-medium text-[#1a2e1a]">
|
|
91
|
+
Mot de passe <span class="text-red-500">*</span>
|
|
92
|
+
</label>
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
@click="$emit('forgot-password')"
|
|
96
|
+
class="text-sm text-[#1B4332] font-medium hover:underline"
|
|
97
|
+
>
|
|
98
|
+
Mot de passe oublié ?
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="relative">
|
|
102
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
103
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
104
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
105
|
+
</svg>
|
|
106
|
+
</span>
|
|
107
|
+
<input
|
|
108
|
+
v-model="form.password"
|
|
109
|
+
:type="showPassword ? 'text' : 'password'"
|
|
110
|
+
placeholder="Entrez votre mot de passe"
|
|
111
|
+
required
|
|
112
|
+
autocomplete="current-password"
|
|
113
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-12 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition"
|
|
114
|
+
/>
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
@click="showPassword = !showPassword"
|
|
118
|
+
class="absolute right-4 top-1/2 -translate-y-1/2 text-[#8a9a8a] hover:text-[#1a2e1a]"
|
|
119
|
+
>
|
|
120
|
+
<svg v-if="!showPassword" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
121
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
122
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
123
|
+
</svg>
|
|
124
|
+
<svg v-else class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
125
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
|
126
|
+
</svg>
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
<p v-if="fieldErrors.password" class="text-red-500 text-xs mt-1">{{ fieldErrors.password }}</p>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<!-- Submit -->
|
|
133
|
+
<button
|
|
134
|
+
type="submit"
|
|
135
|
+
:disabled="loading"
|
|
136
|
+
class="w-full bg-[#1B4332] hover:bg-[#163828] text-[#D4FF6B] font-semibold py-3.5 rounded-2xl transition-colors disabled:opacity-60 disabled:cursor-not-allowed mt-2"
|
|
137
|
+
>
|
|
138
|
+
<span v-if="!loading">Se connecter</span>
|
|
139
|
+
<span v-else class="flex items-center justify-center gap-2">
|
|
140
|
+
<svg class="animate-spin w-4 h-4" viewBox="0 0 24 24" fill="none">
|
|
141
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
|
142
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
|
|
143
|
+
</svg>
|
|
144
|
+
Connexion en cours...
|
|
145
|
+
</span>
|
|
146
|
+
</button>
|
|
147
|
+
</form>
|
|
148
|
+
|
|
149
|
+
<!-- Register link -->
|
|
150
|
+
<p class="text-center text-sm text-[#6b7c6b] mt-6">
|
|
151
|
+
Pas encore de compte ?
|
|
152
|
+
<button type="button" @click="$emit('register')" class="text-[#1B4332] font-semibold hover:underline ml-1">
|
|
153
|
+
S'inscrire
|
|
154
|
+
</button>
|
|
155
|
+
</p>
|
|
156
|
+
</div>
|
|
157
|
+
</template>
|
|
158
|
+
|
|
159
|
+
<script setup lang="ts">
|
|
160
|
+
import { ref, reactive } from 'vue'
|
|
161
|
+
import { useAuth } from '../../composables/useAuth'
|
|
162
|
+
|
|
163
|
+
const props = withDefaults(defineProps<{
|
|
164
|
+
title?: string
|
|
165
|
+
subtitle?: string
|
|
166
|
+
showSocial?: boolean
|
|
167
|
+
roles?: Array<{ value: string; label: string }>
|
|
168
|
+
}>(), {
|
|
169
|
+
title: 'Connexion',
|
|
170
|
+
subtitle: 'Bienvenue ! Entrez vos informations pour continuer.',
|
|
171
|
+
showSocial: true,
|
|
172
|
+
roles: () => []
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const emit = defineEmits<{
|
|
176
|
+
'forgot-password': []
|
|
177
|
+
'register': []
|
|
178
|
+
'google-login': []
|
|
179
|
+
'apple-login': []
|
|
180
|
+
'success': [user: any]
|
|
181
|
+
}>()
|
|
182
|
+
|
|
183
|
+
const { login, loading } = useAuth()
|
|
184
|
+
|
|
185
|
+
const form = reactive({ email: '', password: '' })
|
|
186
|
+
const showPassword = ref(false)
|
|
187
|
+
const error = ref<string | null>(null)
|
|
188
|
+
const fieldErrors = reactive<Record<string, string>>({})
|
|
189
|
+
const selectedRole = ref(props.roles?.[0]?.value || '')
|
|
190
|
+
|
|
191
|
+
async function handleSubmit() {
|
|
192
|
+
error.value = null
|
|
193
|
+
Object.keys(fieldErrors).forEach(k => delete fieldErrors[k])
|
|
194
|
+
|
|
195
|
+
const result = await login({ email: form.email, password: form.password })
|
|
196
|
+
|
|
197
|
+
if (!result.success && result.error) {
|
|
198
|
+
if (result.error.errors) {
|
|
199
|
+
Object.entries(result.error.errors).forEach(([k, v]) => {
|
|
200
|
+
fieldErrors[k] = v[0]
|
|
201
|
+
})
|
|
202
|
+
} else {
|
|
203
|
+
error.value = result.error.message
|
|
204
|
+
}
|
|
205
|
+
} else if (result.success) {
|
|
206
|
+
emit('success', null)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
</script>
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="auth-register">
|
|
3
|
+
<h1 class="text-3xl font-bold text-[#1a2e1a] mb-2">{{ title }}</h1>
|
|
4
|
+
<p class="text-[#6b7c6b] mb-8">{{ subtitle }}</p>
|
|
5
|
+
|
|
6
|
+
<form @submit.prevent="handleSubmit" class="space-y-4">
|
|
7
|
+
<div v-if="error" class="bg-red-50 border border-red-200 text-red-700 rounded-xl px-4 py-3 text-sm">
|
|
8
|
+
{{ error }}
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<!-- Name -->
|
|
12
|
+
<div>
|
|
13
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">
|
|
14
|
+
Nom complet <span class="text-red-500">*</span>
|
|
15
|
+
</label>
|
|
16
|
+
<div class="relative">
|
|
17
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
18
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
19
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
|
20
|
+
</svg>
|
|
21
|
+
</span>
|
|
22
|
+
<input
|
|
23
|
+
v-model="form.name"
|
|
24
|
+
type="text"
|
|
25
|
+
placeholder="Jean Dupont"
|
|
26
|
+
required
|
|
27
|
+
autocomplete="name"
|
|
28
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-4 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
<p v-if="fieldErrors.name" class="text-red-500 text-xs mt-1">{{ fieldErrors.name }}</p>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Email -->
|
|
35
|
+
<div>
|
|
36
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">
|
|
37
|
+
Email <span class="text-red-500">*</span>
|
|
38
|
+
</label>
|
|
39
|
+
<div class="relative">
|
|
40
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
41
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
42
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
43
|
+
</svg>
|
|
44
|
+
</span>
|
|
45
|
+
<input
|
|
46
|
+
v-model="form.email"
|
|
47
|
+
type="email"
|
|
48
|
+
placeholder="hello@example.com"
|
|
49
|
+
required
|
|
50
|
+
autocomplete="email"
|
|
51
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-4 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<p v-if="fieldErrors.email" class="text-red-500 text-xs mt-1">{{ fieldErrors.email }}</p>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Password -->
|
|
58
|
+
<div>
|
|
59
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">
|
|
60
|
+
Mot de passe <span class="text-red-500">*</span>
|
|
61
|
+
</label>
|
|
62
|
+
<div class="relative">
|
|
63
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
64
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
65
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
66
|
+
</svg>
|
|
67
|
+
</span>
|
|
68
|
+
<input
|
|
69
|
+
v-model="form.password"
|
|
70
|
+
:type="showPassword ? 'text' : 'password'"
|
|
71
|
+
placeholder="Minimum 8 caractères"
|
|
72
|
+
required
|
|
73
|
+
autocomplete="new-password"
|
|
74
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-12 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition"
|
|
75
|
+
/>
|
|
76
|
+
<button type="button" @click="showPassword = !showPassword" class="absolute right-4 top-1/2 -translate-y-1/2 text-[#8a9a8a] hover:text-[#1a2e1a]">
|
|
77
|
+
<svg v-if="!showPassword" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
78
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0zM2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
79
|
+
</svg>
|
|
80
|
+
<svg v-else class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
81
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
|
82
|
+
</svg>
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
<p v-if="fieldErrors.password" class="text-red-500 text-xs mt-1">{{ fieldErrors.password }}</p>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Password Confirmation -->
|
|
89
|
+
<div>
|
|
90
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">
|
|
91
|
+
Confirmer le mot de passe <span class="text-red-500">*</span>
|
|
92
|
+
</label>
|
|
93
|
+
<div class="relative">
|
|
94
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
95
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
96
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
97
|
+
</svg>
|
|
98
|
+
</span>
|
|
99
|
+
<input
|
|
100
|
+
v-model="form.password_confirmation"
|
|
101
|
+
:type="showPassword ? 'text' : 'password'"
|
|
102
|
+
placeholder="Confirmez votre mot de passe"
|
|
103
|
+
required
|
|
104
|
+
autocomplete="new-password"
|
|
105
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-4 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition"
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<button
|
|
111
|
+
type="submit"
|
|
112
|
+
:disabled="loading"
|
|
113
|
+
class="w-full bg-[#1B4332] hover:bg-[#163828] text-[#D4FF6B] font-semibold py-3.5 rounded-2xl transition-colors disabled:opacity-60 disabled:cursor-not-allowed mt-2"
|
|
114
|
+
>
|
|
115
|
+
<span v-if="!loading">Créer un compte</span>
|
|
116
|
+
<span v-else class="flex items-center justify-center gap-2">
|
|
117
|
+
<svg class="animate-spin w-4 h-4" viewBox="0 0 24 24" fill="none">
|
|
118
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
|
119
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
|
|
120
|
+
</svg>
|
|
121
|
+
Création en cours...
|
|
122
|
+
</span>
|
|
123
|
+
</button>
|
|
124
|
+
</form>
|
|
125
|
+
|
|
126
|
+
<p class="text-center text-sm text-[#6b7c6b] mt-6">
|
|
127
|
+
Déjà un compte ?
|
|
128
|
+
<button type="button" @click="$emit('login')" class="text-[#1B4332] font-semibold hover:underline ml-1">
|
|
129
|
+
Se connecter
|
|
130
|
+
</button>
|
|
131
|
+
</p>
|
|
132
|
+
</div>
|
|
133
|
+
</template>
|
|
134
|
+
|
|
135
|
+
<script setup lang="ts">
|
|
136
|
+
import { ref, reactive } from 'vue'
|
|
137
|
+
import { useAuth } from '../../composables/useAuth'
|
|
138
|
+
|
|
139
|
+
withDefaults(defineProps<{
|
|
140
|
+
title?: string
|
|
141
|
+
subtitle?: string
|
|
142
|
+
}>(), {
|
|
143
|
+
title: 'Créer un compte',
|
|
144
|
+
subtitle: 'Rejoignez-nous dès aujourd\'hui.'
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const emit = defineEmits<{
|
|
148
|
+
login: []
|
|
149
|
+
success: [user: any]
|
|
150
|
+
}>()
|
|
151
|
+
|
|
152
|
+
const { register, loading } = useAuth()
|
|
153
|
+
|
|
154
|
+
const form = reactive({
|
|
155
|
+
name: '',
|
|
156
|
+
email: '',
|
|
157
|
+
password: '',
|
|
158
|
+
password_confirmation: ''
|
|
159
|
+
})
|
|
160
|
+
const showPassword = ref(false)
|
|
161
|
+
const error = ref<string | null>(null)
|
|
162
|
+
const fieldErrors = reactive<Record<string, string>>({})
|
|
163
|
+
|
|
164
|
+
async function handleSubmit() {
|
|
165
|
+
error.value = null
|
|
166
|
+
Object.keys(fieldErrors).forEach(k => delete fieldErrors[k])
|
|
167
|
+
|
|
168
|
+
const result = await register(form)
|
|
169
|
+
|
|
170
|
+
if (!result.success && result.error) {
|
|
171
|
+
if (result.error.errors) {
|
|
172
|
+
Object.entries(result.error.errors).forEach(([k, v]) => {
|
|
173
|
+
fieldErrors[k] = v[0]
|
|
174
|
+
})
|
|
175
|
+
} else {
|
|
176
|
+
error.value = result.error.message
|
|
177
|
+
}
|
|
178
|
+
} else if (result.success) {
|
|
179
|
+
emit('success', null)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
</script>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="auth-reset-password">
|
|
3
|
+
<div v-if="!done">
|
|
4
|
+
<h1 class="text-3xl font-bold text-[#1a2e1a] mb-2">{{ title }}</h1>
|
|
5
|
+
<p class="text-[#6b7c6b] mb-8">{{ subtitle }}</p>
|
|
6
|
+
|
|
7
|
+
<form @submit.prevent="handleSubmit" class="space-y-4">
|
|
8
|
+
<div v-if="error" class="bg-red-50 border border-red-200 text-red-700 rounded-xl px-4 py-3 text-sm">
|
|
9
|
+
{{ error }}
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div>
|
|
13
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">Email <span class="text-red-500">*</span></label>
|
|
14
|
+
<div class="relative">
|
|
15
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
16
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
17
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
18
|
+
</svg>
|
|
19
|
+
</span>
|
|
20
|
+
<input v-model="form.email" type="email" required placeholder="hello@example.com"
|
|
21
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-4 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition" />
|
|
22
|
+
</div>
|
|
23
|
+
<p v-if="fieldErrors.email" class="text-red-500 text-xs mt-1">{{ fieldErrors.email }}</p>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div>
|
|
27
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">Nouveau mot de passe <span class="text-red-500">*</span></label>
|
|
28
|
+
<div class="relative">
|
|
29
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
30
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
31
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
32
|
+
</svg>
|
|
33
|
+
</span>
|
|
34
|
+
<input v-model="form.password" :type="showPassword ? 'text' : 'password'" required placeholder="Minimum 8 caractères"
|
|
35
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-12 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition" />
|
|
36
|
+
<button type="button" @click="showPassword = !showPassword" class="absolute right-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
37
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
38
|
+
<path v-if="!showPassword" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0zM2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
39
|
+
<path v-else stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
|
40
|
+
</svg>
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
<p v-if="fieldErrors.password" class="text-red-500 text-xs mt-1">{{ fieldErrors.password }}</p>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div>
|
|
47
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">Confirmer le mot de passe <span class="text-red-500">*</span></label>
|
|
48
|
+
<div class="relative">
|
|
49
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
50
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
51
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
52
|
+
</svg>
|
|
53
|
+
</span>
|
|
54
|
+
<input v-model="form.password_confirmation" :type="showPassword ? 'text' : 'password'" required placeholder="Confirmez votre mot de passe"
|
|
55
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-4 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition" />
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<button type="submit" :disabled="loading"
|
|
60
|
+
class="w-full bg-[#1B4332] hover:bg-[#163828] text-[#D4FF6B] font-semibold py-3.5 rounded-2xl transition-colors disabled:opacity-60 mt-2">
|
|
61
|
+
<span v-if="!loading">Réinitialiser le mot de passe</span>
|
|
62
|
+
<span v-else class="flex items-center justify-center gap-2">
|
|
63
|
+
<svg class="animate-spin w-4 h-4" viewBox="0 0 24 24" fill="none">
|
|
64
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
|
65
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
|
|
66
|
+
</svg>
|
|
67
|
+
Réinitialisation...
|
|
68
|
+
</span>
|
|
69
|
+
</button>
|
|
70
|
+
</form>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div v-else class="text-center">
|
|
74
|
+
<div class="w-16 h-16 bg-[#1B4332]/10 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
75
|
+
<svg class="w-8 h-8 text-[#1B4332]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
76
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
77
|
+
</svg>
|
|
78
|
+
</div>
|
|
79
|
+
<h2 class="text-2xl font-bold text-[#1a2e1a] mb-3">Mot de passe réinitialisé !</h2>
|
|
80
|
+
<p class="text-[#6b7c6b] mb-8">Votre mot de passe a été mis à jour avec succès.</p>
|
|
81
|
+
<button type="button" @click="$emit('back-to-login')"
|
|
82
|
+
class="bg-[#1B4332] hover:bg-[#163828] text-[#D4FF6B] font-semibold py-3 px-8 rounded-2xl transition-colors">
|
|
83
|
+
Se connecter
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<script setup lang="ts">
|
|
90
|
+
import { ref, reactive } from 'vue'
|
|
91
|
+
import { useAuth } from '../../composables/useAuth'
|
|
92
|
+
import { useRoute } from '#app'
|
|
93
|
+
|
|
94
|
+
withDefaults(defineProps<{
|
|
95
|
+
title?: string
|
|
96
|
+
subtitle?: string
|
|
97
|
+
}>(), {
|
|
98
|
+
title: 'Nouveau mot de passe',
|
|
99
|
+
subtitle: 'Choisissez un nouveau mot de passe sécurisé.'
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const emit = defineEmits<{ 'back-to-login': [] }>()
|
|
103
|
+
|
|
104
|
+
const route = useRoute()
|
|
105
|
+
const { resetPassword, loading } = useAuth()
|
|
106
|
+
|
|
107
|
+
const form = reactive({
|
|
108
|
+
email: (route.query.email as string) || '',
|
|
109
|
+
password: '',
|
|
110
|
+
password_confirmation: '',
|
|
111
|
+
token: (route.query.token as string) || (route.params.token as string) || ''
|
|
112
|
+
})
|
|
113
|
+
const showPassword = ref(false)
|
|
114
|
+
const error = ref<string | null>(null)
|
|
115
|
+
const fieldErrors = reactive<Record<string, string>>({})
|
|
116
|
+
const done = ref(false)
|
|
117
|
+
|
|
118
|
+
async function handleSubmit() {
|
|
119
|
+
error.value = null
|
|
120
|
+
Object.keys(fieldErrors).forEach(k => delete fieldErrors[k])
|
|
121
|
+
|
|
122
|
+
const result = await resetPassword(form)
|
|
123
|
+
|
|
124
|
+
if (result.success) {
|
|
125
|
+
done.value = true
|
|
126
|
+
} else if (result.error) {
|
|
127
|
+
if (result.error.errors) {
|
|
128
|
+
Object.entries(result.error.errors).forEach(([k, v]) => { fieldErrors[k] = v[0] })
|
|
129
|
+
} else {
|
|
130
|
+
error.value = result.error.message
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
</script>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="profile-update-password">
|
|
3
|
+
<h2 class="text-2xl font-bold text-[#1a2e1a] mb-6">{{ title }}</h2>
|
|
4
|
+
|
|
5
|
+
<form @submit.prevent="handleSubmit" class="space-y-5">
|
|
6
|
+
<div v-if="successMsg" class="bg-green-50 border border-green-200 text-green-700 rounded-xl px-4 py-3 text-sm">
|
|
7
|
+
{{ successMsg }}
|
|
8
|
+
</div>
|
|
9
|
+
<div v-if="error" class="bg-red-50 border border-red-200 text-red-700 rounded-xl px-4 py-3 text-sm">
|
|
10
|
+
{{ error }}
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div>
|
|
14
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">Mot de passe actuel <span class="text-red-500">*</span></label>
|
|
15
|
+
<div class="relative">
|
|
16
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
17
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
18
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
19
|
+
</svg>
|
|
20
|
+
</span>
|
|
21
|
+
<input v-model="form.current_password" :type="show.current ? 'text' : 'password'" required placeholder="Votre mot de passe actuel"
|
|
22
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-12 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition" />
|
|
23
|
+
<button type="button" @click="show.current = !show.current" class="absolute right-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
24
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
25
|
+
<path v-if="!show.current" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0zM2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
26
|
+
<path v-else stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
|
27
|
+
</svg>
|
|
28
|
+
</button>
|
|
29
|
+
</div>
|
|
30
|
+
<p v-if="fieldErrors.current_password" class="text-red-500 text-xs mt-1">{{ fieldErrors.current_password }}</p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div>
|
|
34
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">Nouveau mot de passe <span class="text-red-500">*</span></label>
|
|
35
|
+
<div class="relative">
|
|
36
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
37
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
38
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
39
|
+
</svg>
|
|
40
|
+
</span>
|
|
41
|
+
<input v-model="form.password" :type="show.new ? 'text' : 'password'" required placeholder="Minimum 8 caractères"
|
|
42
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-12 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition" />
|
|
43
|
+
<button type="button" @click="show.new = !show.new" class="absolute right-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
44
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
45
|
+
<path v-if="!show.new" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0zM2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
46
|
+
<path v-else stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
|
47
|
+
</svg>
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
<p v-if="fieldErrors.password" class="text-red-500 text-xs mt-1">{{ fieldErrors.password }}</p>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div>
|
|
54
|
+
<label class="block text-sm font-medium text-[#1a2e1a] mb-1.5">Confirmer le nouveau mot de passe <span class="text-red-500">*</span></label>
|
|
55
|
+
<div class="relative">
|
|
56
|
+
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-[#8a9a8a]">
|
|
57
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
58
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
59
|
+
</svg>
|
|
60
|
+
</span>
|
|
61
|
+
<input v-model="form.password_confirmation" :type="show.new ? 'text' : 'password'" required placeholder="Confirmez votre nouveau mot de passe"
|
|
62
|
+
class="w-full bg-white border border-[#e0e0d8] rounded-2xl py-3 pl-11 pr-4 text-[#1a2e1a] placeholder-[#aab4aa] focus:outline-none focus:border-[#1B4332] focus:ring-2 focus:ring-[#1B4332]/20 transition" />
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<button type="submit" :disabled="loading"
|
|
67
|
+
class="bg-[#1B4332] hover:bg-[#163828] text-[#D4FF6B] font-semibold py-3 px-8 rounded-2xl transition-colors disabled:opacity-60">
|
|
68
|
+
<span v-if="!loading">Changer le mot de passe</span>
|
|
69
|
+
<span v-else class="flex items-center gap-2">
|
|
70
|
+
<svg class="animate-spin w-4 h-4" viewBox="0 0 24 24" fill="none">
|
|
71
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
|
72
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
|
|
73
|
+
</svg>
|
|
74
|
+
Modification...
|
|
75
|
+
</span>
|
|
76
|
+
</button>
|
|
77
|
+
</form>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<script setup lang="ts">
|
|
82
|
+
import { ref, reactive } from 'vue'
|
|
83
|
+
import { useAuth } from '../../composables/useAuth'
|
|
84
|
+
|
|
85
|
+
withDefaults(defineProps<{ title?: string }>(), { title: 'Changer le mot de passe' })
|
|
86
|
+
|
|
87
|
+
const { updatePassword, loading } = useAuth()
|
|
88
|
+
|
|
89
|
+
const form = reactive({ current_password: '', password: '', password_confirmation: '' })
|
|
90
|
+
const show = reactive({ current: false, new: false })
|
|
91
|
+
const error = ref<string | null>(null)
|
|
92
|
+
const successMsg = ref<string | null>(null)
|
|
93
|
+
const fieldErrors = reactive<Record<string, string>>({})
|
|
94
|
+
|
|
95
|
+
async function handleSubmit() {
|
|
96
|
+
error.value = null
|
|
97
|
+
successMsg.value = null
|
|
98
|
+
Object.keys(fieldErrors).forEach(k => delete fieldErrors[k])
|
|
99
|
+
|
|
100
|
+
const result = await updatePassword(form)
|
|
101
|
+
|
|
102
|
+
if (result.success) {
|
|
103
|
+
successMsg.value = 'Mot de passe modifié avec succès !'
|
|
104
|
+
form.current_password = ''
|
|
105
|
+
form.password = ''
|
|
106
|
+
form.password_confirmation = ''
|
|
107
|
+
} else if (result.error) {
|
|
108
|
+
if (result.error.errors) {
|
|
109
|
+
Object.entries(result.error.errors).forEach(([k, v]) => { fieldErrors[k] = v[0] })
|
|
110
|
+
} else {
|
|
111
|
+
error.value = result.error.message
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
</script>
|