create-nuxt-base 0.3.15 → 0.3.17
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 +9 -0
- package/nuxt-base-template/.env.example +2 -1
- package/nuxt-base-template/CLAUDE.md +361 -0
- package/nuxt-base-template/README.md +127 -13
- package/nuxt-base-template/app/app.config.ts +67 -0
- package/nuxt-base-template/app/app.vue +10 -2
- package/nuxt-base-template/app/assets/css/tailwind.css +124 -84
- package/nuxt-base-template/app/components/Modal/ModalBase.vue +65 -0
- package/nuxt-base-template/app/components/Transition/TransitionSlide.vue +0 -2
- package/nuxt-base-template/app/components/Transition/TransitionSlideBottom.vue +0 -2
- package/nuxt-base-template/app/components/Transition/TransitionSlideRevert.vue +0 -2
- package/nuxt-base-template/app/composables/use-file.ts +19 -3
- package/nuxt-base-template/app/composables/use-share.ts +26 -10
- package/nuxt-base-template/app/error.vue +7 -43
- package/nuxt-base-template/app/layouts/default.vue +76 -4
- package/nuxt-base-template/app/layouts/slim.vue +5 -0
- package/nuxt-base-template/app/pages/auth/forgot-password.vue +64 -0
- package/nuxt-base-template/app/pages/auth/login.vue +71 -0
- package/nuxt-base-template/app/pages/auth/reset-password/[token].vue +110 -0
- package/nuxt-base-template/app/pages/index.vue +139 -2
- package/nuxt-base-template/app/public/favicon.ico +0 -0
- package/nuxt-base-template/docs/nuxt.config.ts +4 -0
- package/nuxt-base-template/docs/pages/docs.vue +663 -0
- package/nuxt-base-template/eslint.config.mjs +2 -1
- package/nuxt-base-template/nuxt.config.ts +73 -31
- package/nuxt-base-template/openapi-ts.config.ts +18 -0
- package/nuxt-base-template/package-lock.json +9781 -15157
- package/nuxt-base-template/package.json +30 -35
- package/nuxt-base-template/tsconfig.json +1 -1
- package/package.json +3 -3
- package/nuxt-base-template/app/composables/use-context-menu.ts +0 -19
- package/nuxt-base-template/app/composables/use-form-helper.ts +0 -41
- package/nuxt-base-template/app/composables/use-modal.ts +0 -84
- package/nuxt-base-template/app/composables/use-notification.ts +0 -29
- package/nuxt-base-template/app/middleware/admin.global.ts +0 -9
- package/nuxt-base-template/app/middleware/auth.global.ts +0 -9
- package/nuxt-base-template/app/middleware/logged-in.global.ts +0 -9
- package/nuxt-base-template/app/plugins/auth.server.ts +0 -72
- package/nuxt-base-template/app/plugins/form.plugin.ts +0 -21
- package/nuxt-base-template/app/plugins/pwa.plugin.ts +0 -114
- package/nuxt-base-template/tailwind.config.js +0 -21
|
@@ -1,98 +1,138 @@
|
|
|
1
1
|
@import 'tailwindcss';
|
|
2
|
+
@import '@nuxt/ui';
|
|
2
3
|
|
|
3
|
-
@
|
|
4
|
-
|
|
5
|
-
@plugin '@tailwindcss/typography';
|
|
6
|
-
@plugin '@tailwindcss/forms';
|
|
4
|
+
@plugin "@tailwindcss/typography";
|
|
7
5
|
|
|
8
6
|
@custom-variant dark (&:is(.dark *));
|
|
7
|
+
@custom-variant light (&:is(.light *));
|
|
9
8
|
|
|
10
|
-
@theme {
|
|
11
|
-
--color-primary-50: #f3faf7;
|
|
12
|
-
--color-primary-100: #d6f1e7;
|
|
13
|
-
--color-primary-200: #ade2d0;
|
|
14
|
-
--color-primary-300: #7cccb3;
|
|
15
|
-
--color-primary-400: #57b39a;
|
|
16
|
-
--color-primary-500: #37957d;
|
|
17
|
-
--color-primary-600: #2a7765;
|
|
18
|
-
--color-primary-700: #256052;
|
|
19
|
-
--color-primary-800: #224d45;
|
|
20
|
-
--color-primary-900: #20413a;
|
|
21
|
-
--color-primary-950: #0d2621;
|
|
22
|
-
--color-primary: #57b39a;
|
|
23
|
-
|
|
24
|
-
--color-background: #ffffff;
|
|
25
|
-
--color-foreground: #000000;
|
|
26
|
-
--color-border: hsl(0 0% 0% / 0.5);
|
|
27
|
-
--color-hover: hsl(0 0% 100% / 0.2);
|
|
28
|
-
--color-active: hsl(0 0% 100% / 0.2);
|
|
29
|
-
|
|
9
|
+
@theme static {
|
|
30
10
|
--breakpoint-3xl: 2400px;
|
|
31
11
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
12
|
+
/* ============================================================================
|
|
13
|
+
NuxtUI Color Scales - Customize for your brand
|
|
14
|
+
Each semantic color requires a scale from 50 to 950
|
|
15
|
+
========================================================================= */
|
|
36
16
|
|
|
37
|
-
/*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
color
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
17
|
+
/* Primary Color - Green (NuxtUI Default) */
|
|
18
|
+
--ui-color-primary-50: #f0fdf4;
|
|
19
|
+
--ui-color-primary-100: #dcfce7;
|
|
20
|
+
--ui-color-primary-200: #bbf7d0;
|
|
21
|
+
--ui-color-primary-300: #86efac;
|
|
22
|
+
--ui-color-primary-400: #4ade80;
|
|
23
|
+
--ui-color-primary-500: #22c55e;
|
|
24
|
+
--ui-color-primary-600: #16a34a;
|
|
25
|
+
--ui-color-primary-700: #15803d;
|
|
26
|
+
--ui-color-primary-800: #166534;
|
|
27
|
+
--ui-color-primary-900: #14532d;
|
|
28
|
+
--ui-color-primary-950: #052e16;
|
|
29
|
+
|
|
30
|
+
/* Secondary Color - Indigo */
|
|
31
|
+
--ui-color-secondary-50: #eef2ff;
|
|
32
|
+
--ui-color-secondary-100: #e0e7ff;
|
|
33
|
+
--ui-color-secondary-200: #c7d2fe;
|
|
34
|
+
--ui-color-secondary-300: #a5b4fc;
|
|
35
|
+
--ui-color-secondary-400: #818cf8;
|
|
36
|
+
--ui-color-secondary-500: #6366f1;
|
|
37
|
+
--ui-color-secondary-600: #4f46e5;
|
|
38
|
+
--ui-color-secondary-700: #4338ca;
|
|
39
|
+
--ui-color-secondary-800: #3730a3;
|
|
40
|
+
--ui-color-secondary-900: #312e81;
|
|
41
|
+
--ui-color-secondary-950: #1e1b4b;
|
|
42
|
+
|
|
43
|
+
/* Success Color - Emerald */
|
|
44
|
+
--ui-color-success-50: #ecfdf5;
|
|
45
|
+
--ui-color-success-100: #d1fae5;
|
|
46
|
+
--ui-color-success-200: #a7f3d0;
|
|
47
|
+
--ui-color-success-300: #6ee7b7;
|
|
48
|
+
--ui-color-success-400: #34d399;
|
|
49
|
+
--ui-color-success-500: #10b981;
|
|
50
|
+
--ui-color-success-600: #059669;
|
|
51
|
+
--ui-color-success-700: #047857;
|
|
52
|
+
--ui-color-success-800: #065f46;
|
|
53
|
+
--ui-color-success-900: #064e3b;
|
|
54
|
+
--ui-color-success-950: #022c22;
|
|
55
|
+
|
|
56
|
+
/* Info Color - Blue */
|
|
57
|
+
--ui-color-info-50: #eff6ff;
|
|
58
|
+
--ui-color-info-100: #dbeafe;
|
|
59
|
+
--ui-color-info-200: #bfdbfe;
|
|
60
|
+
--ui-color-info-300: #93c5fd;
|
|
61
|
+
--ui-color-info-400: #60a5fa;
|
|
62
|
+
--ui-color-info-500: #3b82f6;
|
|
63
|
+
--ui-color-info-600: #2563eb;
|
|
64
|
+
--ui-color-info-700: #1d4ed8;
|
|
65
|
+
--ui-color-info-800: #1e40af;
|
|
66
|
+
--ui-color-info-900: #1e3a8a;
|
|
67
|
+
--ui-color-info-950: #172554;
|
|
68
|
+
|
|
69
|
+
/* Warning Color - Amber */
|
|
70
|
+
--ui-color-warning-50: #fffbeb;
|
|
71
|
+
--ui-color-warning-100: #fef3c7;
|
|
72
|
+
--ui-color-warning-200: #fde68a;
|
|
73
|
+
--ui-color-warning-300: #fcd34d;
|
|
74
|
+
--ui-color-warning-400: #fbbf24;
|
|
75
|
+
--ui-color-warning-500: #f59e0b;
|
|
76
|
+
--ui-color-warning-600: #d97706;
|
|
77
|
+
--ui-color-warning-700: #b45309;
|
|
78
|
+
--ui-color-warning-800: #92400e;
|
|
79
|
+
--ui-color-warning-900: #78350f;
|
|
80
|
+
--ui-color-warning-950: #451a03;
|
|
81
|
+
|
|
82
|
+
/* Error Color - Red */
|
|
83
|
+
--ui-color-error-50: #fef2f2;
|
|
84
|
+
--ui-color-error-100: #fee2e2;
|
|
85
|
+
--ui-color-error-200: #fecaca;
|
|
86
|
+
--ui-color-error-300: #fca5a5;
|
|
87
|
+
--ui-color-error-400: #f87171;
|
|
88
|
+
--ui-color-error-500: #ef4444;
|
|
89
|
+
--ui-color-error-600: #dc2626;
|
|
90
|
+
--ui-color-error-700: #b91c1c;
|
|
91
|
+
--ui-color-error-800: #991b1b;
|
|
92
|
+
--ui-color-error-900: #7f1d1d;
|
|
93
|
+
--ui-color-error-950: #450a0a;
|
|
94
|
+
|
|
95
|
+
/* Neutral Color - Slate */
|
|
96
|
+
--ui-color-neutral-50: #f8fafc;
|
|
97
|
+
--ui-color-neutral-100: #f1f5f9;
|
|
98
|
+
--ui-color-neutral-200: #e2e8f0;
|
|
99
|
+
--ui-color-neutral-300: #cbd5e1;
|
|
100
|
+
--ui-color-neutral-400: #94a3b8;
|
|
101
|
+
--ui-color-neutral-500: #64748b;
|
|
102
|
+
--ui-color-neutral-600: #475569;
|
|
103
|
+
--ui-color-neutral-700: #334155;
|
|
104
|
+
--ui-color-neutral-800: #1e293b;
|
|
105
|
+
--ui-color-neutral-900: #0f172a;
|
|
106
|
+
--ui-color-neutral-950: #020617;
|
|
107
|
+
|
|
108
|
+
/* ============================================================================
|
|
109
|
+
NuxtUI Semantic Shortcuts
|
|
110
|
+
========================================================================= */
|
|
111
|
+
|
|
112
|
+
--ui-primary: var(--ui-color-primary-500);
|
|
113
|
+
--ui-secondary: var(--ui-color-secondary-500);
|
|
114
|
+
--ui-success: var(--ui-color-success-500);
|
|
115
|
+
--ui-info: var(--ui-color-info-500);
|
|
116
|
+
--ui-warning: var(--ui-color-warning-500);
|
|
117
|
+
--ui-error: var(--ui-color-error-500);
|
|
118
|
+
|
|
119
|
+
/* ============================================================================
|
|
120
|
+
NuxtUI Global Settings
|
|
121
|
+
========================================================================= */
|
|
54
122
|
|
|
55
|
-
|
|
56
|
-
body {
|
|
57
|
-
@apply transition-colors duration-300;
|
|
58
|
-
}
|
|
59
|
-
h1 {
|
|
60
|
-
@apply text-[32px] leading-[140%] lg:text-[66px] lg:leading-[140%] font-bold;
|
|
61
|
-
}
|
|
62
|
-
h2 {
|
|
63
|
-
@apply text-[28px] leading-[140%] lg:text-[51px] lg:leading-[140%] font-semibold;
|
|
64
|
-
}
|
|
65
|
-
h3 {
|
|
66
|
-
@apply text-[21px] leading-[140%] lg:text-[39px] lg:leading-[140%] font-bold;
|
|
67
|
-
}
|
|
68
|
-
h4 {
|
|
69
|
-
@apply text-[18px] leading-[140%] lg:text-[30px] lg:leading-[140%] font-bold;
|
|
70
|
-
}
|
|
71
|
-
h5 {
|
|
72
|
-
@apply text-[15px] leading-[140%] lg:text-[23px] lg:leading-[140%] font-bold;
|
|
73
|
-
}
|
|
74
|
-
h6 {
|
|
75
|
-
@apply text-[16px] leading-[140%] lg:text-[18px] lg:leading-[140%] font-bold;
|
|
76
|
-
}
|
|
77
|
-
p {
|
|
78
|
-
@apply text-[15px] leading-[140%] lg:text-[18px] lg:leading-[140%];
|
|
79
|
-
}
|
|
80
|
-
small {
|
|
81
|
-
@apply text-[14px] leading-[140%] lg:text-[16px] lg:leading-[140%];
|
|
82
|
-
}
|
|
83
|
-
a {
|
|
84
|
-
@apply text-[15px] leading-[140%] lg:text-[18px] lg:leading-[140%];
|
|
85
|
-
}
|
|
86
|
-
button {
|
|
87
|
-
@apply text-[15px] leading-[140%] lg:text-[18px] lg:leading-[140%];
|
|
88
|
-
}
|
|
123
|
+
--ui-radius: 0.5rem;
|
|
89
124
|
}
|
|
90
125
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
.dark {
|
|
127
|
+
/* Adjust primary color for dark mode */
|
|
128
|
+
--ui-primary: var(--ui-color-primary-400);
|
|
129
|
+
--ui-secondary: var(--ui-color-secondary-400);
|
|
130
|
+
--ui-success: var(--ui-color-success-400);
|
|
131
|
+
--ui-info: var(--ui-color-info-400);
|
|
132
|
+
--ui-warning: var(--ui-color-warning-400);
|
|
133
|
+
--ui-error: var(--ui-color-error-400);
|
|
94
134
|
}
|
|
95
135
|
|
|
96
|
-
|
|
97
|
-
|
|
136
|
+
html {
|
|
137
|
+
@apply scroll-pt-24;
|
|
98
138
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Props & Emits
|
|
4
|
+
// ============================================================================
|
|
5
|
+
interface Props {
|
|
6
|
+
description?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
withDefaults(defineProps<Props>(), {
|
|
11
|
+
description: 'This is a base modal example using Nuxt UI. You can customize this component to fit your needs.',
|
|
12
|
+
title: 'Modal Example',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
close: [result?: unknown];
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Functions
|
|
21
|
+
// ============================================================================
|
|
22
|
+
function handleCancel(): void {
|
|
23
|
+
emit('close', false);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function handleConfirm(): void {
|
|
27
|
+
emit('close', true);
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<UModal :title="title" :description="description" :close="{ onClick: () => emit('close', false) }">
|
|
33
|
+
<template #body>
|
|
34
|
+
<div class="space-y-4">
|
|
35
|
+
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
|
36
|
+
This modal demonstrates how to use the <code class="px-1.5 py-0.5 bg-neutral-100 dark:bg-neutral-800 rounded text-xs">useOverlay</code> composable to open modals
|
|
37
|
+
programmatically.
|
|
38
|
+
</p>
|
|
39
|
+
<div class="rounded-lg bg-neutral-50 dark:bg-neutral-900 p-4">
|
|
40
|
+
<h4 class="text-sm font-medium text-neutral-900 dark:text-white mb-2">Key Features:</h4>
|
|
41
|
+
<ul class="space-y-2 text-sm text-neutral-600 dark:text-neutral-400">
|
|
42
|
+
<li class="flex items-start gap-2">
|
|
43
|
+
<UIcon name="i-lucide-check" class="size-4 mt-0.5 text-success shrink-0" />
|
|
44
|
+
<span>Programmatic control with useOverlay()</span>
|
|
45
|
+
</li>
|
|
46
|
+
<li class="flex items-start gap-2">
|
|
47
|
+
<UIcon name="i-lucide-check" class="size-4 mt-0.5 text-success shrink-0" />
|
|
48
|
+
<span>Return values via emit('close')</span>
|
|
49
|
+
</li>
|
|
50
|
+
<li class="flex items-start gap-2">
|
|
51
|
+
<UIcon name="i-lucide-check" class="size-4 mt-0.5 text-success shrink-0" />
|
|
52
|
+
<span>Fully typed props and emits</span>
|
|
53
|
+
</li>
|
|
54
|
+
</ul>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
58
|
+
<template #footer>
|
|
59
|
+
<div class="flex justify-end gap-3">
|
|
60
|
+
<UButton color="neutral" variant="outline" @click="handleCancel"> Cancel </UButton>
|
|
61
|
+
<UButton color="primary" @click="handleConfirm"> Confirm </UButton>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
</UModal>
|
|
65
|
+
</template>
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
interface FileInfo {
|
|
2
|
+
[key: string]: unknown;
|
|
3
|
+
filename: string;
|
|
4
|
+
id: string;
|
|
5
|
+
mimetype: string;
|
|
6
|
+
size: number;
|
|
7
|
+
url?: string;
|
|
8
|
+
}
|
|
2
9
|
|
|
3
10
|
export function useFile() {
|
|
4
11
|
const { isValidMongoID } = useHelper();
|
|
5
|
-
|
|
12
|
+
|
|
13
|
+
async function getFileInfo(id: string | undefined): Promise<FileInfo | null | string> {
|
|
6
14
|
const config = useRuntimeConfig();
|
|
7
15
|
|
|
8
16
|
if (!id) {
|
|
@@ -13,7 +21,15 @@ export function useFile() {
|
|
|
13
21
|
return id;
|
|
14
22
|
}
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
try {
|
|
25
|
+
const response = await $fetch<FileInfo>(config.public.host + '/files/info/' + id, {
|
|
26
|
+
method: 'GET',
|
|
27
|
+
});
|
|
28
|
+
return response;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Error fetching file info:', error);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
17
33
|
}
|
|
18
34
|
|
|
19
35
|
return { getFileInfo };
|
|
@@ -1,18 +1,34 @@
|
|
|
1
|
-
import ModalShare from '~/components/ModalShare.vue';
|
|
2
|
-
import { useModal } from '~/composables/use-modal';
|
|
3
|
-
|
|
4
1
|
export function useShare() {
|
|
5
2
|
const route = useRoute();
|
|
6
3
|
|
|
7
|
-
function share(title?: string, text?: string, url?: string) {
|
|
4
|
+
async function share(title?: string, text?: string, url?: string) {
|
|
5
|
+
if (!process.client) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
8
9
|
if (window?.navigator?.share) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
try {
|
|
11
|
+
await window.navigator.share({
|
|
12
|
+
text: text ?? window.location.origin,
|
|
13
|
+
title: title,
|
|
14
|
+
url: url ?? route.fullPath,
|
|
15
|
+
});
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error('Error sharing:', error);
|
|
18
|
+
}
|
|
14
19
|
} else {
|
|
15
|
-
|
|
20
|
+
// Fallback: Copy to clipboard
|
|
21
|
+
try {
|
|
22
|
+
await navigator.clipboard.writeText(url ?? window.location.origin);
|
|
23
|
+
const toast = useToast();
|
|
24
|
+
toast.add({
|
|
25
|
+
color: 'success',
|
|
26
|
+
description: 'Der Link wurde in die Zwischenablage kopiert.',
|
|
27
|
+
title: 'Link kopiert',
|
|
28
|
+
});
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Error copying to clipboard:', error);
|
|
31
|
+
}
|
|
16
32
|
}
|
|
17
33
|
}
|
|
18
34
|
|
|
@@ -1,49 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
error: Object,
|
|
4
|
-
});
|
|
2
|
+
import type { NuxtError } from '#app';
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
watch(
|
|
11
|
-
() => current.values(),
|
|
12
|
-
() => {
|
|
13
|
-
if (current.values().next().value === 'escape') {
|
|
14
|
-
debugWord.value = '';
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (debugWord.value === 'debug') {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (current.values().next().value && !debugWord.value.includes(current.values().next().value)) {
|
|
23
|
-
debugWord.value += current.values().next().value;
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const handleError = () => clearError({ redirect: '/' });
|
|
4
|
+
defineProps<{
|
|
5
|
+
error: NuxtError;
|
|
6
|
+
}>();
|
|
29
7
|
</script>
|
|
30
8
|
|
|
31
9
|
<template>
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
<h1 class="text-[12rem] font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-primary-600 to-primary-400">Oops!</h1>
|
|
36
|
-
<h2 v-if="error?.statusCode" class="text-3xl text-gray-600">
|
|
37
|
-
{{ error?.statusCode }}
|
|
38
|
-
</h2>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<pre v-if="debugWord === 'debug'" class="w-full max-w-3xl mb-5 mx-auto bg-black/80 text-white font-mono p-2 rounded-lg overflow-x-scroll">
|
|
42
|
-
{{ { error } }}
|
|
43
|
-
</pre
|
|
44
|
-
>
|
|
45
|
-
|
|
46
|
-
<BaseButton color="primary" appearance="outline" @click="handleError"> Zurück zur Startseite </BaseButton>
|
|
47
|
-
</div>
|
|
48
|
-
</NuxtLayout>
|
|
10
|
+
<UApp>
|
|
11
|
+
<UError :error="error" />
|
|
12
|
+
</UApp>
|
|
49
13
|
</template>
|
|
@@ -1,8 +1,80 @@
|
|
|
1
|
-
<script setup lang="ts"
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { NavigationMenuItem } from '@nuxt/ui';
|
|
3
|
+
|
|
4
|
+
const headerItems = computed<NavigationMenuItem[]>(() => [
|
|
5
|
+
{
|
|
6
|
+
label: 'Docs',
|
|
7
|
+
to: '#',
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
label: 'Components',
|
|
11
|
+
to: '#',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
label: 'Figma',
|
|
15
|
+
target: '_blank',
|
|
16
|
+
to: '#',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: 'Releases',
|
|
20
|
+
target: '_blank',
|
|
21
|
+
to: '#',
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const footerItems: NavigationMenuItem[] = [
|
|
26
|
+
{
|
|
27
|
+
label: 'Figma Kit',
|
|
28
|
+
target: '_blank',
|
|
29
|
+
to: '#',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: 'Playground',
|
|
33
|
+
target: '_blank',
|
|
34
|
+
to: '#',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: 'Releases',
|
|
38
|
+
target: '_blank',
|
|
39
|
+
to: '#',
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
</script>
|
|
2
43
|
|
|
3
44
|
<template>
|
|
4
|
-
<div>
|
|
5
|
-
<
|
|
6
|
-
|
|
45
|
+
<div class="flex flex-col min-h-screen">
|
|
46
|
+
<UHeader>
|
|
47
|
+
<template #title>
|
|
48
|
+
<UIcon name="i-lucide-code" class="text-primary" />
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<UNavigationMenu :items="headerItems" />
|
|
52
|
+
|
|
53
|
+
<template #right>
|
|
54
|
+
<UColorModeButton />
|
|
55
|
+
|
|
56
|
+
<UTooltip text="Open on GitHub" :kbds="['meta', 'G']">
|
|
57
|
+
<UButton color="neutral" variant="ghost" to="https://github.com/lenneTech/nuxt-base-starter" target="_blank" icon="i-simple-icons-github" aria-label="GitHub" />
|
|
58
|
+
</UTooltip>
|
|
59
|
+
</template>
|
|
60
|
+
</UHeader>
|
|
61
|
+
<UMain>
|
|
62
|
+
<slot></slot>
|
|
63
|
+
</UMain>
|
|
64
|
+
<USeparator icon="i-simple-icons-nuxtdotjs" type="dashed" class="h-px" />
|
|
65
|
+
|
|
66
|
+
<UFooter>
|
|
67
|
+
<template #left>
|
|
68
|
+
<p class="text-muted text-sm">Copyright © {{ new Date().getFullYear() }}</p>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<UNavigationMenu :items="footerItems" variant="link" />
|
|
72
|
+
|
|
73
|
+
<template #right>
|
|
74
|
+
<UButton icon="i-simple-icons-discord" color="neutral" variant="ghost" to="#" target="_blank" aria-label="Discord" />
|
|
75
|
+
<UButton icon="i-simple-icons-x" color="neutral" variant="ghost" to="#" target="_blank" aria-label="X" />
|
|
76
|
+
<UButton icon="i-simple-icons-github" color="neutral" variant="ghost" to="#" target="_blank" aria-label="GitHub" />
|
|
77
|
+
</template>
|
|
78
|
+
</UFooter>
|
|
7
79
|
</div>
|
|
8
80
|
</template>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Imports
|
|
4
|
+
// ============================================================================
|
|
5
|
+
import type { AuthFormField, FormSubmitEvent } from '@nuxt/ui';
|
|
6
|
+
import type { InferOutput } from 'valibot';
|
|
7
|
+
|
|
8
|
+
import * as v from 'valibot';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Page Meta
|
|
12
|
+
// ============================================================================
|
|
13
|
+
definePageMeta({
|
|
14
|
+
layout: 'slim',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Variables
|
|
19
|
+
// ============================================================================
|
|
20
|
+
const fields: AuthFormField[] = [
|
|
21
|
+
{
|
|
22
|
+
label: 'Email',
|
|
23
|
+
name: 'email',
|
|
24
|
+
placeholder: 'Enter your email',
|
|
25
|
+
required: true,
|
|
26
|
+
type: 'email',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const schema = v.object({
|
|
31
|
+
email: v.pipe(v.string('Email is required'), v.email('Has to be a valid email address')),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
type Schema = InferOutput<typeof schema>;
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Functions
|
|
38
|
+
// ============================================================================
|
|
39
|
+
async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
|
|
40
|
+
console.debug('Forgot password request for:', payload.data.email);
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<UPageCard class="w-md" variant="naked">
|
|
46
|
+
<UAuthForm
|
|
47
|
+
:schema="schema"
|
|
48
|
+
title="Forgot password"
|
|
49
|
+
icon="i-heroicons-lock-closed"
|
|
50
|
+
:fields="fields"
|
|
51
|
+
loadingAuto
|
|
52
|
+
:submit="{
|
|
53
|
+
label: 'Continue',
|
|
54
|
+
block: true,
|
|
55
|
+
}"
|
|
56
|
+
@submit="onSubmit"
|
|
57
|
+
>
|
|
58
|
+
<template #footer>
|
|
59
|
+
Back to
|
|
60
|
+
<ULink to="/auth/login" class="text-primary font-medium" tabindex="-1">Sign In</ULink>
|
|
61
|
+
</template>
|
|
62
|
+
</UAuthForm>
|
|
63
|
+
</UPageCard>
|
|
64
|
+
</template>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Imports
|
|
4
|
+
// ============================================================================
|
|
5
|
+
import type { AuthFormField, FormSubmitEvent } from '@nuxt/ui';
|
|
6
|
+
import type { InferOutput } from 'valibot';
|
|
7
|
+
|
|
8
|
+
import * as v from 'valibot';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Page Meta
|
|
12
|
+
// ============================================================================
|
|
13
|
+
definePageMeta({
|
|
14
|
+
layout: 'slim',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Variables
|
|
19
|
+
// ============================================================================
|
|
20
|
+
const fields: AuthFormField[] = [
|
|
21
|
+
{
|
|
22
|
+
label: 'Email',
|
|
23
|
+
name: 'email',
|
|
24
|
+
placeholder: 'Enter your email',
|
|
25
|
+
required: true,
|
|
26
|
+
type: 'email',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: 'Password',
|
|
30
|
+
name: 'password',
|
|
31
|
+
placeholder: 'Enter your password',
|
|
32
|
+
required: true,
|
|
33
|
+
type: 'password',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const schema = v.object({
|
|
38
|
+
email: v.pipe(v.string('Email is required'), v.email('Has to be a valid email address')),
|
|
39
|
+
password: v.pipe(v.string('Password is required'), v.minLength(5, 'Must be at least 5 characters')),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
type Schema = InferOutput<typeof schema>;
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Functions
|
|
46
|
+
// ============================================================================
|
|
47
|
+
async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
|
|
48
|
+
console.debug('Login request for:', payload.data.email);
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<UPageCard class="w-md" variant="naked">
|
|
54
|
+
<UAuthForm
|
|
55
|
+
:schema="schema"
|
|
56
|
+
title="Login"
|
|
57
|
+
icon="i-lucide-user"
|
|
58
|
+
:fields="fields"
|
|
59
|
+
loadingAuto
|
|
60
|
+
:submit="{
|
|
61
|
+
label: 'Log In',
|
|
62
|
+
block: true,
|
|
63
|
+
}"
|
|
64
|
+
@submit="onSubmit"
|
|
65
|
+
>
|
|
66
|
+
<template #password-hint>
|
|
67
|
+
<ULink to="/auth/forgot-password" class="text-primary font-medium" tabindex="-1">Forgot your password? </ULink>
|
|
68
|
+
</template>
|
|
69
|
+
</UAuthForm>
|
|
70
|
+
</UPageCard>
|
|
71
|
+
</template>
|