nuxt-bake 1.0.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/README.md +79 -0
- package/dist/helpers/constants.js +28 -0
- package/dist/helpers/git.js +32 -0
- package/dist/helpers/package-manager.js +77 -0
- package/dist/helpers/template.js +27 -0
- package/dist/helpers/utils.js +42 -0
- package/dist/index.js +96 -0
- package/eslint.config.ts +26 -0
- package/package.json +58 -0
- package/playwright.config.ts +6 -0
- package/src/helpers/git.ts +28 -0
- package/src/helpers/package-manager.ts +86 -0
- package/src/helpers/template.ts +35 -0
- package/src/helpers/utils.ts +83 -0
- package/src/index.ts +106 -0
- package/templates/base/.env.example +11 -0
- package/templates/base/README.md +58 -0
- package/templates/base/app/app.vue +20 -0
- package/templates/base/app/assets/styles.css +257 -0
- package/templates/base/app/components/dialog.vue +54 -0
- package/templates/base/app/components/navbar.vue +51 -0
- package/templates/base/app/components/toast.vue +116 -0
- package/templates/base/app/composables/use-session-monitor.ts +40 -0
- package/templates/base/app/composables/use-theme.ts +41 -0
- package/templates/base/app/composables/use-toast.ts +37 -0
- package/templates/base/app/error.vue +23 -0
- package/templates/base/app/layouts/default.vue +7 -0
- package/templates/base/app/pages/index.vue +53 -0
- package/templates/base/app/pages/sign-in.vue +39 -0
- package/templates/base/app/stores/user-store.ts +50 -0
- package/templates/base/app/utils/helpers.ts +42 -0
- package/templates/base/eslint.config.ts +74 -0
- package/templates/base/nuxt.config.ts +39 -0
- package/templates/base/package.json +39 -0
- package/templates/base/prisma/schema.prisma +30 -0
- package/templates/base/prisma.config.ts +12 -0
- package/templates/base/server/api/auth/[provider].ts +67 -0
- package/templates/base/server/api/user/index.delete.ts +8 -0
- package/templates/base/server/api/user/index.get.ts +10 -0
- package/templates/base/server/utils/auth.ts +48 -0
- package/templates/base/server/utils/db.ts +15 -0
- package/templates/base/server/utils/helpers.ts +14 -0
- package/templates/base/shared/types/auth.d.ts +31 -0
- package/templates/base/shared/types/globals.d.ts +15 -0
- package/templates/base/tsconfig.json +18 -0
- package/templates/with-i18n/app/app.vue +29 -0
- package/templates/with-i18n/app/components/navbar.vue +74 -0
- package/templates/with-i18n/app/error.vue +25 -0
- package/templates/with-i18n/app/pages/index.vue +53 -0
- package/templates/with-i18n/app/pages/sign-in.vue +39 -0
- package/templates/with-i18n/app/utils/i18n.config.ts +14 -0
- package/templates/with-i18n/app/utils/locales/en-US.json +50 -0
- package/templates/with-i18n/app/utils/locales/fr-FR.json +50 -0
- package/templates/with-i18n/nuxt.config.ts +51 -0
- package/templates/with-tests/app/pages/index.vue +51 -0
- package/templates/with-tests/nuxt.config.ts +31 -0
- package/templates/with-tests/playwright.config.ts +13 -0
- package/templates/with-tests/tests/e2e/e2e-hello.test.ts +8 -0
- package/templates/with-tests/tests/hello.test.ts +7 -0
- package/templates/with-tests/vitest.config.ts +16 -0
- package/tests/git.test.ts +54 -0
- package/tests/package-manager.test.ts +100 -0
- package/tests/template.test.ts +73 -0
- package/tests/utils.test.ts +155 -0
- package/tsconfig.json +13 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { EventHandlerRequest, H3Event } from "h3"
|
|
2
|
+
|
|
3
|
+
const configs: Record<string, any> = {
|
|
4
|
+
google: {
|
|
5
|
+
clientId: process.env.NUXT_OAUTH_GOOGLE_CLIENT_ID,
|
|
6
|
+
clientSecret: process.env.NUXT_OAUTH_GOOGLE_CLIENT_SECRET,
|
|
7
|
+
redirectURL: `${process.env.NUXT_PUBLIC_BASE_URL}/api/auth/google`,
|
|
8
|
+
},
|
|
9
|
+
github: {
|
|
10
|
+
emailRequired: true,
|
|
11
|
+
clientId: process.env.NUXT_OAUTH_GITHUB_CLIENT_ID,
|
|
12
|
+
clientSecret: process.env.NUXT_OAUTH_GITHUB_CLIENT_SECRET,
|
|
13
|
+
redirectURL: `${process.env.NUXT_PUBLIC_BASE_URL}/api/auth/github`,
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const extractUserData: Record<string, (user: any) => any> = {
|
|
18
|
+
google: user => ({
|
|
19
|
+
id: user.id?.toString() || user.sub?.toString(),
|
|
20
|
+
name: user.name || user.given_name,
|
|
21
|
+
email: user.email,
|
|
22
|
+
image: user.picture,
|
|
23
|
+
}),
|
|
24
|
+
github: user => ({
|
|
25
|
+
id: user.id?.toString(),
|
|
26
|
+
name: user.name,
|
|
27
|
+
email: user.email,
|
|
28
|
+
image: user.avatar_url,
|
|
29
|
+
}),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default defineEventHandler(async (event: H3Event) => {
|
|
33
|
+
const provider = event.context.params?.provider
|
|
34
|
+
if (!provider || !configs[provider]) {
|
|
35
|
+
throw createError({ status: 400, message: "Unknown OAuth provider" })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const oauthHandler = { google: defineOAuthGoogleEventHandler, github: defineOAuthGitHubEventHandler }[provider]
|
|
40
|
+
if (!oauthHandler) {
|
|
41
|
+
throw createError({ status: 400, message: `OAuth handler not found for provider: ${provider}` })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return await oauthHandler({ config: configs[provider], async onSuccess(event: H3Event<EventHandlerRequest>, { user }: any) {
|
|
45
|
+
if (!user || typeof user !== "object") {
|
|
46
|
+
throw createError({ status: 400, message: `Invalid user data from ${provider}` })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const extractor = extractUserData[provider]
|
|
50
|
+
if (!extractor) {
|
|
51
|
+
throw createError({ status: 400, message: `User data extractor not found for provider: ${provider}` })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const userData = extractor(user)
|
|
55
|
+
if (!userData.id || !userData.email) {
|
|
56
|
+
throw createError({ status: 400, message: `Missing required user data from ${provider}` })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return handleOAuthUser(event, { ...userData, provider })
|
|
60
|
+
}, async onError(event: H3Event<EventHandlerRequest>) {
|
|
61
|
+
return sendRedirect(event, `/sign-in?error=${provider}_oauth_failed`)
|
|
62
|
+
} })(event)
|
|
63
|
+
}
|
|
64
|
+
catch (err: any) {
|
|
65
|
+
throw createError({ status: 500, message: "OAuth processing failed", data: { provider, error: err instanceof Error ? err.message : String(err) } })
|
|
66
|
+
}
|
|
67
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default defineEventHandler(async (event) => {
|
|
2
|
+
const user = await getUserFromSession(event)
|
|
3
|
+
if (!user) {
|
|
4
|
+
throw createError({ status: 404, message: "User not found" })
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const userData = await db.user.findUnique({ where: { id: user.id } })
|
|
8
|
+
|
|
9
|
+
return { userData }
|
|
10
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { H3Event } from "h3"
|
|
2
|
+
|
|
3
|
+
export async function handleOAuthUser(event: H3Event, userData: OAuthUserData) {
|
|
4
|
+
const { id: providerAccountId, name, email, image, provider } = userData
|
|
5
|
+
|
|
6
|
+
// Find existing account by provider
|
|
7
|
+
let account = await db.account.findUnique({
|
|
8
|
+
where: { provider_providerAccountId: { provider, providerAccountId } },
|
|
9
|
+
include: { user: true },
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
let user: any = account?.user ?? null
|
|
13
|
+
if (!user) {
|
|
14
|
+
user = await db.user.findUnique({ where: { email } })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// If still no user, create one
|
|
18
|
+
if (!user) {
|
|
19
|
+
user = await db.user.create({
|
|
20
|
+
data: {
|
|
21
|
+
email,
|
|
22
|
+
name: name?.trim() || email.split("@")[0],
|
|
23
|
+
image: image || undefined,
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Upsert account
|
|
29
|
+
account = await db.account.upsert({
|
|
30
|
+
where: { provider_providerAccountId: { provider, providerAccountId } },
|
|
31
|
+
update: {},
|
|
32
|
+
create: { userId: user.id, provider, providerAccountId },
|
|
33
|
+
include: { user: true },
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
user = account.user
|
|
37
|
+
|
|
38
|
+
// Build session object
|
|
39
|
+
const sessionUser = {
|
|
40
|
+
id: user.id,
|
|
41
|
+
email: user.email,
|
|
42
|
+
name: user.name,
|
|
43
|
+
image: user.image ?? null,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await setUserSession(event, { user: sessionUser, loggedInAt: new Date() })
|
|
47
|
+
return sendRedirect(event, "/admin/profile")
|
|
48
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PrismaPg } from "@prisma/adapter-pg"
|
|
2
|
+
import dotenv from "dotenv"
|
|
3
|
+
import { PrismaClient } from "../../.data/prisma/client"
|
|
4
|
+
|
|
5
|
+
dotenv.config()
|
|
6
|
+
|
|
7
|
+
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL })
|
|
8
|
+
const db = new PrismaClient({ adapter })
|
|
9
|
+
|
|
10
|
+
db.$connect().catch((err) => {
|
|
11
|
+
console.error("Failed to connect to database:", err)
|
|
12
|
+
process.exit(1)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export default db
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EventHandlerRequest, H3Event } from "h3"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves the authenticated user from the current session.
|
|
5
|
+
* Throws 401 if no valid session exists.
|
|
6
|
+
*/
|
|
7
|
+
export async function getUserFromSession(event: H3Event<EventHandlerRequest>) {
|
|
8
|
+
const session = await getUserSession(event)
|
|
9
|
+
if (!session?.user?.id) {
|
|
10
|
+
throw createError({ status: 401, statusText: "Unauthorized" })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return session.user
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
declare module "#auth-utils" {
|
|
2
|
+
interface User {
|
|
3
|
+
id: string
|
|
4
|
+
email: string
|
|
5
|
+
name: string
|
|
6
|
+
image?: string | null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface UserSession {
|
|
10
|
+
user: User
|
|
11
|
+
loggedInAt: Date
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Account {
|
|
15
|
+
id: string
|
|
16
|
+
userId: string
|
|
17
|
+
provider: string
|
|
18
|
+
providerAccountId: string
|
|
19
|
+
createdAt?: Date | string
|
|
20
|
+
updatedAt?: Date | string
|
|
21
|
+
user?: User
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface OAuthUserData {
|
|
26
|
+
id: string
|
|
27
|
+
email: string
|
|
28
|
+
name: string
|
|
29
|
+
image: string | null
|
|
30
|
+
provider: "google" | "github"
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface User {
|
|
2
|
+
id: string
|
|
3
|
+
name: string
|
|
4
|
+
email: string
|
|
5
|
+
image?: string | null
|
|
6
|
+
createdAt?: Date | string
|
|
7
|
+
updatedAt?: Date | string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Toast {
|
|
11
|
+
id: string
|
|
12
|
+
message: string
|
|
13
|
+
type: "success" | "error"
|
|
14
|
+
duration?: number
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"references": [
|
|
3
|
+
{
|
|
4
|
+
"path": "./.nuxt/tsconfig.app.json"
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"path": "./.nuxt/tsconfig.server.json"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "./.nuxt/tsconfig.shared.json"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "./.nuxt/tsconfig.node.json"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
// https://nuxt.com/docs/guide/concepts/typescript
|
|
17
|
+
"files": []
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<NuxtLayout>
|
|
3
|
+
<NuxtPage />
|
|
4
|
+
</NuxtLayout>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
|
|
9
|
+
const { locale } = useI18n()
|
|
10
|
+
const isLoading = ref(true)
|
|
11
|
+
|
|
12
|
+
onMounted(() => {
|
|
13
|
+
const onLoad = () => (isLoading.value = false)
|
|
14
|
+
if (document.readyState === "complete") {
|
|
15
|
+
isLoading.value = false
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
window.addEventListener("load", onLoad)
|
|
19
|
+
onBeforeUnmount(() => window.removeEventListener("load", onLoad))
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
useLocaleHead({ dir: true, seo: true })
|
|
24
|
+
useHead({
|
|
25
|
+
htmlAttrs: { lang: computed(() => locale.value) },
|
|
26
|
+
link: [{ rel: "icon", href: "/favicon.svg" }],
|
|
27
|
+
meta: [{ name: "viewport", content: "width=device-width, initial-scale=1" },],
|
|
28
|
+
})
|
|
29
|
+
</script>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav class="flex w-full flex-row items-center justify-between gap-2 p-4">
|
|
3
|
+
<div class="flex flex-row items-center gap-2">
|
|
4
|
+
<nuxt-link to="/">
|
|
5
|
+
<icon name="simple-icons:nuxt" size="35" class="text-primary" />
|
|
6
|
+
</nuxt-link>
|
|
7
|
+
|
|
8
|
+
<div v-if="userStore.user" class="flex flex-row items-center gap-2">
|
|
9
|
+
<p class="text-sm">
|
|
10
|
+
{{ t('navbar.greeting', { name: userStore.user?.name }) }}
|
|
11
|
+
</p>
|
|
12
|
+
<button class="btn" @click="signOut">
|
|
13
|
+
{{ t('navbar.logout') }}
|
|
14
|
+
</button>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div v-else class="flex flex-row items-center gap-2">
|
|
18
|
+
<p class="text-sm">
|
|
19
|
+
{{ t('navbar.unauthenticated') }}
|
|
20
|
+
</p>
|
|
21
|
+
<nuxt-link to="/sign-in" class="btn">
|
|
22
|
+
{{ t('navbar.signIn') }}
|
|
23
|
+
</nuxt-link>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="flex flex-row items-center gap-2">
|
|
28
|
+
<nuxt-link to="https://github.com/matimortari/nuxt-bake" class="btn">
|
|
29
|
+
<icon name="simple-icons:github" size="20" />
|
|
30
|
+
</nuxt-link>
|
|
31
|
+
|
|
32
|
+
<button class="btn" @click="toggleTheme">
|
|
33
|
+
<icon :name="themeIcon" size="20" />
|
|
34
|
+
</button>
|
|
35
|
+
|
|
36
|
+
<button v-for="language in availableLocales" :key="language" class="cursor-pointer outline-none hover:underline" @click="() => setLanguage(language)">
|
|
37
|
+
{{ t(`locale.${language}`) }}
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
</nav>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script setup lang="ts">
|
|
44
|
+
const { t, locale, availableLocales } = useI18n()
|
|
45
|
+
const { clear } = useUserSession()
|
|
46
|
+
const { toggleTheme, themeIcon } = useTheme()
|
|
47
|
+
const userStore = useUserStore()
|
|
48
|
+
|
|
49
|
+
async function setLanguage(language: string) {
|
|
50
|
+
locale.value = language as "en-US" | "fr-FR"
|
|
51
|
+
localStorage.setItem("nuxt-lang", language)
|
|
52
|
+
await nextTick()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onMounted(async () => {
|
|
56
|
+
try {
|
|
57
|
+
await userStore.getUser()
|
|
58
|
+
}
|
|
59
|
+
catch (err: any) {
|
|
60
|
+
console.error("Failed to fetch user:", err)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const savedLang = localStorage.getItem("nuxt-lang")
|
|
64
|
+
if (savedLang && (savedLang === "en-US" || savedLang === "fr-FR")) {
|
|
65
|
+
locale.value = savedLang as "en-US" | "fr-FR"
|
|
66
|
+
await nextTick()
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
async function signOut() {
|
|
71
|
+
await clear()
|
|
72
|
+
return navigateTo("/")
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex h-screen flex-col items-center justify-center gap-4">
|
|
3
|
+
<h1>
|
|
4
|
+
{{ error.status }}
|
|
5
|
+
</h1>
|
|
6
|
+
|
|
7
|
+
<p class="text-muted-foreground text-center">
|
|
8
|
+
{{ error.statusText || t("error.unknown") }}
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<nuxt-link to="/">
|
|
12
|
+
{{ t("error.goHome") }}
|
|
13
|
+
</nuxt-link>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import type { NuxtError } from "#app"
|
|
19
|
+
|
|
20
|
+
defineProps<{
|
|
21
|
+
error: NuxtError
|
|
22
|
+
}>()
|
|
23
|
+
|
|
24
|
+
const { t } = useI18n()
|
|
25
|
+
</script>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col items-center justify-center gap-8">
|
|
3
|
+
<header class="flex w-full flex-col items-center gap-4 p-4 text-center">
|
|
4
|
+
<div class="flex flex-col items-center gap-2 text-center">
|
|
5
|
+
<icon name="simple-icons:nuxt" size="50" class="text-primary" />
|
|
6
|
+
<h1>
|
|
7
|
+
{{ t("index.header.title") }}
|
|
8
|
+
</h1>
|
|
9
|
+
<p>
|
|
10
|
+
{{ t("index.header.description") }}
|
|
11
|
+
</p>
|
|
12
|
+
</div>
|
|
13
|
+
</header>
|
|
14
|
+
|
|
15
|
+
<hr class="border-primary! w-4/5">
|
|
16
|
+
|
|
17
|
+
<div class="flex flex-col items-center gap-2">
|
|
18
|
+
<h2 class="text-lg font-semibold">
|
|
19
|
+
{{ t("index.features") }}
|
|
20
|
+
</h2>
|
|
21
|
+
|
|
22
|
+
<ul class="grid grid-cols-2 gap-4 md:grid-cols-4">
|
|
23
|
+
<li v-for="(feature, index) in features" :key="index" class="flex items-center gap-2 text-xs font-semibold md:text-sm">
|
|
24
|
+
<icon :name="feature.icon" size="20" class="text-primary" />
|
|
25
|
+
<span>{{ t(`index.featureList.${feature.key}`) }}</span>
|
|
26
|
+
</li>
|
|
27
|
+
</ul>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
const { t } = useI18n()
|
|
34
|
+
|
|
35
|
+
const features = [
|
|
36
|
+
{ icon: "simple-icons:nuxtdotjs", key: "nuxt" },
|
|
37
|
+
{ icon: "simple-icons:typescript", key: "typescript" },
|
|
38
|
+
{ icon: "simple-icons:tailwindcss", key: "tailwind" },
|
|
39
|
+
{ icon: "simple-icons:iconify", key: "icons" },
|
|
40
|
+
{ icon: "ph:text-aa", key: "fonts" },
|
|
41
|
+
{ icon: "icon-park-outline:pineapple", key: "pinia" },
|
|
42
|
+
{ icon: "ph:translate", key: "i18n" },
|
|
43
|
+
{ icon: "ph:lock-key-open", key: "oauth" },
|
|
44
|
+
{ icon: "simple-icons:prisma", key: "prisma" },
|
|
45
|
+
{ icon: "simple-icons:eslint", key: "eslint" },
|
|
46
|
+
{ icon: "ph:magnifying-glass", key: "seo" },
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
useHead({
|
|
50
|
+
title: t("index.meta.title"),
|
|
51
|
+
meta: [{ name: "description", content: t("index.meta.description") }],
|
|
52
|
+
})
|
|
53
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col items-center justify-center gap-8">
|
|
3
|
+
<header class="my-4 flex flex-col items-center justify-center gap-2">
|
|
4
|
+
<h2>{{ t("signIn.header") }}</h2>
|
|
5
|
+
</header>
|
|
6
|
+
|
|
7
|
+
<div class="my-4 flex flex-col items-center gap-4">
|
|
8
|
+
<p class="text-muted-foreground text-lg font-semibold">
|
|
9
|
+
{{ t("signIn.chooseProvider") }}
|
|
10
|
+
</p>
|
|
11
|
+
<div class="flex flex-row items-center gap-4">
|
|
12
|
+
<button
|
|
13
|
+
v-for="provider in providers" :key="provider.name"
|
|
14
|
+
class="btn" @click="navigateTo(`/api/auth/${provider.name}`, { external: true })"
|
|
15
|
+
>
|
|
16
|
+
<icon :name="provider.icon" size="25" />
|
|
17
|
+
<span>{{ t(`signIn.providers.${provider.name}`) }}</span>
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script setup lang="ts">
|
|
25
|
+
const { t } = useI18n()
|
|
26
|
+
|
|
27
|
+
const providers = [
|
|
28
|
+
{ name: "github", icon: "simple-icons:github" },
|
|
29
|
+
{ name: "google", icon: "simple-icons:google" },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
useSeoMeta({
|
|
33
|
+
title: t("signIn.meta.title"),
|
|
34
|
+
description: t("signIn.meta.description"),
|
|
35
|
+
ogTitle: t("signIn.meta.title"),
|
|
36
|
+
ogDescription: t("signIn.meta.description"),
|
|
37
|
+
robots: "noindex, nofollow",
|
|
38
|
+
})
|
|
39
|
+
</script>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import en from "~/utils/locales/en-US.json"
|
|
2
|
+
import br from "~/utils/locales/fr-FR.json"
|
|
3
|
+
|
|
4
|
+
export default defineI18nConfig(() => {
|
|
5
|
+
return {
|
|
6
|
+
legacy: false,
|
|
7
|
+
langDir: "./locales",
|
|
8
|
+
messages: { "en-US": en, "fr-FR": br },
|
|
9
|
+
locales: [
|
|
10
|
+
{ code: "en-US", iso: "en-US" },
|
|
11
|
+
{ code: "fr-FR", iso: "fr-FR" },
|
|
12
|
+
],
|
|
13
|
+
}
|
|
14
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"locale.en-US": "EN",
|
|
3
|
+
"locale.fr-FR": "FR",
|
|
4
|
+
"index": {
|
|
5
|
+
"meta": {
|
|
6
|
+
"title": "Nuxt Bake",
|
|
7
|
+
"description": "If you're seeing this, it means you've successfully set up your Nuxt.js project via nuxt-bake."
|
|
8
|
+
},
|
|
9
|
+
"header": {
|
|
10
|
+
"title": "Nuxt Bake",
|
|
11
|
+
"description": "If you're seeing this, it means you've successfully set up your Nuxt.js project via nuxt-bake."
|
|
12
|
+
},
|
|
13
|
+
"features": "Features",
|
|
14
|
+
"featureList": {
|
|
15
|
+
"nuxt": "Nuxt 4",
|
|
16
|
+
"typescript": "TypeScript",
|
|
17
|
+
"tailwind": "Tailwind CSS 4",
|
|
18
|
+
"icons": "Nuxt Icons",
|
|
19
|
+
"fonts": "Google Fonts",
|
|
20
|
+
"pinia": "Pinia",
|
|
21
|
+
"i18n": "Nuxt i18n",
|
|
22
|
+
"oauth": "OAuth",
|
|
23
|
+
"prisma": "Prisma",
|
|
24
|
+
"eslint": "ESLint",
|
|
25
|
+
"seo": "SEO"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"signIn": {
|
|
29
|
+
"meta": {
|
|
30
|
+
"title": "Sign In",
|
|
31
|
+
"description": "Sign In page"
|
|
32
|
+
},
|
|
33
|
+
"header": "Sign In",
|
|
34
|
+
"chooseProvider": "Choose a provider to continue.",
|
|
35
|
+
"providers": {
|
|
36
|
+
"github": "Sign In With GitHub",
|
|
37
|
+
"google": "Sign In With Google"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"error": {
|
|
41
|
+
"unknown": "Unknown error",
|
|
42
|
+
"goHome": "Go back home"
|
|
43
|
+
},
|
|
44
|
+
"navbar": {
|
|
45
|
+
"greeting": "Hi",
|
|
46
|
+
"unauthenticated": "Unauthenticated",
|
|
47
|
+
"logout": "Logout",
|
|
48
|
+
"signIn": "Sign In"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"locale.en-US": "EN",
|
|
3
|
+
"locale.fr-FR": "FR",
|
|
4
|
+
"index": {
|
|
5
|
+
"meta": {
|
|
6
|
+
"title": "Nuxt.js Départ",
|
|
7
|
+
"description": "Si vous voyez ceci, cela signifie que vous avez configuré avec succès votre projet Nuxt.js via nuxt-bake."
|
|
8
|
+
},
|
|
9
|
+
"header": {
|
|
10
|
+
"title": "Nuxt.js Départ",
|
|
11
|
+
"description": "Si vous voyez ceci, cela signifie que vous avez configuré avec succès votre projet Nuxt.js via nuxt-bake."
|
|
12
|
+
},
|
|
13
|
+
"features": "Fonctionnalités",
|
|
14
|
+
"featureList": {
|
|
15
|
+
"nuxt": "Nuxt 4",
|
|
16
|
+
"typescript": "TypeScript",
|
|
17
|
+
"tailwind": "Tailwind CSS 4",
|
|
18
|
+
"icons": "Nuxt Icons",
|
|
19
|
+
"fonts": "Google Fonts",
|
|
20
|
+
"pinia": "Pinia",
|
|
21
|
+
"i18n": "Nuxt i18n",
|
|
22
|
+
"oauth": "OAuth",
|
|
23
|
+
"prisma": "Prisma",
|
|
24
|
+
"eslint": "ESLint",
|
|
25
|
+
"seo": "SEO"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"signIn": {
|
|
29
|
+
"meta": {
|
|
30
|
+
"title": "Connexion",
|
|
31
|
+
"description": "Page de connexion"
|
|
32
|
+
},
|
|
33
|
+
"header": "Connexion",
|
|
34
|
+
"chooseProvider": "Choisissez un fournisseur pour continuer.",
|
|
35
|
+
"providers": {
|
|
36
|
+
"github": "Se connecter avec GitHub",
|
|
37
|
+
"google": "Se connecter avec Google"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"error": {
|
|
41
|
+
"unknown": "Erreur inconnue",
|
|
42
|
+
"goHome": "Retour à l'accueil"
|
|
43
|
+
},
|
|
44
|
+
"navbar": {
|
|
45
|
+
"greeting": "Salut",
|
|
46
|
+
"unauthenticated": "Non authentifié",
|
|
47
|
+
"logout": "Déconnexion",
|
|
48
|
+
"signIn": "Connexion"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import tailwindcss from "@tailwindcss/vite"
|
|
2
|
+
|
|
3
|
+
export default defineNuxtConfig({
|
|
4
|
+
modules: [
|
|
5
|
+
"@nuxt/fonts",
|
|
6
|
+
"@nuxt/icon",
|
|
7
|
+
"@nuxtjs/color-mode",
|
|
8
|
+
"@nuxtjs/i18n",
|
|
9
|
+
"@nuxtjs/seo",
|
|
10
|
+
"@pinia/nuxt",
|
|
11
|
+
"nuxt-auth-utils",
|
|
12
|
+
],
|
|
13
|
+
runtimeConfig: {
|
|
14
|
+
public: {
|
|
15
|
+
baseUrl: process.env.NUXT_PUBLIC_BASE_URL,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
vite: {
|
|
19
|
+
plugins: [tailwindcss() as any],
|
|
20
|
+
},
|
|
21
|
+
css: ["~/assets/styles.css"],
|
|
22
|
+
site: {
|
|
23
|
+
url: process.env.NUXT_PUBLIC_BASE_URL,
|
|
24
|
+
},
|
|
25
|
+
colorMode: {
|
|
26
|
+
classSuffix: "",
|
|
27
|
+
preference: "system",
|
|
28
|
+
fallback: "light",
|
|
29
|
+
storageKey: "nuxt-color-mode",
|
|
30
|
+
},
|
|
31
|
+
i18n: {
|
|
32
|
+
restructureDir: "app/utils",
|
|
33
|
+
baseUrl: process.env.NUXT_PUBLIC_BASE_URL,
|
|
34
|
+
locales: [
|
|
35
|
+
{ code: "en-US", iso: "en-US", file: "en-US.json" },
|
|
36
|
+
{ code: "fr-FR", iso: "fr-FR", file: "fr-FR.json" },
|
|
37
|
+
],
|
|
38
|
+
defaultLocale: "en-US",
|
|
39
|
+
strategy: "no_prefix",
|
|
40
|
+
detectBrowserLanguage: {
|
|
41
|
+
useCookie: true,
|
|
42
|
+
cookieKey: "nuxt-lang",
|
|
43
|
+
alwaysRedirect: true,
|
|
44
|
+
fallbackLocale: "en-US",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
icon: {
|
|
48
|
+
mode: "svg",
|
|
49
|
+
clientBundle: { scan: true },
|
|
50
|
+
},
|
|
51
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col items-center justify-center gap-8">
|
|
3
|
+
<header class="flex w-full flex-col items-center gap-4 p-4">
|
|
4
|
+
<div class="flex flex-col items-center gap-2 text-center">
|
|
5
|
+
<icon name="simple-icons:nuxt" size="50" class="text-primary" />
|
|
6
|
+
<h1>
|
|
7
|
+
Nuxt Bake
|
|
8
|
+
</h1>
|
|
9
|
+
<p>
|
|
10
|
+
If you're seeing this, it means you've successfully set up your Nuxt.js project via <code class="text-primary">Nuxt Bake</code>.
|
|
11
|
+
</p>
|
|
12
|
+
</div>
|
|
13
|
+
</header>
|
|
14
|
+
|
|
15
|
+
<hr class="border-primary! w-4/5">
|
|
16
|
+
|
|
17
|
+
<div class="flex flex-col items-center gap-2">
|
|
18
|
+
<h2 class="text-lg font-semibold">
|
|
19
|
+
Features
|
|
20
|
+
</h2>
|
|
21
|
+
|
|
22
|
+
<ul class="grid grid-cols-2 gap-4 md:grid-cols-6">
|
|
23
|
+
<li v-for="(feature, index) in features" :key="index" class="flex flex-row items-center gap-2 text-xs font-semibold md:text-sm">
|
|
24
|
+
<icon :name="feature.icon" :size="20" class="text-primary" />
|
|
25
|
+
<span>{{ feature.label }}</span>
|
|
26
|
+
</li>
|
|
27
|
+
</ul>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
const features = [
|
|
34
|
+
{ icon: "simple-icons:nuxtdotjs", label: "Nuxt 4" },
|
|
35
|
+
{ icon: "simple-icons:typescript", label: "TypeScript" },
|
|
36
|
+
{ icon: "simple-icons:tailwindcss", label: "Tailwind CSS 4" },
|
|
37
|
+
{ icon: "simple-icons:iconify", label: "Nuxt Icons" },
|
|
38
|
+
{ icon: "ph:text-aa", label: "Google Fonts" },
|
|
39
|
+
{ icon: "icon-park-outline:pineapple", label: "Pinia" },
|
|
40
|
+
{ icon: "ph:lock-key-open", label: "OAuth" },
|
|
41
|
+
{ icon: "simple-icons:prisma", label: "Prisma" },
|
|
42
|
+
{ icon: "simple-icons:playwright", label: "Playwright" },
|
|
43
|
+
{ icon: "simple-icons:vitest", label: "Vitest" },
|
|
44
|
+
{ icon: "simple-icons:eslint", label: "ESLint" },
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
useHead({
|
|
48
|
+
title: "Home",
|
|
49
|
+
meta: [{ name: "description", content: "Home page." }],
|
|
50
|
+
})
|
|
51
|
+
</script>
|