create-next-pro-cli 0.1.25 → 0.1.27

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 (107) hide show
  1. package/README.md +205 -292
  2. package/create-next-pro-completion.sh +8 -38
  3. package/create-next-pro-completion.zsh +13 -0
  4. package/dist/bin.bun.js +1127 -628
  5. package/dist/bin.node.js +1170 -617
  6. package/dist/bin.node.js.map +1 -1
  7. package/dist/create-next-pro +35 -6
  8. package/package.json +49 -27
  9. package/templates/Projects/default/.env.example +17 -0
  10. package/templates/Projects/default/.github/workflows/quality.yml +24 -0
  11. package/templates/Projects/default/.gitignore.template +47 -0
  12. package/templates/Projects/default/.prettierignore +3 -0
  13. package/templates/Projects/default/README.md +66 -21
  14. package/templates/Projects/default/bun.lock +1152 -0
  15. package/templates/Projects/default/eslint.config.mjs +27 -11
  16. package/templates/Projects/default/messages/en/_global_ui.json +23 -10
  17. package/templates/Projects/default/messages/en.ts +17 -2
  18. package/templates/Projects/default/messages/fr/_global_ui.json +23 -10
  19. package/templates/Projects/default/messages/fr.ts +17 -2
  20. package/templates/Projects/default/next.config.ts +43 -3
  21. package/templates/Projects/default/package.json +42 -24
  22. package/templates/Projects/default/playwright.config.ts +26 -0
  23. package/templates/Projects/default/pnpm-workspace.yaml +5 -0
  24. package/templates/Projects/default/public/{cnp-logo.svg → logo.svg} +1 -1
  25. package/templates/Projects/default/src/app/[locale]/(public)/layout.tsx +8 -1
  26. package/templates/Projects/default/src/app/[locale]/(public)/login/page.tsx +8 -0
  27. package/templates/Projects/default/src/app/[locale]/(public)/register/page.tsx +8 -0
  28. package/templates/Projects/default/src/app/[locale]/(user)/dashboard/error.tsx +25 -0
  29. package/templates/Projects/default/src/app/[locale]/(user)/dashboard/loading.tsx +5 -0
  30. package/templates/Projects/default/src/app/[locale]/(user)/{Dashboard → dashboard}/page.tsx +1 -1
  31. package/templates/Projects/default/src/app/[locale]/(user)/layout.tsx +24 -2
  32. package/templates/Projects/default/src/app/[locale]/(user)/settings/loading.tsx +5 -0
  33. package/templates/Projects/default/src/app/[locale]/(user)/{Settings → settings}/page.tsx +1 -2
  34. package/templates/Projects/default/src/app/[locale]/(user)/userInfo/loading.tsx +5 -0
  35. package/templates/Projects/default/src/app/[locale]/(user)/{UserInfo → userInfo}/page.tsx +1 -2
  36. package/templates/Projects/default/src/app/[locale]/layout.tsx +34 -10
  37. package/templates/Projects/default/src/app/[locale]/loading.tsx +0 -9
  38. package/templates/Projects/default/src/app/[locale]/not-found.tsx +6 -15
  39. package/templates/Projects/default/src/app/[locale]/page.tsx +10 -1
  40. package/templates/Projects/default/src/app/api/auth/[...nextauth]/route.ts +8 -60
  41. package/templates/Projects/default/src/app/not-found.tsx +1 -1
  42. package/templates/Projects/default/src/app/sitemap.ts +2 -2
  43. package/templates/Projects/default/src/app/styles/globals.css +166 -113
  44. package/templates/Projects/default/src/auth.ts +20 -0
  45. package/templates/Projects/default/src/config.ts +3 -3
  46. package/templates/Projects/default/src/env.ts +65 -0
  47. package/templates/Projects/default/src/lib/i18n/messages.ts +8 -0
  48. package/templates/Projects/default/src/lib/i18n/request.ts +2 -16
  49. package/templates/Projects/default/src/lib/security/csp.ts +16 -0
  50. package/templates/Projects/default/src/lib/utils.ts +2 -1
  51. package/templates/Projects/default/src/proxy.ts +13 -0
  52. package/templates/Projects/default/src/ui/_global/BackButton.tsx +4 -2
  53. package/templates/Projects/default/src/ui/_global/Button.tsx +3 -8
  54. package/templates/Projects/default/src/ui/_global/GlobalHeader.tsx +10 -28
  55. package/templates/Projects/default/src/ui/_global/GlobalMain.tsx +1 -1
  56. package/templates/Projects/default/src/ui/_global/LocaleSwitcher.tsx +9 -10
  57. package/templates/Projects/default/src/ui/_global/PublicNav.tsx +51 -17
  58. package/templates/Projects/default/src/ui/_global/ThemeToggle.tsx +45 -39
  59. package/templates/Projects/default/src/ui/_global/UserNav.tsx +6 -6
  60. package/templates/Projects/default/src/ui/_home/page-ui.tsx +5 -7
  61. package/templates/Projects/default/src/ui/{Dashboard → dashboard}/LogoutButton.tsx +9 -7
  62. package/templates/Projects/default/src/ui/{Dashboard → dashboard}/StatsCard.tsx +4 -4
  63. package/templates/Projects/default/src/ui/{Dashboard → dashboard}/page-ui.tsx +7 -8
  64. package/templates/Projects/default/src/ui/login/page-ui.tsx +36 -0
  65. package/templates/Projects/default/src/ui/register/page-ui.tsx +38 -0
  66. package/templates/Projects/default/src/ui/settings/page-ui.tsx +15 -0
  67. package/templates/Projects/default/src/ui/userInfo/page-ui.tsx +15 -0
  68. package/templates/Projects/default/tailwind.config.ts +81 -1
  69. package/templates/Projects/default/tests/consumer/validate-template.ts +66 -0
  70. package/templates/Projects/default/tests/e2e/template-remediation.playwright.ts +106 -0
  71. package/templates/Projects/default/tests/rendering/verify-rendering.ts +56 -0
  72. package/templates/Projects/default/tests/unit/csp.test.ts +19 -0
  73. package/templates/Projects/default/tests/unit/env.test.ts +76 -0
  74. package/templates/Projects/default/tsconfig.json +6 -6
  75. package/templates/Projects/default/messages/getMergedMessages.ts +0 -31
  76. package/templates/Projects/default/middleware.ts +0 -11
  77. package/templates/Projects/default/src/app/[locale]/(public)/Login/page.tsx +0 -6
  78. package/templates/Projects/default/src/app/[locale]/(public)/Register/page.tsx +0 -6
  79. package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/error.tsx +0 -38
  80. package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/loading.tsx +0 -10
  81. package/templates/Projects/default/src/app/[locale]/(user)/Settings/loading.tsx +0 -17
  82. package/templates/Projects/default/src/app/[locale]/(user)/UserInfo/loading.tsx +0 -17
  83. package/templates/Projects/default/src/app/api/auth/post-login/route.ts +0 -26
  84. package/templates/Projects/default/src/app/api/hello/route.ts +0 -5
  85. package/templates/Projects/default/src/app/layout.tsx +0 -11
  86. package/templates/Projects/default/src/app/page.tsx +0 -6
  87. package/templates/Projects/default/src/auth.config.ts +0 -0
  88. package/templates/Projects/default/src/lib/auth/disconnect.ts +0 -11
  89. package/templates/Projects/default/src/lib/auth/isConnected.ts +0 -18
  90. package/templates/Projects/default/src/lib/sample/example.ts +0 -3
  91. package/templates/Projects/default/src/lib/sample/index.ts +0 -3
  92. package/templates/Projects/default/src/ui/Login/page-ui.tsx +0 -22
  93. package/templates/Projects/default/src/ui/Register/page-ui.tsx +0 -26
  94. package/templates/Projects/default/src/ui/Settings/page-ui.tsx +0 -17
  95. package/templates/Projects/default/src/ui/UserInfo/page-ui.tsx +0 -17
  96. /package/templates/Projects/default/messages/en/{Dashboard.json → dashboard.json} +0 -0
  97. /package/templates/Projects/default/messages/en/{Login.json → login.json} +0 -0
  98. /package/templates/Projects/default/messages/en/{Register.json → register.json} +0 -0
  99. /package/templates/Projects/default/messages/en/{Settings.json → settings.json} +0 -0
  100. /package/templates/Projects/default/messages/en/{UserInfo.json → userInfo.json} +0 -0
  101. /package/templates/Projects/default/messages/fr/{Dashboard.json → dashboard.json} +0 -0
  102. /package/templates/Projects/default/messages/fr/{Login.json → login.json} +0 -0
  103. /package/templates/Projects/default/messages/fr/{Register.json → register.json} +0 -0
  104. /package/templates/Projects/default/messages/fr/{Settings.json → settings.json} +0 -0
  105. /package/templates/Projects/default/messages/fr/{UserInfo.json → userInfo.json} +0 -0
  106. /package/templates/Projects/default/public/{cnp-logo.png → logo.png} +0 -0
  107. /package/templates/Projects/default/src/ui/{Dashboard → dashboard}/WelcomeCard.tsx +0 -0
