create-next-pro-cli 0.1.14 → 0.1.16

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 (64) hide show
  1. package/README.md +3 -2
  2. package/create-next-pro-completion.sh +9 -1
  3. package/dist/{bin.js → bin.bun.js} +124 -9
  4. package/dist/bin.node.js +681 -0
  5. package/dist/bin.node.js.map +1 -0
  6. package/dist/create-next-pro +9 -0
  7. package/package.json +24 -8
  8. package/templates/Projects/default/README.md +36 -0
  9. package/templates/Projects/default/src/app/[locale]/(public)/Login/page.tsx +6 -0
  10. package/templates/Projects/default/src/app/[locale]/(public)/Register/page.tsx +6 -0
  11. package/templates/Projects/default/src/app/[locale]/(public)/_home/loading.tsx +5 -0
  12. package/templates/Projects/default/src/app/[locale]/(public)/_home/page.tsx +6 -0
  13. package/templates/Projects/default/src/app/[locale]/(public)/layout.tsx +9 -0
  14. package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/error.tsx +38 -0
  15. package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/loading.tsx +10 -0
  16. package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/page.tsx +5 -0
  17. package/templates/Projects/default/src/app/[locale]/(user)/Settings/loading.tsx +17 -0
  18. package/templates/Projects/default/src/app/[locale]/(user)/Settings/page.tsx +6 -0
  19. package/templates/Projects/default/src/app/[locale]/(user)/UserInfo/loading.tsx +17 -0
  20. package/templates/Projects/default/src/app/[locale]/(user)/UserInfo/page.tsx +6 -0
  21. package/templates/Projects/default/src/app/[locale]/(user)/layout.tsx +9 -0
  22. package/templates/Projects/default/src/app/[locale]/layout.tsx +31 -0
  23. package/templates/Projects/default/src/app/[locale]/loading.tsx +14 -0
  24. package/templates/Projects/default/src/app/[locale]/not-found.tsx +22 -0
  25. package/templates/Projects/default/src/app/[locale]/page.tsx +6 -0
  26. package/templates/Projects/default/src/app/api/auth/[...nextauth]/route.ts +62 -0
  27. package/templates/Projects/default/src/app/api/auth/post-login/route.ts +26 -0
  28. package/templates/Projects/default/src/app/favicon.ico +0 -0
  29. package/templates/Projects/default/src/app/layout.tsx +11 -0
  30. package/templates/Projects/default/src/app/not-found.tsx +17 -0
  31. package/templates/Projects/default/src/app/page.tsx +6 -0
  32. package/templates/Projects/default/src/app/sitemap.ts +27 -0
  33. package/templates/Projects/default/src/app/styles/globals.css +305 -0
  34. package/templates/Projects/default/src/auth.config.ts +0 -0
  35. package/templates/Projects/default/src/config.ts +4 -0
  36. package/templates/Projects/default/src/lib/auth/disconnect.ts +11 -0
  37. package/templates/Projects/default/src/lib/auth/isConnected.ts +18 -0
  38. package/templates/Projects/default/src/lib/i18n/navigation.ts +19 -0
  39. package/templates/Projects/default/src/lib/i18n/request.ts +31 -0
  40. package/templates/Projects/default/src/lib/i18n/routing.ts +20 -0
  41. package/templates/Projects/default/src/lib/utils.ts +6 -0
  42. package/templates/Projects/default/src/ui/Dashboard/LogoutButton.tsx +27 -0
  43. package/templates/Projects/default/src/ui/Dashboard/StatsCard.tsx +14 -0
  44. package/templates/Projects/default/src/ui/Dashboard/WelcomeCard.tsx +10 -0
  45. package/templates/Projects/default/src/ui/Dashboard/page-ui.tsx +23 -0
  46. package/templates/Projects/default/src/ui/Login/page-ui.tsx +22 -0
  47. package/templates/Projects/default/src/ui/Register/page-ui.tsx +26 -0
  48. package/templates/Projects/default/src/ui/Settings/page-ui.tsx +17 -0
  49. package/templates/Projects/default/src/ui/UserInfo/page-ui.tsx +17 -0
  50. package/templates/Projects/default/src/ui/_global/BackButton.tsx +17 -0
  51. package/templates/Projects/default/src/ui/_global/Button.tsx +75 -0
  52. package/templates/Projects/default/src/ui/_global/GlobalHeader.tsx +55 -0
  53. package/templates/Projects/default/src/ui/_global/GlobalMain.tsx +15 -0
  54. package/templates/Projects/default/src/ui/_global/Loading.tsx +13 -0
  55. package/templates/Projects/default/src/ui/_global/LocaleSwitcher.tsx +38 -0
  56. package/templates/Projects/default/src/ui/_global/PublicNav.tsx +91 -0
  57. package/templates/Projects/default/src/ui/_global/ThemeToggle.tsx +53 -0
  58. package/templates/Projects/default/src/ui/_global/UserNav.tsx +35 -0
  59. package/templates/Projects/default/src/ui/_home/page-ui.tsx +27 -0
  60. package/.github/workflows/ci.yml +0 -38
  61. package/bin.ts +0 -3
  62. package/commitlint.config.cjs +0 -12
  63. package/install.sh +0 -74
  64. package/tsconfig.json +0 -27
