@cogito.ai/cli 0.3.1 → 0.3.3

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 (50) hide show
  1. package/README.md +181 -0
  2. package/dist/templates/web-nextjs/.env.example +4 -0
  3. package/dist/templates/web-nextjs/.vscode/settings.json +3 -0
  4. package/dist/templates/web-nextjs/README.md +25 -1
  5. package/dist/templates/web-nextjs/apps/docs/.source/browser.ts +1 -1
  6. package/dist/templates/web-nextjs/apps/docs/.source/server.ts +4 -3
  7. package/dist/templates/web-nextjs/apps/docs/content/docs/features/auth.mdx +139 -0
  8. package/dist/templates/web-nextjs/apps/web/components.json +25 -0
  9. package/dist/templates/web-nextjs/apps/web/messages/en.json +27 -3
  10. package/dist/templates/web-nextjs/apps/web/messages/zh.json +29 -5
  11. package/dist/templates/web-nextjs/apps/web/middleware.ts +54 -9
  12. package/dist/templates/web-nextjs/apps/web/package.json +13 -1
  13. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(auth)/login/page.tsx +142 -0
  14. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(auth)/signup/page.tsx +151 -0
  15. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/dashboard/page.tsx +42 -0
  16. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/layout.tsx +22 -0
  17. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/globals.css +129 -3
  18. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/layout.tsx +12 -7
  19. package/dist/templates/web-nextjs/apps/web/src/app/auth/callback/route.ts +21 -0
  20. package/dist/templates/web-nextjs/apps/web/src/components/ui/alert.tsx +76 -0
  21. package/dist/templates/web-nextjs/apps/web/src/components/ui/button.tsx +58 -0
  22. package/dist/templates/web-nextjs/apps/web/src/components/ui/form.tsx +154 -0
  23. package/dist/templates/web-nextjs/apps/web/src/components/ui/input.tsx +20 -0
  24. package/dist/templates/web-nextjs/apps/web/src/components/ui/label.tsx +20 -0
  25. package/dist/templates/web-nextjs/apps/web/src/components/ui/sonner.tsx +50 -0
  26. package/dist/templates/web-nextjs/apps/web/src/core/repositories/IAuthRepository.ts +9 -0
  27. package/dist/templates/web-nextjs/apps/web/src/core/types/auth.ts +14 -0
  28. package/dist/templates/web-nextjs/apps/web/src/features/auth/__contract__.ts +14 -0
  29. package/dist/templates/web-nextjs/apps/web/src/features/auth/actions.ts +86 -0
  30. package/dist/templates/web-nextjs/apps/web/src/features/auth/index.ts +1 -0
  31. package/dist/templates/web-nextjs/apps/web/src/i18n/config.ts +12 -0
  32. package/dist/templates/web-nextjs/apps/web/src/i18n/request.ts +3 -1
  33. package/dist/templates/web-nextjs/apps/web/src/infra/db/SupabaseAuthRepository.ts +63 -0
  34. package/dist/templates/web-nextjs/apps/web/src/infra/db/client.ts +39 -1
  35. package/dist/templates/web-nextjs/apps/web/src/infra/db/schema.ts +5 -5
  36. package/dist/templates/web-nextjs/apps/web/src/infra/providers.ts +6 -0
  37. package/dist/templates/web-nextjs/apps/web/src/lib/utils.ts +6 -0
  38. package/dist/templates/web-nextjs/apps/web/src/lib/validations/auth.ts +20 -0
  39. package/dist/templates/web-nextjs/apps/web/src/styles/shadcn-tailwind.css +95 -0
  40. package/dist/templates/web-nextjs/apps/web/src/styles/tw-animate.css +1 -0
  41. package/dist/templates/web-nextjs/pnpm-lock.yaml +2327 -17
  42. package/package.json +1 -1
  43. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/hello/page.tsx +0 -45
  44. package/dist/templates/web-nextjs/apps/web/src/core/repositories/IGreetingRepository.ts +0 -14
  45. package/dist/templates/web-nextjs/apps/web/src/core/types/greeting.ts +0 -9
  46. package/dist/templates/web-nextjs/apps/web/src/features/hello/__contract__.ts +0 -16
  47. package/dist/templates/web-nextjs/apps/web/src/features/hello/hello.test.ts +0 -16
  48. package/dist/templates/web-nextjs/apps/web/src/features/hello/index.ts +0 -5
  49. package/dist/templates/web-nextjs/apps/web/src/features/hello/service.ts +0 -12
  50. package/dist/templates/web-nextjs/apps/web/src/infra/db/SupabaseGreetingRepository.ts +0 -58