@@ -1,62 +1,10 @@
1
- import NextAuth from "next-auth";
2
- import type {
3
- NextAuthOptions,
4
- Session,
5
- User,
6
- Account,
7
- Profile,
8
- } from "next-auth";
9
- import GoogleProvider from "next-auth/providers/google";
10
- import type { JWT } from "next-auth/jwt";
1
+ import { handlers, isAuthConfigured } from "@/auth";
11
2
 
12
- const authOptions: NextAuthOptions = {
13
- providers: [
14
- GoogleProvider({
15
- clientId: process.env.GOOGLE_CLIENT_ID!,
16
- clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
17
- }),
18
- ],
19
- callbacks: {
20
- async jwt({ token, user }: { token: JWT; user?: User }) {
21
- if (user) {
22
- token.id = user.id;
23
- token.email = user.email;
24
- token.name = user.name;
25
- }
26
- console.log("JWT Callback:", token);
3
+ const unavailable = () =>
4
+ Response.json(
5
+ { error: "Authentication is not configured for this deployment." },
6
+ { status: 503 },
7
+ );
27
8
 
28
- return token;
29
- },
30
- async session({ session, token }: { session: Session; token: JWT }) {
31
- if (session.user) {
32
- session.user.email = token.email;
33
- session.user.name = token.name;
34
- }
35
- return session;
36
- },
37
- async signIn({
38
- user,
39
- account,
40
- profile,
41
- email,
42
- credentials,
43
- }: {
44
- user: User;
45
- account: Account | null;
46
- profile?: Profile;
47
- email?: string | { verificationRequest?: boolean };
48
- credentials?: Record<string, unknown>;
49
- }) {
50
- return true;
51
- },
52
- async redirect({ url, baseUrl }: { url: string; baseUrl: string }) {
53
- const customCallbackRoute = "/api/auth/post-login";
54
-
55
- return baseUrl + customCallbackRoute;
56
- },
57
- },
58
- };
59
-
60
- const handler = NextAuth(authOptions);
61
-
62
- export { handler as GET, handler as POST };
9
+ export const GET = isAuthConfigured() ? handlers.GET : unavailable;
10
+ export const POST = isAuthConfigured() ? handlers.POST : unavailable;
@@ -10,7 +10,7 @@ export default function GlobalNotFound() {
10
10
  return (
11
11
  <html lang="en">
12
12
  <body>
13
- <Error statusCode={404} />;
13
+ <Error statusCode={404} />
14
14
  </body>
15
15
  </html>
16
16
  );
@@ -5,7 +5,7 @@ import { routing } from "@/lib/i18n/routing";
5
5
  import { getPathname } from "@/lib/i18n/navigation";
6
6
 
7
7
  export default function sitemap(): MetadataRoute.Sitemap {
8
- return [...getEntries("/"), ...getEntries("/pathnames")];
8
+ return ["/", "/login", "/register"].flatMap((href) => getEntries(href));
9
9
  }
10
10
 
11
11
  type Href = Parameters<typeof getPathname>[0]["href"];
@@ -15,7 +15,7 @@ function getEntries(href: Href) {
15
15
  url: getUrl(href, locale),
16
16
  alternates: {
17
17
  languages: Object.fromEntries(
18
- routing.locales.map((cur) => [cur, getUrl(href, cur)])
18
+ routing.locales.map((cur) => [cur, getUrl(href, cur)]),
19
19
  ),
20
20
  },
21
21
  }));