@@ -0,0 +1,305 @@
1
+ @import "tailwindcss";
2
+
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;
31
+ }
32
+
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%;
54
+ }
55
+
56
+ * {
57
+ border-color: hsl(var(--border));
58
+ }
59
+
60
+ body {
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%;
305
+ }
File without changes
@@ -0,0 +1,4 @@
1
+ export const port = process.env.PORT || 3000;
2
+ export const host = process.env.PROJECT_PRODUCTION_URL
3
+ ? `https://${process.env.PROJECT_PRODUCTION_URL}`
4
+ : `http://localhost:${port}`;
@@ -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
+ }
@@ -0,0 +1,18 @@
1
+ "use server";
2
+ // src/lib/auth/isConnected.ts
3
+ import { cookies } from "next/headers";
4
+ import { verify } from "jsonwebtoken";
5
+
6
+ export async function isConnected(): Promise<boolean> {
7
+ const cookieStore = await cookies();
8
+ const accessToken = cookieStore.get("access_token")?.value;
9
+ if (accessToken) {
10
+ try {
11
+ verify(accessToken, process.env.APP_JWT_SECRET!);
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+ return false;
18
+ }
@@ -0,0 +1,19 @@
1
+ // lib/i18n/navigation.ts
2
+ import { createNavigation } from "next-intl/navigation";
3
+ import { routing } from "./routing";
4
+
5
+ // Lightweight wrappers around Next.js' navigation
6
+ // APIs that consider the routing configuration
7
+ export const { Link, redirect, usePathname, useRouter, getPathname } =
8
+ createNavigation(routing);
9
+ // This file sets up navigation for internationalization (i18n) in a Next.js application
10
+ // It uses the createLocalizedPathnamesNavigation function from next-intl/navigation
11
+ // to create localized links and navigation functions
12
+ // The locales and defaultLocale are imported from the routing module
13
+ // This allows the application to support multiple languages, such as English and French
14
+ // The Link component can be used to create links that respect the current locale
15
+ // The redirect function can be used to redirect users to the appropriate locale
16
+ // The usePathname and useRouter hooks can be used to access the current pathname and router
17
+ // This setup is essential for providing a seamless navigation experience in a multilingual application
18
+ // Make sure to import this module in your application to enable i18n navigation
19
+ // You can also customize the Link component to add additional props or styles
@@ -0,0 +1,31 @@
1
+ // lib/i18n/request.ts
2
+ import { getRequestConfig } from "next-intl/server";
3
+ import { hasLocale } from "next-intl";
4
+ import { routing } from "./routing";
5
+
6
+ export default getRequestConfig(async ({ requestLocale }) => {
7
+ //console.log("Requesting locale... :", requestLocale);
8
+ // Typically corresponds to the `[locale]` segment
9
+ //console.log("Routing :", routing);
10
+ const requested = await requestLocale;
11
+ //console.log(`Requested locale: "${requested}"`);
12
+ const locale = hasLocale(routing.locales, requested)
13
+ ? requested
14
+ : routing.defaultLocale;
15
+
16
+ //console.log(`Locale requested: "${locale}"`);
17
+
18
+ return {
19
+ locale,
20
+ messages: (await import(`@/../messages/${locale}.ts`)).default,
21
+ };
22
+ });
23
+
24
+ // This file is used to configure internationalization (i18n) for the Next.js application
25
+ // It imports the getRequestConfig function from next-intl/server to set up locale-specific messages
26
+ // The messages are loaded dynamically based on the requested locale
27
+ // The locales and defaultLocale are imported from the routing module
28
+ // This allows the application to support multiple languages, such as English and French
29
+ // The messages are expected to be in JSON format and located in the messages directory
30
+ // This setup is essential for providing localized content to users based on their language preferences
31
+ // Make sure to create the necessary message files for each locale in the specified directory
@@ -0,0 +1,20 @@
1
+ // lib/i18n/routing.ts
2
+ import { defineRouting } from "next-intl/routing";
3
+
4
+ export const routing = defineRouting({
5
+ // A list of all locales that are supported
6
+ locales: ["en", "fr"],
7
+ localeDetection: true,
8
+ localeCookie: true,
9
+
10
+ // Used when no locale matches
11
+ defaultLocale: "en",
12
+ });
13
+ // This file defines the routing configuration for internationalization (i18n) in a Next.js application
14
+ // It uses the defineRouting function from next-intl/routing to set up the locales and default locale
15
+ // The locales array specifies the supported languages, such as English and German
16
+ // The defaultLocale is set to English, which will be used when no specific locale is requested
17
+ // The pathnames object maps the root path ("/") to itself and provides a localized version
18
+ // for the "/pathnames" route in German, which is mapped to "/pfadnamen"
19
+ // This setup allows the application to handle different languages and provide localized content
20
+ // Make sure to import this routing configuration in your application to enable i18n routing
@@ -0,0 +1,6 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: unknown[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -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;
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+
3
+ export default function StatsCard({ t }: { t: (key: string) => string }) {
4
+ return (
5
+ <div className="rounded border bg-white p-6 shadow-sm">
6
+ <h2 className="text-lg font-semibold mb-2">Statistiques</h2>
7
+ <ul className="text-sm text-gray-600 space-y-1">
8
+ <li>• {t("widgets.stats.views")}: 123</li>
9
+ <li>• {t("widgets.stats.likes")}: 45</li>
10
+ <li>• {t("widgets.stats.comments")}: 7</li>
11
+ </ul>
12
+ </div>
13
+ );
14
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+
3
+ export default function WelcomeCard({ t }: { t: (key: string) => string }) {
4
+ return (
5
+ <div className="rounded border bg-white p-6 shadow-sm">
6
+ <h2 className="text-lg font-semibold mb-2">{t("widgets.welcome")}</h2>
7
+ <p className="text-gray-600">{t("widgets.description")}</p>
8
+ </div>
9
+ );
10
+ }
@@ -0,0 +1,23 @@
1
+ // src/ui/Dashboard/page-ui.tsx
2
+ "use client";
3
+ import { useTranslations } from "next-intl";
4
+ import BackButton from "@/ui/_global/BackButton";
5
+ import WelcomeCard from "@/ui/Dashboard/WelcomeCard";
6
+ import StatsCard from "@/ui/Dashboard/StatsCard";
7
+
8
+ export default function Dashboard() {
9
+ const t = useTranslations("Dashboard");
10
+ return (
11
+ <>
12
+ <div className="flex items-center justify-between mb-6">
13
+ <h1 className="text-2xl font-bold">{t("title")}</h1>
14
+ <BackButton />
15
+ </div>
16
+ <p className="text-muted mb-8">{t("description")}</p>
17
+ <section className="grid grid-cols-1 md:grid-cols-2 gap-6">
18
+ <WelcomeCard t={t} />
19
+ <StatsCard t={t} />
20
+ </section>
21
+ </>
22
+ );
23
+ }
@@ -0,0 +1,22 @@
1
+ // src/ui/Login/page-ui.tsx
2
+ "use client";
3
+
4
+ import { useTranslations } from "next-intl";
5
+ import { signIn } from "next-auth/react";
6
+
7
+ export default function LoginPageUI() {
8
+ const t = useTranslations("Login");
9
+
10
+ return (
11
+ <section className="py-8 px-4 max-w-3xl mx-auto">
12
+ <h1 className="text-2xl font-bold">{t("title")}</h1>
13
+ <p className="text-muted mt-2">{t("description")}</p>
14
+ <button
15
+ onClick={() => signIn("google")}
16
+ className="mt-6 w-full py-2 px-4 rounded bg-blue-600 text-white font-semibold hover:bg-blue-700 transition"
17
+ >
18
+ {t("login_with_google")}
19
+ </button>
20
+ </section>
21
+ );
22
+ }
@@ -0,0 +1,26 @@
1
+ // src/ui/Register/page-ui.tsx
2
+ "use client";
3
+
4
+ import { useTranslations } from "next-intl";
5
+ import { signIn } from "next-auth/react";
6
+ import BackButton from "@/ui/_global/BackButton";
7
+
8
+ export default function RegisterPageUI() {
9
+ const t = useTranslations("Register");
10
+
11
+ return (
12
+ <section className="py-8 px-4 max-w-md mx-auto">
13
+ <div className="flex items-center justify-between mb-6">
14
+ <h1 className="text-2xl font-bold">{t("title")}</h1>
15
+ <BackButton />
16
+ </div>
17
+ <p className="text-muted mb-6">{t("description")}</p>
18
+ <button
19
+ onClick={() => signIn()}
20
+ className="w-full py-2 px-4 rounded bg-blue-600 text-white font-semibold hover:bg-blue-700 transition"
21
+ >
22
+ {t("register_with_google")}
23
+ </button>
24
+ </section>
25
+ );
26
+ }
@@ -0,0 +1,17 @@
1
+ // src/ui/Settings/page-ui.tsx
2
+ "use client";
3
+
4
+ import { useTranslations } from "next-intl";
5
+
6
+ export default function SettingsPageUI() {
7
+ const t = useTranslations("Settings");
8
+
9
+ return (
10
+ <>
11
+ <section className="py-8 px-4 max-w-3xl mx-auto">
12
+ <h1 className="text-2xl font-bold">{t("title")}</h1>
13
+ <p className="text-muted mt-2">{t("description")}</p>
14
+ </section>
15
+ </>
16
+ );
17
+ }
@@ -0,0 +1,17 @@
1
+ // src/ui/UserInfo/page-ui.tsx
2
+ "use client";
3
+
4
+ import { useTranslations } from "next-intl";
5
+
6
+ export default function UserInfoPageUI() {
7
+ const t = useTranslations("UserInfo");
8
+
9
+ return (
10
+ <>
11
+ <section className="py-8 px-4 max-w-3xl mx-auto">
12
+ <h1 className="text-2xl font-bold">{t("title")}</h1>
13
+ <p className="text-muted mt-2">{t("description")}</p>
14
+ </section>
15
+ </>
16
+ );
17
+ }
@@ -0,0 +1,17 @@
1
+ "use client"; // DO NOT FORGET , this is a client component.
2
+ import { useRouter } from "next/navigation";
3
+ import { StepBackIcon } from "lucide-react";
4
+ import { Button } from "./Button";
5
+
6
+ export default function BackButton({ className = "" }: { className?: string }) {
7
+ const router = useRouter();
8
+ return (
9
+ <Button
10
+ type="button"
11
+ onClick={() => router.back()}
12
+ className={`inline-flex items-center gap-2 px-3 py-1.5 rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 transition ${className}`}
13
+ >
14
+ <StepBackIcon className="h-4 w-4" /> Retour
15
+ </Button>
16
+ );
17
+ }
@@ -0,0 +1,75 @@
1
+ // src/ui/_global/Button.tsx
2
+ "use client";
3
+ import { cn } from "@/lib/utils";
4
+ import { Slot } from "@radix-ui/react-slot";
5
+ import { cva } from "class-variance-authority";
6
+ import React from "react";
7
+
8
+ const buttonVariants = cva(
9
+ "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer disabled:pointer-events-none disabled:opacity-50",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default:
14
+ "bg-primary text-primary-foreground hover:bg-primary/90 dark:text-white",
15
+ destructive:
16
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
17
+ outline:
18
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
19
+ secondary:
20
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
21
+ ghost: "hover:bg-accent hover:text-accent-foreground",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+ negative:
24
+ "bg-black text-white dark:bg-white dark:text-black hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors",
25
+ },
26
+ size: {
27
+ default: "h-10 px-4 py-2",
28
+ sm: "h-9 rounded-md px-3",
29
+ lg: "h-11 rounded-md px-8",
30
+ icon: "h-10 w-10",
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ variant: "default",
35
+ size: "default",
36
+ },
37
+ }
38
+ );
39
+
40
+ import type { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode } from "react";
41
+
42
+ type ButtonProps = {
43
+ className?: string;
44
+ variant?:
45
+ | "default"
46
+ | "destructive"
47
+ | "outline"
48
+ | "secondary"
49
+ | "ghost"
50
+ | "link";
51
+ size?: "default" | "sm" | "lg" | "icon";
52
+ asChild?: boolean;
53
+ children?: ReactNode;
54
+ } & DetailedHTMLProps<
55
+ ButtonHTMLAttributes<HTMLButtonElement>,
56
+ HTMLButtonElement
57
+ >;
58
+
59
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
60
+ ({ className, variant, size, asChild = false, children, ...props }, ref) => {
61
+ const Comp = asChild ? Slot : "button";
62
+ return (
63
+ <Comp
64
+ className={cn(buttonVariants({ variant, size, className }))}
65
+ ref={ref}
66
+ {...props}
67
+ >
68
+ {children}
69
+ </Comp>
70
+ );
71
+ }
72
+ );
73
+ Button.displayName = "Button";
74
+
75
+ export { Button, buttonVariants };