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.
@@ -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>