Compare commits
3 Commits
db8b951e5a
...
23560ac9c3
| Author | SHA1 | Date | |
|---|---|---|---|
| 23560ac9c3 | |||
| ec89ddc9dc | |||
| abb6602e5e |
2
.env
2
.env
@@ -2,3 +2,5 @@
|
|||||||
# Override with your real URL in .env.local for runtime
|
# Override with your real URL in .env.local for runtime
|
||||||
DATABASE_URL="postgresql://admin:adminpassword@plumedb.kaelstudio.tech:5432/plumeia?schema=public"
|
DATABASE_URL="postgresql://admin:adminpassword@plumedb.kaelstudio.tech:5432/plumeia?schema=public"
|
||||||
BETTER_AUTH_SECRET=cbe18fc18cd18fa590bd8be51204fac2ee0b1cc704adf8fa0192f77d42b262dd
|
BETTER_AUTH_SECRET=cbe18fc18cd18fa590bd8be51204fac2ee0b1cc704adf8fa0192f77d42b262dd
|
||||||
|
# Server-only
|
||||||
|
GEMINI_API_KEY=AIzaSyBjMxaRq4psBbvtdks0iYGkv-r9midKSh4
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
.next
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
|
|||||||
BIN
.next/dev/cache/turbopack/23c46498/00000316.del
vendored
BIN
.next/dev/cache/turbopack/23c46498/00000316.del
vendored
Binary file not shown.
BIN
.next/dev/cache/turbopack/23c46498/CURRENT
vendored
BIN
.next/dev/cache/turbopack/23c46498/CURRENT
vendored
Binary file not shown.
114
.next/dev/cache/turbopack/23c46498/LOG
vendored
114
.next/dev/cache/turbopack/23c46498/LOG
vendored
@@ -418,3 +418,117 @@ FAM | META SEQ | SST SEQ | RANGE
|
|||||||
0 | 00000362 | 00000361 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
0 | 00000362 | 00000361 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
1 | 00000363 | 00000359 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
1 | 00000363 | 00000359 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
2 | 00000364 | 00000360 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
2 | 00000364 | 00000360 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:28:32.4670348Z
|
||||||
|
Commit 00000370 1498 keys in 8ms 232µs 900ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000368 | 00000367 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
2 | 00000369 | 00000366 SST | [==================================================================================================] | 015aa7af8c46be57-ff1373558b528b52 (0 MiB, fresh)
|
||||||
|
1 | 00000370 | 00000365 SST | [==================================================================================================] | 0013a9e4f34335e5-ffd284765f657204 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:28:45.6986838Z
|
||||||
|
Commit 00000376 187 keys in 8ms 382µs 700ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000374 | 00000373 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000375 | 00000372 SST | [=================================================================================================] | 038551e4bbed3e04-fe395009436c0ad9 (0 MiB, fresh)
|
||||||
|
2 | 00000376 | 00000371 SST | [=================================================================================================] | 038551e4bbed3e04-fe395009436c0ad9 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:30:30.120118Z
|
||||||
|
Commit 00000382 4 keys in 6ms 814µs 600ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000380 | 00000379 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000381 | 00000377 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000382 | 00000378 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:30:32.509576Z
|
||||||
|
Commit 00000388 4 keys in 15ms 142µs 100ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000386 | 00000385 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000387 | 00000383 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000388 | 00000384 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:32:04.6280952Z
|
||||||
|
Commit 00000394 4 keys in 16ms 348µs 500ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000392 | 00000391 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000393 | 00000389 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000394 | 00000390 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:33:34.7202101Z
|
||||||
|
Commit 00000400 4 keys in 16ms 731µs 100ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000398 | 00000397 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000399 | 00000395 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000400 | 00000396 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:37:34.9456148Z
|
||||||
|
Commit 00000406 4 keys in 7ms 343µs 100ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000404 | 00000403 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000405 | 00000401 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000406 | 00000402 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:41:31.9517975Z
|
||||||
|
Commit 00000412 4 keys in 16ms 904µs 200ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000410 | 00000409 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000411 | 00000407 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000412 | 00000408 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:43:04.161226Z
|
||||||
|
Commit 00000418 4 keys in 15ms 846µs 300ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000416 | 00000415 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000417 | 00000413 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000418 | 00000414 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:44:30.1233467Z
|
||||||
|
Commit 00000424 4 keys in 15ms 550µs
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000422 | 00000421 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
2 | 00000423 | 00000420 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
1 | 00000424 | 00000419 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:47:04.4274594Z
|
||||||
|
Commit 00000430 4 keys in 16ms 834µs 800ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000428 | 00000427 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000429 | 00000425 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000430 | 00000426 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:50:33.834705Z
|
||||||
|
Commit 00000436 10 keys in 6ms 493µs 100ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000434 | 00000433 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
2 | 00000435 | 00000431 SST | [====================================================================] | 3ffdfb3b7d50fcf1-ef311d8b965c9633 (0 MiB, fresh)
|
||||||
|
1 | 00000436 | 00000432 SST | [====================================================================] | 3ffdfb3b7d50fcf1-ef311d8b965c9633 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:50:49.6529443Z
|
||||||
|
Commit 00000442 4 keys in 16ms 392µs 200ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000440 | 00000439 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000441 | 00000437 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000442 | 00000438 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:54:12.3969106Z
|
||||||
|
Commit 00000448 743 keys in 7ms 750µs 700ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000446 | 00000445 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000447 | 00000444 SST | [==================================================================================================] | 017b6b0c5cfd90fa-ff86d89df2e7cda0 (0 MiB, fresh)
|
||||||
|
2 | 00000448 | 00000443 SST | [=================================================================================================] | 038551e4bbed3e04-fdac997cbf24ab54 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T11:59:49.5661942Z
|
||||||
|
Commit 00000454 4 keys in 17ms 73µs 500ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000452 | 00000451 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000453 | 00000449 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000454 | 00000450 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T12:02:30.1429156Z
|
||||||
|
Commit 00000460 4 keys in 15ms 856µs
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000458 | 00000457 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000459 | 00000455 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
2 | 00000460 | 00000456 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T12:03:45.6079186Z
|
||||||
|
Commit 00000466 4 keys in 7ms 463µs 100ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000464 | 00000463 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000465 | 00000461 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh)
|
||||||
|
2 | 00000466 | 00000462 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T12:03:58.3142436Z
|
||||||
|
Commit 00000472 4 keys in 8ms 367µs 900ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000470 | 00000469 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000471 | 00000467 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh)
|
||||||
|
2 | 00000472 | 00000468 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh)
|
||||||
|
Time 2026-02-27T12:04:00.758105Z
|
||||||
|
Commit 00000478 4 keys in 8ms 663µs 100ns
|
||||||
|
FAM | META SEQ | SST SEQ | RANGE
|
||||||
|
0 | 00000476 | 00000475 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
|
||||||
|
1 | 00000477 | 00000473 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh)
|
||||||
|
2 | 00000478 | 00000474 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh)
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"dynamicRoutes": {},
|
"dynamicRoutes": {},
|
||||||
"notFoundRoutes": [],
|
"notFoundRoutes": [],
|
||||||
"preview": {
|
"preview": {
|
||||||
"previewModeId": "4dfe148fddf52fa0c666ab30d011de66",
|
"previewModeId": "7a45a941e4d22df88ad9355f3991ab6d",
|
||||||
"previewModeSigningKey": "c62393dc79015b89894debd7bb2fb06aff1c8fc37e19c164af907d98c2c9270c",
|
"previewModeSigningKey": "2835bcf805d69d3ea501de6e9e8215ed6e476c92e3ce1b39d4c7154490c5d9cd",
|
||||||
"previewModeEncryptionKey": "dcaf1eb965c8df03a88b502bd7a4c86f53bb8c19fa97d6495dd428370cb40dae"
|
"previewModeEncryptionKey": "be1f0a8fe5a7df32e766951bb0286021153e859bf5affb5871269998a3fcdadd"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"/api/ai/generate/route": "app/api/ai/generate/route.js",
|
||||||
"/api/auth/[...nextauth]/route": "app/api/auth/[...nextauth]/route.js",
|
"/api/auth/[...nextauth]/route": "app/api/auth/[...nextauth]/route.js",
|
||||||
"/api/auth/register/route": "app/api/auth/register/route.js",
|
"/api/projects/[id]/route": "app/api/projects/[id]/route.js",
|
||||||
"/api/projects/route": "app/api/projects/route.js",
|
"/api/projects/route": "app/api/projects/route.js",
|
||||||
"/api/user/profile/route": "app/api/user/profile/route.js",
|
"/api/user/profile/route": "app/api/user/profile/route.js",
|
||||||
"/dashboard/page": "app/dashboard/page.js",
|
"/dashboard/page": "app/dashboard/page.js",
|
||||||
"/login/page": "app/login/page.js",
|
"/login/page": "app/login/page.js",
|
||||||
"/page": "app/page.js",
|
"/page": "app/page.js",
|
||||||
"/signup/page": "app/signup/page.js"
|
"/project/[id]/page": "app/project/[id]/page.js"
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
self.__NEXT_FONT_MANIFEST="{\n \"app\": {\n \"[project]/Documents/00 - projet/plumeia/src/app/dashboard/page\": [\n \"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2\",\n \"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2\"\n ],\n \"[project]/Documents/00 - projet/plumeia/src/app/login/page\": [\n \"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2\",\n \"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2\"\n ],\n \"[project]/Documents/00 - projet/plumeia/src/app/page\": [\n \"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2\",\n \"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2\"\n ],\n \"[project]/Documents/00 - projet/plumeia/src/app/signup/page\": [\n \"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2\",\n \"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2\"\n ]\n },\n \"appUsingSizeAdjust\": true,\n \"pages\": {},\n \"pagesUsingSizeAdjust\": false\n}"
|
self.__NEXT_FONT_MANIFEST="{\n \"app\": {\n \"[project]/Documents/00 - projet/plumeia/src/app/dashboard/page\": [\n \"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2\",\n \"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2\"\n ],\n \"[project]/Documents/00 - projet/plumeia/src/app/login/page\": [\n \"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2\",\n \"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2\"\n ],\n \"[project]/Documents/00 - projet/plumeia/src/app/page\": [\n \"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2\",\n \"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2\"\n ],\n \"[project]/Documents/00 - projet/plumeia/src/app/project/[id]/page\": [\n \"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2\",\n \"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2\"\n ]\n },\n \"appUsingSizeAdjust\": true,\n \"pages\": {},\n \"pagesUsingSizeAdjust\": false\n}"
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2",
|
"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2",
|
||||||
"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2"
|
"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2"
|
||||||
],
|
],
|
||||||
"[project]/Documents/00 - projet/plumeia/src/app/signup/page": [
|
"[project]/Documents/00 - projet/plumeia/src/app/project/[id]/page": [
|
||||||
"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2",
|
"static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2",
|
||||||
"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2"
|
"static/media/248e1dc0efc99276-s.p.8a6b2436.woff2"
|
||||||
]
|
]
|
||||||
|
|||||||
43
build_utf8.txt
Normal file
43
build_utf8.txt
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
> plumeia@0.1.0 build
|
||||||
|
> next build
|
||||||
|
|
||||||
|
node.exe : ÔÜá Warning: Next.js inferred your workspace root, but it
|
||||||
|
may not be correct.
|
||||||
|
Au caractère Ligne:1 : 1
|
||||||
|
+ & "C:\Program Files\nodejs/node.exe" "C:\Program
|
||||||
|
Files\nodejs/node_mo ...
|
||||||
|
+
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
+ CategoryInfo : NotSpecified: (ÔÜá Warning: Ne...not b
|
||||||
|
e correct.:String) [], RemoteException
|
||||||
|
+ FullyQualifiedErrorId : NativeCommandError
|
||||||
|
|
||||||
|
We detected multiple lockfiles and selected the directory of
|
||||||
|
C:\Users\streaper2\package-lock.json as the root directory.
|
||||||
|
To silence this warning, set `turbopack.root` in your Next.js
|
||||||
|
config, or consider removing one of the lockfiles if it's not needed.
|
||||||
|
See https://nextjs.org/docs/app/api-reference/config/next-config-j
|
||||||
|
s/turbopack#root-directory for more information.
|
||||||
|
Detected additional lockfiles:
|
||||||
|
* C:\Users\streaper2\Documents\00 -
|
||||||
|
projet\plumeia\package-lock.json
|
||||||
|
|
||||||
|
Ôû▓ Next.js 16.1.6 (Turbopack)
|
||||||
|
- Environments: .env.local, .env
|
||||||
|
|
||||||
|
Creating an optimized production build ...
|
||||||
|
Ô£ô Compiled successfully in 1253.2ms
|
||||||
|
Skipping validation of types
|
||||||
|
Collecting page data using 31 workers ...
|
||||||
|
Generating static pages using 31 workers (0/10) ...
|
||||||
|
Error occurred prerendering page "/pricing". Read more:
|
||||||
|
https://nextjs.org/docs/messages/prerender-error
|
||||||
|
ReferenceError: useState is not defined
|
||||||
|
at h (C:\Users\streaper2\Documents\00 - projet\plumeia\.next\serv
|
||||||
|
er\chunks\ssr\[root-of-the-server]__1b51e774._.js:1:4513) {
|
||||||
|
digest: '4248291053'
|
||||||
|
}
|
||||||
|
Export encountered an error on /pricing/page: /pricing, exiting the
|
||||||
|
build.
|
||||||
|
Ô¿» Next.js build worker exited with code: 1 and signal: null
|
||||||
524
package-lock.json
generated
524
package-lock.json
generated
@@ -7,6 +7,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "plumeia",
|
"name": "plumeia",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/genai": "^1.38.0",
|
"@google/genai": "^1.38.0",
|
||||||
"@prisma/adapter-pg": "^7.4.1",
|
"@prisma/adapter-pg": "^7.4.1",
|
||||||
@@ -31,7 +32,11 @@
|
|||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.1.6",
|
"eslint-config-next": "16.1.6",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
@@ -409,6 +414,448 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.9.1",
|
"version": "4.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
|
||||||
@@ -3719,6 +4166,48 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
|
||||||
|
"integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.27.3",
|
||||||
|
"@esbuild/android-arm": "0.27.3",
|
||||||
|
"@esbuild/android-arm64": "0.27.3",
|
||||||
|
"@esbuild/android-x64": "0.27.3",
|
||||||
|
"@esbuild/darwin-arm64": "0.27.3",
|
||||||
|
"@esbuild/darwin-x64": "0.27.3",
|
||||||
|
"@esbuild/freebsd-arm64": "0.27.3",
|
||||||
|
"@esbuild/freebsd-x64": "0.27.3",
|
||||||
|
"@esbuild/linux-arm": "0.27.3",
|
||||||
|
"@esbuild/linux-arm64": "0.27.3",
|
||||||
|
"@esbuild/linux-ia32": "0.27.3",
|
||||||
|
"@esbuild/linux-loong64": "0.27.3",
|
||||||
|
"@esbuild/linux-mips64el": "0.27.3",
|
||||||
|
"@esbuild/linux-ppc64": "0.27.3",
|
||||||
|
"@esbuild/linux-riscv64": "0.27.3",
|
||||||
|
"@esbuild/linux-s390x": "0.27.3",
|
||||||
|
"@esbuild/linux-x64": "0.27.3",
|
||||||
|
"@esbuild/netbsd-arm64": "0.27.3",
|
||||||
|
"@esbuild/netbsd-x64": "0.27.3",
|
||||||
|
"@esbuild/openbsd-arm64": "0.27.3",
|
||||||
|
"@esbuild/openbsd-x64": "0.27.3",
|
||||||
|
"@esbuild/openharmony-arm64": "0.27.3",
|
||||||
|
"@esbuild/sunos-x64": "0.27.3",
|
||||||
|
"@esbuild/win32-arm64": "0.27.3",
|
||||||
|
"@esbuild/win32-ia32": "0.27.3",
|
||||||
|
"@esbuild/win32-x64": "0.27.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
@@ -4398,6 +4887,21 @@
|
|||||||
"node": ">=12.20.0"
|
"node": ">=12.20.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -8000,6 +8504,26 @@
|
|||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
|
"node_modules/tsx": {
|
||||||
|
"version": "4.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||||
|
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"esbuild": "~0.27.0",
|
||||||
|
"get-tsconfig": "^4.7.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"tsx": "dist/cli.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"postinstall": "prisma generate"
|
"postinstall": "npx prisma db push && next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/genai": "^1.38.0",
|
"@google/genai": "^1.38.0",
|
||||||
@@ -36,6 +36,10 @@
|
|||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.1.6",
|
"eslint-config-next": "16.1.6",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"prisma": {
|
||||||
|
"seed": "npx tsx prisma/seed.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,16 +7,38 @@ generator client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// AUTH
|
// AUTH & SUBSCRIPTIONS
|
||||||
// =====================
|
// =====================
|
||||||
|
model Plan {
|
||||||
|
id String @id // e.g., 'free', 'pro', 'master'
|
||||||
|
name String @unique
|
||||||
|
displayName String
|
||||||
|
price Float
|
||||||
|
description String
|
||||||
|
maxProjects Int // -1 for unlimited
|
||||||
|
maxAiActions Int // -1 for unlimited
|
||||||
|
features String[]
|
||||||
|
isPopular Boolean @default(false)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
users User[]
|
||||||
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String?
|
name String?
|
||||||
email String @unique
|
email String @unique
|
||||||
hashedPassword String
|
hashedPassword String
|
||||||
avatar String?
|
|
||||||
bio String?
|
bio String?
|
||||||
plan String @default("free") // free | pro | master
|
|
||||||
|
// Legacy string plan (temporarily kept to avoid DB drop errors)
|
||||||
|
plan String @default("free")
|
||||||
|
|
||||||
|
// New Subscription
|
||||||
|
planId String? @default("free")
|
||||||
|
subscriptionPlan Plan? @relation(fields: [planId], references: [id])
|
||||||
|
|
||||||
aiActionsUsed Int @default(0)
|
aiActionsUsed Int @default(0)
|
||||||
dailyWordGoal Int @default(500)
|
dailyWordGoal Int @default(500)
|
||||||
writingStreak Int @default(0)
|
writingStreak Int @default(0)
|
||||||
|
|||||||
64
prisma/seed.ts
Normal file
64
prisma/seed.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { config } from 'dotenv';
|
||||||
|
config({ path: '.env.local' });
|
||||||
|
import getDB from '../src/lib/prisma';
|
||||||
|
|
||||||
|
const prisma = getDB();
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Seeding plans...');
|
||||||
|
|
||||||
|
const plans = [
|
||||||
|
{
|
||||||
|
id: 'free',
|
||||||
|
name: 'free',
|
||||||
|
displayName: 'Gratuit',
|
||||||
|
price: 0,
|
||||||
|
description: 'Idéal pour découvrir PlumeIA.',
|
||||||
|
maxProjects: 1,
|
||||||
|
maxAiActions: 10,
|
||||||
|
features: ['10 actions IA / mois', '1 projet actif', 'Bible du monde simple'],
|
||||||
|
isPopular: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pro',
|
||||||
|
name: 'pro',
|
||||||
|
displayName: 'Auteur Pro',
|
||||||
|
price: 12,
|
||||||
|
description: 'Pour les écrivains sérieux.',
|
||||||
|
maxProjects: -1, // -1 means unlimited
|
||||||
|
maxAiActions: 500,
|
||||||
|
features: ['500 actions IA / mois', 'Projets illimités', 'Export Word & EPUB', 'Support prioritaire'],
|
||||||
|
isPopular: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'master',
|
||||||
|
name: 'master',
|
||||||
|
displayName: 'Maître Plume',
|
||||||
|
price: 29,
|
||||||
|
description: 'Le summum de l\'écriture IA.',
|
||||||
|
maxProjects: -1,
|
||||||
|
maxAiActions: -1, // -1 means unlimited
|
||||||
|
features: ['Actions IA illimitées', 'Accès Gemini 3 Pro', 'Bible du monde avancée', 'Outils de révision avancés'],
|
||||||
|
isPopular: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const plan of plans) {
|
||||||
|
await prisma.plan.upsert({
|
||||||
|
where: { id: plan.id },
|
||||||
|
update: plan,
|
||||||
|
create: plan,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Plans seeded successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
BIN
push_log.txt
Normal file
BIN
push_log.txt
Normal file
Binary file not shown.
@@ -5,11 +5,7 @@ import { auth } from '@/lib/auth';
|
|||||||
import getDB from '@/lib/prisma';
|
import getDB from '@/lib/prisma';
|
||||||
import { generateStoryContent } from '@/lib/gemini';
|
import { generateStoryContent } from '@/lib/gemini';
|
||||||
|
|
||||||
const PLAN_AI_LIMITS: Record<string, number> = {
|
|
||||||
free: 100,
|
|
||||||
pro: 5000,
|
|
||||||
master: 999999,
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@@ -23,17 +19,19 @@ export async function POST(request: NextRequest) {
|
|||||||
// Check AI usage limit from DB
|
// Check AI usage limit from DB
|
||||||
const dbUser = await prisma.user.findUnique({
|
const dbUser = await prisma.user.findUnique({
|
||||||
where: { id: session.user.id },
|
where: { id: session.user.id },
|
||||||
select: { plan: true, aiActionsUsed: true },
|
include: { subscriptionPlan: true },
|
||||||
});
|
}) as any; // Bypass Prisma client types for this relation
|
||||||
|
|
||||||
if (!dbUser) {
|
if (!dbUser) {
|
||||||
return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const limit = PLAN_AI_LIMITS[dbUser.plan] || PLAN_AI_LIMITS.free;
|
const limit = dbUser.subscriptionPlan?.maxAiActions ?? 100;
|
||||||
if (dbUser.aiActionsUsed >= limit) {
|
const planName = dbUser.subscriptionPlan?.displayName || 'Gratuit';
|
||||||
|
|
||||||
|
if (limit !== -1 && dbUser.aiActionsUsed >= limit) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: `Limite de ${limit} actions IA atteinte pour le plan ${dbUser.plan}. Passez au plan supérieur !` },
|
{ error: `Limite de ${limit} actions IA atteinte pour le plan ${planName}. Passez au plan supérieur !` },
|
||||||
{ status: 403 }
|
{ status: 403 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import { auth } from '@/lib/auth';
|
|||||||
import getDB from '@/lib/prisma';
|
import getDB from '@/lib/prisma';
|
||||||
import { transformTextServer } from '@/lib/gemini';
|
import { transformTextServer } from '@/lib/gemini';
|
||||||
|
|
||||||
const PLAN_AI_LIMITS: Record<string, number> = {
|
|
||||||
free: 100,
|
|
||||||
pro: 5000,
|
|
||||||
master: 999999,
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@@ -23,17 +19,19 @@ export async function POST(request: NextRequest) {
|
|||||||
// Check AI usage limit from DB
|
// Check AI usage limit from DB
|
||||||
const dbUser = await prisma.user.findUnique({
|
const dbUser = await prisma.user.findUnique({
|
||||||
where: { id: session.user.id },
|
where: { id: session.user.id },
|
||||||
select: { plan: true, aiActionsUsed: true },
|
include: { subscriptionPlan: true },
|
||||||
});
|
}) as any; // Bypass Prisma type cache
|
||||||
|
|
||||||
if (!dbUser) {
|
if (!dbUser) {
|
||||||
return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const limit = PLAN_AI_LIMITS[dbUser.plan] || PLAN_AI_LIMITS.free;
|
const limit = dbUser.subscriptionPlan?.maxAiActions ?? 100;
|
||||||
if (dbUser.aiActionsUsed >= limit) {
|
const planName = dbUser.subscriptionPlan?.displayName || 'Gratuit';
|
||||||
|
|
||||||
|
if (limit !== -1 && dbUser.aiActionsUsed >= limit) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: `Limite de ${limit} actions IA atteinte pour le plan ${dbUser.plan}. Passez au plan supérieur !` },
|
{ error: `Limite de ${limit} actions IA atteinte pour le plan ${planName}. Passez au plan supérieur !` },
|
||||||
{ status: 403 }
|
{ status: 403 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/app/api/plans/route.ts
Normal file
19
src/app/api/plans/route.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import getDB from '@/lib/prisma';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const prisma = getDB();
|
||||||
|
const plans = await prisma.plan.findMany({
|
||||||
|
orderBy: { price: 'asc' }
|
||||||
|
});
|
||||||
|
const response = NextResponse.json(plans);
|
||||||
|
response.headers.set('Cache-Control', 'no-store, max-age=0');
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch plans', error);
|
||||||
|
return NextResponse.json({ error: 'Failed to fetch plans' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,12 +22,7 @@ export async function GET() {
|
|||||||
return NextResponse.json(projects);
|
return NextResponse.json(projects);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plan limits for project creation
|
|
||||||
const PLAN_PROJECT_LIMITS: Record<string, number> = {
|
|
||||||
free: 3,
|
|
||||||
pro: 20,
|
|
||||||
master: 999,
|
|
||||||
};
|
|
||||||
|
|
||||||
// POST /api/projects — Create a new project
|
// POST /api/projects — Create a new project
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
@@ -36,17 +31,20 @@ export async function POST(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check plan limits
|
||||||
const prisma = getDB();
|
const prisma = getDB();
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: session.user.id },
|
||||||
|
include: { subscriptionPlan: true }
|
||||||
|
}) as any; // Cast to any to bypass Prisma type cache issues
|
||||||
|
|
||||||
// Check plan limit
|
const limit = user?.subscriptionPlan?.maxProjects ?? 3;
|
||||||
const user = await prisma.user.findUnique({ where: { id: session.user.id }, select: { plan: true } });
|
const planName = user?.subscriptionPlan?.displayName || 'Gratuit';
|
||||||
const plan = user?.plan || 'free';
|
|
||||||
const limit = PLAN_PROJECT_LIMITS[plan] || PLAN_PROJECT_LIMITS.free;
|
|
||||||
const currentCount = await prisma.project.count({ where: { userId: session.user.id } });
|
const currentCount = await prisma.project.count({ where: { userId: session.user.id } });
|
||||||
|
|
||||||
if (currentCount >= limit) {
|
if (limit !== -1 && currentCount >= limit) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: `Limite de ${limit} projets atteinte pour le plan ${plan}. Passez au plan supérieur !` },
|
{ error: `Limite de ${limit} projets atteinte pour le plan ${planName}. Passez au plan supérieur !` },
|
||||||
{ status: 403 }
|
{ status: 403 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ export async function GET() {
|
|||||||
const prisma = getDB();
|
const prisma = getDB();
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: session.user.id },
|
where: { id: session.user.id },
|
||||||
});
|
include: { subscriptionPlan: true }
|
||||||
|
} as any) as any; // Bypass Prisma type cache
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
|
||||||
@@ -31,13 +32,24 @@ export async function GET() {
|
|||||||
return total + (text ? text.split(/\s+/).length : 0);
|
return total + (text ? text.split(/\s+/).length : 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
return NextResponse.json({
|
const response = NextResponse.json({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
avatar: user.avatar,
|
avatar: user.avatar,
|
||||||
bio: user.bio,
|
bio: user.bio,
|
||||||
plan: user.plan,
|
plan: user.planId || user.plan || 'free',
|
||||||
|
planDetails: user.subscriptionPlan ? {
|
||||||
|
id: user.subscriptionPlan.id,
|
||||||
|
name: user.subscriptionPlan.name,
|
||||||
|
displayName: user.subscriptionPlan.displayName,
|
||||||
|
price: user.subscriptionPlan.price,
|
||||||
|
description: user.subscriptionPlan.description,
|
||||||
|
features: user.subscriptionPlan.features,
|
||||||
|
maxProjects: user.subscriptionPlan.maxProjects,
|
||||||
|
maxAiActions: user.subscriptionPlan.maxAiActions,
|
||||||
|
isPopular: user.subscriptionPlan.isPopular
|
||||||
|
} : undefined,
|
||||||
aiActionsUsed: user.aiActionsUsed,
|
aiActionsUsed: user.aiActionsUsed,
|
||||||
dailyWordGoal: user.dailyWordGoal,
|
dailyWordGoal: user.dailyWordGoal,
|
||||||
writingStreak: user.writingStreak,
|
writingStreak: user.writingStreak,
|
||||||
@@ -45,6 +57,8 @@ export async function GET() {
|
|||||||
createdAt: user.createdAt,
|
createdAt: user.createdAt,
|
||||||
totalWords,
|
totalWords,
|
||||||
});
|
});
|
||||||
|
response.headers.set('Cache-Control', 'no-store, max-age=0');
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT /api/user/profile — Update user profile
|
// PUT /api/user/profile — Update user profile
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
import Pricing from '@/components/Pricing';
|
import Pricing from '@/components/Pricing';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useAuthContext } from '@/providers/AuthProvider';
|
import { useAuthContext } from '@/providers/AuthProvider';
|
||||||
@@ -8,8 +9,26 @@ export default function PricingPage() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { user } = useAuthContext();
|
const { user } = useAuthContext();
|
||||||
|
|
||||||
|
const [plans, setPlans] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/plans', { cache: 'no-store' })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
setPlans(data);
|
||||||
|
setIsLoading(false);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pricing
|
<Pricing
|
||||||
|
plans={plans}
|
||||||
|
isLoading={isLoading}
|
||||||
currentPlan={user?.subscription.plan || 'free'}
|
currentPlan={user?.subscription.plan || 'free'}
|
||||||
onBack={() => router.push(user ? '/dashboard' : '/')}
|
onBack={() => router.push(user ? '/dashboard' : '/')}
|
||||||
onSelectPlan={() => router.push(user ? '/checkout' : '/login')}
|
onSelectPlan={() => router.push(user ? '/checkout' : '/login')}
|
||||||
|
|||||||
@@ -81,9 +81,28 @@ const AppRouter: React.FC<AppRouterProps> = (props) => {
|
|||||||
// but AuthPage manages its own state. We can pass a prop if AuthPage supports it, or just let user toggle.
|
// but AuthPage manages its own state. We can pass a prop if AuthPage supports it, or just let user toggle.
|
||||||
// Since AuthPage has internal state for mode, we might just render it.
|
// Since AuthPage has internal state for mode, we might just render it.
|
||||||
// Ideally AuthPage should accept an initialMode prop. Let's check AuthPage again or just render it.
|
// Ideally AuthPage should accept an initialMode prop. Let's check AuthPage again or just render it.
|
||||||
|
const [plans, setPlans] = useState<any[]>([]);
|
||||||
|
const [isPricingLoading, setIsPricingLoading] = useState(true);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (viewMode === 'pricing' && plans.length === 0) {
|
||||||
|
setIsPricingLoading(true);
|
||||||
|
fetch('/api/plans', { cache: 'no-store' })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
setPlans(data);
|
||||||
|
setIsPricingLoading(false);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
setIsPricingLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [viewMode, plans.length]);
|
||||||
|
|
||||||
if (viewMode === 'signup') return <AuthPage onBack={() => props.onViewModeChange('landing')} onSuccess={() => props.onViewModeChange('dashboard')} initialMode='signup' />;
|
if (viewMode === 'signup') return <AuthPage onBack={() => props.onViewModeChange('landing')} onSuccess={() => props.onViewModeChange('dashboard')} initialMode='signup' />;
|
||||||
if (viewMode === 'features') return <FeaturesPage onBack={() => props.onViewModeChange(user ? 'dashboard' : 'landing')} />;
|
if (viewMode === 'features') return <FeaturesPage onBack={() => props.onViewModeChange(user ? 'dashboard' : 'landing')} />;
|
||||||
if (viewMode === 'pricing') return <Pricing currentPlan={user?.subscription.plan || 'free'} onBack={() => props.onViewModeChange(user ? 'dashboard' : 'landing')} onSelectPlan={() => user ? props.onViewModeChange('checkout') : props.onViewModeChange('auth')} />;
|
if (viewMode === 'pricing') return <Pricing plans={plans} isLoading={isPricingLoading} currentPlan={user?.subscription.plan || 'free'} onBack={() => props.onViewModeChange(user ? 'dashboard' : 'landing')} onSelectPlan={() => user ? props.onViewModeChange('checkout') : props.onViewModeChange('auth')} />;
|
||||||
if (viewMode === 'checkout') return <Checkout onComplete={() => props.onUpgradePlan('pro')} onCancel={() => props.onViewModeChange('pricing')} />;
|
if (viewMode === 'checkout') return <Checkout onComplete={() => props.onUpgradePlan('pro')} onCancel={() => props.onViewModeChange('pricing')} />;
|
||||||
if (viewMode === 'dashboard' && user) return <Dashboard user={user} projects={projects} onSelect={props.onSelectProject} onCreate={props.onCreateProject} onLogout={props.onLogout} onPricing={() => props.onViewModeChange('pricing')} onProfile={() => props.onViewModeChange('profile')} />;
|
if (viewMode === 'dashboard' && user) return <Dashboard user={user} projects={projects} onSelect={props.onSelectProject} onCreate={props.onCreateProject} onLogout={props.onLogout} onPricing={() => props.onViewModeChange('pricing')} onProfile={() => props.onViewModeChange('profile')} />;
|
||||||
if (viewMode === 'profile' && user) return <UserProfileSettings user={user} onUpdate={props.onUpdateProfile} onBack={() => props.onViewModeChange('dashboard')} />;
|
if (viewMode === 'profile' && user) return <UserProfileSettings user={user} onUpdate={props.onUpdateProfile} onBack={() => props.onViewModeChange('dashboard')} />;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const Dashboard: React.FC<DashboardProps> = ({ user, projects, onSelect, onCreat
|
|||||||
<div>
|
<div>
|
||||||
<h2 className="text-3xl font-black text-slate-900">Bonjour, {user.name} 👋</h2>
|
<h2 className="text-3xl font-black text-slate-900">Bonjour, {user.name} 👋</h2>
|
||||||
<div className="flex items-center gap-3 mt-1">
|
<div className="flex items-center gap-3 mt-1">
|
||||||
<span className="px-3 py-1 rounded-full bg-indigo-100 text-indigo-700 text-[10px] uppercase font-black tracking-widest">{user.subscription.plan}</span>
|
<span className="px-3 py-1 rounded-full bg-indigo-100 text-indigo-700 text-[10px] uppercase font-black tracking-widest">{user.subscription.planDetails?.displayName || user.subscription.plan}</span>
|
||||||
<span className="text-slate-400 text-xs font-medium">Membre depuis le 24 janv.</span>
|
<span className="text-slate-400 text-xs font-medium">Membre depuis le 24 janv.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,55 +3,61 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Check, ArrowLeft } from 'lucide-react';
|
import { Check, ArrowLeft } from 'lucide-react';
|
||||||
import { PlanType } from '@/lib/types';
|
|
||||||
|
|
||||||
interface PricingProps {
|
interface PlanData {
|
||||||
currentPlan: PlanType;
|
id: string;
|
||||||
onBack: () => void;
|
name: string;
|
||||||
onSelectPlan: (plan: PlanType) => void;
|
displayName: string;
|
||||||
|
price: number;
|
||||||
|
description: string;
|
||||||
|
features: string[];
|
||||||
|
isPopular: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Pricing: React.FC<PricingProps> = ({ currentPlan, onBack, onSelectPlan }) => {
|
interface PricingProps {
|
||||||
const plans = [
|
plans: PlanData[];
|
||||||
{ id: 'free', name: 'Gratuit', price: '0€', desc: 'Idéal pour découvrir PlumeIA.', features: ['10 actions IA / mois', '1 projet actif', 'Bible du monde simple'] },
|
currentPlan: string;
|
||||||
{ id: 'pro', name: 'Auteur Pro', price: '12€', desc: 'Pour les écrivains sérieux.', features: ['500 actions IA / mois', 'Projets illimités', 'Export Word & EPUB', 'Support prioritaire'], popular: true },
|
onBack: () => void;
|
||||||
{ id: 'master', name: 'Maître Plume', price: '29€', desc: 'Le summum de l\'écriture IA.', features: ['Actions IA illimitées', 'Accès Gemini 3 Pro', 'Bible du monde avancée', 'Outils de révision avancés'] },
|
onSelectPlan: (planId: string) => void;
|
||||||
];
|
isLoading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Pricing: React.FC<PricingProps> = ({ plans, currentPlan, onBack, onSelectPlan, isLoading }) => {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#eef2ff] py-20 px-8">
|
<div className="min-h-screen bg-[#eef2ff] py-20 px-8">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<button onClick={onBack} className="flex items-center gap-2 text-slate-500 hover:text-blue-600 mb-12 font-bold transition-colors">
|
<button onClick={onBack} className="flex items-center gap-2 text-slate-500 hover:text-blue-600 mb-12 font-bold transition-colors">
|
||||||
<ArrowLeft size={20} /> Retour
|
<ArrowLeft size={20} /> Retour
|
||||||
</button>
|
</button>
|
||||||
<div className="text-center mb-16">
|
<div className="text-center mb-16">
|
||||||
<h2 className="text-4xl font-black text-slate-900 mb-4">Choisissez votre destin d'écrivain.</h2>
|
<h2 className="text-4xl font-black text-slate-900 mb-4">Choisissez votre destin d'écrivain.</h2>
|
||||||
<p className="text-slate-500">Passez au plan supérieur pour libérer toute la puissance de l'IA.</p>
|
<p className="text-slate-500">Passez au plan supérieur pour libérer toute la puissance de l'IA.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
{plans.map((p) => (
|
{isLoading && <p className="text-center col-span-3 py-10">Chargement des offres...</p>}
|
||||||
<div key={p.id} className={`bg-white rounded-3xl p-8 border transition-all ${p.popular ? 'border-blue-500 shadow-2xl scale-105 z-10' : 'border-indigo-100 shadow-xl'}`}>
|
{!isLoading && plans.map((p) => (
|
||||||
<div className="mb-8">
|
<div key={p.id} className={`bg-white rounded-3xl p-8 border transition-all ${p.isPopular ? 'border-blue-500 shadow-2xl scale-105 z-10' : 'border-indigo-100 shadow-xl'}`}>
|
||||||
<h4 className="text-xl font-bold text-slate-900 mb-2">{p.name}</h4>
|
<div className="mb-8">
|
||||||
<div className="text-4xl font-black text-slate-900 mb-2">{p.price}<span className="text-sm font-normal text-slate-400">/mois</span></div>
|
<h4 className="text-xl font-bold text-slate-900 mb-2">{p.displayName}</h4>
|
||||||
<p className="text-sm text-slate-500">{p.desc}</p>
|
<div className="text-4xl font-black text-slate-900 mb-2">{p.price}€<span className="text-sm font-normal text-slate-400">/mois</span></div>
|
||||||
</div>
|
<p className="text-sm text-slate-500">{p.description}</p>
|
||||||
<ul className="space-y-4 mb-10">
|
</div>
|
||||||
{p.features.map((f, i) => (
|
<ul className="space-y-4 mb-10">
|
||||||
<li key={i} className="flex items-center gap-3 text-sm text-slate-700">
|
{p.features.map((f, i) => (
|
||||||
<div className="text-blue-500 bg-blue-50 p-0.5 rounded-full"><Check size={14} /></div>
|
<li key={i} className="flex items-center gap-3 text-sm text-slate-700">
|
||||||
{f}
|
<div className="text-blue-500 bg-blue-50 p-0.5 rounded-full"><Check size={14} /></div>
|
||||||
</li>
|
{f}
|
||||||
))}
|
</li>
|
||||||
</ul>
|
))}
|
||||||
<button
|
</ul>
|
||||||
onClick={() => onSelectPlan(p.id as PlanType)}
|
<button
|
||||||
className={`w-full py-4 rounded-2xl font-black transition-all ${p.id === currentPlan ? 'bg-slate-100 text-slate-400 cursor-default' : p.popular ? 'bg-blue-600 text-white hover:bg-blue-700' : 'bg-slate-900 text-white hover:bg-slate-800'}`}
|
onClick={() => onSelectPlan(p.id)}
|
||||||
>
|
className={`w-full py-4 rounded-2xl font-black transition-all ${p.id === currentPlan ? 'bg-slate-100 text-slate-400 cursor-default' : p.isPopular ? 'bg-blue-600 text-white hover:bg-blue-700' : 'bg-slate-900 text-white hover:bg-slate-800'}`}
|
||||||
{p.id === currentPlan ? 'Plan Actuel' : 'Sélectionner'}
|
>
|
||||||
</button>
|
{p.id === currentPlan ? 'Plan Actuel' : 'Sélectionner'}
|
||||||
</div>
|
</button>
|
||||||
))}
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
|
|||||||
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-300">
|
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-300">
|
||||||
<div className="p-4 bg-blue-50 border border-blue-100 rounded-xl flex justify-between items-center">
|
<div className="p-4 bg-blue-50 border border-blue-100 rounded-xl flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-bold text-blue-900">Plan {user.subscription.plan.toUpperCase()}</h4>
|
<h4 className="font-bold text-blue-900">Plan {(user.subscription.planDetails?.displayName || user.subscription.plan).toUpperCase()}</h4>
|
||||||
<p className="text-xs text-blue-700">Prochaine facturation le 15 Mars 2024</p>
|
<p className="text-xs text-blue-700">Prochaine facturation le 15 Mars 2024</p>
|
||||||
</div>
|
</div>
|
||||||
<button className="bg-blue-600 text-white px-4 py-2 rounded-lg text-xs font-bold hover:bg-blue-700 shadow-md shadow-blue-200">Gérer</button>
|
<button className="bg-blue-600 text-white px-4 py-2 rounded-lg text-xs font-bold hover:bg-blue-700 shadow-md shadow-blue-200">Gérer</button>
|
||||||
|
|||||||
@@ -2,15 +2,9 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { signIn, signOut, useSession } from 'next-auth/react';
|
import { signIn, signOut, useSession } from 'next-auth/react';
|
||||||
import { UserProfile, PlanType } from '@/lib/types';
|
import { UserProfile } from '@/lib/types';
|
||||||
import api from '@/lib/api';
|
import api from '@/lib/api';
|
||||||
|
|
||||||
const PLAN_LIMITS: Record<string, { aiActions: number; projects: number }> = {
|
|
||||||
free: { aiActions: 100, projects: 3 },
|
|
||||||
pro: { aiActions: 5000, projects: 20 },
|
|
||||||
master: { aiActions: 999999, projects: 999 },
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
const [user, setUser] = useState<UserProfile | null>(null);
|
const [user, setUser] = useState<UserProfile | null>(null);
|
||||||
@@ -19,11 +13,16 @@ export const useAuth = () => {
|
|||||||
// Fetch real profile from DB when session is available
|
// Fetch real profile from DB when session is available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session?.user?.id) {
|
if (session?.user?.id) {
|
||||||
fetch('/api/user/profile')
|
fetch('/api/user/profile', { cache: 'no-store' })
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(dbUser => {
|
.then(dbUser => {
|
||||||
const plan = (dbUser.plan || 'free') as PlanType;
|
const planId = dbUser.plan || 'free';
|
||||||
const limits = PLAN_LIMITS[plan] || PLAN_LIMITS.free;
|
const planDetails = dbUser.planDetails || {
|
||||||
|
id: 'free',
|
||||||
|
displayName: 'Gratuit',
|
||||||
|
maxAiActions: 100,
|
||||||
|
maxProjects: 3
|
||||||
|
};
|
||||||
|
|
||||||
setUser({
|
setUser({
|
||||||
id: dbUser.id,
|
id: dbUser.id,
|
||||||
@@ -31,11 +30,16 @@ export const useAuth = () => {
|
|||||||
name: dbUser.name || 'User',
|
name: dbUser.name || 'User',
|
||||||
avatar: dbUser.avatar,
|
avatar: dbUser.avatar,
|
||||||
bio: dbUser.bio,
|
bio: dbUser.bio,
|
||||||
subscription: { plan, startDate: new Date(dbUser.createdAt).getTime(), status: 'active' },
|
subscription: {
|
||||||
|
plan: planId,
|
||||||
|
planDetails: planDetails,
|
||||||
|
startDate: new Date(dbUser.createdAt).getTime(),
|
||||||
|
status: 'active'
|
||||||
|
},
|
||||||
usage: {
|
usage: {
|
||||||
aiActionsCurrent: dbUser.aiActionsUsed || 0,
|
aiActionsCurrent: dbUser.aiActionsUsed || 0,
|
||||||
aiActionsLimit: limits.aiActions,
|
aiActionsLimit: planDetails.maxAiActions,
|
||||||
projectsLimit: limits.projects,
|
projectsLimit: planDetails.maxProjects,
|
||||||
},
|
},
|
||||||
preferences: { theme: 'light', dailyWordGoal: dbUser.dailyWordGoal || 500, language: 'fr' },
|
preferences: { theme: 'light', dailyWordGoal: dbUser.dailyWordGoal || 500, language: 'fr' },
|
||||||
stats: {
|
stats: {
|
||||||
|
|||||||
@@ -129,10 +129,21 @@ export interface ChatMessage {
|
|||||||
|
|
||||||
// --- SAAS TYPES ---
|
// --- SAAS TYPES ---
|
||||||
|
|
||||||
export type PlanType = 'free' | 'pro' | 'master';
|
export interface PlanData {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
price: number;
|
||||||
|
description: string;
|
||||||
|
features: string[];
|
||||||
|
isPopular: boolean;
|
||||||
|
maxProjects: number;
|
||||||
|
maxAiActions: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Subscription {
|
export interface Subscription {
|
||||||
plan: PlanType;
|
plan: string; // The ID of the plan
|
||||||
|
planDetails?: PlanData; // The populated plan details from DB
|
||||||
startDate: number;
|
startDate: number;
|
||||||
status: 'active' | 'canceled' | 'past_due';
|
status: 'active' | 'canceled' | 'past_due';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user