@@ -0,0 +1,142 @@
1
+ 'use client'
2
+
3
+ import { useActionState, useEffect } from 'react'
4
+ import { useParams, useRouter } from 'next/navigation'
5
+ import Link from 'next/link'
6
+ import { useTranslations } from 'next-intl'
7
+ import { useForm } from 'react-hook-form'
8
+ import { zodResolver } from '@hookform/resolvers/zod'
9
+ import { GitBranch } from 'lucide-react'
10
+ import { toast } from 'sonner'
11
+ import { Button } from '@/components/ui/button'
12
+ import { Input } from '@/components/ui/input'
13
+ import {
14
+ Form,
15
+ FormControl,
16
+ FormField,
17
+ FormItem,
18
+ FormLabel,
19
+ FormMessage,
20
+ } from '@/components/ui/form'
21
+ import { signIn, signInWithGithubForLocale } from '@/features/auth'
22
+ import { signInSchema, type SignInInput } from '@/lib/validations/auth'
23
+ import type { ActionResult } from '@/core/types/auth'
24
+
25
+ export default function LoginPage() {
26
+ const t = useTranslations('auth')
27
+ const router = useRouter()
28
+ const routeParams = useParams<{ locale: string }>()
29
+ const locale = routeParams.locale ?? 'en'
30
+
31
+ const form = useForm<SignInInput>({
32
+ resolver: zodResolver(signInSchema),
33
+ defaultValues: { email: '', password: '' },
34
+ })
35
+
36
+ const [state, formAction, isPending] = useActionState<ActionResult | null, FormData>(
37
+ signIn,
38
+ null,
39
+ )
40
+
41
+ useEffect(() => {
42
+ if (state?.error) {
43
+ toast.error(state.error)
44
+ }
45
+ }, [state])
46
+
47
+ async function handleGithub() {
48
+ const result = await signInWithGithubForLocale(locale)
49
+ if (result.error) {
50
+ toast.error(result.error)
51
+ return
52
+ }
53
+ if (result.data?.url) {
54
+ router.push(result.data.url)
55
+ }
56
+ }
57
+
58
+ return (
59
+ <div className="flex min-h-screen items-center justify-center px-4 py-12">
60
+ <div className="w-full max-w-sm space-y-6">
61
+ <div className="space-y-2 text-center">
62
+ <h1 className="text-3xl font-bold">{t('loginTitle')}</h1>
63
+ <p className="text-muted-foreground">{t('loginSubtitle')}</p>
64
+ </div>
65
+
66
+ <Button
67
+ type="button"
68
+ variant="outline"
69
+ className="w-full"
70
+ onClick={handleGithub}
71
+ >
72
+ <GitBranch className="mr-2 h-4 w-4" />
73
+ {t('githubButton')}
74
+ </Button>
75
+
76
+ <div className="relative">
77
+ <div className="absolute inset-0 flex items-center">
78
+ <span className="w-full border-t" />
79
+ </div>
80
+ <div className="relative flex justify-center text-xs uppercase">
81
+ <span className="bg-background px-2 text-muted-foreground">{t('orContinueWith')}</span>
82
+ </div>
83
+ </div>
84
+
85
+ <Form {...form}>
86
+ <form action={formAction} className="space-y-4">
87
+ <input type="hidden" name="locale" value={locale} />
88
+ <FormField
89
+ control={form.control}
90
+ name="email"
91
+ render={({ field }) => (
92
+ <FormItem>
93
+ <FormLabel>{t('emailLabel')}</FormLabel>
94
+ <FormControl>
95
+ <Input
96
+ type="email"
97
+ placeholder={t('emailPlaceholder')}
98
+ {...field}
99
+ />
100
+ </FormControl>
101
+ <FormMessage />
102
+ </FormItem>
103
+ )}
104
+ />
105
+
106
+ <FormField
107
+ control={form.control}
108
+ name="password"
109
+ render={({ field }) => (
110
+ <FormItem>
111
+ <FormLabel>{t('passwordLabel')}</FormLabel>
112
+ <FormControl>
113
+ <Input
114
+ type="password"
115
+ placeholder={t('passwordPlaceholder')}
116
+ {...field}
117
+ />
118
+ </FormControl>
119
+ <FormMessage />
120
+ </FormItem>
121
+ )}
122
+ />
123
+
124
+ <Button type="submit" className="w-full" disabled={isPending}>
125
+ {isPending ? '...' : t('signInButton')}
126
+ </Button>
127
+ </form>
128
+ </Form>
129
+
130
+ <p className="text-center text-sm text-muted-foreground">
131
+ {t('noAccountText')}{' '}
132
+ <Link
133
+ href={`/${locale}/signup`}
134
+ className="underline underline-offset-4 hover:text-primary"
135
+ >
136
+ {t('signUpLink')}
137
+ </Link>
138
+ </p>
139
+ </div>
140
+ </div>
141
+ )
142
+ }
@@ -0,0 +1,151 @@
1
+ 'use client'
2
+
3
+ import { useActionState, useEffect, useState } from 'react'
4
+ import Link from 'next/link'
5
+ import { useParams } from 'next/navigation'
6
+ import { useTranslations } from 'next-intl'
7
+ import { useForm } from 'react-hook-form'
8
+ import { zodResolver } from '@hookform/resolvers/zod'
9
+ import { toast } from 'sonner'
10
+ import { Button } from '@/components/ui/button'
11
+ import { Input } from '@/components/ui/input'
12
+ import {
13
+ Form,
14
+ FormControl,
15
+ FormDescription,
16
+ FormField,
17
+ FormItem,
18
+ FormLabel,
19
+ FormMessage,
20
+ } from '@/components/ui/form'
21
+ import { signUp } from '@/features/auth'
22
+ import { signUpSchema, type SignUpInput } from '@/lib/validations/auth'
23
+ import type { ActionResult } from '@/core/types/auth'
24
+ import type { SignUpSuccessData } from '@/features/auth/__contract__'
25
+
26
+ export default function SignupPage() {
27
+ const t = useTranslations('auth')
28
+ const routeParams = useParams<{ locale: string }>()
29
+ const locale = routeParams.locale ?? 'en'
30
+ const [verifyEmail, setVerifyEmail] = useState<string | null>(null)
31
+
32
+ const form = useForm<SignUpInput>({
33
+ resolver: zodResolver(signUpSchema),
34
+ defaultValues: { email: '', password: '', confirmPassword: '' },
35
+ })
36
+
37
+ const [state, formAction, isPending] = useActionState<ActionResult<SignUpSuccessData> | null, FormData>(
38
+ signUp,
39
+ null,
40
+ )
41
+
42
+ useEffect(() => {
43
+ if (state?.error) {
44
+ toast.error(state.error)
45
+ }
46
+ if (state?.data?.success) {
47
+ setVerifyEmail(state.data.email)
48
+ }
49
+ }, [state])
50
+
51
+ if (verifyEmail) {
52
+ return (
53
+ <div className="flex min-h-screen items-center justify-center px-4 py-12">
54
+ <div className="w-full max-w-sm space-y-4 text-center">
55
+ <h1 className="text-2xl font-bold">{t('verifyEmailTitle')}</h1>
56
+ <p className="text-muted-foreground">
57
+ {t('verifyEmailMessage', { email: verifyEmail })}
58
+ </p>
59
+ <Link href={`/${locale}/login`} className="text-sm underline underline-offset-4">
60
+ {t('signInLink')}
61
+ </Link>
62
+ </div>
63
+ </div>
64
+ )
65
+ }
66
+
67
+ return (
68
+ <div className="flex min-h-screen items-center justify-center px-4 py-12">
69
+ <div className="w-full max-w-sm space-y-6">
70
+ <div className="space-y-2 text-center">
71
+ <h1 className="text-3xl font-bold">{t('signupTitle')}</h1>
72
+ <p className="text-muted-foreground">{t('signupSubtitle')}</p>
73
+ </div>
74
+
75
+ <Form {...form}>
76
+ <form action={formAction} className="space-y-4">
77
+ <input type="hidden" name="locale" value={locale} />
78
+ <FormField
79
+ control={form.control}
80
+ name="email"
81
+ render={({ field }) => (
82
+ <FormItem>
83
+ <FormLabel>{t('emailLabel')}</FormLabel>
84
+ <FormControl>
85
+ <Input
86
+ type="email"
87
+ placeholder={t('emailPlaceholder')}
88
+ {...field}
89
+ />
90
+ </FormControl>
91
+ <FormDescription>{t('emailHelperText')}</FormDescription>
92
+ <FormMessage />
93
+ </FormItem>
94
+ )}
95
+ />
96
+
97
+ <FormField
98
+ control={form.control}
99
+ name="password"
100
+ render={({ field }) => (
101
+ <FormItem>
102
+ <FormLabel>{t('passwordLabel')}</FormLabel>
103
+ <FormControl>
104
+ <Input
105
+ type="password"
106
+ placeholder={t('passwordPlaceholder')}
107
+ {...field}
108
+ />
109
+ </FormControl>
110
+ <FormMessage />
111
+ </FormItem>
112
+ )}
113
+ />
114
+
115
+ <FormField
116
+ control={form.control}
117
+ name="confirmPassword"
118
+ render={({ field }) => (
119
+ <FormItem>
120
+ <FormLabel>{t('confirmPasswordLabel')}</FormLabel>
121
+ <FormControl>
122
+ <Input
123
+ type="password"
124
+ placeholder={t('confirmPasswordPlaceholder')}
125
+ {...field}
126
+ />
127
+ </FormControl>
128
+ <FormMessage />
129
+ </FormItem>
130
+ )}
131
+ />
132
+
133
+ <Button type="submit" className="w-full" disabled={isPending}>
134
+ {isPending ? '...' : t('signUpButton')}
135
+ </Button>
136
+ </form>
137
+ </Form>
138
+
139
+ <p className="text-center text-sm text-muted-foreground">
140
+ {t('hasAccountText')}{' '}
141
+ <Link
142
+ href={`/${locale}/login`}
143
+ className="underline underline-offset-4 hover:text-primary"
144
+ >
145
+ {t('signInLink')}
146
+ </Link>
147
+ </p>
148
+ </div>
149
+ </div>
150
+ )
151
+ }
@@ -0,0 +1,42 @@
1
+ import { getTranslations } from 'next-intl/server'
2
+ import { getServerClient } from '@/infra/db/client'
3
+ import { signOut } from '@/features/auth'
4
+ import { Button } from '@/components/ui/button'
5
+
6
+ export default async function DashboardPage({
7
+ params,
8
+ }: {
9
+ params: Promise<{ locale: string }>
10
+ }) {
11
+ const { locale } = await params
12
+ const t = await getTranslations({ locale, namespace: 'auth' })
13
+ const supabase = await getServerClient()
14
+ const {
15
+ data: { user },
16
+ } = await supabase.auth.getUser()
17
+
18
+ return (
19
+ <div className="min-h-screen bg-background">
20
+ <header className="border-b">
21
+ <div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-4">
22
+ <h1 className="text-xl font-semibold">{t('dashboardTitle')}</h1>
23
+ <div className="flex items-center gap-4">
24
+ <span className="text-sm text-muted-foreground">{user?.email}</span>
25
+ <form action={signOut}>
26
+ <input type="hidden" name="locale" value={locale} />
27
+ <Button type="submit" variant="outline" size="sm">
28
+ {t('signOutButton')}
29
+ </Button>
30
+ </form>
31
+ </div>
32
+ </div>
33
+ </header>
34
+
35
+ <main className="mx-auto max-w-7xl px-4 py-8">
36
+ <p className="text-muted-foreground">
37
+ {t('dashboardWelcome', { email: user?.email ?? '' })}
38
+ </p>
39
+ </main>
40
+ </div>
41
+ )
42
+ }
@@ -0,0 +1,22 @@
1
+ import { redirect } from 'next/navigation'
2
+ import { getServerClient } from '@/infra/db/client'
3
+
4
+ export default async function ProtectedLayout({
5
+ children,
6
+ params,
7
+ }: {
8
+ children: React.ReactNode
9
+ params: Promise<{ locale: string }>
10
+ }) {
11
+ const { locale } = await params
12
+ const supabase = await getServerClient()
13
+ const {
14
+ data: { user },
15
+ } = await supabase.auth.getUser()
16
+
17
+ if (!user) {
18
+ redirect(`/${locale}/login`)
19
+ }
20
+
21
+ return <>{children}</>
22
+ }
@@ -1,3 +1,129 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
1
+ @import "../../styles/tw-animate.css";
2
+ @import "../../styles/shadcn-tailwind.css";
3
+ @import "tailwindcss";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ @theme inline {
8
+ --font-heading: var(--font-sans);
9
+ --font-sans: var(--font-sans);
10
+ --color-sidebar-ring: var(--sidebar-ring);
11
+ --color-sidebar-border: var(--sidebar-border);
12
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
13
+ --color-sidebar-accent: var(--sidebar-accent);
14
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
15
+ --color-sidebar-primary: var(--sidebar-primary);
16
+ --color-sidebar-foreground: var(--sidebar-foreground);
17
+ --color-sidebar: var(--sidebar);
18
+ --color-chart-5: var(--chart-5);
19
+ --color-chart-4: var(--chart-4);
20
+ --color-chart-3: var(--chart-3);
21
+ --color-chart-2: var(--chart-2);
22
+ --color-chart-1: var(--chart-1);
23
+ --color-ring: var(--ring);
24
+ --color-input: var(--input);
25
+ --color-border: var(--border);
26
+ --color-destructive: var(--destructive);
27
+ --color-accent-foreground: var(--accent-foreground);
28
+ --color-accent: var(--accent);
29
+ --color-muted-foreground: var(--muted-foreground);
30
+ --color-muted: var(--muted);
31
+ --color-secondary-foreground: var(--secondary-foreground);
32
+ --color-secondary: var(--secondary);
33
+ --color-primary-foreground: var(--primary-foreground);
34
+ --color-primary: var(--primary);
35
+ --color-popover-foreground: var(--popover-foreground);
36
+ --color-popover: var(--popover);
37
+ --color-card-foreground: var(--card-foreground);
38
+ --color-card: var(--card);
39
+ --color-foreground: var(--foreground);
40
+ --color-background: var(--background);
41
+ --radius-sm: calc(var(--radius) * 0.6);
42
+ --radius-md: calc(var(--radius) * 0.8);
43
+ --radius-lg: var(--radius);
44
+ --radius-xl: calc(var(--radius) * 1.4);
45
+ --radius-2xl: calc(var(--radius) * 1.8);
46
+ --radius-3xl: calc(var(--radius) * 2.2);
47
+ --radius-4xl: calc(var(--radius) * 2.6);
48
+ }
49
+
50
+ :root {
51
+ --background: oklch(1 0 0);
52
+ --foreground: oklch(0.145 0 0);
53
+ --card: oklch(1 0 0);
54
+ --card-foreground: oklch(0.145 0 0);
55
+ --popover: oklch(1 0 0);
56
+ --popover-foreground: oklch(0.145 0 0);
57
+ --primary: oklch(0.205 0 0);
58
+ --primary-foreground: oklch(0.985 0 0);
59
+ --secondary: oklch(0.97 0 0);
60
+ --secondary-foreground: oklch(0.205 0 0);
61
+ --muted: oklch(0.97 0 0);
62
+ --muted-foreground: oklch(0.556 0 0);
63
+ --accent: oklch(0.97 0 0);
64
+ --accent-foreground: oklch(0.205 0 0);
65
+ --destructive: oklch(0.577 0.245 27.325);
66
+ --border: oklch(0.922 0 0);
67
+ --input: oklch(0.922 0 0);
68
+ --ring: oklch(0.708 0 0);
69
+ --chart-1: oklch(0.87 0 0);
70
+ --chart-2: oklch(0.556 0 0);
71
+ --chart-3: oklch(0.439 0 0);
72
+ --chart-4: oklch(0.371 0 0);
73
+ --chart-5: oklch(0.269 0 0);
74
+ --radius: 0.625rem;
75
+ --sidebar: oklch(0.985 0 0);
76
+ --sidebar-foreground: oklch(0.145 0 0);
77
+ --sidebar-primary: oklch(0.205 0 0);
78
+ --sidebar-primary-foreground: oklch(0.985 0 0);
79
+ --sidebar-accent: oklch(0.97 0 0);
80
+ --sidebar-accent-foreground: oklch(0.205 0 0);
81
+ --sidebar-border: oklch(0.922 0 0);
82
+ --sidebar-ring: oklch(0.708 0 0);
83
+ }
84
+
85
+ .dark {
86
+ --background: oklch(0.145 0 0);
87
+ --foreground: oklch(0.985 0 0);
88
+ --card: oklch(0.205 0 0);
89
+ --card-foreground: oklch(0.985 0 0);
90
+ --popover: oklch(0.205 0 0);
91
+ --popover-foreground: oklch(0.985 0 0);
92
+ --primary: oklch(0.922 0 0);
93
+ --primary-foreground: oklch(0.205 0 0);
94
+ --secondary: oklch(0.269 0 0);
95
+ --secondary-foreground: oklch(0.985 0 0);
96
+ --muted: oklch(0.269 0 0);
97
+ --muted-foreground: oklch(0.708 0 0);
98
+ --accent: oklch(0.269 0 0);
99
+ --accent-foreground: oklch(0.985 0 0);
100
+ --destructive: oklch(0.704 0.191 22.216);
101
+ --border: oklch(1 0 0 / 10%);
102
+ --input: oklch(1 0 0 / 15%);
103
+ --ring: oklch(0.556 0 0);
104
+ --chart-1: oklch(0.87 0 0);
105
+ --chart-2: oklch(0.556 0 0);
106
+ --chart-3: oklch(0.439 0 0);
107
+ --chart-4: oklch(0.371 0 0);
108
+ --chart-5: oklch(0.269 0 0);
109
+ --sidebar: oklch(0.205 0 0);
110
+ --sidebar-foreground: oklch(0.985 0 0);
111
+ --sidebar-primary: oklch(0.488 0.243 264.376);
112
+ --sidebar-primary-foreground: oklch(0.985 0 0);
113
+ --sidebar-accent: oklch(0.269 0 0);
114
+ --sidebar-accent-foreground: oklch(0.985 0 0);
115
+ --sidebar-border: oklch(1 0 0 / 10%);
116
+ --sidebar-ring: oklch(0.556 0 0);
117
+ }
118
+
119
+ @layer base {
120
+ * {
121
+ @apply border-border outline-ring/50;
122
+ }
123
+ body {
124
+ @apply bg-background text-foreground;
125
+ }
126
+ html {
127
+ @apply font-sans;
128
+ }
129
+ }
@@ -1,12 +1,12 @@
1
1
  import type { Metadata } from 'next'
