create-nuxt-base 1.2.0 → 2.1.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/nuxt-base-template/.github/workflows/test.yml +90 -0
  3. package/nuxt-base-template/app/components/Modal/ModalBackupCodes.vue +2 -1
  4. package/nuxt-base-template/app/components/Upload/TusFileUpload.vue +7 -7
  5. package/nuxt-base-template/app/interfaces/user.interface.ts +5 -12
  6. package/nuxt-base-template/app/layouts/default.vue +1 -1
  7. package/nuxt-base-template/app/middleware/admin.global.ts +2 -2
  8. package/nuxt-base-template/app/middleware/auth.global.ts +2 -2
  9. package/nuxt-base-template/app/middleware/guest.global.ts +2 -2
  10. package/nuxt-base-template/app/pages/app/index.vue +1 -1
  11. package/nuxt-base-template/app/pages/app/settings/security.vue +54 -43
  12. package/nuxt-base-template/app/pages/auth/2fa.vue +2 -3
  13. package/nuxt-base-template/app/pages/auth/forgot-password.vue +2 -1
  14. package/nuxt-base-template/app/pages/auth/login.vue +6 -4
  15. package/nuxt-base-template/app/pages/auth/register.vue +85 -61
  16. package/nuxt-base-template/app/pages/auth/reset-password.vue +2 -1
  17. package/nuxt-base-template/docs/pages/docs.vue +1 -1
  18. package/nuxt-base-template/nuxt.config.ts +50 -1
  19. package/nuxt-base-template/package-lock.json +1311 -2920
  20. package/nuxt-base-template/package.json +27 -2
  21. package/nuxt-base-template/playwright.config.ts +1 -1
  22. package/nuxt-base-template/tests/e2e/auth.spec.ts +467 -0
  23. package/nuxt-base-template/tests/unit/auth/auth.spec.ts +439 -0
  24. package/nuxt-base-template/tests/unit/auth/error-translation.spec.ts +279 -0
  25. package/nuxt-base-template/tests/unit/mocks/auth-client.mock.ts +165 -0
  26. package/nuxt-base-template/tests/unit/mocks/nuxt-imports.ts +105 -0
  27. package/nuxt-base-template/tests/unit/setup.ts +56 -0
  28. package/nuxt-base-template/vitest.config.ts +25 -0
  29. package/package.json +1 -1
  30. package/nuxt-base-template/app/components/Transition/TransitionFade.vue +0 -27
  31. package/nuxt-base-template/app/components/Transition/TransitionFadeScale.vue +0 -27
  32. package/nuxt-base-template/app/components/Transition/TransitionSlide.vue +0 -12
  33. package/nuxt-base-template/app/components/Transition/TransitionSlideBottom.vue +0 -12
  34. package/nuxt-base-template/app/components/Transition/TransitionSlideRevert.vue +0 -12
  35. package/nuxt-base-template/app/composables/use-better-auth.ts +0 -597
  36. package/nuxt-base-template/app/composables/use-file.ts +0 -71
  37. package/nuxt-base-template/app/composables/use-share.ts +0 -38
  38. package/nuxt-base-template/app/composables/use-tus-upload.ts +0 -278
  39. package/nuxt-base-template/app/composables/use-tw.ts +0 -1
  40. package/nuxt-base-template/app/interfaces/upload.interface.ts +0 -58
  41. package/nuxt-base-template/app/lib/auth-client.ts +0 -229
  42. package/nuxt-base-template/app/lib/auth-state.ts +0 -206
  43. package/nuxt-base-template/app/plugins/auth-interceptor.client.ts +0 -151
  44. package/nuxt-base-template/app/utils/crypto.ts +0 -44
  45. package/nuxt-base-template/tests/iam.spec.ts +0 -247
  46. /package/nuxt-base-template/tests/{init.spec.ts → e2e/init.spec.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
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.0](https://github.com/lenneTech/nuxt-base-starter/compare/v2.0.0...v2.1.0) (2026-01-24)
6
+
7
+
8
+ ### Features
9
+
10
+ * **tests, i18n:** add test infrastructure and error translation integration ([f8aff01](https://github.com/lenneTech/nuxt-base-starter/commit/f8aff01b5c763ea54cee270e10a1a29e2bece867))
11
+
12
+ ## [2.0.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.2.0...v2.0.0) (2026-01-24)
13
+
14
+
15
+ ### ⚠ BREAKING CHANGES
16
+
17
+ * Local auth, upload, and utility files replaced by package
18
+
19
+ Migration to @lenne.tech/nuxt-extensions v1.0.1:
20
+ - Remove local composables (use-better-auth, use-tus-upload, use-file, use-share, use-tw)
21
+ - Remove local lib files (auth-client, auth-state)
22
+ - Remove local plugins (auth-interceptor.client)
23
+ - Remove local utils (crypto)
24
+ - Remove local interfaces (upload.interface)
25
+ - Remove local Transition components (now provided by package as Lt* prefix)
26
+ - Simplify user.interface.ts to re-export LtUser from package
27
+ - Update all imports to use auto-imported composables (useLtAuth, useLtTusUpload, etc.)
28
+ - Update package.json to use npm package instead of yalc
29
+
30
+ All authentication features now provided by the package:
31
+ - Cookie/JWT dual-mode authentication
32
+ - Passkey/WebAuthn support
33
+ - 2FA/TOTP support
34
+ - Auto-logout on 401
35
+
36
+ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
37
+
38
+ ### Bug Fixes
39
+
40
+ * use exact version 1.0.1 for [@lenne](https://github.com/lenne).tech/nuxt-extensions ([fe8d4c2](https://github.com/lenneTech/nuxt-base-starter/commit/fe8d4c21575977bd093a304adc2e18c7edc61110))
41
+
42
+
43
+ * migrate to [@lenne](https://github.com/lenne).tech/nuxt-extensions package ([4c8b732](https://github.com/lenneTech/nuxt-base-starter/commit/4c8b732b9d017a0ff7fb21ef09136dcd7fff81f0))
44
+
5
45
  ## [1.2.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.1.2...v1.2.0) (2026-01-22)
6
46
 
7
47
 
@@ -0,0 +1,90 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+
9
+ jobs:
10
+ unit-tests:
11
+ name: Unit Tests
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout repository
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup Node.js
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: '22'
22
+ cache: 'npm'
23
+
24
+ - name: Install dependencies
25
+ run: npm ci
26
+
27
+ - name: Run unit tests
28
+ run: npm run test:unit
29
+
30
+ - name: Run unit tests with coverage
31
+ run: npm run test:unit:coverage
32
+
33
+ - name: Upload coverage report
34
+ uses: actions/upload-artifact@v4
35
+ if: always()
36
+ with:
37
+ name: coverage-report
38
+ path: coverage/
39
+ retention-days: 7
40
+
41
+ lint:
42
+ name: Lint
43
+ runs-on: ubuntu-latest
44
+
45
+ steps:
46
+ - name: Checkout repository
47
+ uses: actions/checkout@v4
48
+
49
+ - name: Setup Node.js
50
+ uses: actions/setup-node@v4
51
+ with:
52
+ node-version: '22'
53
+ cache: 'npm'
54
+
55
+ - name: Install dependencies
56
+ run: npm ci
57
+
58
+ - name: Run linter
59
+ run: npm run lint
60
+
61
+ - name: Check formatting
62
+ run: npm run format:check
63
+
64
+ build:
65
+ name: Build
66
+ runs-on: ubuntu-latest
67
+ needs: [unit-tests, lint]
68
+
69
+ steps:
70
+ - name: Checkout repository
71
+ uses: actions/checkout@v4
72
+
73
+ - name: Setup Node.js
74
+ uses: actions/setup-node@v4
75
+ with:
76
+ node-version: '22'
77
+ cache: 'npm'
78
+
79
+ - name: Install dependencies
80
+ run: npm ci
81
+
82
+ - name: Build application
83
+ run: npm run build
84
+
85
+ - name: Upload build artifacts
86
+ uses: actions/upload-artifact@v4
87
+ with:
88
+ name: build
89
+ path: .output/
90
+ retention-days: 7
@@ -7,7 +7,8 @@ import type { InferOutput } from 'valibot';
7
7
 
8
8
  import * as v from 'valibot';
9
9
 
10
- import { authClient } from '~/lib/auth-client';
10
+ // Auth client from @lenne.tech/nuxt-extensions (auto-imported as ltAuthClient)
11
+ const authClient = ltAuthClient;
11
12
 
12
13
  // ============================================================================
13
14
  // Props & Emits
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import type { UploadItem } from '~/interfaces/upload.interface';
2
+ import type { LtUploadItem } from '@lenne.tech/nuxt-extensions';
3
3
 
4
4
  // ============================================================================
5
5
  // Props & Emits
@@ -45,11 +45,11 @@ const props = withDefaults(defineProps<Props>(), {
45
45
 
46
46
  const emit = defineEmits<{
47
47
  /** Alle Uploads abgeschlossen */
48
- complete: [items: UploadItem[]];
48
+ complete: [items: LtUploadItem[]];
49
49
  /** Upload-Fehler */
50
- error: [item: UploadItem, error: Error];
50
+ error: [item: LtUploadItem, error: Error];
51
51
  /** Ein Upload abgeschlossen */
52
- success: [item: UploadItem];
52
+ success: [item: LtUploadItem];
53
53
  /** Files geaendert */
54
54
  'update:modelValue': [files: File[]];
55
55
  }>();
@@ -58,12 +58,12 @@ const emit = defineEmits<{
58
58
  // Composables
59
59
  // ============================================================================
60
60
  const toast = useToast();
61
- const { formatDuration, formatFileSize } = useFile();
61
+ const { formatDuration, formatFileSize } = useLtFile();
62
62
 
63
63
  // ============================================================================
64
64
  // TUS Upload Setup
65
65
  // ============================================================================
66
- const { addFiles, cancelAll, cancelUpload, clearCompleted, isUploading, pauseAll, pauseUpload, resumeAll, resumeUpload, retryUpload, totalProgress, uploads } = useTusUpload({
66
+ const { addFiles, cancelAll, cancelUpload, clearCompleted, isUploading, pauseAll, pauseUpload, resumeAll, resumeUpload, retryUpload, totalProgress, uploads } = useLtTusUpload({
67
67
  autoStart: props.autoStart,
68
68
  chunkSize: props.chunkSize,
69
69
  endpoint: props.endpoint,
@@ -162,7 +162,7 @@ function getStatusLabel(status: string): string {
162
162
  // ============================================================================
163
163
  // Helpers
164
164
  // ============================================================================
165
- function getUploadForFile(file: File, index: number): undefined | UploadItem {
165
+ function getUploadForFile(file: File, index: number): undefined | LtUploadItem {
166
166
  // Versuche ueber Index zu matchen (funktioniert wenn Reihenfolge gleich)
167
167
  const upload = uploads.value[index];
168
168
  if (upload?.file.name === file.name && upload?.file.size === file.size) {
@@ -1,12 +1,5 @@
1
- export interface User {
2
- banExpires?: Date;
3
- banned?: boolean;
4
- banReason?: string;
5
- email: string;
6
- emailVerified: boolean;
7
- id: string;
8
- image?: string;
9
- name?: string;
10
- role?: string;
11
- twoFactorEnabled?: boolean;
12
- }
1
+ /**
2
+ * Re-export LtUser from @lenne.tech/nuxt-extensions as User
3
+ * This provides backwards compatibility while using the package type
4
+ */
5
+ export type { LtUser as User } from '@lenne.tech/nuxt-extensions';
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { NavigationMenuItem } from '@nuxt/ui';
3
3
 
4
- const { isAuthenticated, signOut, user } = useBetterAuth();
4
+ const { isAuthenticated, signOut, user } = useLtAuth();
5
5
 
6
6
  async function handleLogout() {
7
7
  await signOut();
@@ -10,7 +10,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
10
10
  // On client, read directly from document.cookie for accurate state
11
11
  if (import.meta.client) {
12
12
  try {
13
- const cookie = document.cookie.split('; ').find((row) => row.startsWith('auth-state='));
13
+ const cookie = document.cookie.split('; ').find((row) => row.startsWith('lt-auth-state='));
14
14
  if (cookie) {
15
15
  const parts = cookie.split('=');
16
16
  const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join('=')) : '';
@@ -23,7 +23,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
23
23
  }
24
24
  } else {
25
25
  // On server, use useCookie
26
- const authStateCookie = useCookie<{ user: { role?: string } | null; authMode: string } | null>('auth-state');
26
+ const authStateCookie = useCookie<{ user: { role?: string } | null; authMode: string } | null>('lt-auth-state');
27
27
  isAuthenticated = !!authStateCookie.value?.user;
28
28
  isAdmin = authStateCookie.value?.user?.role === 'admin';
29
29
  }
@@ -9,7 +9,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
9
9
  // On client, read directly from document.cookie for accurate state
10
10
  if (import.meta.client) {
11
11
  try {
12
- const cookie = document.cookie.split('; ').find((row) => row.startsWith('auth-state='));
12
+ const cookie = document.cookie.split('; ').find((row) => row.startsWith('lt-auth-state='));
13
13
  if (cookie) {
14
14
  const parts = cookie.split('=');
15
15
  const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join('=')) : '';
@@ -21,7 +21,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
21
21
  }
22
22
  } else {
23
23
  // On server, use useCookie
24
- const authStateCookie = useCookie<{ user: unknown; authMode: string } | null>('auth-state');
24
+ const authStateCookie = useCookie<{ user: unknown; authMode: string } | null>('lt-auth-state');
25
25
  isAuthenticated = !!authStateCookie.value?.user;
26
26
  }
27
27
 
@@ -9,7 +9,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
9
9
  // On client, read directly from document.cookie for accurate state
10
10
  if (import.meta.client) {
11
11
  try {
12
- const cookie = document.cookie.split('; ').find((row) => row.startsWith('auth-state='));
12
+ const cookie = document.cookie.split('; ').find((row) => row.startsWith('lt-auth-state='));
13
13
  if (cookie) {
14
14
  const parts = cookie.split('=');
15
15
  const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join('=')) : '';
@@ -21,7 +21,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
21
21
  }
22
22
  } else {
23
23
  // On server, use useCookie
24
- const authStateCookie = useCookie<{ user: unknown; authMode: string } | null>('auth-state');
24
+ const authStateCookie = useCookie<{ user: unknown; authMode: string } | null>('lt-auth-state');
25
25
  isAuthenticated = !!authStateCookie.value?.user;
26
26
  }
27
27
 
@@ -2,7 +2,7 @@
2
2
  // ============================================================================
3
3
  // Composables
4
4
  // ============================================================================
5
- const { user, signOut } = useBetterAuth();
5
+ const { user, signOut } = useLtAuth();
6
6
 
7
7
  // ============================================================================
8
8
  // Variables
@@ -2,13 +2,7 @@
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
-
10
5
  import ModalBackupCodes from '~/components/Modal/ModalBackupCodes.vue';
11
- import { authClient } from '~/lib/auth-client';
12
6
 
13
7
  // ============================================================================
14
8
  // Interfaces
@@ -24,7 +18,8 @@ interface Passkey {
24
18
  // ============================================================================
25
19
  const toast = useToast();
26
20
  const overlay = useOverlay();
27
- const { is2FAEnabled, registerPasskey, setUser, user } = useBetterAuth();
21
+ const { is2FAEnabled, registerPasskey, setUser, user } = useLtAuth();
22
+ const authClient = useLtAuthClient();
28
23
 
29
24
  // ============================================================================
30
25
  // Variables
@@ -39,21 +34,10 @@ const passkeyLoading = ref<boolean>(false);
39
34
  const newPasskeyName = ref<string>('');
40
35
  const showAddPasskey = ref<boolean>(false);
41
36
 
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>;
37
+ // Form states - using reactive for direct v-model binding
38
+ const enable2FAPassword = ref('');
39
+ const disable2FAPassword = ref('');
40
+ const totpCode = ref('');
57
41
 
58
42
  // ============================================================================
59
43
  // Lifecycle Hooks
@@ -130,12 +114,21 @@ async function deletePasskey(id: string): Promise<void> {
130
114
  }
131
115
  }
132
116
 
133
- async function disable2FA(payload: FormSubmitEvent<PasswordSchema>): Promise<void> {
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
+
134
127
  loading.value = true;
135
128
 
136
129
  try {
137
130
  const { error } = await authClient.twoFactor.disable({
138
- password: payload.data.password,
131
+ password: disable2FAPassword.value,
139
132
  });
140
133
 
141
134
  if (error) {
@@ -159,18 +152,27 @@ async function disable2FA(payload: FormSubmitEvent<PasswordSchema>): Promise<voi
159
152
  });
160
153
 
161
154
  show2FADisable.value = false;
162
- disable2FAForm.password = '';
155
+ disable2FAPassword.value = '';
163
156
  } finally {
164
157
  loading.value = false;
165
158
  }
166
159
  }
167
160
 
168
- async function enable2FA(payload: FormSubmitEvent<PasswordSchema>): Promise<void> {
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
+
169
171
  loading.value = true;
170
172
 
171
173
  try {
172
174
  const { data, error } = await authClient.twoFactor.enable({
173
- password: payload.data.password,
175
+ password: enable2FAPassword.value,
174
176
  });
175
177
 
176
178
  if (error) {
@@ -185,7 +187,7 @@ async function enable2FA(payload: FormSubmitEvent<PasswordSchema>): Promise<void
185
187
  totpUri.value = data?.totpURI ?? '';
186
188
  backupCodes.value = data?.backupCodes ?? [];
187
189
  showTotpSetup.value = true;
188
- enable2FAForm.password = '';
190
+ enable2FAPassword.value = '';
189
191
  } finally {
190
192
  loading.value = false;
191
193
  }
@@ -218,12 +220,21 @@ async function openBackupCodesModal(codes: string[] = []): Promise<void> {
218
220
  await modal.open();
219
221
  }
220
222
 
221
- async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
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
+
222
233
  loading.value = true;
223
234
 
224
235
  try {
225
236
  const { error } = await authClient.twoFactor.verifyTotp({
226
- code: payload.data.code,
237
+ code: totpCode.value,
227
238
  });
228
239
 
229
240
  if (error) {
@@ -247,7 +258,7 @@ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
247
258
  });
248
259
 
249
260
  showTotpSetup.value = false;
250
- totpForm.code = '';
261
+ totpCode.value = '';
251
262
  await openBackupCodesModal(backupCodes.value);
252
263
  } finally {
253
264
  loading.value = false;
@@ -288,12 +299,12 @@ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
288
299
  </div>
289
300
 
290
301
  <template v-if="!is2FAEnabled && !showTotpSetup">
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" />
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" />
294
305
  </UFormField>
295
306
  <UButton type="submit" :loading="loading"> 2FA aktivieren </UButton>
296
- </UForm>
307
+ </form>
297
308
  </template>
298
309
 
299
310
  <template v-if="showTotpSetup">
@@ -304,15 +315,15 @@ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
304
315
  <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" />
305
316
  </div>
306
317
 
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" />
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" />
310
321
  </UFormField>
311
322
  <div class="flex gap-2">
312
323
  <UButton type="submit" :loading="loading"> Verifizieren </UButton>
313
324
  <UButton variant="outline" color="neutral" @click="showTotpSetup = false"> Abbrechen </UButton>
314
325
  </div>
315
- </UForm>
326
+ </form>
316
327
  </div>
317
328
  </template>
318
329
 
@@ -324,16 +335,16 @@ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
324
335
  </template>
325
336
 
326
337
  <template v-if="show2FADisable">
327
- <UForm :schema="passwordSchema" :state="disable2FAForm" class="space-y-4" @submit="disable2FA">
338
+ <form class="space-y-4" @submit.prevent="disable2FA">
328
339
  <UAlert color="warning" icon="i-lucide-alert-triangle"> 2FA zu deaktivieren verringert die Sicherheit deines Kontos. </UAlert>
329
- <UFormField label="Passwort bestätigen" name="password">
330
- <UInput v-model="disable2FAForm.password" type="password" placeholder="Dein Passwort" />
340
+ <UFormField label="Passwort bestätigen" name="password" class="w-full">
341
+ <UInput v-model="disable2FAPassword" type="password" placeholder="Dein Passwort" class="w-full" />
331
342
  </UFormField>
332
343
  <div class="flex gap-2">
333
344
  <UButton type="submit" color="error" :loading="loading"> 2FA deaktivieren </UButton>
334
345
  <UButton variant="outline" color="neutral" @click="show2FADisable = false"> Abbrechen </UButton>
335
346
  </div>
336
- </UForm>
347
+ </form>
337
348
  </template>
338
349
  </div>
339
350
  </UCard>
@@ -7,13 +7,12 @@ import type { InferOutput } from 'valibot';
7
7
 
8
8
  import * as v from 'valibot';
9
9
 
10
- import { authClient } from '~/lib/auth-client';
11
-
12
10
  // ============================================================================
13
11
  // Composables
14
12
  // ============================================================================
15
13
  const toast = useToast();
16
- const { fetchWithAuth, setUser, switchToJwtMode, jwtToken } = useBetterAuth();
14
+ const { fetchWithAuth, setUser, switchToJwtMode, jwtToken } = useLtAuth();
15
+ const authClient = useLtAuthClient();
17
16
 
18
17
  // ============================================================================
19
18
  // Page Meta
@@ -7,7 +7,8 @@ import type { InferOutput } from 'valibot';
7
7
 
8
8
  import * as v from 'valibot';
9
9
 
10
- import { authClient } from '~/lib/auth-client';
10
+ // Auth client from @lenne.tech/nuxt-extensions (auto-imported as ltAuthClient)
11
+ const authClient = ltAuthClient;
11
12
 
12
13
  // ============================================================================
13
14
  // Composables
@@ -11,7 +11,8 @@ import * as v from 'valibot';
11
11
  // Composables
12
12
  // ============================================================================
13
13
  const toast = useToast();
14
- const { signIn, setUser, isLoading, validateSession, authenticateWithPasskey } = useBetterAuth();
14
+ const { signIn, setUser, isLoading, validateSession, authenticateWithPasskey } = useLtAuth();
15
+ const { translateError } = useLtErrorTranslation();
15
16
 
16
17
  // ============================================================================
17
18
  // Page Meta
@@ -114,10 +115,11 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
114
115
 
115
116
  // Check for error in response
116
117
  if ('error' in result && result.error) {
118
+ const errorMessage = (result.error as { message?: string }).message || 'Anmeldung fehlgeschlagen';
117
119
  toast.add({
118
120
  color: 'error',
119
- description: (result.error as { message?: string }).message || 'Anmeldung fehlgeschlagen',
120
- title: 'Fehler',
121
+ description: translateError(errorMessage),
122
+ title: 'Anmeldung fehlgeschlagen',
121
123
  });
122
124
  return;
123
125
  }
@@ -135,7 +137,7 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
135
137
  // Check if login was successful (user data in response)
136
138
  const userData = 'user' in result ? result.user : 'data' in result ? result.data?.user : null;
137
139
  if (userData) {
138
- // Auth state is already stored by useBetterAuth
140
+ // Auth state is already stored by useLtAuth
139
141
  // Navigate to app
140
142
  await navigateTo('/app');
141
143
  } else {