@@ -1,68 +1,76 @@
1
1
  @import "tailwindcss";
2
-
3
- @tailwind base;
4
- @tailwind components;
5
- @tailwind utilities;
2
+ @config "../../../tailwind.config.ts";
6
3
 
7
4
  /* @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"); */
8
5
 
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%;
6
+ @custom-variant dark (&:where(.dark, .dark *));
7
+ @custom-variant light (&:where(.light, .light *));
8
+
9
+ :root {
30
10
  --radius: 0.75rem;
31
11
  }
32
12
 
33
- /* Light mode : fond blanc, texte noir */
13
+ /* Light mode : background white, text black */
14
+ :root,
34
15
  .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%;
16
+ --background: 245 245 245; /* white*/
17
+ --foreground: 0 0 0; /* black */
18
+ --card: 255 255 255;
19
+ --card-foreground: 0 0 0;
20
+ --popover: 255 255 255;
21
+ --popover-foreground: 0 0 0;
22
+ --primary: 0 0 0; /* black */
23
+ --primary-foreground: 255 255 255; /* white */
24
+ --secondary: 220 220 220; /* very light gray */
25
+ --secondary-foreground: 0 0 0;
26
+ --muted: 242 242 242;
27
+ --muted-foreground: 51 51 51; /* dark gray */
28
+ --accent: 242 242 242;
29
+ --accent-foreground: 0 0 0;
30
+ --destructive: 239 68 68;
31
+ --destructive-foreground: 255 255 255;
32
+ --border: 204 204 204;
33
+ --input: 204 204 204;
34
+ --ring: 0 0 0;
35
+ --radius: 0.75rem;
36
+ }
37
+
38
+ /* Dark mode : background black, text white */
39
+ .dark {
40
+ --background: 0 0 0; /* black */
41
+ --foreground: 255 255 255; /* white */
42
+ --card: 0 0 0;
43
+ --card-foreground: 255 255 255;
44
+ --popover: 0 0 0;
45
+ --popover-foreground: 255 255 255;
46
+ --primary: 255 255 255; /* white */
47
+ --primary-foreground: 0 0 0; /* black */
48
+ --secondary: 30 30 30; /* dark gray */
49
+ --secondary-foreground: 255 255 255;
50
+ --muted: 51 51 51; /* dark gray */
51
+ --muted-foreground: 204 204 204;
52
+ --accent: 51 51 51; /* dark gray */
53
+ --accent-foreground: 255 255 255;
54
+ --destructive: 127 29 29;
55
+ --destructive-foreground: 255 255 255;
56
+ --border: 51 51 51;
57
+ --input: 51 51 51;
58
+ --ring: 255 255 255;
59
+ --radius: 0.75rem;
54
60
  }
