create-next-pro-cli 0.1.4 → 0.1.6
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/package.json +5 -1
- package/src/index.ts +10 -454
- package/src/lib/addComponent.ts +113 -0
- package/src/lib/addPage.ts +170 -0
- package/src/lib/createProject.ts +18 -0
- package/src/lib/createProjectWithPrompt.ts +79 -0
- package/src/lib/rmPage.ts +52 -0
- package/src/lib/utils.ts +36 -0
- package/src/save-index.ts +520 -0
- package/templates/Page/loading.tsx +2 -7
- package/templates/Page/not-found.tsx +11 -0
- package/templates/Page/page-ui.tsx +1 -1
- package/templates/Projects/default/messages/en/{dashboard.json → Dashboard.json} +2 -1
- package/templates/Projects/default/messages/en/_global_ui.json +4 -0
- package/templates/Projects/default/messages/fr/{dashboard.json → Dashboard.json} +2 -1
- package/templates/Projects/default/messages/fr/_global_ui.json +4 -0
- package/templates/Projects/default/package.json +8 -2
- package/templates/Projects/default/src/app/[locale]/(public)/Login/page.tsx +6 -0
- package/templates/Projects/default/src/app/[locale]/(public)/Register/page.tsx +6 -0
- package/templates/Projects/default/src/app/[locale]/(public)/_home/loading.tsx +5 -1
- package/templates/Projects/default/src/app/[locale]/(public)/_home/page.tsx +3 -13
- package/templates/Projects/default/src/app/[locale]/(public)/layout.tsx +0 -2
- package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/loading.tsx +10 -0
- package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/page.tsx +5 -0
- package/templates/Projects/default/src/app/[locale]/(user)/Settings/page.tsx +6 -0
- package/templates/Projects/default/src/app/[locale]/(user)/UserInfo/page.tsx +6 -0
- package/templates/Projects/default/src/app/[locale]/(user)/layout.tsx +0 -3
- package/templates/Projects/default/src/app/[locale]/layout.tsx +3 -4
- package/templates/Projects/default/src/app/[locale]/loading.tsx +3 -6
- package/templates/Projects/default/src/app/[locale]/page.tsx +1 -111
- package/templates/Projects/default/src/app/api/auth/[...nextauth]/route.ts +6 -25
- package/templates/Projects/default/src/app/api/auth/post-login/route.ts +1 -2
- package/templates/Projects/default/src/app/styles/globals.css +295 -16
- package/templates/Projects/default/src/lib/auth/disconnect.ts +11 -0
- package/templates/Projects/default/src/lib/auth/isConnected.ts +5 -3
- package/templates/Projects/default/src/lib/utils.ts +6 -0
- package/templates/Projects/default/src/ui/Dashboard/LogoutButton.tsx +27 -0
- package/templates/Projects/default/src/ui/{dashboard/page.tsx → Dashboard/page-ui.tsx} +5 -4
- package/templates/Projects/default/src/{app/[locale]/(public)/login/page.tsx → ui/Login/page-ui.tsx} +7 -4
- package/templates/Projects/default/src/{app/[locale]/(public)/register/page.tsx → ui/Register/page-ui.tsx} +7 -4
- package/templates/Projects/default/src/ui/Settings/page-ui.tsx +17 -0
- package/templates/Projects/default/src/ui/UserInfo/page-ui.tsx +17 -0
- package/templates/Projects/default/src/ui/_global/BackButton.tsx +5 -3
- package/templates/Projects/default/src/ui/_global/Button.tsx +75 -0
- package/templates/Projects/default/src/ui/_global/GlobalHeader.tsx +42 -21
- package/templates/Projects/default/src/ui/_global/GlobalMain.tsx +3 -3
- package/templates/Projects/default/src/ui/_global/Loading.tsx +13 -0
- package/templates/Projects/default/src/ui/_global/LocaleSwitcher.tsx +1 -1
- package/templates/Projects/default/src/ui/_global/PublicNav.tsx +74 -13
- package/templates/Projects/default/src/ui/_global/ThemeToggle.tsx +53 -0
- package/templates/Projects/default/src/ui/_global/UserNav.tsx +8 -5
- package/templates/Projects/default/src/ui/_home/page-ui.tsx +27 -0
- package/templates/Projects/default/public/cnp-logo.ico +0 -0
- package/templates/Projects/default/public/file.svg +0 -1
- package/templates/Projects/default/public/globe.svg +0 -1
- package/templates/Projects/default/public/next.svg +0 -1
- package/templates/Projects/default/public/vercel.svg +0 -1
- package/templates/Projects/default/public/window.svg +0 -1
- package/templates/Projects/default/src/app/[locale]/(user)/dashboard/loading.tsx +0 -17
- package/templates/Projects/default/src/app/[locale]/(user)/dashboard/page.tsx +0 -12
- package/templates/Projects/default/src/app/[locale]/(user)/settings/page.tsx +0 -12
- package/templates/Projects/default/src/app/[locale]/(user)/user_info/page.tsx +0 -12
- package/templates/Projects/default/src/app/api/auth/[...nextauth].ts +0 -0
- package/templates/Projects/default/src/app/api/me/route.ts +0 -15
- /package/templates/Projects/default/messages/en/{login.json → Login.json} +0 -0
- /package/templates/Projects/default/messages/en/{register.json → Register.json} +0 -0
- /package/templates/Projects/default/messages/en/{settings.json → Settings.json} +0 -0
- /package/templates/Projects/default/messages/en/{user_info.json → UserInfo.json} +0 -0
- /package/templates/Projects/default/messages/fr/{login.json → Login.json} +0 -0
- /package/templates/Projects/default/messages/fr/{register.json → Register.json} +0 -0
- /package/templates/Projects/default/messages/fr/{settings.json → Settings.json} +0 -0
- /package/templates/Projects/default/messages/fr/{user_info.json → UserInfo.json} +0 -0
- /package/templates/Projects/default/src/app/[locale]/(user)/{dashboard → Dashboard}/error.tsx +0 -0
- /package/templates/Projects/default/src/app/[locale]/(user)/{settings → Settings}/loading.tsx +0 -0
- /package/templates/Projects/default/src/app/[locale]/(user)/{user_info → UserInfo}/loading.tsx +0 -0
- /package/templates/Projects/default/src/ui/{dashboard → Dashboard}/StatsCard.tsx +0 -0
- /package/templates/Projects/default/src/ui/{dashboard → Dashboard}/WelcomeCard.tsx +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// app/[locale]/layout.tsx
|
|
2
|
+
import { NextIntlClientProvider } from "next-intl";
|
|
2
3
|
import { setRequestLocale } from "next-intl/server";
|
|
3
|
-
import { NextIntlClientProvider, hasLocale, Locale } from "next-intl";
|
|
4
4
|
import React from "react";
|
|
5
5
|
import "@/app/styles/globals.css"; // Import global styles
|
|
6
6
|
import GlobalHeader from "@/ui/_global/GlobalHeader";
|
|
@@ -16,14 +16,13 @@ export default async function LocaleLayout({
|
|
|
16
16
|
}) {
|
|
17
17
|
const { locale } = await params;
|
|
18
18
|
setRequestLocale(locale);
|
|
19
|
-
|
|
20
|
-
const isConnectedValue = await isConnected();
|
|
19
|
+
const isConnectedInitial = await isConnected();
|
|
21
20
|
|
|
22
21
|
return (
|
|
23
22
|
<html lang={locale}>
|
|
24
23
|
<body>
|
|
25
24
|
<NextIntlClientProvider>
|
|
26
|
-
<GlobalHeader
|
|
25
|
+
<GlobalHeader isConnectedInitial={isConnectedInitial} />
|
|
27
26
|
<GlobalMain>{children}</GlobalMain>
|
|
28
27
|
</NextIntlClientProvider>
|
|
29
28
|
</body>
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
// app/[locale]/dashboard/loading.tsx
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import LoadingUI from "@/ui/_global/Loading";
|
|
3
4
|
|
|
4
5
|
export default function Loading() {
|
|
5
|
-
return
|
|
6
|
-
<div className="flex flex-col flex-1 items-center justify-center">
|
|
7
|
-
<p className="text-gray-500">Loading ROOT...</p>
|
|
8
|
-
</div>
|
|
9
|
-
);
|
|
6
|
+
return <LoadingUI />;
|
|
10
7
|
}
|
|
11
8
|
// This component is used to show a loading state while the dashboard is being prepared
|
|
12
9
|
// You can customize it with a spinner, skeletons, or any loading UI you prefer
|
|
@@ -1,116 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import Image from "next/image";
|
|
3
2
|
import HomePage from "@/app/[locale]/(public)/_home/page";
|
|
4
|
-
import DashboardPage from "@/app/[locale]/(user)/dashboard/page";
|
|
5
|
-
import PublicNav from "@/ui/_global/PublicNav";
|
|
6
|
-
import UserNav from "@/ui/_global/UserNav";
|
|
7
|
-
import LocaleSwitcher from "@/ui/_global/LocaleSwitcher";
|
|
8
|
-
import { cookies } from "next/headers";
|
|
9
|
-
import { verify } from "jsonwebtoken";
|
|
10
3
|
|
|
11
4
|
export default async function FallbackPage() {
|
|
12
|
-
|
|
13
|
-
const cookieStore = await cookies();
|
|
14
|
-
const appToken = cookieStore.get("app_token")?.value;
|
|
15
|
-
if (appToken) {
|
|
16
|
-
try {
|
|
17
|
-
verify(appToken, process.env.APP_JWT_SECRET!);
|
|
18
|
-
isConnected = true;
|
|
19
|
-
} catch {
|
|
20
|
-
isConnected = false;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<HomePage />
|
|
26
|
-
/* <main className="min-h-screen bg-neutral-50 font-sans text-gray-900">
|
|
27
|
-
<header className="sticky top-0 z-30 w-full border-b bg-white/80 backdrop-blur supports-[backdrop-filter]:bg-white/60 shadow-sm">
|
|
28
|
-
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
|
|
29
|
-
<div className="flex items-center gap-3">
|
|
30
|
-
<Image
|
|
31
|
-
src="/cnp-logo.svg"
|
|
32
|
-
alt="Logo"
|
|
33
|
-
width={63}
|
|
34
|
-
height={63}
|
|
35
|
-
className="mr-2"
|
|
36
|
-
/>
|
|
37
|
-
<span className="text-lg font-semibold tracking-tight select-none">
|
|
38
|
-
Create Next Pro
|
|
39
|
-
</span>
|
|
40
|
-
</div>
|
|
41
|
-
<nav className="flex items-center gap-4">
|
|
42
|
-
{isConnected ? <UserNav /> : <PublicNav />}
|
|
43
|
-
<LocaleSwitcher />
|
|
44
|
-
</nav>
|
|
45
|
-
</div>
|
|
46
|
-
</header>
|
|
47
|
-
|
|
48
|
-
<section className="mx-auto w-full max-w-3xl py-16 px-6">
|
|
49
|
-
{isConnected ? <DashboardPage /> : <HomePage />}
|
|
50
|
-
</section>
|
|
51
|
-
</main> */
|
|
52
|
-
);
|
|
5
|
+
return <HomePage />;
|
|
53
6
|
}
|
|
54
|
-
|
|
55
|
-
// Design inspiré de nextjs.org avec Tailwind CSS.
|
|
56
|
-
|
|
57
|
-
// import React from "react";
|
|
58
|
-
// import HomePage from "@/app/[locale]/(public)/_home/page";
|
|
59
|
-
// import DashboardPage from "@/app/[locale]/(user)/dashboard/page";
|
|
60
|
-
// import PublicNav from "@/ui/_global/PublicNav";
|
|
61
|
-
// import UserNav from "@/ui/_global/UserNav";
|
|
62
|
-
// import LocaleSwitcher from "@/ui/_global/LocaleSwitcher";
|
|
63
|
-
|
|
64
|
-
// export default async function FallbackPage({
|
|
65
|
-
// params,
|
|
66
|
-
// }: {
|
|
67
|
-
// params: Promise<{ locale: string }>;
|
|
68
|
-
// }) {
|
|
69
|
-
// // TODO: Remplacer par la logique réelle d'authentification (next-auth)
|
|
70
|
-
// const isConnected = false;
|
|
71
|
-
// const { locale } = await params;
|
|
72
|
-
|
|
73
|
-
// return (
|
|
74
|
-
// <main className="min-h-screen bg-neutral-50 font-sans text-gray-900">
|
|
75
|
-
// {/* Header sticky façon nextjs.org */}
|
|
76
|
-
// <header className="sticky top-0 z-30 w-full border-b bg-white/80 backdrop-blur supports-[backdrop-filter]:bg-white/60 shadow-sm">
|
|
77
|
-
// <div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
|
|
78
|
-
// <div className="flex items-center gap-3">
|
|
79
|
-
// {/* Logo Next.js SVG */}
|
|
80
|
-
// <svg
|
|
81
|
-
// width="32"
|
|
82
|
-
// height="32"
|
|
83
|
-
// viewBox="0 0 32 32"
|
|
84
|
-
// fill="none"
|
|
85
|
-
// xmlns="http://www.w3.org/2000/svg"
|
|
86
|
-
// className="mr-2"
|
|
87
|
-
// >
|
|
88
|
-
// <rect width="32" height="32" rx="16" fill="#000" />
|
|
89
|
-
// <path
|
|
90
|
-
// d="M16.018 29.917c7.66 0 13.899-6.238 13.899-13.899 0-7.66-6.239-13.899-13.899-13.899-7.66 0-13.899 6.239-13.899 13.899 0 7.661 6.239 13.899 13.899 13.899Z"
|
|
91
|
-
// fill="#fff"
|
|
92
|
-
// />
|
|
93
|
-
// <path
|
|
94
|
-
// d="M23.635 22.295 12.7 10.36h-1.68v11.28h1.44v-9.12l10.08 10.08 1.095-1.095ZM19.36 21.64h1.44V10.36h-1.44v11.28Z"
|
|
95
|
-
// fill="#000"
|
|
96
|
-
// />
|
|
97
|
-
// </svg>
|
|
98
|
-
// <span className="text-lg font-semibold tracking-tight select-none">
|
|
99
|
-
// Create Next Pro
|
|
100
|
-
// </span>
|
|
101
|
-
// </div>
|
|
102
|
-
// <nav className="flex items-center gap-4">
|
|
103
|
-
// {isConnected ? <UserNav /> : <PublicNav />}
|
|
104
|
-
// <LocaleSwitcher />
|
|
105
|
-
// </nav>
|
|
106
|
-
// </div>
|
|
107
|
-
// </header>
|
|
108
|
-
// {/* Main content */}
|
|
109
|
-
// <section className="mx-auto w-full max-w-3xl py-16 px-6">
|
|
110
|
-
// {isConnected ? <DashboardPage /> : <HomePage />}
|
|
111
|
-
// </section>
|
|
112
|
-
// </main>
|
|
113
|
-
// );
|
|
114
|
-
// }
|
|
115
|
-
|
|
116
|
-
// // Design inspiré de nextjs.org avec Tailwind CSS.
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { sign } from "jsonwebtoken";
|
|
2
1
|
import NextAuth from "next-auth";
|
|
3
|
-
//import { authOptions } from "@/auth.config";
|
|
4
2
|
import type {
|
|
5
3
|
NextAuthOptions,
|
|
6
4
|
Session,
|
|
@@ -10,8 +8,6 @@ import type {
|
|
|
10
8
|
} from "next-auth";
|
|
11
9
|
import GoogleProvider from "next-auth/providers/google";
|
|
12
10
|
import type { JWT } from "next-auth/jwt";
|
|
13
|
-
import { getToken } from "next-auth/jwt";
|
|
14
|
-
import { NextRequest, NextResponse } from "next/server";
|
|
15
11
|
|
|
16
12
|
const authOptions: NextAuthOptions = {
|
|
17
13
|
providers: [
|
|
@@ -28,20 +24,14 @@ const authOptions: NextAuthOptions = {
|
|
|
28
24
|
token.name = user.name;
|
|
29
25
|
}
|
|
30
26
|
console.log("JWT Callback:", token);
|
|
31
|
-
|
|
32
|
-
// Le token JWT applicatif est géré dans le middleware
|
|
33
|
-
// et dans le callback de la route d'authentification
|
|
34
|
-
// pour éviter de le stocker dans la session NextAuth
|
|
35
|
-
// et de le renvoyer au client.
|
|
36
|
-
// Il est uniquement utilisé côté serveur pour vérifier l'authentification.
|
|
37
|
-
// On peut ajouter des données supplémentaires si nécessaire
|
|
38
|
-
// mais pour l'instant, on garde le token minimaliste.
|
|
39
|
-
// Exemple d'ajout de données :
|
|
40
|
-
// token.customData = "some data";
|
|
27
|
+
|
|
41
28
|
return token;
|
|
42
29
|
},
|
|
43
30
|
async session({ session, token }: { session: Session; token: JWT }) {
|
|
44
|
-
|
|
31
|
+
if (session.user) {
|
|
32
|
+
session.user.email = token.email;
|
|
33
|
+
session.user.name = token.name;
|
|
34
|
+
}
|
|
45
35
|
return session;
|
|
46
36
|
},
|
|
47
37
|
async signIn({
|
|
@@ -60,22 +50,13 @@ const authOptions: NextAuthOptions = {
|
|
|
60
50
|
return true;
|
|
61
51
|
},
|
|
62
52
|
async redirect({ url, baseUrl }: { url: string; baseUrl: string }) {
|
|
63
|
-
// Après login, redirige vers l'endpoint qui pose le cookie JWT applicatif
|
|
64
|
-
// console.log("Before Redirect Callback :", { url: url, baseUrl: baseUrl });
|
|
65
53
|
const customCallbackRoute = "/api/auth/post-login";
|
|
66
|
-
|
|
67
|
-
console.log("Redirecting to post-login endpoint");
|
|
68
|
-
return "/api/auth/post-login";
|
|
69
|
-
} */
|
|
70
|
-
// console.log("Redirecting to base URL + customCallbackRoute");
|
|
54
|
+
|
|
71
55
|
return baseUrl + customCallbackRoute;
|
|
72
56
|
},
|
|
73
57
|
},
|
|
74
|
-
// Ajoute ici d'autres options/callbacks si besoin
|
|
75
58
|
};
|
|
76
59
|
|
|
77
|
-
// Suppression de customJwtCookie : la pose du cookie se fait dans /api/auth/post-login
|
|
78
|
-
|
|
79
60
|
const handler = NextAuth(authOptions);
|
|
80
61
|
|
|
81
62
|
export { handler as GET, handler as POST };
|
|
@@ -2,7 +2,6 @@ import { getToken } from "next-auth/jwt";
|
|
|
2
2
|
import { sign } from "jsonwebtoken";
|
|
3
3
|
import { NextResponse, NextRequest } from "next/server";
|
|
4
4
|
import { redirect } from "@/lib/i18n/navigation";
|
|
5
|
-
// import { NextURL } from "next/dist/server/web/next-url";
|
|
6
5
|
|
|
7
6
|
export async function GET(req: NextRequest) {
|
|
8
7
|
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
|
|
@@ -13,7 +12,7 @@ export async function GET(req: NextRequest) {
|
|
|
13
12
|
{ expiresIn: "7d" }
|
|
14
13
|
);
|
|
15
14
|
const res = NextResponse.redirect(req.nextUrl.origin + "/");
|
|
16
|
-
res.cookies.set("
|
|
15
|
+
res.cookies.set("access_token", appJwt, {
|
|
17
16
|
httpOnly: true,
|
|
18
17
|
secure: process.env.NODE_ENV === "production",
|
|
19
18
|
sameSite: "strict",
|
|
@@ -1,26 +1,305 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
@tailwind base;
|
|
4
|
+
@tailwind components;
|
|
5
|
+
@tailwind utilities;
|
|
6
|
+
|
|
7
|
+
/* @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"); */
|
|
8
|
+
|
|
9
|
+
/* Dark mode : fond noir, texte blanc */
|
|
10
|
+
.dark {
|
|
11
|
+
--background: 0 0% 0%; /* noir */
|
|
12
|
+
--foreground: 0 0% 100%; /* blanc */
|
|
13
|
+
--card: 0 0% 0%;
|
|
14
|
+
--card-foreground: 0 0% 100%;
|
|
15
|
+
--popover: 0 0% 0%;
|
|
16
|
+
--popover-foreground: 0 0% 100%;
|
|
17
|
+
--primary: 0 0% 100%; /* blanc */
|
|
18
|
+
--primary-foreground: 0 0% 0%; /* noir */
|
|
19
|
+
--secondary: 0 0% 20%; /* gris foncé */
|
|
20
|
+
--secondary-foreground: 0 0% 100%;
|
|
21
|
+
--muted: 0 0% 20%;
|
|
22
|
+
--muted-foreground: 0 0% 80%;
|
|
23
|
+
--accent: 0 0% 20%;
|
|
24
|
+
--accent-foreground: 0 0% 100%;
|
|
25
|
+
--destructive: 0 62.8% 30.6%;
|
|
26
|
+
--destructive-foreground: 0 0% 100%;
|
|
27
|
+
--border: 0 0% 20%;
|
|
28
|
+
--input: 0 0% 20%;
|
|
29
|
+
--ring: 0 0% 100%;
|
|
30
|
+
--radius: 0.75rem;
|
|
6
31
|
}
|
|
7
32
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
--
|
|
33
|
+
/* Light mode : fond blanc, texte noir */
|
|
34
|
+
.light {
|
|
35
|
+
--background: 0 0% 100%; /* blanc */
|
|
36
|
+
--foreground: 0 0% 0%; /* noir */
|
|
37
|
+
--card: 0 0% 100%;
|
|
38
|
+
--card-foreground: 0 0% 0%;
|
|
39
|
+
--popover: 0 0% 100%;
|
|
40
|
+
--popover-foreground: 0 0% 0%;
|
|
41
|
+
--primary: 0 0% 0%; /* noir */
|
|
42
|
+
--primary-foreground: 0 0% 100%; /* blanc */
|
|
43
|
+
--secondary: 0 0% 95%; /* gris très clair */
|
|
44
|
+
--secondary-foreground: 0 0% 0%;
|
|
45
|
+
--muted: 0 0% 95%;
|
|
46
|
+
--muted-foreground: 0 0% 20%;
|
|
47
|
+
--accent: 0 0% 95%;
|
|
48
|
+
--accent-foreground: 0 0% 0%;
|
|
49
|
+
--destructive: 0 84.2% 60.2%;
|
|
50
|
+
--destructive-foreground: 0 0% 100%;
|
|
51
|
+
--border: 0 0% 80%;
|
|
52
|
+
--input: 0 0% 80%;
|
|
53
|
+
--ring: 0 0% 0%;
|
|
13
54
|
}
|
|
14
55
|
|
|
15
|
-
|
|
16
|
-
:
|
|
17
|
-
--background: #0a0a0a;
|
|
18
|
-
--foreground: #ededed;
|
|
19
|
-
}
|
|
56
|
+
* {
|
|
57
|
+
border-color: hsl(var(--border));
|
|
20
58
|
}
|
|
21
59
|
|
|
22
60
|
body {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
61
|
+
font-family: "Inter", sans-serif;
|
|
62
|
+
background: hsl(var(--background));
|
|
63
|
+
color: hsl(var(--foreground));
|
|
64
|
+
line-height: 1.6;
|
|
65
|
+
transition: background 0.3s, color 0.3s;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.gradient-text {
|
|
69
|
+
background: linear-gradient(135deg, #fff 0%, #bbb 100%);
|
|
70
|
+
-webkit-background-clip: text;
|
|
71
|
+
-webkit-text-fill-color: transparent;
|
|
72
|
+
background-clip: text;
|
|
73
|
+
}
|
|
74
|
+
.light .gradient-text {
|
|
75
|
+
background: linear-gradient(135deg, #000 0%, #888 100%);
|
|
76
|
+
-webkit-background-clip: text;
|
|
77
|
+
-webkit-text-fill-color: transparent;
|
|
78
|
+
background-clip: text;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.dark .glass-effect {
|
|
82
|
+
background: rgba(0, 0, 0, 0.8);
|
|
83
|
+
backdrop-filter: blur(10px);
|
|
84
|
+
border: 1px solid #444;
|
|
85
|
+
}
|
|
86
|
+
.light .glass-effect {
|
|
87
|
+
background: rgba(0, 0, 0, 0.08);
|
|
88
|
+
border: 1px solid #ccc;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.chat-bubble-user {
|
|
92
|
+
background: linear-gradient(135deg, #fff 0%, #bbb 100%);
|
|
93
|
+
border-radius: 18px 18px 4px 18px;
|
|
94
|
+
color: #000;
|
|
95
|
+
}
|
|
96
|
+
.light .chat-bubble-user {
|
|
97
|
+
background: linear-gradient(135deg, #000 0%, #888 100%);
|
|
98
|
+
color: #fff;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.chat-bubble-ai {
|
|
102
|
+
background: rgba(255, 255, 255, 0.12);
|
|
103
|
+
border-radius: 18px 18px 18px 4px;
|
|
104
|
+
border: 1px solid #444;
|
|
105
|
+
color: #000;
|
|
106
|
+
}
|
|
107
|
+
.light .chat-bubble-ai {
|
|
108
|
+
background: rgba(0, 0, 0, 0.12);
|
|
109
|
+
border: 1px solid #ccc;
|
|
110
|
+
color: #fff;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.typing-indicator {
|
|
114
|
+
display: inline-block;
|
|
115
|
+
width: 8px;
|
|
116
|
+
height: 8px;
|
|
117
|
+
border-radius: 50%;
|
|
118
|
+
background-color: #fff;
|
|
119
|
+
animation: typing 1.4s infinite ease-in-out;
|
|
120
|
+
}
|
|
121
|
+
.light .typing-indicator {
|
|
122
|
+
background-color: #000;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.typing-indicator:nth-child(1) {
|
|
126
|
+
animation-delay: -0.32s;
|
|
127
|
+
}
|
|
128
|
+
.typing-indicator:nth-child(2) {
|
|
129
|
+
animation-delay: -0.16s;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@keyframes typing {
|
|
133
|
+
0%,
|
|
134
|
+
80%,
|
|
135
|
+
100% {
|
|
136
|
+
transform: scale(0);
|
|
137
|
+
opacity: 0.5;
|
|
138
|
+
}
|
|
139
|
+
40% {
|
|
140
|
+
transform: scale(1);
|
|
141
|
+
opacity: 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.hero-pattern {
|
|
146
|
+
background-image: radial-gradient(
|
|
147
|
+
circle at 25% 25%,
|
|
148
|
+
rgba(255, 255, 255, 0.18) 0%,
|
|
149
|
+
transparent 50%
|
|
150
|
+
),
|
|
151
|
+
radial-gradient(
|
|
152
|
+
circle at 75% 75%,
|
|
153
|
+
rgba(180, 180, 180, 0.18) 0%,
|
|
154
|
+
transparent 50%
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
.light .hero-pattern {
|
|
158
|
+
background-image: radial-gradient(
|
|
159
|
+
circle at 25% 25%,
|
|
160
|
+
rgba(0, 0, 0, 0.08) 0%,
|
|
161
|
+
transparent 50%
|
|
162
|
+
),
|
|
163
|
+
radial-gradient(
|
|
164
|
+
circle at 75% 75%,
|
|
165
|
+
rgba(120, 120, 120, 0.08) 0%,
|
|
166
|
+
transparent 50%
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.light .mobil-menu {
|
|
171
|
+
background: rgba(255, 255, 255, 0.9);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.dark .mobil-menu {
|
|
175
|
+
background: rgba(0, 0, 0, 0.9);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.light .text-white,
|
|
179
|
+
.light .text-gray-300,
|
|
180
|
+
.light .text-gray-400 {
|
|
181
|
+
color: black;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.light button.text-white:hover,
|
|
185
|
+
.light a:hover {
|
|
186
|
+
color: rgb(142, 197, 255);
|
|
187
|
+
}
|
|
188
|
+
.gsi-material-button {
|
|
189
|
+
user-select: none;
|
|
190
|
+
-webkit-user-select: none;
|
|
191
|
+
-ms-user-select: none;
|
|
192
|
+
appearance: none;
|
|
193
|
+
-webkit-appearance: none;
|
|
194
|
+
/* background-color: linear-gradient(135deg, #fff 0%, #a78bfa 100%);
|
|
195
|
+
backdrop-filter: blur(8px); */
|
|
196
|
+
/* background-color: #131314; */
|
|
197
|
+
/* background-image: none; */
|
|
198
|
+
/* background: linear-gradient(135deg, #ffffff 0%, #a78bfa 100%); */
|
|
199
|
+
backdrop-filter: blur(8px);
|
|
200
|
+
border: 1px solid #747775;
|
|
201
|
+
border-radius: 4px;
|
|
202
|
+
box-sizing: border-box;
|
|
203
|
+
color: #e3e3e3;
|
|
204
|
+
/* color: #000000; */
|
|
205
|
+
cursor: pointer;
|
|
206
|
+
font-family: "Roboto", arial, sans-serif;
|
|
207
|
+
font-size: 14px;
|
|
208
|
+
height: 40px;
|
|
209
|
+
letter-spacing: 0.25px;
|
|
210
|
+
outline: none;
|
|
211
|
+
overflow: hidden;
|
|
212
|
+
padding: 0 12px;
|
|
213
|
+
position: relative;
|
|
214
|
+
text-align: center;
|
|
215
|
+
transition: background-color 0.218s, border-color 0.218s, box-shadow 0.218s;
|
|
216
|
+
vertical-align: middle;
|
|
217
|
+
white-space: nowrap;
|
|
218
|
+
width: auto;
|
|
219
|
+
max-width: 400px;
|
|
220
|
+
min-width: min-content;
|
|
221
|
+
border-color: #8e918f;
|
|
222
|
+
}
|
|
223
|
+
.light .gsi-material-button {
|
|
224
|
+
color: #222;
|
|
225
|
+
background: #fff;
|
|
226
|
+
border-color: #d1d5db;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.gsi-material-button .gsi-material-button-icon {
|
|
230
|
+
height: 20px;
|
|
231
|
+
margin-right: 12px;
|
|
232
|
+
min-width: 20px;
|
|
233
|
+
width: 20px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.gsi-material-button .gsi-material-button-content-wrapper {
|
|
237
|
+
-webkit-align-items: center;
|
|
238
|
+
align-items: center;
|
|
239
|
+
display: flex;
|
|
240
|
+
-webkit-flex-direction: row;
|
|
241
|
+
flex-direction: row;
|
|
242
|
+
-webkit-flex-wrap: nowrap;
|
|
243
|
+
flex-wrap: nowrap;
|
|
244
|
+
height: 100%;
|
|
245
|
+
justify-content: space-between;
|
|
246
|
+
position: relative;
|
|
247
|
+
width: 100%;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.gsi-material-button .gsi-material-button-contents {
|
|
251
|
+
-webkit-flex-grow: 1;
|
|
252
|
+
flex-grow: 1;
|
|
253
|
+
font-family: "Roboto", arial, sans-serif;
|
|
254
|
+
font-weight: 500;
|
|
255
|
+
overflow: hidden;
|
|
256
|
+
text-overflow: ellipsis;
|
|
257
|
+
vertical-align: top;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.gsi-material-button .gsi-material-button-state {
|
|
261
|
+
-webkit-transition: opacity 0.218s;
|
|
262
|
+
transition: opacity 0.218s;
|
|
263
|
+
bottom: 0;
|
|
264
|
+
left: 0;
|
|
265
|
+
opacity: 0;
|
|
266
|
+
position: absolute;
|
|
267
|
+
right: 0;
|
|
268
|
+
top: 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.gsi-material-button:disabled {
|
|
272
|
+
cursor: default;
|
|
273
|
+
background-color: #13131461;
|
|
274
|
+
border-color: #8e918f1f;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.gsi-material-button:disabled .gsi-material-button-state {
|
|
278
|
+
background-color: #e3e3e31f;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.gsi-material-button:disabled .gsi-material-button-contents {
|
|
282
|
+
opacity: 38%;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.gsi-material-button:disabled .gsi-material-button-icon {
|
|
286
|
+
opacity: 38%;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.gsi-material-button:not(:disabled):active .gsi-material-button-state,
|
|
290
|
+
.gsi-material-button:not(:disabled):focus .gsi-material-button-state {
|
|
291
|
+
background-color: white;
|
|
292
|
+
opacity: 12%;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.gsi-material-button:not(:disabled):hover {
|
|
296
|
+
-webkit-box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3),
|
|
297
|
+
0 1px 3px 1px rgba(60, 64, 67, 0.15);
|
|
298
|
+
box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3),
|
|
299
|
+
0 1px 3px 1px rgba(60, 64, 67, 0.15);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.gsi-material-button:not(:disabled):hover .gsi-material-button-state {
|
|
303
|
+
background-color: white;
|
|
304
|
+
opacity: 8%;
|
|
26
305
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/lib/auth/disconnect.ts
|
|
2
|
+
"use server";
|
|
3
|
+
import { cookies } from "next/headers";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Supprime le cookie access_token pour déconnecter l'utilisateur côté serveur.
|
|
7
|
+
*/
|
|
8
|
+
export async function disconnect() {
|
|
9
|
+
const cookieStore = await cookies();
|
|
10
|
+
cookieStore.delete("access_token");
|
|
11
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
// src/lib/auth/isConnected.ts
|
|
1
3
|
import { cookies } from "next/headers";
|
|
2
4
|
import { verify } from "jsonwebtoken";
|
|
3
5
|
|
|
4
6
|
export async function isConnected(): Promise<boolean> {
|
|
5
7
|
const cookieStore = await cookies();
|
|
6
|
-
const
|
|
7
|
-
if (
|
|
8
|
+
const accessToken = cookieStore.get("access_token")?.value;
|
|
9
|
+
if (accessToken) {
|
|
8
10
|
try {
|
|
9
|
-
verify(
|
|
11
|
+
verify(accessToken, process.env.APP_JWT_SECRET!);
|
|
10
12
|
return true;
|
|
11
13
|
} catch {
|
|
12
14
|
return false;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/ui/Dashboard/LogoutButton.tsx
|
|
2
|
+
"use client"; // DO NOT FORGET , this is a client component.
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { Button } from "@/ui/_global/Button";
|
|
5
|
+
import { LogOut } from "lucide-react";
|
|
6
|
+
import { disconnect } from "@/lib/auth/disconnect";
|
|
7
|
+
|
|
8
|
+
const LogoutButton = () => {
|
|
9
|
+
const t = useTranslations("Dashboard");
|
|
10
|
+
const handleLogout = () => {
|
|
11
|
+
disconnect().then(() => {
|
|
12
|
+
window.location.href = "/";
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
return (
|
|
16
|
+
<Button
|
|
17
|
+
variant="ghost"
|
|
18
|
+
size="sm"
|
|
19
|
+
onClick={handleLogout}
|
|
20
|
+
className="text-gray-400 hover:text-black glass-effect"
|
|
21
|
+
>
|
|
22
|
+
<LogOut className="h-4 w-4 sm:mr-2" />
|
|
23
|
+
<span className="hidden sm:inline">{t("logout")}</span>
|
|
24
|
+
</Button>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
export default LogoutButton;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
// src/ui/Dashboard/page-ui.tsx
|
|
2
|
+
"use client";
|
|
2
3
|
import { useTranslations } from "next-intl";
|
|
3
4
|
import BackButton from "@/ui/_global/BackButton";
|
|
4
|
-
import WelcomeCard from "@/ui/
|
|
5
|
-
import StatsCard from "@/ui/
|
|
5
|
+
import WelcomeCard from "@/ui/Dashboard/WelcomeCard";
|
|
6
|
+
import StatsCard from "@/ui/Dashboard/StatsCard";
|
|
6
7
|
|
|
7
8
|
export default function Dashboard() {
|
|
8
|
-
const t = useTranslations("
|
|
9
|
+
const t = useTranslations("Dashboard");
|
|
9
10
|
return (
|
|
10
11
|
<>
|
|
11
12
|
<div className="flex items-center justify-between mb-6">
|