2
2
  import { NextIntlClientProvider } from 'next-intl'
3
3
  import { getMessages } from 'next-intl/server'
4
+ import { ThemeProvider } from 'next-themes'
4
5
  import { notFound } from 'next/navigation'
6
+ import { Toaster } from '@/components/ui/sonner'
7
+ import { isLocale } from '@/i18n/config'
5
8
  import './globals.css'
6
9
 
7
- const locales = ['en', 'zh'] as const
8
- type Locale = (typeof locales)[number]
9
-
10
10
  export const metadata: Metadata = {
11
11
  title: 'AgentDock Web Template',
12
12
  description: 'Generated by AgentDock scaffold platform',
@@ -21,16 +21,21 @@ export default async function LocaleLayout({
21
21
  }) {
22
22
  const { locale } = await params
23
23
 
24
- if (!locales.includes(locale as Locale)) {
24
+ if (!isLocale(locale)) {
25
25
  notFound()
26
26
  }
27
27
 
28
28
  const messages = await getMessages()
29
29
 
30
30
  return (
31
- <html lang={locale}>
32
- <body>
33
- <NextIntlClientProvider messages={messages}>{children}</NextIntlClientProvider>
31
+ <html lang={locale} suppressHydrationWarning>
32
+ <body className="min-h-screen bg-background font-sans antialiased">
33
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
34
+ <NextIntlClientProvider messages={messages}>
35
+ {children}
36
+ </NextIntlClientProvider>
37
+ <Toaster />
38
+ </ThemeProvider>
34
39
  </body>
35
40
  </html>
36
41
  )
@@ -0,0 +1,21 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { getServerClient } from '@/infra/db/client'
3
+ import { defaultLocale } from '@/i18n/config'
4
+
5
+ export async function GET(request: NextRequest) {
6
+ const { searchParams, origin } = new URL(request.url)
7
+ const code = searchParams.get('code')
8
+ const nextParam = searchParams.get('next')
9
+ const next = nextParam && nextParam.startsWith('/') ? nextParam : `/${defaultLocale}/dashboard`
10
+
11
+ if (code) {
12
+ const supabase = await getServerClient()
13
+ const { error } = await supabase.auth.exchangeCodeForSession(code)
14
+
15
+ if (!error) {
16
+ return NextResponse.redirect(`${origin}${next}`)
17
+ }
18
+ }
19
+
20
+ return NextResponse.redirect(`${origin}/${defaultLocale}/login?error=auth_callback_error`)
21
+ }
@@ -0,0 +1,76 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-card text-card-foreground",
12
+ destructive:
13
+ "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ function Alert({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
27
+ return (
28
+ <div
29
+ data-slot="alert"
30
+ role="alert"
31
+ className={cn(alertVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ )
35
+ }
36
+
37
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
+ return (
39
+ <div
40
+ data-slot="alert-title"
41
+ className={cn(
42
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ )
48
+ }
49
+
50
+ function AlertDescription({
51
+ className,
52
+ ...props
53
+ }: React.ComponentProps<"div">) {
54
+ return (
55
+ <div
56
+ data-slot="alert-description"
57
+ className={cn(
58
+ "text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
67
+ return (
68
+ <div
69
+ data-slot="alert-action"
70
+ className={cn("absolute top-2 right-2", className)}
71
+ {...props}
72
+ />
73
+ )
74
+ }
75
+
76
+ export { Alert, AlertTitle, AlertDescription, AlertAction }
@@ -0,0 +1,58 @@
1
+ import { Button as ButtonPrimitive } from "@base-ui/react/button"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const buttonVariants = cva(
7
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
12
+ outline:
13
+ "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
14
+ secondary:
15
+ "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
16
+ ghost:
17
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
18
+ destructive:
19
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default:
24
+ "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
25
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
26
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
27
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
28
+ icon: "size-8",
29
+ "icon-xs":
30
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
31
+ "icon-sm":
32
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
33
+ "icon-lg": "size-9",
34
+ },
35
+ },
36
+ defaultVariants: {
37
+ variant: "default",
38
+ size: "default",
39
+ },
40
+ }
41
+ )
42
+
43
+ function Button({
44
+ className,
45
+ variant = "default",
46
+ size = "default",
47
+ ...props
48
+ }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
49
+ return (
50
+ <ButtonPrimitive
51
+ data-slot="button"
52
+ className={cn(buttonVariants({ variant, size, className }))}
53
+ {...props}
54
+ />
55
+ )
56
+ }
57
+
58
+ export { Button, buttonVariants }