55
61
 
56
62
  * {
57
- border-color: hsl(var(--border));
63
+ border-color: rgb(var(--border));
58
64
  }
59
65
 
60
66
  body {
61
67
  font-family: "Inter", sans-serif;
62
- background: hsl(var(--background));
63
- color: hsl(var(--foreground));
68
+ background: rgb(var(--background));
69
+ color: rgb(var(--foreground));
64
70
  line-height: 1.6;
65
- transition: background 0.3s, color 0.3s;
71
+ transition:
72
+ background 0.3s,
73
+ color 0.3s;
66
74
  }
67
75
 
68
76
  .gradient-text {
@@ -84,51 +92,10 @@ body {
84
92
  border: 1px solid #444;
85
93
  }
86
94
  .light .glass-effect {
87
- background: rgba(0, 0, 0, 0.08);
95
+ background: rgba(255, 255, 255, 0.8);
88
96
  border: 1px solid #ccc;
89
97
  }
90
98
 
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
99
  @keyframes typing {
133
100
  0%,
134
101
  80%,
@@ -142,31 +109,116 @@ body {
142
109
  }
143
110
  }
144
111
 
112
+ /* 1) Déclarer des variables animables (pour interpolation fluide) */
113
+ @property --c1x {
114
+ syntax: "<percentage>";
115
+ inherits: true;
116
+ initial-value: 25%;
117
+ }
118
+ @property --c1y {
119
+ syntax: "<percentage>";
120
+ inherits: true;
121
+ initial-value: 25%;
122
+ }
123
+ @property --c2x {
124
+ syntax: "<percentage>";
125
+ inherits: true;
126
+ initial-value: 75%;
127
+ }
128
+ @property --c2y {
129
+ syntax: "<percentage>";
130
+ inherits: true;
131
+ initial-value: 75%;
132
+ }
133
+
134
+ /* alpha (0..1) */
135
+ @property --a1 {
136
+ syntax: "<number>";
137
+ inherits: true;
138
+ initial-value: 0.9;
139
+ }
140
+ @property --a2 {
141
+ syntax: "<number>";
142
+ inherits: true;
143
+ initial-value: 0.9;
144
+ }
145
+
145
146
  .hero-pattern {
146
- background-image: radial-gradient(
147
- circle at 25% 25%,
148
- rgba(255, 255, 255, 0.18) 0%,
149
- transparent 50%
147
+ /* 2) Animation légère */
148
+ animation: heroDrift 60s ease-in-out infinite;
149
+ }
150
+
151
+ /* 3) Keyframes: déplacement de quelques % + alpha léger */
152
+ @keyframes heroDrift {
153
+ 0% {
154
+ --c1x: 20%;
155
+ --c1y: 25%;
156
+ --c2x: 75%;
157
+ --c2y: 75%;
158
+ --a1: 0.9;
159
+ --a2: 0.99;
160
+ }
161
+ 50% {
162
+ --c1x: 28%;
163
+ --c1y: 60%;
164
+ --c2x: 65%;
165
+ --c2y: 45%;
166
+ --a1: 0.99;
167
+ --a2: 0.95;
168
+ }
169
+ 100% {
170
+ --c1x: 20%;
171
+ --c1y: 25%;
172
+ --c2x: 75%;
173
+ --c2y: 75%;
174
+ --a1: 0.9;
175
+ --a2: 0.99;
176
+ }
177
+ }
178
+
179
+ /* 4) Tu reconstruis ensuite tes gradients en utilisant ces variables */
180
+
181
+ /* DARK */
182
+ .dark .hero-pattern {
183
+ background-image:
184
+ radial-gradient(
185
+ circle at var(--c1x) var(--c1y),
186
+ rgba(0, 0, 0, var(--a1)) 0%,
187
+ transparent 80%
150
188
  ),
151
189
  radial-gradient(
152
- circle at 75% 75%,
153
- rgba(180, 180, 180, 0.18) 0%,
154
- transparent 50%
190
+ circle at var(--c2x) var(--c2y),
191
+ rgba(31, 31, 31, var(--a2)) 0%,
192
+ transparent 80%
155
193
  );
156
194
  }
195
+
196
+ /* LIGHT (mêmes positions animées, mais alpha + stops adaptés) */
157
197
  .light .hero-pattern {
158
- background-image: radial-gradient(
159
- circle at 25% 25%,
160
- rgba(0, 0, 0, 0.08) 0%,
161
- transparent 50%
198
+ /* option: alpha un peu plus bas en light */
199
+ --a1: 0.1;
200
+ --a2: 0.5;
201
+
202
+ background-image:
203
+ radial-gradient(
204
+ circle at var(--c1x) var(--c1y),
205
+ rgba(150, 150, 150, var(--a1)) 0%,
206
+ transparent 40%
162
207
  ),
163
208
  radial-gradient(
164
- circle at 75% 75%,
165
- rgba(120, 120, 120, 0.08) 0%,
209
+ circle at var(--c2x) var(--c2y),
210
+ rgba(150, 150, 150, var(--a2)) 0%,
166
211
  transparent 50%
167
212
  );
168
213
  }
169
214
 
215
+ /* 5) Accessibilité */
216
+ @media (prefers-reduced-motion: reduce) {
217
+ .hero-pattern {
218
+ animation: none;
219
+ }
220
+ }
221
+
170
222
  .light .mobil-menu {
171
223
  background: rgba(255, 255, 255, 0.9);
172
224
  }
@@ -175,16 +227,12 @@ body {
175
227
  background: rgba(0, 0, 0, 0.9);
176
228
  }
177
229
 
178
- .light .text-white,
230
+ /* .light .text-white,
179
231
  .light .text-gray-300,
180
232
  .light .text-gray-400 {
181
233
  color: black;
182
- }
234
+ } */
183
235
 
184
- .light button.text-white:hover,
185
- .light a:hover {
186
- color: rgb(142, 197, 255);
187
- }
188
236
  .gsi-material-button {
189
237
  user-select: none;
190
238
  -webkit-user-select: none;
@@ -212,7 +260,10 @@ body {
212
260
  padding: 0 12px;
213
261
  position: relative;
214
262
  text-align: center;
215
- transition: background-color 0.218s, border-color 0.218s, box-shadow 0.218s;
263
+ transition:
264
+ background-color 0.218s,
265
+ border-color 0.218s,
266
+ box-shadow 0.218s;
216
267
  vertical-align: middle;
217
268
  white-space: nowrap;
218
269
  width: auto;
@@ -293,9 +344,11 @@ body {
293
344
  }
294
345
 
295
346
  .gsi-material-button:not(:disabled):hover {
296
- -webkit-box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3),
347
+ -webkit-box-shadow:
348
+ 0 1px 2px 0 rgba(60, 64, 67, 0.3),
297
349
  0 1px 3px 1px rgba(60, 64, 67, 0.15);
298
- box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3),
350
+ box-shadow:
351
+ 0 1px 2px 0 rgba(60, 64, 67, 0.3),
299
352
  0 1px 3px 1px rgba(60, 64, 67, 0.15);
300
353
  }
301
354
 
@@ -0,0 +1,20 @@
1
+ import NextAuth from "next-auth";
2
+ import Google from "next-auth/providers/google";
3
+ import { getAuthEnv } from "@/env";
4
+
5
+ const authEnv = getAuthEnv();
6
+
7
+ export const { handlers, auth, signIn, signOut } = NextAuth({
8
+ secret: authEnv?.secret,
9
+ trustHost: process.env.AUTH_TRUST_HOST === "true",
10
+ providers: authEnv
11
+ ? [
12
+ Google({
13
+ clientId: authEnv.googleClientId,
14
+ clientSecret: authEnv.googleClientSecret,
15
+ }),
16
+ ]
17
+ : [],
18
+ });
19
+
20
+ export const isAuthConfigured = () => authEnv !== null;
@@ -1,4 +1,4 @@
1
+ import { getPublicUrl } from "@/env";
2
+
1
3
  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}`;
4
+ export const host = getPublicUrl();
@@ -0,0 +1,65 @@
1
+ const env = process.env;
2
+
3
+ function firstDefined(...names: string[]) {
4
+ for (const name of names) {
5
+ const value = env[name];
6
+ if (value && value.trim().length > 0) {
7
+ return value;
8
+ }
9
+ }
10
+ }
11
+
12
+ export function requireEnv(...names: string[]) {
13
+ const value = firstDefined(...names);
14
+ if (!value) {
15
+ throw new Error(
16
+ `Missing required environment variable: ${names.join(" or ")}`,
17
+ );
18
+ }
19
+ return value;
20
+ }
21
+
22
+ export type AuthEnv = {
23
+ secret: string;
24
+ googleClientId: string;
25
+ googleClientSecret: string;
26
+ };
27
+
28
+ export function getAuthEnv() {
29
+ if (env.AUTH_DISABLED === "true") return null;
30
+
31
+ const values = {
32
+ secret: firstDefined("AUTH_SECRET", "NEXTAUTH_SECRET"),
33
+ googleClientId: firstDefined("AUTH_GOOGLE_ID", "GOOGLE_CLIENT_ID"),
34
+ googleClientSecret: firstDefined(
35
+ "AUTH_GOOGLE_SECRET",
36
+ "GOOGLE_CLIENT_SECRET",
37
+ ),
38
+ };
39
+ const configuredValues = Object.values(values).filter(Boolean).length;
40
+
41
+ if (configuredValues === 0) return null;
42
+ if (configuredValues !== 3) {
43
+ throw new Error(
44
+ "Incomplete Auth.js configuration: secret, Google client ID and Google client secret must be set together",
45
+ );
46
+ }
47
+
48
+ return values as AuthEnv;
49
+ }
50
+
51
+ export function getPublicUrl() {
52
+ const configured = firstDefined(
53
+ "NEXT_PUBLIC_APP_URL",
54
+ "PROJECT_PRODUCTION_URL",
55
+ );
56
+ if (!configured) {
57
+ return `http://localhost:${env.PORT || "3000"}`;
58
+ }
59
+
60
+ const candidate = configured.startsWith("http")
61
+ ? configured
62
+ : `https://${configured}`;
63
+
64
+ return new URL(candidate).origin;
65
+ }
@@ -0,0 +1,8 @@
1
+ import en from "../../../messages/en";
2
+ import fr from "../../../messages/fr";
3
+
4
+ const messages = { en, fr } as const;
5
+
6
+ export function getMessages(locale: keyof typeof messages) {
7
+ return messages[locale];
8
+ }
@@ -2,30 +2,16 @@
2
2
  import { getRequestConfig } from "next-intl/server";
