@nuxt-tmpl/nuxt 0.0.5

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 (67) hide show
  1. package/dist/module.d.mts +25 -0
  2. package/dist/module.json +9 -0
  3. package/dist/module.mjs +177 -0
  4. package/dist/runtime/components/auth/AuthGuard.vue +11 -0
  5. package/dist/runtime/components/auth/AuthGuard.vue.d.ts +2 -0
  6. package/dist/runtime/components/auth/AuthSignIn.vue +14 -0
  7. package/dist/runtime/components/auth/AuthSignIn.vue.d.ts +2 -0
  8. package/dist/runtime/components/auth/AuthSignInButton.vue +10 -0
  9. package/dist/runtime/components/auth/AuthSignInButton.vue.d.ts +2 -0
  10. package/dist/runtime/components/auth/AuthSignUp.vue +14 -0
  11. package/dist/runtime/components/auth/AuthSignUp.vue.d.ts +2 -0
  12. package/dist/runtime/components/auth/AuthSignUpButton.vue +10 -0
  13. package/dist/runtime/components/auth/AuthSignUpButton.vue.d.ts +2 -0
  14. package/dist/runtime/components/auth/AuthUserAvatar.vue +20 -0
  15. package/dist/runtime/components/auth/AuthUserAvatar.vue.d.ts +2 -0
  16. package/dist/runtime/components/auth/DefaultSignIn.vue +70 -0
  17. package/dist/runtime/components/auth/DefaultSignIn.vue.d.ts +2 -0
  18. package/dist/runtime/components/auth/DefaultSignUp.vue +81 -0
  19. package/dist/runtime/components/auth/DefaultSignUp.vue.d.ts +2 -0
  20. package/dist/runtime/composables/useAuth.d.ts +2 -0
  21. package/dist/runtime/composables/useAuth.js +5 -0
  22. package/dist/runtime/composables/useAuthForms.d.ts +9 -0
  23. package/dist/runtime/composables/useAuthForms.js +8 -0
  24. package/dist/runtime/layouts/default.vue +79 -0
  25. package/dist/runtime/layouts/default.vue.d.ts +2 -0
  26. package/dist/runtime/middleware/auth.d.ts +2 -0
  27. package/dist/runtime/middleware/auth.js +32 -0
  28. package/dist/runtime/pages/sign-in.vue +18 -0
  29. package/dist/runtime/pages/sign-in.vue.d.ts +2 -0
  30. package/dist/runtime/pages/sign-up.vue +18 -0
  31. package/dist/runtime/pages/sign-up.vue.d.ts +2 -0
  32. package/dist/runtime/plugins/auth.d.ts +6 -0
  33. package/dist/runtime/plugins/auth.js +10 -0
  34. package/dist/runtime/providers/better-auth.d.ts +2 -0
  35. package/dist/runtime/providers/better-auth.js +60 -0
  36. package/dist/runtime/providers/clerk.d.ts +7 -0
  37. package/dist/runtime/providers/clerk.js +112 -0
  38. package/dist/runtime/providers/index.d.ts +2 -0
  39. package/dist/runtime/providers/index.js +48 -0
  40. package/dist/runtime/providers/supabase.d.ts +3 -0
  41. package/dist/runtime/providers/supabase.js +77 -0
  42. package/dist/runtime/server/guards/better-auth.d.ts +9 -0
  43. package/dist/runtime/server/guards/better-auth.js +13 -0
  44. package/dist/runtime/server/guards/clerk.d.ts +3 -0
  45. package/dist/runtime/server/guards/clerk.js +16 -0
  46. package/dist/runtime/server/guards/supabase.d.ts +3 -0
  47. package/dist/runtime/server/guards/supabase.js +12 -0
  48. package/dist/runtime/server/middleware/auth.d.ts +2 -0
  49. package/dist/runtime/server/middleware/auth.js +26 -0
  50. package/dist/runtime/server/middleware/log.d.ts +2 -0
  51. package/dist/runtime/server/middleware/log.js +4 -0
  52. package/dist/runtime/server/plugins/api-response.d.ts +11 -0
  53. package/dist/runtime/server/plugins/api-response.js +32 -0
  54. package/dist/runtime/server/utils/api-response.d.ts +17 -0
  55. package/dist/runtime/server/utils/api-response.js +11 -0
  56. package/dist/runtime/server/utils/auth.d.ts +6 -0
  57. package/dist/runtime/server/utils/auth.js +7 -0
  58. package/dist/runtime/server/utils/handler.d.ts +8 -0
  59. package/dist/runtime/server/utils/handler.js +18 -0
  60. package/dist/runtime/server/utils/llm.d.ts +12 -0
  61. package/dist/runtime/server/utils/llm.js +35 -0
  62. package/dist/runtime/server/utils/supabase.d.ts +2 -0
  63. package/dist/runtime/server/utils/supabase.js +28 -0
  64. package/dist/runtime/utils/authRoute.d.ts +10 -0
  65. package/dist/runtime/utils/authRoute.js +10 -0
  66. package/dist/types.d.mts +9 -0
  67. package/package.json +53 -0
