create-nuxt-base 2.1.0 → 2.1.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [2.1.2](https://github.com/lenneTech/nuxt-base-starter/compare/v2.1.1...v2.1.2) (2026-01-24)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **forms:** restore UForm/UAuthForm with Valibot schema validation ([aa5e93c](https://github.com/lenneTech/nuxt-base-starter/commit/aa5e93cdd19193cc4dd1153fc4f629407b0273f0))
11
+
12
+ ### [2.1.1](https://github.com/lenneTech/nuxt-base-starter/compare/v2.1.0...v2.1.1) (2026-01-24)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * update packages ([c5c733a](https://github.com/lenneTech/nuxt-base-starter/commit/c5c733ad07b6d5cee248b2f78d65a69b3b4369d4))
18
+
5
19
  ## [2.1.0](https://github.com/lenneTech/nuxt-base-starter/compare/v2.0.0...v2.1.0) (2026-01-24)
6
20
 
7
21
 
@@ -2,6 +2,11 @@
2
2
  // ============================================================================
3
3
  // Imports
4
4
  // ============================================================================
5
+ import type { FormSubmitEvent } from '@nuxt/ui';
6
+ import type { InferOutput } from 'valibot';
7
+
8
+ import * as v from 'valibot';
9
+
5
10
  import ModalBackupCodes from '~/components/Modal/ModalBackupCodes.vue';
6
11
 
7
12
  // ============================================================================
@@ -34,10 +39,21 @@ const passkeyLoading = ref<boolean>(false);
34
39
  const newPasskeyName = ref<string>('');
35
40
  const showAddPasskey = ref<boolean>(false);
36
41
 
37
- // Form states - using reactive for direct v-model binding
38
- const enable2FAPassword = ref('');
39
- const disable2FAPassword = ref('');
40
- const totpCode = ref('');
42
+ // Form states for UForm (required for proper data binding)
43
+ const enable2FAForm = reactive({ password: '' });
44
+ const disable2FAForm = reactive({ password: '' });
45
+ const totpForm = reactive({ code: '' });
46
+
47
+ const passwordSchema = v.object({
48
+ password: v.pipe(v.string('Passwort ist erforderlich'), v.minLength(1, 'Passwort ist erforderlich')),
49
+ });
50
+
51
+ const totpSchema = v.object({
52
+ code: v.pipe(v.string('Code ist erforderlich'), v.length(6, 'Code muss 6 Ziffern haben')),
53
+ });
54
+
55
+ type PasswordSchema = InferOutput<typeof passwordSchema>;
56
+ type TotpSchema = InferOutput<typeof totpSchema>;
41
57
 
42
58
  // ============================================================================
43
59
  // Lifecycle Hooks
@@ -114,21 +130,12 @@ async function deletePasskey(id: string): Promise<void> {
114
130
  }
115
131
  }
116
132
 