3
3
  import { hasLocale } from "next-intl";
4
4
  import { routing } from "./routing";
5
+ import { getMessages } from "./messages";
5
6
 
6
7
  export default getRequestConfig(async ({ requestLocale }) => {
7
- //console.log("Requesting locale... :", requestLocale);
8
- // Typically corresponds to the `[locale]` segment
9
- //console.log("Routing :", routing);
10
8
  const requested = await requestLocale;
11
- //console.log(`Requested locale: "${requested}"`);
12
9
  const locale = hasLocale(routing.locales, requested)
13
10
  ? requested
14
11
  : routing.defaultLocale;
15
12
 
16
- //console.log(`Locale requested: "${locale}"`);
17
-
18
13
  return {
19
14
  locale,
20
- messages: (await import(`@/../messages/${locale}.ts`)).default,
15
+ messages: getMessages(locale),
21
16
  };
22
17
  });
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,16 @@
1
+ export function createContentSecurityPolicy(isDevelopment: boolean) {
2
+ return [
3
+ "default-src 'self'",
4
+ `script-src 'self' 'unsafe-inline'${isDevelopment ? " 'unsafe-eval'" : ""}`,
5
+ "style-src 'self' 'unsafe-inline'",
6
+ "img-src 'self' blob: data: https://lh3.googleusercontent.com",
7
+ "font-src 'self' data:",
8
+ "connect-src 'self' https://accounts.google.com",
9
+ "frame-src https://accounts.google.com",
10
+ "object-src 'none'",
11
+ "base-uri 'self'",
12
+ "form-action 'self' https://accounts.google.com",
13
+ "frame-ancestors 'none'",
14
+ "upgrade-insecure-requests",
15
+ ].join("; ");
16
+ }
@@ -1,6 +1,7 @@
1
1
  import { clsx } from "clsx";
