create-nuxt-base 2.1.4 → 2.2.1

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.2.1](https://github.com/lenneTech/nuxt-base-starter/compare/v2.2.0...v2.2.1) (2026-02-04)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * bundle lucide loader-circle icon to prevent runtime loading failure ([afc7ae7](https://github.com/lenneTech/nuxt-base-starter/commit/afc7ae79d70db283fe4b2042c624feaf6b6e65cc))
11
+
12
+ ## [2.2.0](https://github.com/lenneTech/nuxt-base-starter/compare/v2.1.4...v2.2.0) (2026-02-04)
13
+
14
+
15
+ ### Features
16
+
17
+ * improve registration and login with email verification, terms checkbox, and TS fixes ([dee6b95](https://github.com/lenneTech/nuxt-base-starter/commit/dee6b95b29890bdccf09570192f869397694b61f))
18
+
5
19
  ### [2.1.4](https://github.com/lenneTech/nuxt-base-starter/compare/v2.1.3...v2.1.4) (2026-01-26)
6
20
 
7
21
 
@@ -1,7 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  import type { NavigationMenuItem } from '@nuxt/ui';
3
3
 
4
- const { isAuthenticated, signOut, user } = useLtAuth();
4
+ const { isAuthenticated, signOut, user, validateSession } = useLtAuth();
5
+
6
+ onMounted(() => {
7
+ validateSession();
8
+ });
5
9
 
6
10
  async function handleLogout() {
7
11
  await signOut();
@@ -345,7 +345,7 @@ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
345
345
 
346
346
  <template v-if="show2FADisable">
347
347
  <UForm :schema="passwordSchema" :state="disable2FAForm" class="space-y-4" @submit="disable2FA">
348
- <UAlert color="warning" icon="i-lucide-alert-triangle"> 2FA zu deaktivieren verringert die Sicherheit deines Kontos. </UAlert>
348
+ <UAlert color="warning" icon="i-lucide-alert-triangle" description="2FA zu deaktivieren verringert die Sicherheit deines Kontos." />
349
349
  <UFormField label="Passwort bestätigen" name="password">
350
350
  <UInput v-model="disable2FAForm.password" type="password" placeholder="Dein Passwort" />
351
351
  </UFormField>
@@ -381,7 +381,7 @@ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
381
381
  <p class="text-xs text-muted">Erstellt am {{ new Date(passkey.createdAt).toLocaleDateString('de-DE') }}</p>
382
382
  </div>
383
383
  </div>
384
- <UButton variant="ghost" color="error" icon="i-lucide-trash-2" size="sm" :loading="passkeyLoading" @click="deletePasskey(passkey.id)" />
384
+ <UButton variant="outline" color="error" icon="i-lucide-trash" size="sm" :loading="passkeyLoading" @click="deletePasskey(passkey.id)">Löschen</UButton>
385
385
  </div>
386
386
  </div>
387
387
  </template>
@@ -7,8 +7,8 @@ import type { InferOutput } from 'valibot';
7
7
 
8
8
  import * as v from 'valibot';
9
9
 
10
- // Auth client from @lenne.tech/nuxt-extensions (auto-imported as ltAuthClient)
11
- const authClient = ltAuthClient;
10
+ // Auth client from @lenne.tech/nuxt-extensions
11
+ const authClient = useLtAuthClient();
12
12
 
13
13
  // ============================================================================
14
14
  // Composables
@@ -7,11 +7,30 @@ import type { InferOutput } from 'valibot';
7
7
 
8
8
  import * as v from 'valibot';
9
9
 
10
+ // ============================================================================
11
+ // Types
12
+ // ============================================================================
13
+ interface SignInResponse {
14
+ data?: {
15
+ redirect?: boolean;
16
+ requiresTwoFactor?: boolean;
17
+ token?: string | null;
18
+ twoFactorRedirect?: boolean;
19
+ url?: string;
20
+ user?: Record<string, unknown>;
21
+ } | null;
22
+ error?: {
23
+ code?: string;
24
+ message?: string;
25
+ status?: number;
26
+ } | null;
27
+ }
28
+
10
29
  // ============================================================================
11
30
  // Composables
12
31
  // ============================================================================
13
32
  const toast = useToast();
14
- const { signIn, setUser, isLoading, validateSession, authenticateWithPasskey } = useLtAuth();
33
+ const { signIn, setUser, validateSession, authenticateWithPasskey } = useLtAuth();
15
34
  const { translateError } = useLtErrorTranslation();
16
35
 
17
36
  // ============================================================================
@@ -108,14 +127,26 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
108
127
  loading.value = true;
109
128
 
110
129
  try {
111
- const result = await signIn.email({
130
+ const result = (await signIn.email({
112
131
  email: payload.data.email,
113
132
  password: payload.data.password,
114
- });
133
+ })) as SignInResponse;
115
134
 
116
135
  // Check for error in response
117
- if ('error' in result && result.error) {
118
- const errorMessage = (result.error as { message?: string }).message || 'Anmeldung fehlgeschlagen';
136
+ if (result.error) {
137
+ const errorMessage = result.error.message || 'Anmeldung fehlgeschlagen';
138
+
139
+ // Check if email verification is required → redirect to verify-email page
140
+ if (errorMessage.includes('LTNS_0023') || errorMessage.toLowerCase().includes('email verification required')) {
141
+ toast.add({
142
+ color: 'warning',
143
+ description: 'Bitte bestätige zuerst deine E-Mail-Adresse.',
144
+ title: 'E-Mail nicht verifiziert',
145
+ });
146
+ await navigateTo({ path: '/auth/verify-email', query: { email: payload.data.email } });
147
+ return;
148
+ }
149
+
119
150
  toast.add({
120
151
  color: 'error',
121
152
  description: translateError(errorMessage),
@@ -126,8 +157,8 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
126
157
 
127
158
  // Check if 2FA is required
128
159
  // Better-Auth native uses 'twoFactorRedirect', nest-server REST API uses 'requiresTwoFactor'
129
- const resultData = 'data' in result ? result.data : result;
130
- const requires2FA = resultData && (('twoFactorRedirect' in resultData && resultData.twoFactorRedirect) || ('requiresTwoFactor' in resultData && resultData.requiresTwoFactor));
160
+ const resultData = result.data as Record<string, unknown> | null | undefined;
161
+ const requires2FA = resultData && (resultData.twoFactorRedirect || resultData.requiresTwoFactor || resultData.redirect);
131
162
  if (requires2FA) {
132
163
  // Redirect to 2FA page
133
164
  await navigateTo('/auth/2fa');
@@ -135,7 +166,7 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
135
166
  }
136
167
 
137
168
  // Check if login was successful (user data in response)
138
- const userData = 'user' in result ? result.user : 'data' in result ? result.data?.user : null;
169
+ const userData = result.data?.user;
139
170
  if (userData) {
140
171
  // Auth state is already stored by useLtAuth
141
172
  // Navigate to app
@@ -7,11 +7,26 @@ import type { InferOutput } from 'valibot';
7
7
 
8
8
  import * as v from 'valibot';
9
9
 
10
+ // ============================================================================
11
+ // Types
12
+ // ============================================================================
13
+ interface AuthResponse {
14
+ data?: {
15
+ token?: string | null;
16
+ user?: Record<string, unknown>;
17
+ } | null;
18
+ error?: {
19
+ code?: string;
20
+ message?: string;
21
+ status?: number;
22
+ } | null;
23
+ }
24
+
10
25
  // ============================================================================
11
26
  // Composables
12
27
  // ============================================================================
13
28
  const toast = useToast();
14
- const { signUp, signIn, registerPasskey } = useLtAuth();
29
+ const { signUp, signIn, registerPasskey, features, clearUser } = useLtAuth();
15
30
  const { translateError } = useLtErrorTranslation();
16
31
 
17
32
  // ============================================================================
@@ -28,7 +43,9 @@ const loading = ref<boolean>(false);
28
43
  const showPasskeyPrompt = ref<boolean>(false);
29
44
  const passkeyLoading = ref<boolean>(false);
30
45
 
31
- const fields: AuthFormField[] = [
46
+ const requireTerms = computed(() => features.value.signUpChecks === true);
47
+
48
+ const baseFields: AuthFormField[] = [
32
49
  {
33
50
  label: 'Name',
34
51
  name: 'name',
@@ -59,12 +76,41 @@ const fields: AuthFormField[] = [
59
76
  },
60
77
  ];
61
78
 
62
- const schema = v.pipe(
79
+ const fields = computed<AuthFormField[]>(() => {
80
+ if (!requireTerms.value) {
81
+ return baseFields;
82
+ }
83
+ return [
84
+ ...baseFields,
85
+ {
86
+ label: '',
87
+ name: 'termsAccepted',
88
+ required: true,
89
+ type: 'checkbox',
90
+ },
91
+ ];
92
+ });
93
+
94
+ const baseSchema = v.pipe(
95
+ v.object({
96
+ confirmPassword: v.pipe(v.string('Passwortbestätigung ist erforderlich'), v.minLength(8, 'Mindestens 8 Zeichen erforderlich')),
97
+ email: v.pipe(v.string('E-Mail ist erforderlich'), v.email('Bitte eine gültige E-Mail eingeben')),
98
+ name: v.pipe(v.string('Name ist erforderlich'), v.minLength(2, 'Mindestens 2 Zeichen erforderlich')),
99
+ password: v.pipe(v.string('Passwort ist erforderlich'), v.minLength(8, 'Mindestens 8 Zeichen erforderlich')),
100
+ }),
101
+ v.forward(
102
+ v.partialCheck([['password'], ['confirmPassword']], (input) => input.password === input.confirmPassword, 'Passwörter stimmen nicht überein'),
103
+ ['confirmPassword'],
104
+ ),
105
+ );
106
+
107
+ const termsSchema = v.pipe(
63
108
  v.object({
64
109
  confirmPassword: v.pipe(v.string('Passwortbestätigung ist erforderlich'), v.minLength(8, 'Mindestens 8 Zeichen erforderlich')),
65
110
  email: v.pipe(v.string('E-Mail ist erforderlich'), v.email('Bitte eine gültige E-Mail eingeben')),
66
111
  name: v.pipe(v.string('Name ist erforderlich'), v.minLength(2, 'Mindestens 2 Zeichen erforderlich')),
67
112
  password: v.pipe(v.string('Passwort ist erforderlich'), v.minLength(8, 'Mindestens 8 Zeichen erforderlich')),
113
+ termsAccepted: v.pipe(v.optional(v.boolean(), false), v.literal(true, 'Bitte akzeptiere die AGB und Datenschutzerklärung')),
68
114
  }),
69
115
  v.forward(
70
116
  v.partialCheck([['password'], ['confirmPassword']], (input) => input.password === input.confirmPassword, 'Passwörter stimmen nicht überein'),
@@ -72,7 +118,9 @@ const schema = v.pipe(
72
118
  ),
73
119
  );
74
120
 
75
- type Schema = InferOutput<typeof schema>;
121
+ const schema = computed(() => requireTerms.value ? termsSchema : baseSchema);
122
+
123
+ type Schema = InferOutput<typeof baseSchema> | InferOutput<typeof termsSchema>;
76
124
 
77
125
  // ============================================================================
78
126
  // Functions
@@ -82,16 +130,15 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
82
130
 
83
131
  try {
84
132
  // Step 1: Sign up
85
- const signUpResult = await signUp.email({
133
+ const signUpResult = (await signUp.email({
86
134
  email: payload.data.email,
87
135
  name: payload.data.name,
88
136
  password: payload.data.password,
89
- });
137
+ ...(requireTerms.value ? { termsAndPrivacyAccepted: true } : {}),
138
+ })) as AuthResponse;
90
139
 
91
- const signUpError = 'error' in signUpResult ? signUpResult.error : null;
92
-
93
- if (signUpError) {
94
- const errorMessage = signUpError.message || 'Registrierung fehlgeschlagen';
140
+ if (signUpResult.error) {
141
+ const errorMessage = signUpResult.error.message || 'Registrierung fehlgeschlagen';
95
142
  toast.add({
96
143
  color: 'error',
97
144
  description: translateError(errorMessage),
@@ -100,13 +147,27 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
100
147
  return;
101
148
  }
102
149
 
150
+ // If email verification is enabled, clear auth state and redirect to verify-email page
151
+ // The backend revokes the session, but we also clear the frontend state to prevent
152
+ // manual navigation to protected routes
153
+ if (features.value.emailVerification) {
154
+ clearUser();
155
+ toast.add({
156
+ color: 'success',
157
+ description: 'Bitte überprüfe dein Postfach und bestätige deine E-Mail-Adresse.',
158
+ title: 'Konto erstellt!',
159
+ });
160
+ await navigateTo({ path: '/auth/verify-email', query: { email: payload.data.email, fromRegister: 'true' } });
161
+ return;
162
+ }
163
+
103
164
  // Step 2: Sign in to create session (required for passkey registration)
104
- const signInResult = await signIn.email({
165
+ const signInResult = (await signIn.email({
105
166
  email: payload.data.email,
106
167
  password: payload.data.password,
107
- });
168
+ })) as AuthResponse;
108
169
 
109
- const signInError = 'error' in signInResult ? signInResult.error : null;
170
+ const signInError = signInResult.error;
110
171
 
111
172
  if (signInError) {
112
173
  // Sign-up was successful but sign-in failed - still show success and redirect
@@ -182,6 +243,19 @@ async function skipPasskey(): Promise<void> {
182
243
  }"
183
244
  @submit="onSubmit"
184
245
  >
246
+ <template v-if="requireTerms" #termsAccepted-field="{ state: formState }">
247
+ <UCheckbox v-model="formState.termsAccepted">
248
+ <template #label>
249
+ <span class="text-sm">
250
+ Ich akzeptiere die
251
+ <ULink to="/legal/terms" class="text-primary font-medium" target="_blank">AGB</ULink>
252
+ und
253
+ <ULink to="/legal/privacy" class="text-primary font-medium" target="_blank">Datenschutzerklärung</ULink>
254
+ </span>
255
+ </template>
256
+ </UCheckbox>
257
+ </template>
258
+
185
259
  <template #footer>
186
260
  <p class="text-center text-sm text-muted">
187
261
  Bereits ein Konto?
@@ -7,8 +7,8 @@ import type { InferOutput } from 'valibot';
7
7
 
8
8
  import * as v from 'valibot';
9
9
 
10
- // Auth client from @lenne.tech/nuxt-extensions (auto-imported as ltAuthClient)
11
- const authClient = ltAuthClient;
10
+ // Auth client from @lenne.tech/nuxt-extensions
11
+ const authClient = useLtAuthClient();
12
12
 
13
13
  // ============================================================================
14
14
  // Composables
@@ -0,0 +1,209 @@
1
+ <script setup lang="ts">
2
+ // ============================================================================
3
+ // Composables
4
+ // ============================================================================
5
+ const route = useRoute();
6
+ const toast = useToast();
7
+ const runtimeConfig = useRuntimeConfig();
8
+ const apiBase = import.meta.dev ? '/api/iam' : `${runtimeConfig.public.apiUrl || 'http://localhost:3000'}/iam`;
9
+
10
+ // ============================================================================
11
+ // Page Meta
12
+ // ============================================================================
13
+ definePageMeta({
14
+ layout: 'slim',
15
+ });
16
+
17
+ // ============================================================================
18
+ // Variables
19
+ // ============================================================================
20
+ const email = computed(() => (route.query.email as string) || '');
21
+ const token = computed(() => (route.query.token as string) || '');
22
+
23
+ const verifying = ref(false);
24
+ const verified = ref(false);
25
+ const verifyError = ref('');
26
+ const resending = ref(false);
27
+ const resendCooldown = ref(0);
28
+ const cooldownSeconds = ref(60);
29
+ let cooldownInterval: ReturnType<typeof setInterval> | null = null;
30
+
31
+ // ============================================================================
32
+ // Functions
33
+ // ============================================================================
34
+ async function verifyEmailWithToken(verificationToken: string): Promise<void> {
35
+ verifying.value = true;
36
+ verifyError.value = '';
37
+
38
+ try {
39
+ const result = await $fetch<{ status: boolean }>(`${apiBase}/verify-email`, {
40
+ params: { token: verificationToken },
41
+ redirect: 'manual',
42
+ });
43
+
44
+ if (!result?.status) {
45
+ verifyError.value = 'Verifizierung fehlgeschlagen';
46
+ return;
47
+ }
48
+
49
+ verified.value = true;
50
+ toast.add({
51
+ color: 'success',
52
+ description: 'Deine E-Mail-Adresse wurde erfolgreich verifiziert.',
53
+ title: 'E-Mail bestätigt',
54
+ });
55
+ } catch {
56
+ verifyError.value = 'Die Verifizierung ist fehlgeschlagen. Der Link ist möglicherweise abgelaufen.';
57
+ } finally {
58
+ verifying.value = false;
59
+ }
60
+ }
61
+
62
+ async function resendVerificationEmail(): Promise<void> {
63
+ if (!email.value || resendCooldown.value > 0) return;
64
+
65
+ resending.value = true;
66
+
67
+ try {
68
+ await $fetch(`${apiBase}/send-verification-email`, {
69
+ body: {
70
+ callbackURL: '/auth/verify-email',
71
+ email: email.value,
72
+ },
73
+ method: 'POST',
74
+ });
75
+
76
+ toast.add({
77
+ color: 'success',
78
+ description: 'Eine neue Bestätigungs-E-Mail wurde gesendet.',
79
+ title: 'E-Mail gesendet',
80
+ });
81
+
82
+ startCooldown(cooldownSeconds.value);
83
+ } catch {
84
+ toast.add({
85
+ color: 'error',
86
+ description: 'Die E-Mail konnte nicht gesendet werden. Bitte versuche es später erneut.',
87
+ title: 'Fehler',
88
+ });
89
+ } finally {
90
+ resending.value = false;
91
+ }
92
+ }
93
+
94
+ function startCooldown(seconds: number): void {
95
+ resendCooldown.value = seconds;
96
+ if (cooldownInterval) clearInterval(cooldownInterval);
97
+ cooldownInterval = setInterval(() => {
98
+ resendCooldown.value--;
99
+ if (resendCooldown.value <= 0) {
100
+ if (cooldownInterval) clearInterval(cooldownInterval);
101
+ cooldownInterval = null;
102
+ }
103
+ }, 1000);
104
+ }
105
+
106
+ // ============================================================================
107
+ // Lifecycle
108
+ // ============================================================================
109
+ onMounted(async () => {
110
+ // Fetch dynamic cooldown configuration from backend
111
+ try {
112
+ const features = await $fetch<Record<string, boolean | number | string[]>>(`${apiBase}/features`);
113
+ if (typeof features?.resendCooldownSeconds === 'number') {
114
+ cooldownSeconds.value = features.resendCooldownSeconds;
115
+ }
116
+ } catch {
117
+ // Use default cooldown if features endpoint is unavailable
118
+ }
119
+
120
+ if (token.value) {
121
+ await verifyEmailWithToken(token.value);
122
+ } else if (route.query.fromRegister === 'true') {
123
+ // Email was just sent during registration, start initial cooldown
124
+ startCooldown(cooldownSeconds.value);
125
+ }
126
+ });
127
+
128
+ onUnmounted(() => {
129
+ if (cooldownInterval) clearInterval(cooldownInterval);
130
+ });
131
+ </script>
132
+
133
+ <template>
134
+ <UPageCard class="w-md" variant="naked">
135
+ <!-- Verifying state: Token verification in progress -->
136
+ <template v-if="verifying">
137
+ <div class="flex flex-col items-center gap-6 py-4">
138
+ <UIcon name="i-lucide-loader-circle" class="size-16 animate-spin text-primary" />
139
+ <div class="text-center">
140
+ <h2 class="text-xl font-semibold">E-Mail wird verifiziert...</h2>
141
+ <p class="mt-2 text-sm text-muted">Bitte warte einen Moment.</p>
142
+ </div>
143
+ </div>
144
+ </template>
145
+
146
+ <!-- Success state: Email verified -->
147
+ <template v-else-if="verified">
148
+ <div class="flex flex-col items-center gap-6 py-4">
149
+ <UIcon name="i-lucide-check-circle" class="size-16 text-success" />
150
+ <div class="text-center">
151
+ <h2 class="text-xl font-semibold">E-Mail bestätigt</h2>
152
+ <p class="mt-2 text-sm text-muted">Deine E-Mail-Adresse wurde erfolgreich verifiziert. Du kannst dich jetzt anmelden.</p>
153
+ </div>
154
+ <UButton block to="/auth/login">Jetzt anmelden</UButton>
155
+ </div>
156
+ </template>
157
+
158
+ <!-- Error state: Verification failed -->
159
+ <template v-else-if="verifyError">
160
+ <div class="flex flex-col items-center gap-6 py-4">
161
+ <UIcon name="i-lucide-x-circle" class="size-16 text-error" />
162
+ <div class="text-center">
163
+ <h2 class="text-xl font-semibold">Verifizierung fehlgeschlagen</h2>
164
+ <p class="mt-2 text-sm text-muted">{{ verifyError }}</p>
165
+ </div>
166
+
167
+ <div class="flex w-full flex-col gap-3">
168
+ <UButton v-if="email" block :color="resendCooldown > 0 ? 'neutral' : 'primary'" :disabled="resendCooldown > 0" :loading="resending" :variant="resendCooldown > 0 ? 'outline' : 'solid'" @click="resendVerificationEmail">
169
+ {{ resendCooldown > 0 ? `Neue E-Mail senden (${resendCooldown}s)` : 'Neue E-Mail senden' }}
170
+ </UButton>
171
+ <UButton block variant="outline" color="neutral" to="/auth/login">Zurück zur Anmeldung</UButton>
172
+ </div>
173
+ </div>
174
+ </template>
175
+
176
+ <!-- Pending state: Waiting for verification (from register page) -->
177
+ <template v-else>
178
+ <div class="flex flex-col items-center gap-6 py-4">
179
+ <UIcon name="i-lucide-mail-check" class="size-16 text-primary" />
180
+ <div class="text-center">
181
+ <h2 class="text-xl font-semibold">E-Mail bestätigen</h2>
182
+ <p class="mt-2 text-sm text-muted">
183
+ Wir haben eine Bestätigungs-E-Mail an
184
+ <strong v-if="email">{{ email }}</strong>
185
+ <span v-else>deine E-Mail-Adresse</span>
186
+ gesendet.
187
+ </p>
188
+ <p class="mt-2 text-sm text-muted">Bitte klicke auf den Link in der E-Mail, um dein Konto zu aktivieren.</p>
189
+ </div>
190
+
191
+ <UAlert color="neutral" icon="i-lucide-info" title="Keine E-Mail erhalten?" variant="subtle">
192
+ <template #description>
193
+ <ul class="mt-1 list-inside list-disc text-sm">
194
+ <li>Prüfe deinen Spam-Ordner</li>
195
+ <li>Stelle sicher, dass die E-Mail-Adresse korrekt ist</li>
196
+ </ul>
197
+ </template>
198
+ </UAlert>
199
+
200
+ <div class="flex w-full flex-col gap-3">
201
+ <UButton v-if="email" block :color="resendCooldown > 0 ? 'neutral' : 'primary'" :disabled="resendCooldown > 0" :loading="resending" :variant="resendCooldown > 0 ? 'subtle' : 'outline'" @click="resendVerificationEmail">
202
+ {{ resendCooldown > 0 ? `Bestätigungs-E-Mail erneut senden (${resendCooldown}s)` : 'Bestätigungs-E-Mail erneut senden' }}
203
+ </UButton>
204
+ <UButton block variant="outline" color="neutral" to="/auth/login">Zurück zur Anmeldung</UButton>
205
+ </div>
206
+ </div>
207
+ </template>
208
+ </UPageCard>
209
+ </template>
@@ -15,6 +15,7 @@ export default defineNuxtConfig({
15
15
  // ============================================================================
16
16
  // Bug Reporting (Linear Integration via @lenne.tech/bug.lt)
17
17
  // ============================================================================
18
+ // @ts-expect-error bug.lt module config - module temporarily disabled
18
19
  bug: {
19
20
  enabled: process.env.APP_ENV !== 'production',
20
21
  linearApiKey: process.env.LINEAR_API_KEY,
@@ -60,6 +61,16 @@ export default defineNuxtConfig({
60
61
  provider: 'ipx',
61
62
  },
62
63
 
64
+ // ============================================================================
65
+ // Icon Configuration
66
+ // ============================================================================
67
+ icon: {
68
+ // Ensure dynamically rendered icons (e.g., inside v-for) are included in the bundle
69
+ clientBundle: {
70
+ icons: ['lucide:trash', 'lucide:key', 'lucide:copy', 'lucide:loader-circle'],
71
+ },
72
+ },
73
+
63
74
  // ============================================================================
64
75
  // Auto-imports
65
76
  // ============================================================================
@@ -10,7 +10,7 @@
10
10
  "@better-auth/passkey": "1.4.10",
11
11
  "@hey-api/client-fetch": "0.13.1",
12
12
  "@lenne.tech/bug.lt": "latest",
13
- "@lenne.tech/nuxt-extensions": "1.1.0",
13
+ "@lenne.tech/nuxt-extensions": "1.2.7",
14
14
  "@nuxt/image": "2.0.0",
15
15
  "@nuxt/ui": "4.3.0",
16
16
  "@pinia/nuxt": "0.11.3",
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@hey-api/openapi-ts": "0.90.3",
25
+ "@iconify-json/lucide": "1.2.88",
25
26
  "@nuxt/devtools": "3.1.1",
26
27
  "@nuxt/test-utils": "3.23.0",
27
28
  "@nuxtjs/color-mode": "4.0.0",
@@ -1330,6 +1331,16 @@
1330
1331
  "typescript": ">=5.5.3"
1331
1332
  }
1332
1333
  },
1334
+ "node_modules/@iconify-json/lucide": {
1335
+ "version": "1.2.88",
1336
+ "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.88.tgz",
1337
+ "integrity": "sha512-QBJq+VSj3yHXoMgf+1I4guUhXA+tpxzAt46LJdTSFN6UKy254GstTh+P/a6GD4Bvyi1fCAfi5hS/yCmu0w3mNw==",
1338
+ "dev": true,
1339
+ "license": "ISC",
1340
+ "dependencies": {
1341
+ "@iconify/types": "*"
1342
+ }
1343
+ },
1333
1344
  "node_modules/@iconify/collections": {
1334
1345
  "version": "1.0.638",
1335
1346
  "license": "MIT",
@@ -2339,12 +2350,12 @@
2339
2350
  }
2340
2351
  },
2341
2352
  "node_modules/@lenne.tech/nuxt-extensions": {
2342
- "version": "1.1.0",
2343
- "resolved": "https://registry.npmjs.org/@lenne.tech/nuxt-extensions/-/nuxt-extensions-1.1.0.tgz",
2344
- "integrity": "sha512-P2PHOejJCql8Cksqym+ZceDv6lqzQvcFavfrjdZyDcNBSy8Ee9xE7AMPRrVmnVcZAqF422A+fjpGVQLt+w42pw==",
2353
+ "version": "1.2.7",
2354
+ "resolved": "https://registry.npmjs.org/@lenne.tech/nuxt-extensions/-/nuxt-extensions-1.2.7.tgz",
2355
+ "integrity": "sha512-NgReTI9b/7GDiBFBkMyxbNlcyIbGK4MOibEar8E0HLs+GScpVXON0vBvZ9f4JDAhrI9MvGXDjH17hRqq8NwXvw==",
2345
2356
  "license": "MIT",
2346
2357
  "dependencies": {
2347
- "@nuxt/kit": "4.2.2"
2358
+ "@nuxt/kit": "4.3.0"
2348
2359
  },
2349
2360
  "peerDependencies": {
2350
2361
  "@better-auth/passkey": ">=1.0.0",
@@ -2369,12 +2380,12 @@
2369
2380
  }
2370
2381
  },
2371
2382
  "node_modules/@lenne.tech/nuxt-extensions/node_modules/@nuxt/kit": {
2372
- "version": "4.2.2",
2373
- "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.2.2.tgz",
2374
- "integrity": "sha512-ZAgYBrPz/yhVgDznBNdQj2vhmOp31haJbO0I0iah/P9atw+OHH7NJLUZ3PK+LOz/0fblKTN1XJVSi8YQ1TQ0KA==",
2383
+ "version": "4.3.0",
2384
+ "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.3.0.tgz",
2385
+ "integrity": "sha512-cD/0UU9RQmlnTbmyJTDyzN8f6CzpziDLv3tFQCnwl0Aoxt3KmFu4k/XA4Sogxqj7jJ/3cdX1kL+Lnsh34sxcQQ==",
2375
2386
  "license": "MIT",
2376
2387
  "dependencies": {
2377
- "c12": "^3.3.2",
2388
+ "c12": "^3.3.3",
2378
2389
  "consola": "^3.4.2",
2379
2390
  "defu": "^6.1.4",
2380
2391
  "destr": "^2.0.5",
@@ -2391,8 +2402,8 @@
2391
2402
  "scule": "^1.3.0",
2392
2403
  "semver": "^7.7.3",
2393
2404
  "tinyglobby": "^0.2.15",
2394
- "ufo": "^1.6.1",
2395
- "unctx": "^2.4.1",
2405
+ "ufo": "^1.6.3",
2406
+ "unctx": "^2.5.0",
2396
2407
  "untyped": "^2.0.0"
2397
2408
  },
2398
2409
  "engines": {
@@ -46,7 +46,7 @@
46
46
  "@better-auth/passkey": "1.4.10",
47
47
  "@hey-api/client-fetch": "0.13.1",
48
48
  "@lenne.tech/bug.lt": "latest",
49
- "@lenne.tech/nuxt-extensions": "1.1.0",
49
+ "@lenne.tech/nuxt-extensions": "1.2.7",
50
50
  "@nuxt/image": "2.0.0",
51
51
  "@nuxt/ui": "4.3.0",
52
52
  "@pinia/nuxt": "0.11.3",
@@ -58,6 +58,7 @@
58
58
  },
59
59
  "devDependencies": {
60
60
  "@hey-api/openapi-ts": "0.90.3",
61
+ "@iconify-json/lucide": "1.2.88",
61
62
  "@nuxt/devtools": "3.1.1",
62
63
  "@nuxt/test-utils": "3.23.0",
63
64
  "@nuxtjs/color-mode": "4.0.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nuxt-base",
3
- "version": "2.1.4",
3
+ "version": "2.2.1",
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",