117
- async function disable2FA(): Promise<void> {
118
- if (!disable2FAPassword.value) {
119
- toast.add({
120
- color: 'error',
121
- description: 'Passwort ist erforderlich',
122
- title: 'Validierungsfehler',
123
- });
124
- return;
125
- }
126
-
133
+ async function disable2FA(payload: FormSubmitEvent<PasswordSchema>): Promise<void> {
127
134
  loading.value = true;
128
135
 
129
136
  try {
130
137
  const { error } = await authClient.twoFactor.disable({
131
- password: disable2FAPassword.value,
138
+ password: payload.data.password,
132
139
  });
133
140
 
134
141
  if (error) {
@@ -152,27 +159,18 @@ async function disable2FA(): Promise<void> {
152
159
  });
153
160
 
154
161
  show2FADisable.value = false;
155
- disable2FAPassword.value = '';
162
+ disable2FAForm.password = '';
156
163
  } finally {
157
164
  loading.value = false;
158
165
  }
159
166
  }
160
167
 
161
- async function enable2FA(): Promise<void> {
162
- if (!enable2FAPassword.value) {
163
- toast.add({
164
- color: 'error',
165
- description: 'Passwort ist erforderlich',
166
- title: 'Validierungsfehler',
167
- });
168
- return;
169
- }
170
-
168
+ async function enable2FA(payload: FormSubmitEvent<PasswordSchema>): Promise<void> {
171
169
  loading.value = true;
172
170
 
173
171
  try {
174
172
  const { data, error } = await authClient.twoFactor.enable({
175
- password: enable2FAPassword.value,
173
+ password: payload.data.password,
176
174
  });
177
175
 
178
176
  if (error) {
@@ -187,7 +185,7 @@ async function enable2FA(): Promise<void> {
187
185
  totpUri.value = data?.totpURI ?? '';
188
186
  backupCodes.value = data?.backupCodes ?? [];
189
187
  showTotpSetup.value = true;
190
- enable2FAPassword.value = '';
188
+ enable2FAForm.password = '';
191
189
  } finally {
192
190
  loading.value = false;
193
191
  }
@@ -220,21 +218,12 @@ async function openBackupCodesModal(codes: string[] = []): Promise<void> {
220
218
  await modal.open();
221
219
  }
222
220
 
223
- async function verifyTotp(): Promise<void> {
224
- if (!totpCode.value || totpCode.value.length !== 6) {
225
- toast.add({
226
- color: 'error',
227
- description: 'Code muss 6 Ziffern haben',
228
- title: 'Validierungsfehler',
229
- });
230
- return;
231
- }
232
-
221
+ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
233
222
  loading.value = true;
234
223
 
235
224
  try {
236
225
  const { error } = await authClient.twoFactor.verifyTotp({
237
- code: totpCode.value,
226
+ code: payload.data.code,
238
227
  });
239
228
 
240
229
  if (error) {
@@ -258,7 +247,7 @@ async function verifyTotp(): Promise<void> {
258
247
  });
259
248
 
260
249
  showTotpSetup.value = false;
261
- totpCode.value = '';
250
+ totpForm.code = '';
262
251
  await openBackupCodesModal(backupCodes.value);
263
252
  } finally {
264
253
  loading.value = false;
@@ -299,12 +288,12 @@ async function verifyTotp(): Promise<void> {
299
288
  </div>
300
289
 
301
290
  <template v-if="!is2FAEnabled && !showTotpSetup">
302
- <form class="space-y-4" @submit.prevent="enable2FA">
303
- <UFormField label="Passwort bestätigen" name="password" class="w-full">
304
- <UInput v-model="enable2FAPassword" type="password" placeholder="Dein Passwort" class="w-full" />
291
+ <UForm :schema="passwordSchema" :state="enable2FAForm" class="space-y-4" @submit="enable2FA">
292
+ <UFormField label="Passwort bestätigen" name="password">
293
+ <UInput v-model="enable2FAForm.password" type="password" placeholder="Dein Passwort" />
305
294
  </UFormField>
306
295
  <UButton type="submit" :loading="loading"> 2FA aktivieren </UButton>
307
- </form>
296
+ </UForm>
308
297
  </template>
309
298
 
310
299
  <template v-if="showTotpSetup">
@@ -315,15 +304,15 @@ async function verifyTotp(): Promise<void> {
315
304
  <img v-if="totpUri" :src="`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(totpUri)}`" alt="TOTP QR Code" class="rounded-lg" />
316
305
  </div>
317
306
 
318
- <form class="space-y-4" @submit.prevent="verifyTotp">
319
- <UFormField label="Verifizierungscode" name="code" class="w-full">
320
- <UInput v-model="totpCode" placeholder="000000" class="w-full text-center font-mono" />
307
+ <UForm :schema="totpSchema" :state="totpForm" class="space-y-4" @submit="verifyTotp">
308
+ <UFormField label="Verifizierungscode" name="code">
309
+ <UInput v-model="totpForm.code" placeholder="000000" class="text-center font-mono" />
321
310
  </UFormField>
322
311
  <div class="flex gap-2">
323
312
  <UButton type="submit" :loading="loading"> Verifizieren </UButton>
324
313
  <UButton variant="outline" color="neutral" @click="showTotpSetup = false"> Abbrechen </UButton>
325
314
  </div>
326
- </form>
315
+ </UForm>
327
316
  </div>
328
317
  </template>
329
318
 
@@ -335,16 +324,16 @@ async function verifyTotp(): Promise<void> {
335
324
  </template>
336
325
 
337
326
  <template v-if="show2FADisable">
338
- <form class="space-y-4" @submit.prevent="disable2FA">
327
+ <UForm :schema="passwordSchema" :state="disable2FAForm" class="space-y-4" @submit="disable2FA">
339
328
  <UAlert color="warning" icon="i-lucide-alert-triangle"> 2FA zu deaktivieren verringert die Sicherheit deines Kontos. </UAlert>
340
- <UFormField label="Passwort bestätigen" name="password" class="w-full">
341
- <UInput v-model="disable2FAPassword" type="password" placeholder="Dein Passwort" class="w-full" />
329
+ <UFormField label="Passwort bestätigen" name="password">
330
+ <UInput v-model="disable2FAForm.password" type="password" placeholder="Dein Passwort" />
342
331
  </UFormField>
343
332
  <div class="flex gap-2">
344
333
  <UButton type="submit" color="error" :loading="loading"> 2FA deaktivieren </UButton>
345
334
  <UButton variant="outline" color="neutral" @click="show2FADisable = false"> Abbrechen </UButton>
346
335
  </div>
347
- </form>
336
+ </UForm>
348
337
  </template>
349
338
  </div>
350
339
  </UCard>
@@ -2,6 +2,9 @@
2
2
  // ============================================================================
3
3
  // Imports
4
4
  // ============================================================================
5
+ import type { AuthFormField, FormSubmitEvent } from '@nuxt/ui';
6
+ import type { InferOutput } from 'valibot';
7
+
5
8
  import * as v from 'valibot';
6
9
 
7
10
  // ============================================================================
@@ -9,6 +12,7 @@ import * as v from 'valibot';
9
12
  // ============================================================================
10
13
  const toast = useToast();
11
14
  const { signUp, signIn, registerPasskey } = useLtAuth();
15
+ const { translateError } = useLtErrorTranslation();
12
16
 
13
17
  // ============================================================================
14
18
  // Page Meta
@@ -24,13 +28,36 @@ const loading = ref<boolean>(false);
24
28
  const showPasskeyPrompt = ref<boolean>(false);
25
29
  const passkeyLoading = ref<boolean>(false);
26
30
 
27
- // Form state - using refs for direct v-model binding
28
- const formState = reactive({
29
- name: '',
30
- email: '',
31
- password: '',
32
- confirmPassword: '',
33
- });
31
+ const fields: AuthFormField[] = [
32
+ {
33
+ label: 'Name',
34
+ name: 'name',
35
+ placeholder: 'Name eingeben',
36
+ required: true,
37
+ type: 'text',
38
+ },
39
+ {
40
+ label: 'E-Mail',
41
+ name: 'email',
42
+ placeholder: 'E-Mail eingeben',
43
+ required: true,
44
+ type: 'email',
45
+ },
46
+ {
47
+ label: 'Passwort',
48
+ name: 'password',
49
+ placeholder: 'Passwort eingeben',
50
+ required: true,
51
+ type: 'password',
52
+ },
53
+ {
54
+ label: 'Passwort bestätigen',
55
+ name: 'confirmPassword',
56
+ placeholder: 'Passwort wiederholen',
57
+ required: true,
58
+ type: 'password',
59
+ },
60
+ ];
34
61
 
35
62
  const schema = v.pipe(
36
63
  v.object({
@@ -45,47 +72,38 @@ const schema = v.pipe(
45
72
  ),
46
73
  );
47
74
 
75
+ type Schema = InferOutput<typeof schema>;
76
+
48
77
  // ============================================================================
49
78
  // Functions
50
79
  // ============================================================================
51
- async function onSubmit(): Promise<void> {
52
- // Validate
53
- const result = v.safeParse(schema, formState);
54
- if (!result.success) {
55
- const firstError = result.issues[0];
56
- toast.add({
57
- color: 'error',
58
- description: firstError.message,
59
- title: 'Validierungsfehler',
60
- });
61
- return;
62
- }
63
-
80
+ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
64
81
  loading.value = true;
65
82
 
66
83
  try {
67
84
  // Step 1: Sign up
68
85
  const signUpResult = await signUp.email({
69
- email: formState.email,
70
- name: formState.name,
71
- password: formState.password,
86
+ email: payload.data.email,
87
+ name: payload.data.name,
88
+ password: payload.data.password,
72
89
  });
73
90
 
74
91
  const signUpError = 'error' in signUpResult ? signUpResult.error : null;
75
92
 
76
93
  if (signUpError) {
94
+ const errorMessage = signUpError.message || 'Registrierung fehlgeschlagen';
77
95
  toast.add({
78
96
  color: 'error',
79
- description: signUpError.message || 'Registrierung fehlgeschlagen',
80
- title: 'Fehler',
97
+ description: translateError(errorMessage),
98
+ title: 'Registrierung fehlgeschlagen',
81
99
  });
82
100
  return;
83
101
  }
84
102
 
85
103
  // Step 2: Sign in to create session (required for passkey registration)
86
104
  const signInResult = await signIn.email({
87
- email: formState.email,
88
- password: formState.password,
105
+ email: payload.data.email,
106
+ password: payload.data.password,
89
107
  });
90
108
 
91
109
  const signInError = 'error' in signInResult ? signInResult.error : null;
@@ -152,65 +170,25 @@ async function skipPasskey(): Promise<void> {
152
170
  <template>
153
171
  <UPageCard class="w-md" variant="naked">
154
172
  <template v-if="!showPasskeyPrompt">
155
- <div class="flex flex-col items-center gap-4">
156
- <UIcon name="i-lucide-user-plus" class="size-12 text-primary" />
157
- <h1 class="text-2xl font-semibold">Registrieren</h1>
158
- </div>
159
-
160
- <form class="mt-6 flex flex-col gap-4" @submit.prevent="onSubmit">
161
- <UFormField label="Name" name="name" required class="w-full">
162
- <UInput
163
- v-model="formState.name"
164
- name="name"
165
- type="text"
166
- placeholder="Name eingeben"
167
- autocomplete="name"
168
- class="w-full"
169
- />
170
- </UFormField>
171
-
172
- <UFormField label="E-Mail" name="email" required class="w-full">
173
- <UInput
174
- v-model="formState.email"
175
- name="email"
176
- type="email"
177
- placeholder="E-Mail eingeben"
178
- autocomplete="email"
179
- class="w-full"
180
- />
181
- </UFormField>
182
-
183
- <UFormField label="Passwort" name="password" required class="w-full">
184
- <UInput
185
- v-model="formState.password"
186
- name="password"
187
- type="password"
188
- placeholder="Passwort eingeben"
189
- autocomplete="new-password"
190
- class="w-full"
191
- />
192
- </UFormField>
193
-
194
- <UFormField label="Passwort bestätigen" name="confirmPassword" required class="w-full">
195
- <UInput
196
- v-model="formState.confirmPassword"
197
- name="confirmPassword"
198
- type="password"
199
- placeholder="Passwort wiederholen"
200
- autocomplete="new-password"
201
- class="w-full"
202
- />
203
- </UFormField>
204
-
205
- <UButton type="submit" block :loading="loading">
206
- Konto erstellen
207
- </UButton>
208
- </form>
209
-
210
- <p class="mt-4 text-center text-sm text-muted">
211
- Bereits ein Konto?
212
- <ULink to="/auth/login" class="text-primary font-medium">Anmelden</ULink>
213
- </p>
173
+ <UAuthForm
174
+ :schema="schema"
175
+ title="Registrieren"
176
+ icon="i-lucide-user-plus"
177
+ :fields="fields"
178
+ :loading="loading"
179
+ :submit="{
180
+ label: 'Konto erstellen',
181
+ block: true,
182
+ }"
183
+ @submit="onSubmit"
184
+ >
185
+ <template #footer>
186
+ <p class="text-center text-sm text-muted">
187
+ Bereits ein Konto?
188
+ <ULink to="/auth/login" class="text-primary font-medium">Anmelden</ULink>
189
+ </p>
190
+ </template>
191
+ </UAuthForm>
214
192
  </template>
215
193
 
216
194
  <template v-else>
@@ -9427,7 +9427,9 @@
9427
9427
  }
9428
9428
  },
9429
9429
  "node_modules/devalue": {
9430
- "version": "5.6.1",
9430
+ "version": "5.6.2",
9431
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
9432
+ "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
9431
9433
  "license": "MIT"
9432
9434
  },
9433
9435
  "node_modules/dfa": {
@@ -9435,7 +9437,9 @@
9435
9437
  "license": "MIT"
9436
9438
  },
9437
9439
  "node_modules/diff": {
9438
- "version": "8.0.2",
9440
+ "version": "8.0.3",
9441
+ "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz",
9442
+ "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==",
9439
9443
  "license": "BSD-3-Clause",
9440
9444
  "engines": {
9441
9445
  "node": ">=0.3.1"
@@ -10269,7 +10273,9 @@
10269
10273
  }
10270
10274
  },
10271
10275
  "node_modules/h3": {
10272
- "version": "1.15.4",
10276
+ "version": "1.15.5",
10277
+ "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz",
10278
+ "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==",
10273
10279
  "license": "MIT",
10274
10280
  "dependencies": {
10275
10281
  "cookie-es": "^1.2.2",
@@ -10277,9 +10283,9 @@
10277
10283
  "defu": "^6.1.4",
10278
10284
  "destr": "^2.0.5",
10279
10285
  "iron-webcrypto": "^1.2.1",
10280
- "node-mock-http": "^1.0.2",
10286
+ "node-mock-http": "^1.0.4",
10281
10287
  "radix3": "^1.1.2",
10282
- "ufo": "^1.6.1",
10288
+ "ufo": "^1.6.3",
10283
10289
  "uncrypto": "^0.1.3"
10284
10290
  }
10285
10291
  },
@@ -11575,7 +11581,9 @@
11575
11581
  }
11576
11582
  },
11577
11583
  "node_modules/lodash": {
11578
- "version": "4.17.21",
11584
+ "version": "4.17.23",
11585
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
11586
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
11579
11587
  "license": "MIT"
11580
11588
  },
11581
11589
  "node_modules/lodash._baseiteratee": {
@@ -15655,7 +15663,9 @@
15655
15663
  }
15656
15664
  },
15657
15665
  "node_modules/tar": {
15658
- "version": "7.5.2",
15666
+ "version": "7.5.6",
15667
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz",
15668
+ "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==",
15659
15669
  "license": "BlueOak-1.0.0",
15660
15670
  "dependencies": {
15661
15671
  "@isaacs/fs-minipass": "^4.0.0",
@@ -15909,7 +15919,9 @@
15909
15919
  "license": "MIT"
15910
15920
  },
15911
15921
  "node_modules/ufo": {
15912
- "version": "1.6.2",
15922
+ "version": "1.6.3",
15923
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
15924
+ "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
15913
15925
  "license": "MIT"
15914
15926
  },
15915
15927
  "node_modules/ultrahtml": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nuxt-base",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Starter to generate a configured environment with VueJS, Nuxt, Tailwind, Eslint, Unit Tests, Playwright etc.",
5
5
  "license": "MIT",
6
6
  "author": "lenne.Tech GmbH",