2
+ import type { ClassValue } from "clsx";
2
3
  import { twMerge } from "tailwind-merge";
3
4
 
4
- export function cn(...inputs: unknown[]) {
5
+ export function cn(...inputs: ClassValue[]) {
5
6
  return twMerge(clsx(inputs));
6
7
  }
@@ -0,0 +1,13 @@
1
+ import createMiddleware from "next-intl/middleware";
2
+ import type { NextRequest } from "next/server";
3
+ import { routing } from "@/lib/i18n/routing";
4
+
5
+ const handleI18nRouting = createMiddleware(routing);
6
+
7
+ export function proxy(request: NextRequest) {
8
+ return handleI18nRouting(request);
9
+ }
10
+
11
+ export const config = {
12
+ matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
13
+ };
@@ -1,17 +1,19 @@
1
- "use client"; // DO NOT FORGET , this is a client component.
1
+ "use client";
2
+ import { useTranslations } from "next-intl";
2
3
  import { useRouter } from "next/navigation";
3
4
  import { StepBackIcon } from "lucide-react";
4
5
  import { Button } from "./Button";
5
6
 
6
7
  export default function BackButton({ className = "" }: { className?: string }) {
7
8
  const router = useRouter();
9
+ const t = useTranslations("_global_ui");
8
10
  return (
9
11
  <Button
10
12
  type="button"
11
13
  onClick={() => router.back()}
12
14
  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
15
  >
14
- <StepBackIcon className="h-4 w-4" /> Retour
16
+ <StepBackIcon className="h-4 w-4" /> {t("back")}
15
17
  </Button>
16
18
  );
17
19
  }
@@ -34,7 +34,7 @@ const buttonVariants = cva(
34
34
  variant: "default",
35
35
  size: "default",
36
36
  },
37
- }
37
+ },
38
38
  );
39
39
 
40
40
  import type { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode } from "react";
@@ -42,12 +42,7 @@ import type { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode } from "react";
42
42
  type ButtonProps = {
43
43
  className?: string;
44
44
  variant?:
45
- | "default"
46
- | "destructive"
47
- | "outline"
48
- | "secondary"
49
- | "ghost"
50
- | "link";
45
+ "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
51
46
  size?: "default" | "sm" | "lg" | "icon";
52
47
  asChild?: boolean;
53
48
  children?: ReactNode;
@@ -68,7 +63,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
68
63
  {children}
69
64
  </Comp>
70
65
  );
71
- }
66
+ },
72
67
  );
73
68
  Button.displayName = "Button";
74
69