@@ -0,0 +1,25 @@
1
+ import * as nuxt_schema from 'nuxt/schema';
2
+
3
+ interface NuxtTmplOptions {
4
+ auth?: {
5
+ enabled?: boolean;
6
+ afterSignInRoute?: string;
7
+ guestRoutes?: string[];
8
+ publicRoutes?: string[];
9
+ };
10
+ apiResponse?: {
11
+ enabled?: boolean;
12
+ };
13
+ logging?: {
14
+ enabled?: boolean;
15
+ };
16
+ }
17
+ declare module '#app' {
18
+ interface PageMeta {
19
+ auth?: 'guest' | 'public' | 'required';
20
+ }
21
+ }
22
+ declare const _default: nuxt_schema.NuxtModule<NuxtTmplOptions, NuxtTmplOptions, false>;
23
+
24
+ export { _default as default };
25
+ export type { NuxtTmplOptions };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "@nuxt-tmpl/nuxt",
3
+ "configKey": "nuxtTmpl",
4
+ "version": "0.0.5",
5
+ "builder": {
6
+ "@nuxt/module-builder": "1.0.2",
7
+ "unbuild": "unknown"
8
+ }
9
+ }
@@ -0,0 +1,177 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { defineNuxtModule, createResolver, addImports, addComponentsDir, addPlugin, addLayout, addServerHandler, addTemplate, addServerPlugin, addRouteMiddleware, extendPages, addServerImportsDir } from '@nuxt/kit';
5
+
6
+ const module$1 = defineNuxtModule({
7
+ meta: {
8
+ name: "@nuxt-tmpl/nuxt",
9
+ configKey: "nuxtTmpl"
10
+ },
11
+ defaults: {
12
+ auth: { enabled: true, afterSignInRoute: "/dashboard" },
13
+ apiResponse: { enabled: true },
14
+ logging: { enabled: false }
15
+ },
16
+ moduleDependencies(nuxt) {
17
+ const provider = process.env.NUXT_PUBLIC_AUTH_PROVIDER || "";
18
+ if (provider !== "clerk")
19
+ return {};
20
+ const publishableKey = process.env.NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
21
+ if (!publishableKey) {
22
+ throw new Error(
23
+ "[nuxt-tmpl] NUXT_PUBLIC_AUTH_PROVIDER=clerk requires NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY to be set"
24
+ );
25
+ }
26
+ const afterSignInRoute = nuxt.options.nuxtTmpl?.auth?.afterSignInRoute ?? "/dashboard";
27
+ return {
28
+ "@clerk/nuxt": {
29
+ defaults: {
30
+ publishableKey,
31
+ afterSignOutUrl: "/",
32
+ signInForceRedirectUrl: afterSignInRoute,
33
+ signInFallbackRedirectUrl: afterSignInRoute
34
+ }
35
+ }
36
+ };
37
+ },
38
+ async setup(options, nuxt) {
39
+ const { resolve } = createResolver(import.meta.url);
40
+ const provider = process.env.NUXT_PUBLIC_AUTH_PROVIDER || "";
41
+ nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
42
+ nuxt.options.runtimeConfig.public.authProvider = provider;
43
+ nuxt.options.runtimeConfig.public.baseUrl = process.env.BASE_URL || "";
44
+ nuxt.options.runtimeConfig.public.nuxtTmpl = {
45
+ afterSignInRoute: options.auth?.afterSignInRoute ?? "/dashboard",
46
+ guestRoutes: options.auth?.guestRoutes ?? [],
47
+ publicRoutes: options.auth?.publicRoutes ?? [],
48
+ ...nuxt.options.runtimeConfig.public.nuxtTmpl || {}
49
+ };
50
+ if (provider === "clerk") {
51
+ nuxt.options.runtimeConfig.clerk = {
52
+ secretKey: process.env.NUXT_CLERK_SECRET_KEY,
53
+ ...nuxt.options.runtimeConfig.clerk || {}
54
+ };
55
+ }
56
+ addImports([
57
+ {
58
+ name: "defineApiHandler",
59
+ as: "defineApiHandler",
60
+ from: resolve("./runtime/server/utils/handler")
61
+ }
62
+ ]);
63
+ if (options.auth?.enabled !== false) {
64
+ addComponentsDir({
65
+ path: resolve("./runtime/components/auth"),
66
+ prefix: "Auth"
67
+ });
68
+ addPlugin(resolve("./runtime/plugins/auth"));
69
+ addImports([
70
+ {
71
+ name: "useAuth",
72
+ as: "useAuth",
73
+ from: resolve("./runtime/composables/useAuth")
74
+ },
75
+ {
76
+ name: "provideAuthForms",
77
+ as: "provideAuthForms",
78
+ from: resolve("./runtime/composables/useAuthForms")
79
+ },
80
+ {
81
+ name: "useAuthForms",
82
+ as: "useAuthForms",
83
+ from: resolve("./runtime/composables/useAuthForms")
84
+ },
85
+ {
86
+ name: "getAuthMode",
87
+ as: "getAuthMode",
88
+ from: resolve("./runtime/utils/authRoute")
89
+ }
90
+ ]);
91
+ addLayout(
92
+ { src: resolve("./runtime/layouts/default.vue") },
93
+ "ipa-default"
94
+ );
95
+ addServerHandler({
96
+ middleware: true,
97
+ handler: resolve("./runtime/server/middleware/auth")
98
+ });
99
+ const tmpl = addTemplate({
100
+ filename: "nuxt-tmpl/auth-guards.mjs",
101
+ write: true,
102
+ getContents: () => generateAuthGuards(nuxt, resolve)
103
+ });
104
+ addServerPlugin(tmpl.dst);
105
+ addRouteMiddleware({
106
+ name: "auth",
107
+ path: resolve("./runtime/middleware/auth"),
108
+ global: true
109
+ });
110
+ extendPages((pages) => {
111
+ const hasSignIn = pages.some((p) => p.path.startsWith("/sign-in"));
112
+ const hasSignUp = pages.some((p) => p.path.startsWith("/sign-up"));
113
+ if (!hasSignIn) {
114
+ pages.push({
115
+ name: "sign-in",
116
+ path: "/sign-in/:slug(.*)*",
117
+ file: resolve("./runtime/pages/sign-in.vue")
118
+ });
119
+ }
120
+ if (!hasSignUp) {
121
+ pages.push({
122
+ name: "sign-up",
123
+ path: "/sign-up/:slug(.*)*",
124
+ file: resolve("./runtime/pages/sign-up.vue")
125
+ });
126
+ }
127
+ });
128
+ }
129
+ if (options.apiResponse?.enabled !== false) {
130
+ addServerPlugin(resolve("./runtime/server/plugins/api-response"));
131
+ }
132
+ if (options.logging?.enabled) {
133
+ addServerHandler({
134
+ middleware: true,
135
+ handler: resolve("./runtime/server/middleware/log")
136
+ });
137
+ }
138
+ addServerImportsDir(resolve("./runtime/server/utils"));
139
+ }
140
+ });
141
+ function generateAuthGuards(nuxt, resolve) {
142
+ const r = (path) => pathToFileURL(`${resolve(path)}.js`).href;
143
+ const imports = [
144
+ `import { registerAuthGuard } from ${JSON.stringify(r("./runtime/server/utils/auth"))}`
145
+ ];
146
+ const registrations = [];
147
+ imports.push(
148
+ `import { clerkGuard } from ${JSON.stringify(r("./runtime/server/guards/clerk"))}`
149
+ );
150
+ registrations.push(` registerAuthGuard('clerk', clerkGuard)`);
151
+ imports.push(
152
+ `import { supabaseGuard } from ${JSON.stringify(r("./runtime/server/guards/supabase"))}`
153
+ );
154
+ registrations.push(` registerAuthGuard('supabase', supabaseGuard)`);
155
+ const sharedDir = nuxt.options.alias?.["#shared"];
156
+ if (sharedDir) {
157
+ const fileExists = [".ts", ".mjs", ".js"].some(
158
+ (ext) => existsSync(join(sharedDir, `utils/better-auth${ext}`))
159
+ );
160
+ if (fileExists) {
161
+ imports.push(
162
+ `import { betterAuthGuard } from ${JSON.stringify(r("./runtime/server/guards/better-auth"))}`
163
+ );
164
+ registrations.push(` registerAuthGuard('better-auth', betterAuthGuard)`);
165
+ }
166
+ }
167
+ return [
168
+ ...imports,
169
+ "",
170
+ "const plugin = (nitroApp) => {",
171
+ ...registrations,
172
+ "}",
173
+ "export default plugin"
174
+ ].join("\n");
175
+ }
176
+
177
+ export { module$1 as default };
@@ -0,0 +1,11 @@
1
+ <script setup>
2
+ import { useAuth } from "#imports";
3
+ const provider = useAuth();
4
+ </script>
5
+
6
+ <template>
7
+ <slot v-if="provider.isSignedIn.value" />
8
+ <slot v-else name="fallback">
9
+ <div>Please sign in to view this content.</div>
10
+ </slot>
11
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,14 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useAuth, useAuthForms } from "#imports";
4
+ import DefaultSignIn from "./DefaultSignIn.vue";
5
+ const appForms = useAuthForms();
6
+ const provider = useAuth();
7
+ const signInForm = computed(
8
+ () => appForms.SignInForm || provider.SignInForm || DefaultSignIn
9
+ );
10
+ </script>
11
+
12
+ <template>
13
+ <component :is="signInForm" v-bind="$attrs" />
14
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,10 @@
1
+ <script setup>
2
+ import { useAuth } from "#imports";
3
+ const provider = useAuth();
4
+ </script>
5
+
6
+ <template>
7
+ <span style="cursor: pointer" @click="provider.openSignIn?.()">
8
+ <slot />
9
+ </span>
10
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,14 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useAuth, useAuthForms } from "#imports";
4
+ import DefaultSignUp from "./DefaultSignUp.vue";
5
+ const appForms = useAuthForms();
6
+ const provider = useAuth();
7
+ const signUpForm = computed(
8
+ () => appForms.SignUpForm || provider.SignUpForm || DefaultSignUp
9
+ );
10
+ </script>
11
+
12
+ <template>
13
+ <component :is="signUpForm" v-bind="$attrs" />
14
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,10 @@
1
+ <script setup>
2
+ import { useAuth } from "#imports";
3
+ const provider = useAuth();
4
+ </script>
5
+
6
+ <template>
7
+ <span style="cursor: pointer" @click="provider.openSignUp?.()">
8
+ <slot />
9
+ </span>
10
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,20 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useAuth } from "#imports";
4
+ const provider = useAuth();
5
+ const user = computed(() => provider.user.value);
6
+ const initials = computed(() => {
7
+ const name = user.value?.name || user.value?.email || "";
8
+ return name ? name.charAt(0).toUpperCase() : "";
9
+ });
10
+ </script>
11
+
12
+ <template>
13
+ <img v-if="user.avatarUrl" :src="user.avatarUrl" class="ipa-user-avatar ipa-user-avatar--image">
14
+ <span v-else class="ipa-user-avatar ipa-user-avatar--text">{{ initials
15
+ }}</span>
16
+ </template>
17
+
18
+ <style>
19
+ .ipa-user-avatar{border-radius:50%;cursor:pointer;height:32px;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:32px}.ipa-user-avatar--image{-o-object-fit:cover;object-fit:cover}.ipa-user-avatar--text{align-items:center;background:#e0e0e0;color:var(--el-text-color-primary,#555);display:inline-flex;font-size:14px;font-weight:600;justify-content:center}
20
+ </style>
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,70 @@
1
+ <script setup>
2
+ import { useTranslator } from "@ipa-vue/core";
3
+ import { ref } from "vue";
4
+ import { useRouter } from "vue-router";
5
+ import { useAuth } from "#imports";
6
+ defineProps({
7
+ withSignUp: { type: Boolean, required: false }
8
+ });
9
+ const { signIn } = useAuth();
10
+ const router = useRouter();
11
+ const t = useTranslator();
12
+ const email = ref("");
13
+ const password = ref("");
14
+ const loading = ref(false);
15
+ const error = ref("");
16
+ async function handleSubmit() {
17
+ loading.value = true;
18
+ error.value = "";
19
+ const result = await signIn({ email: email.value, password: password.value });
20
+ if (!result.success)
21
+ error.value = result.error?.message ?? "";
22
+ loading.value = false;
23
+ }
24
+ </script>
25
+
26
+ <template>
27
+ <form class="ntm-form" @submit.prevent="handleSubmit">
28
+ <p v-if="error" class="ntm-form__error" role="alert">
29
+ {{ error }}
30
+ </p>
31
+
32
+ <div class="ntm-form__field">
33
+ <input
34
+ v-model="email"
35
+ class="ntm-form__input"
36
+ type="email"
37
+ :placeholder="t('common.email') || 'Email'"
38
+ autocomplete="email"
39
+ required
40
+ >
41
+ </div>
42
+
43
+ <div class="ntm-form__field">
44
+ <input
45
+ v-model="password"
46
+ class="ntm-form__input"
47
+ type="password"
48
+ :placeholder="t('common.password') || 'Password'"
49
+ autocomplete="current-password"
50
+ required
51
+ >
52
+ </div>
53
+
54
+ <button type="submit" class="ntm-form__submit" :disabled="loading">
55
+ <span v-if="loading" class="ntm-form__spinner" aria-hidden="true" />
56
+ {{ t("signIn") || "Sign In" }}
57
+ </button>
58
+
59
+ <p v-if="withSignUp" class="ntm-form__switch">
60
+ {{ t("auth.noAccount", "") || "Don't have an account?" }}
61
+ <a class="ntm-form__link" @click.prevent="router.push('/sign-up')">
62
+ {{ t("signUp") || "Sign Up" }}
63
+ </a>
64
+ </p>
65
+ </form>
66
+ </template>
67
+
68
+ <style scoped>
69
+ .ntm-form{display:flex;flex-direction:column;gap:14px;width:100%}.ntm-form__input{background:var(--el-fill-color-blank,#fff);border:1px solid var(--el-border-color,#dcdfe6);border-radius:var(--el-border-radius-base,4px);box-sizing:border-box;color:var(--el-text-color-primary,#303133);font-size:14px;height:38px;outline:none;padding:0 12px;transition:border-color .2s;width:100%}.ntm-form__input::-moz-placeholder{color:var(--el-text-color-placeholder,#a8abb2)}.ntm-form__input::placeholder{color:var(--el-text-color-placeholder,#a8abb2)}.ntm-form__input:focus{border-color:var(--el-color-primary,#409eff)}.ntm-form__submit{align-items:center;background:var(--el-color-primary,#409eff);border:none;border-radius:var(--el-border-radius-base,4px);color:#fff;cursor:pointer;display:flex;font-size:14px;font-weight:500;gap:8px;height:38px;justify-content:center;transition:opacity .2s;width:100%}.ntm-form__submit:hover:not(:disabled){opacity:.85}.ntm-form__submit:disabled{cursor:not-allowed;opacity:.6}.ntm-form__spinner{animation:ntm-spin .6s linear infinite;border:2px solid hsla(0,0%,100%,.4);border-radius:50%;border-top-color:#fff;flex-shrink:0;height:14px;width:14px}@keyframes ntm-spin{to{transform:rotate(1turn)}}.ntm-form__error{background:var(--el-color-danger-light-9,#fef0f0);border-radius:var(--el-border-radius-base,4px);color:var(--el-color-danger,#f56c6c);font-size:13px;margin:0;padding:8px 12px}.ntm-form__switch{color:var(--el-text-color-secondary,#909399);font-size:13px;margin:0;text-align:center}.ntm-form__link{color:var(--el-color-primary,#409eff);cursor:pointer;text-decoration:none}.ntm-form__link:hover{opacity:.8}@media (prefers-color-scheme:dark){.ntm-form__input{background:var(--el-fill-color-blank,#1d1e1f);border-color:var(--el-border-color,#4c4d4f);color:var(--el-text-color-primary,#e5eaf3)}.ntm-form__input::-moz-placeholder{color:var(--el-text-color-placeholder,#606266)}.ntm-form__input::placeholder{color:var(--el-text-color-placeholder,#606266)}.ntm-form__error{background:var(--el-color-danger-light-9,#2b1d1d)}}
70
+ </style>
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,81 @@
1
+ <script setup>
2
+ import { useTranslator } from "@ipa-vue/core";
3
+ import { ref } from "vue";
4
+ import { useRouter } from "vue-router";
5
+ import { useAuth } from "#imports";
6
+ defineProps({
7
+ withSignIn: { type: Boolean, required: false }
8
+ });
9
+ const { signUp } = useAuth();
10
+ const router = useRouter();
11
+ const t = useTranslator();
12
+ const name = ref("");
13
+ const email = ref("");
14
+ const password = ref("");
15
+ const loading = ref(false);
16
+ const error = ref("");
17
+ async function handleSubmit() {
18
+ loading.value = true;
19
+ error.value = "";
20
+ const result = await signUp({ name: name.value, email: email.value, password: password.value });
21
+ if (!result.success)
22
+ error.value = result.error?.message ?? "";
23
+ loading.value = false;
24
+ }
25
+ </script>
26
+
27
+ <template>
28
+ <form class="ntm-form" @submit.prevent="handleSubmit">
29
+ <p v-if="error" class="ntm-form__error" role="alert">
30
+ {{ error }}
31
+ </p>
32
+
33
+ <div class="ntm-form__field">
34
+ <input
35
+ v-model="name"
36
+ class="ntm-form__input"
37
+ type="text"
38
+ :placeholder="t('common.name') || 'Name'"
39
+ autocomplete="name"
40
+ >
41
+ </div>
42
+
43
+ <div class="ntm-form__field">
44
+ <input
45
+ v-model="email"
46
+ class="ntm-form__input"
47
+ type="email"
48
+ :placeholder="t('common.email') || 'Email'"
49
+ autocomplete="email"
50
+ required
51
+ >
52
+ </div>
53
+
54
+ <div class="ntm-form__field">
55
+ <input
56
+ v-model="password"
57
+ class="ntm-form__input"
58
+ type="password"
59
+ :placeholder="t('common.password') || 'Password'"
60
+ autocomplete="new-password"
61
+ required
62
+ >
63
+ </div>
64
+
65
+ <button type="submit" class="ntm-form__submit" :disabled="loading">
66
+ <span v-if="loading" class="ntm-form__spinner" aria-hidden="true" />
67
+ {{ t("signUp") || "Sign Up" }}
68
+ </button>
69
+
70
+ <p v-if="withSignIn" class="ntm-form__switch">
71
+ {{ t("auth.hasAccount") || "Already have an account?" }}
72
+ <a class="ntm-form__link" @click.prevent="router.push('/sign-in')">
73
+ {{ t("signIn") || "Sign In" }}
74
+ </a>
75
+ </p>
76
+ </form>
77
+ </template>
78
+
79
+ <style scoped>
80
+ .ntm-form{display:flex;flex-direction:column;gap:14px;width:100%}.ntm-form__input{background:var(--el-fill-color-blank,#fff);border:1px solid var(--el-border-color,#dcdfe6);border-radius:var(--el-border-radius-base,4px);box-sizing:border-box;color:var(--el-text-color-primary,#303133);font-size:14px;height:38px;outline:none;padding:0 12px;transition:border-color .2s;width:100%}.ntm-form__input::-moz-placeholder{color:var(--el-text-color-placeholder,#a8abb2)}.ntm-form__input::placeholder{color:var(--el-text-color-placeholder,#a8abb2)}.ntm-form__input:focus{border-color:var(--el-color-primary,#409eff)}.ntm-form__submit{align-items:center;background:var(--el-color-primary,#409eff);border:none;border-radius:var(--el-border-radius-base,4px);color:#fff;cursor:pointer;display:flex;font-size:14px;font-weight:500;gap:8px;height:38px;justify-content:center;transition:opacity .2s;width:100%}.ntm-form__submit:hover:not(:disabled){opacity:.85}.ntm-form__submit:disabled{cursor:not-allowed;opacity:.6}.ntm-form__spinner{animation:ntm-spin .6s linear infinite;border:2px solid hsla(0,0%,100%,.4);border-radius:50%;border-top-color:#fff;flex-shrink:0;height:14px;width:14px}@keyframes ntm-spin{to{transform:rotate(1turn)}}.ntm-form__error{background:var(--el-color-danger-light-9,#fef0f0);border-radius:var(--el-border-radius-base,4px);color:var(--el-color-danger,#f56c6c);font-size:13px;margin:0;padding:8px 12px}.ntm-form__switch{color:var(--el-text-color-secondary,#909399);font-size:13px;margin:0;text-align:center}.ntm-form__link{color:var(--el-color-primary,#409eff);cursor:pointer;text-decoration:none}.ntm-form__link:hover{opacity:.8}@media (prefers-color-scheme:dark){.ntm-form__input{background:var(--el-fill-color-blank,#1d1e1f);border-color:var(--el-border-color,#4c4d4f);color:var(--el-text-color-primary,#e5eaf3)}.ntm-form__input::-moz-placeholder{color:var(--el-text-color-placeholder,#606266)}.ntm-form__input::placeholder{color:var(--el-text-color-placeholder,#606266)}.ntm-form__error{background:var(--el-color-danger-light-9,#2b1d1d)}}
81
+ </style>
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ import type { AuthProvider } from '@nuxt-tmpl/core';
2
+ export declare function useAuth(): AuthProvider;
@@ -0,0 +1,5 @@
1
+ import { useNuxtApp } from "nuxt/app";
2
+ export function useAuth() {
3
+ const nuxtApp = useNuxtApp();
4
+ return nuxtApp.$auth;
5
+ }
@@ -0,0 +1,9 @@
1
+ import type { Component } from "vue";
2
+ interface AuthForms {
3
+ SignInForm?: Component;
4
+ SignUpForm?: Component;
5
+ }
6
+ /** Call inside a component's `<script setup>` (e.g. `app.vue`) to override the default auth form components. */
7
+ export declare function provideAuthForms(forms: AuthForms): void;
8
+ export declare function useAuthForms(): AuthForms;
9
+ export {};
@@ -0,0 +1,8 @@
1
+ import { provide, inject } from "vue";
2
+ const AUTH_FORMS_KEY = Symbol("nuxt-tmpl:auth-forms");
3
+ export function provideAuthForms(forms) {
4
+ provide(AUTH_FORMS_KEY, forms);
5
+ }
6
+ export function useAuthForms() {
7
+ return inject(AUTH_FORMS_KEY, {});
8
+ }
@@ -0,0 +1,79 @@
1
+ <script setup>
2
+ import { useTranslator } from "@ipa-vue/core";
3
+ import { useRuntimeConfig } from "nuxt/app";
4
+ import { computed, watch } from "vue";
5
+ import { useRoute, useRouter } from "vue-router";
6
+ import { getAuthMode, useAuth } from "#imports";
7
+ const t = useTranslator();
8
+ const provider = useAuth();
9
+ const user = computed(() => provider.user.value);
10
+ const route = useRoute();
11
+ const router = useRouter();
12
+ const config = useRuntimeConfig();
13
+ const afterSignInRoute = config.public?.nuxtTmpl?.afterSignInRoute ?? "/dashboard";
14
+ const guestRoutes = config.public?.nuxtTmpl?.guestRoutes ?? [];
15
+ const publicRoutes = config.public?.nuxtTmpl?.publicRoutes ?? [];
16
+ const authMode = computed(
17
+ () => getAuthMode(route.path, route.meta, guestRoutes, publicRoutes)
18
+ );
19
+ watch(
20
+ [() => provider.isLoaded.value, () => provider.isSignedIn.value, authMode],
21
+ ([loaded, signedIn, mode]) => {
22
+ if (!loaded || provider.id === "none")
23
+ return;
24
+ if (signedIn && mode === "guest") {
25
+ router.push(afterSignInRoute);
26
+ return;
27
+ }
28
+ if (!signedIn && mode === "required") {
29
+ router.push("/");
30
+ }
31
+ },
32
+ { immediate: true }
33
+ );
34
+ </script>
35
+
36
+ <template>
37
+ <div class="flex flex-col w-full h-full">
38
+ <div style="border-bottom: 1px solid var(--el-border-color)">
39
+ <div class="mx-auto px-4 sm:px-6 lg:px-8">
40
+ <div class="flex justify-between items-center h-10">
41
+ <slot name="app-name">
42
+ <el-link type="primary" href="/" class="text-lg font-bold">
43
+ {{ t("appName") }}
44
+ </el-link>
45
+ </slot>
46
+ <slot name="nav" />
47
+ <nav class="flex items-center space-x-4">
48
+ <slot name="header-actions" />
49
+ <template v-if="provider.id !== 'none'">
50
+ <template v-if="!provider.isSignedIn.value">
51
+ <AuthSignInButton>
52
+ <el-button size="small" type="primary">
53
+ {{ t("signIn") }}
54
+ </el-button>
55
+ </AuthSignInButton>
56
+ <AuthSignUpButton>
57
+ <el-button size="small" type="primary">
58
+ {{ t("signUp") }}
59
+ </el-button>
60
+ </AuthSignUpButton>
61
+ </template>
62
+ <slot v-else name="user-avatar" :user="user" :provider="provider">
63
+ <AuthUserAvatar @click="provider.openUserProfile?.()" />
64
+ </slot>
65
+ </template>
66
+ </nav>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ <slot name="content">
71
+ <div class="flex-1 flex overflow-hidden">
72
+ <slot name="sidebar" />
73
+ <div class="flex-1 overflow-auto">
74
+ <slot />
75
+ </div>
76
+ </div>
77
+ </slot>
78
+ </div>
79
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nuxt/app").RouteMiddleware;
2
+ export default _default;
@@ -0,0 +1,32 @@
1
+ import { defineNuxtRouteMiddleware, navigateTo, useNuxtApp, useRuntimeConfig } from "nuxt/app";
2
+ import { watch } from "vue";
3
+ import { getAuthMode } from "#imports";
4
+ export default defineNuxtRouteMiddleware(async (to, _from) => {
5
+ const config = useRuntimeConfig();
6
+ const providerId = config.public?.authProvider;
7
+ if (!providerId || providerId === "none")
8
+ return;
9
+ const guestRoutes = config.public?.nuxtTmpl?.guestRoutes ?? [];
10
+ const publicRoutes = config.public?.nuxtTmpl?.publicRoutes ?? [];
11
+ const mode = getAuthMode(to.path, to.meta, guestRoutes, publicRoutes);
12
+ if (mode === "public")
13
+ return;
14
+ const { $auth } = useNuxtApp();
15
+ if (!$auth.isLoaded.value) {
16
+ await new Promise((resolve) => {
17
+ const stop = watch(
18
+ () => $auth.isLoaded.value,
19
+ (loaded) => {
20
+ if (loaded) {
21
+ stop();
22
+ resolve();
23
+ }
24
+ },
25
+ { immediate: true }
26
+ );
27
+ });
28
+ }
29
+ if (!$auth.isSignedIn.value && mode === "required" && _from.path !== "/") {
30
+ return navigateTo("/");
31
+ }
32
+ });
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <div class="auth-page">
3
+ <div class="auth-card">
4
+ <h2 class="auth-card__title">
5
+ {{ $t?.("signIn") ?? "Sign In" }}
6
+ </h2>
7
+ <AuthSignIn with-sign-up />
8
+ </div>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup>
13
+ definePageMeta({ tab: false });
14
+ </script>
15
+
16
+ <style scoped>
17
+ .auth-page{align-items:center;display:flex;height:100%;justify-content:center}.auth-card{background:var(--el-bg-color);border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,.08);display:flex;flex-direction:column;gap:24px;padding:40px;width:400px}.auth-card__title{color:var(--el-text-color-primary);font-size:22px;font-weight:600;margin:0;text-align:center}
18
+ </style>