nextworks 0.0.1 → 0.1.0-alpha.0

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 (277) hide show
  1. package/README.md +209 -30
  2. package/dist/.gitkeep +0 -0
  3. package/dist/cli_manifests/auth_manifest.json +86 -0
  4. package/dist/cli_manifests/blocks_manifest.json +185 -0
  5. package/dist/cli_manifests/data_manifest.json +51 -0
  6. package/dist/cli_manifests/forms_manifest.json +61 -0
  7. package/dist/commands/admin-posts.d.ts +2 -0
  8. package/dist/commands/admin-posts.d.ts.map +1 -0
  9. package/dist/commands/admin-posts.js +15 -0
  10. package/dist/commands/admin-posts.js.map +1 -0
  11. package/dist/commands/admin-users.d.ts +2 -0
  12. package/dist/commands/admin-users.d.ts.map +1 -0
  13. package/dist/commands/admin-users.js +15 -0
  14. package/dist/commands/admin-users.js.map +1 -0
  15. package/dist/commands/auth-core.d.ts +2 -0
  16. package/dist/commands/auth-core.d.ts.map +1 -0
  17. package/dist/commands/auth-core.js +83 -0
  18. package/dist/commands/auth-core.js.map +1 -0
  19. package/dist/commands/auth-forms.d.ts +2 -0
  20. package/dist/commands/auth-forms.d.ts.map +1 -0
  21. package/dist/commands/auth-forms.js +15 -0
  22. package/dist/commands/auth-forms.js.map +1 -0
  23. package/dist/commands/blocks-options.d.ts +7 -0
  24. package/dist/commands/blocks-options.d.ts.map +1 -0
  25. package/dist/commands/blocks-options.js +19 -0
  26. package/dist/commands/blocks-options.js.map +1 -0
  27. package/dist/commands/blocks.d.ts +7 -0
  28. package/dist/commands/blocks.d.ts.map +1 -0
  29. package/dist/commands/blocks.js +140 -0
  30. package/dist/commands/blocks.js.map +1 -0
  31. package/dist/commands/data.d.ts +3 -0
  32. package/dist/commands/data.d.ts.map +1 -0
  33. package/dist/commands/data.js +88 -0
  34. package/dist/commands/data.js.map +1 -0
  35. package/dist/commands/forms.d.ts +6 -0
  36. package/dist/commands/forms.d.ts.map +1 -0
  37. package/dist/commands/forms.js +107 -0
  38. package/dist/commands/forms.js.map +1 -0
  39. package/dist/commands/remove-auth-core.d.ts +2 -0
  40. package/dist/commands/remove-auth-core.d.ts.map +1 -0
  41. package/dist/commands/remove-auth-core.js +69 -0
  42. package/dist/commands/remove-auth-core.js.map +1 -0
  43. package/dist/commands/remove-blocks.d.ts +2 -0
  44. package/dist/commands/remove-blocks.d.ts.map +1 -0
  45. package/dist/commands/remove-blocks.js +36 -0
  46. package/dist/commands/remove-blocks.js.map +1 -0
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +109 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/kits/auth-core/README.md +82 -0
  52. package/dist/kits/auth-core/app/(protected)/dashboard/page.tsx +8 -0
  53. package/dist/kits/auth-core/app/(protected)/layout.tsx +18 -0
  54. package/dist/kits/auth-core/app/(protected)/settings/profile/page.tsx +15 -0
  55. package/dist/kits/auth-core/app/(protected)/settings/profile/profile-form.tsx +114 -0
  56. package/dist/kits/auth-core/app/api/auth/[...nextauth]/route.ts +1 -0
  57. package/dist/kits/auth-core/app/api/auth/forgot-password/route.ts +114 -0
  58. package/dist/kits/auth-core/app/api/auth/providers/route.ts +6 -0
  59. package/dist/kits/auth-core/app/api/auth/reset-password/route.ts +63 -0
  60. package/dist/kits/auth-core/app/api/auth/send-verify-email/route.ts +6 -0
  61. package/dist/kits/auth-core/app/api/signup/route.ts +41 -0
  62. package/dist/kits/auth-core/app/auth/forgot-password/page.tsx +21 -0
  63. package/dist/kits/auth-core/app/auth/login/page.tsx +5 -0
  64. package/dist/kits/auth-core/app/auth/reset-password/page.tsx +187 -0
  65. package/dist/kits/auth-core/app/auth/signup/page.tsx +5 -0
  66. package/dist/kits/auth-core/app/auth/verify-email/page.tsx +11 -0
  67. package/dist/kits/auth-core/components/admin/admin-header.tsx +57 -0
  68. package/dist/kits/auth-core/components/auth/dashboard.tsx +237 -0
  69. package/dist/kits/auth-core/components/auth/forgot-password-form.tsx +90 -0
  70. package/dist/kits/auth-core/components/auth/login-form.tsx +467 -0
  71. package/dist/kits/auth-core/components/auth/logout-button.tsx +50 -0
  72. package/dist/kits/auth-core/components/auth/minimal-logout-button.tsx +40 -0
  73. package/dist/kits/auth-core/components/auth/signup-form.tsx +468 -0
  74. package/dist/kits/auth-core/components/require-auth.tsx +59 -0
  75. package/dist/kits/auth-core/components/session-provider.tsx +11 -0
  76. package/dist/kits/auth-core/components/ui/README.txt +1 -0
  77. package/dist/kits/auth-core/components/ui/button.tsx +55 -0
  78. package/dist/kits/auth-core/components/ui/input.tsx +25 -0
  79. package/dist/kits/auth-core/components/ui/label.tsx +23 -0
  80. package/dist/kits/auth-core/lib/api/errors.ts +14 -0
  81. package/dist/kits/auth-core/lib/auth-helpers.ts +29 -0
  82. package/dist/kits/auth-core/lib/auth.ts +142 -0
  83. package/dist/kits/auth-core/lib/email/dev-transport.ts +42 -0
  84. package/dist/kits/auth-core/lib/email/index.ts +28 -0
  85. package/dist/kits/auth-core/lib/email/provider-smtp.ts +36 -0
  86. package/dist/kits/auth-core/lib/forms/map-errors.ts +11 -0
  87. package/dist/kits/auth-core/lib/hash.ts +6 -0
  88. package/dist/kits/auth-core/lib/prisma.ts +15 -0
  89. package/dist/kits/auth-core/lib/server/result.ts +45 -0
  90. package/dist/kits/auth-core/lib/utils.ts +6 -0
  91. package/dist/kits/auth-core/lib/validation/forms.ts +88 -0
  92. package/dist/kits/auth-core/package-deps.json +19 -0
  93. package/dist/kits/auth-core/prisma/auth-models.prisma +81 -0
  94. package/dist/kits/auth-core/prisma/schema.prisma +81 -0
  95. package/dist/kits/auth-core/scripts/populate-tokenhash.mjs +26 -0
  96. package/dist/kits/auth-core/scripts/promote-admin.mjs +33 -0
  97. package/dist/kits/auth-core/scripts/seed-demo.mjs +40 -0
  98. package/dist/kits/auth-core/types/next-auth.d.ts +25 -0
  99. package/dist/kits/blocks/README.md +53 -0
  100. package/dist/kits/blocks/app/globals.css +175 -0
  101. package/dist/kits/blocks/app/templates/digitalagency/PresetThemeVars.tsx +80 -0
  102. package/dist/kits/blocks/app/templates/digitalagency/README.md +36 -0
  103. package/dist/kits/blocks/app/templates/digitalagency/components/About.tsx +99 -0
  104. package/dist/kits/blocks/app/templates/digitalagency/components/CTA.tsx +74 -0
  105. package/dist/kits/blocks/app/templates/digitalagency/components/Contact.tsx +227 -0
  106. package/dist/kits/blocks/app/templates/digitalagency/components/Footer.tsx +89 -0
  107. package/dist/kits/blocks/app/templates/digitalagency/components/Hero.tsx +90 -0
  108. package/dist/kits/blocks/app/templates/digitalagency/components/Navbar.tsx +168 -0
  109. package/dist/kits/blocks/app/templates/digitalagency/components/NetworkPattern.tsx +297 -0
  110. package/dist/kits/blocks/app/templates/digitalagency/components/Portfolio.tsx +157 -0
  111. package/dist/kits/blocks/app/templates/digitalagency/components/Pricing.tsx +114 -0
  112. package/dist/kits/blocks/app/templates/digitalagency/components/Process.tsx +59 -0
  113. package/dist/kits/blocks/app/templates/digitalagency/components/Services.tsx +55 -0
  114. package/dist/kits/blocks/app/templates/digitalagency/components/Team.tsx +28 -0
  115. package/dist/kits/blocks/app/templates/digitalagency/components/Testimonials.tsx +65 -0
  116. package/dist/kits/blocks/app/templates/digitalagency/page.tsx +38 -0
  117. package/dist/kits/blocks/app/templates/gallery/PresetThemeVars.tsx +85 -0
  118. package/dist/kits/blocks/app/templates/gallery/page.tsx +303 -0
  119. package/dist/kits/blocks/app/templates/productlaunch/PresetThemeVars.tsx +74 -0
  120. package/dist/kits/blocks/app/templates/productlaunch/README.md +55 -0
  121. package/dist/kits/blocks/app/templates/productlaunch/components/About.tsx +178 -0
  122. package/dist/kits/blocks/app/templates/productlaunch/components/CTA.tsx +93 -0
  123. package/dist/kits/blocks/app/templates/productlaunch/components/Contact.tsx +231 -0
  124. package/dist/kits/blocks/app/templates/productlaunch/components/FAQ.tsx +93 -0
  125. package/dist/kits/blocks/app/templates/productlaunch/components/Features.tsx +84 -0
  126. package/dist/kits/blocks/app/templates/productlaunch/components/Footer.tsx +132 -0
  127. package/dist/kits/blocks/app/templates/productlaunch/components/Hero.tsx +89 -0
  128. package/dist/kits/blocks/app/templates/productlaunch/components/Navbar.tsx +162 -0
  129. package/dist/kits/blocks/app/templates/productlaunch/components/Pricing.tsx +106 -0
  130. package/dist/kits/blocks/app/templates/productlaunch/components/ProcessTimeline.tsx +110 -0
  131. package/dist/kits/blocks/app/templates/productlaunch/components/ServicesGrid.tsx +68 -0
  132. package/dist/kits/blocks/app/templates/productlaunch/components/Team.tsx +104 -0
  133. package/dist/kits/blocks/app/templates/productlaunch/components/Testimonials.tsx +89 -0
  134. package/dist/kits/blocks/app/templates/productlaunch/components/TrustBadges.tsx +76 -0
  135. package/dist/kits/blocks/app/templates/productlaunch/page.tsx +45 -0
  136. package/dist/kits/blocks/app/templates/saasdashboard/PresetThemeVars.tsx +80 -0
  137. package/dist/kits/blocks/app/templates/saasdashboard/README.md +38 -0
  138. package/dist/kits/blocks/app/templates/saasdashboard/components/Contact.tsx +176 -0
  139. package/dist/kits/blocks/app/templates/saasdashboard/components/Dashboard.tsx +293 -0
  140. package/dist/kits/blocks/app/templates/saasdashboard/components/FAQ.tsx +55 -0
  141. package/dist/kits/blocks/app/templates/saasdashboard/components/Features.tsx +91 -0
  142. package/dist/kits/blocks/app/templates/saasdashboard/components/Footer.tsx +77 -0
  143. package/dist/kits/blocks/app/templates/saasdashboard/components/Hero.tsx +105 -0
  144. package/dist/kits/blocks/app/templates/saasdashboard/components/Hero_mask.tsx +127 -0
  145. package/dist/kits/blocks/app/templates/saasdashboard/components/Navbar.tsx +159 -0
  146. package/dist/kits/blocks/app/templates/saasdashboard/components/Pricing.tsx +90 -0
  147. package/dist/kits/blocks/app/templates/saasdashboard/components/SmoothScroll.tsx +97 -0
  148. package/dist/kits/blocks/app/templates/saasdashboard/components/Testimonials.tsx +72 -0
  149. package/dist/kits/blocks/app/templates/saasdashboard/components/TrustBadges.tsx +53 -0
  150. package/dist/kits/blocks/app/templates/saasdashboard/page.tsx +39 -0
  151. package/dist/kits/blocks/components/app-providers.tsx +1 -0
  152. package/dist/kits/blocks/components/enhanced-theme-provider.tsx +195 -0
  153. package/dist/kits/blocks/components/sections/About.tsx +291 -0
  154. package/dist/kits/blocks/components/sections/CTA.tsx +258 -0
  155. package/dist/kits/blocks/components/sections/Contact.tsx +267 -0
  156. package/dist/kits/blocks/components/sections/FAQ.tsx +226 -0
  157. package/dist/kits/blocks/components/sections/Features.tsx +269 -0
  158. package/dist/kits/blocks/components/sections/Footer.tsx +302 -0
  159. package/dist/kits/blocks/components/sections/HeroMotion.tsx +307 -0
  160. package/dist/kits/blocks/components/sections/HeroOverlay.tsx +358 -0
  161. package/dist/kits/blocks/components/sections/HeroSplit.tsx +352 -0
  162. package/dist/kits/blocks/components/sections/Navbar.tsx +353 -0
  163. package/dist/kits/blocks/components/sections/Newsletter.tsx +156 -0
  164. package/dist/kits/blocks/components/sections/PortfolioSimple.tsx +550 -0
  165. package/dist/kits/blocks/components/sections/Pricing.tsx +264 -0
  166. package/dist/kits/blocks/components/sections/ProcessTimeline.tsx +325 -0
  167. package/dist/kits/blocks/components/sections/ServicesGrid.tsx +210 -0
  168. package/dist/kits/blocks/components/sections/Team.tsx +309 -0
  169. package/dist/kits/blocks/components/sections/Testimonials.tsx +158 -0
  170. package/dist/kits/blocks/components/sections/TrustBadges.tsx +162 -0
  171. package/dist/kits/blocks/components/theme-provider.tsx +34 -0
  172. package/dist/kits/blocks/components/ui/alert-dialog.tsx +134 -0
  173. package/dist/kits/blocks/components/ui/brand-node.tsx +121 -0
  174. package/dist/kits/blocks/components/ui/button.tsx +122 -0
  175. package/dist/kits/blocks/components/ui/button_bck.tsx +93 -0
  176. package/dist/kits/blocks/components/ui/card.tsx +95 -0
  177. package/dist/kits/blocks/components/ui/checkbox.tsx +30 -0
  178. package/dist/kits/blocks/components/ui/cta-button.tsx +125 -0
  179. package/dist/kits/blocks/components/ui/dropdown-menu.tsx +201 -0
  180. package/dist/kits/blocks/components/ui/feature-card.tsx +91 -0
  181. package/dist/kits/blocks/components/ui/input.tsx +27 -0
  182. package/dist/kits/blocks/components/ui/label.tsx +29 -0
  183. package/dist/kits/blocks/components/ui/pricing-card.tsx +120 -0
  184. package/dist/kits/blocks/components/ui/select.tsx +25 -0
  185. package/dist/kits/blocks/components/ui/skeleton.tsx +13 -0
  186. package/dist/kits/blocks/components/ui/switch.tsx +78 -0
  187. package/dist/kits/blocks/components/ui/table.tsx +98 -0
  188. package/dist/kits/blocks/components/ui/testimonial-card.tsx +108 -0
  189. package/dist/kits/blocks/components/ui/textarea.tsx +26 -0
  190. package/dist/kits/blocks/components/ui/theme-selector.tsx +247 -0
  191. package/dist/kits/blocks/components/ui/theme-toggle.tsx +74 -0
  192. package/dist/kits/blocks/components/ui/toaster.tsx +7 -0
  193. package/dist/kits/blocks/lib/themes.ts +399 -0
  194. package/dist/kits/blocks/lib/themes_old.ts +37 -0
  195. package/dist/kits/blocks/lib/utils.ts +9 -0
  196. package/dist/kits/blocks/next.config.ts +11 -0
  197. package/dist/kits/blocks/notes/THEME_GUIDE.md +29 -0
  198. package/dist/kits/blocks/notes/THEMING_CONVERSION_SUMMARY.md +14 -0
  199. package/dist/kits/blocks/package-deps.json +22 -0
  200. package/dist/kits/blocks/public/placeholders/gallery/hero-pexels-broken-9945014.avif +0 -0
  201. package/dist/kits/blocks/public/placeholders/gallery/pexels-googledeepmind-25626431.jpg +0 -0
  202. package/dist/kits/blocks/public/placeholders/gallery/pexels-googledeepmind-25626432.jpg +0 -0
  203. package/dist/kits/blocks/public/placeholders/gallery/pexels-googledeepmind-25626434.jpg +0 -0
  204. package/dist/kits/blocks/public/placeholders/gallery/pexels-googledeepmind-25626436.jpg +0 -0
  205. package/dist/kits/blocks/public/placeholders/product_launch/feature_1.png +0 -0
  206. package/dist/kits/blocks/public/placeholders/product_launch/feature_2.png +0 -0
  207. package/dist/kits/blocks/public/placeholders/product_launch/feature_3.png +0 -0
  208. package/dist/kits/blocks/public/placeholders/product_launch/feature_4.png +0 -0
  209. package/dist/kits/blocks/public/placeholders/product_launch/hero.png +0 -0
  210. package/dist/kits/blocks/public/placeholders/saas_dashboard/analytics.png +0 -0
  211. package/dist/kits/blocks/public/placeholders/saas_dashboard/chat.png +0 -0
  212. package/dist/kits/blocks/public/placeholders/saas_dashboard/projectBoard.png +0 -0
  213. package/dist/kits/data/.gitkeep +0 -0
  214. package/dist/kits/data/README.md +80 -0
  215. package/dist/kits/data/app/(protected)/admin/posts/page.tsx +5 -0
  216. package/dist/kits/data/app/(protected)/admin/users/page.tsx +5 -0
  217. package/dist/kits/data/app/api/posts/[id]/route.ts +83 -0
  218. package/dist/kits/data/app/api/posts/route.ts +138 -0
  219. package/dist/kits/data/app/api/seed-demo/route.ts +45 -0
  220. package/dist/kits/data/app/api/users/[id]/route.ts +127 -0
  221. package/dist/kits/data/app/api/users/check-email/route.ts +18 -0
  222. package/dist/kits/data/app/api/users/check-unique/route.ts +27 -0
  223. package/dist/kits/data/app/api/users/route.ts +79 -0
  224. package/dist/kits/data/app/examples/demo/README.md +4 -0
  225. package/dist/kits/data/app/examples/demo/create-post-form.tsx +106 -0
  226. package/dist/kits/data/app/examples/demo/page.tsx +118 -0
  227. package/dist/kits/data/app/examples/demo/seed-demo-button.tsx +37 -0
  228. package/dist/kits/data/components/admin/posts-manager.tsx +719 -0
  229. package/dist/kits/data/components/admin/users-manager.tsx +432 -0
  230. package/dist/kits/data/lib/prisma.ts +15 -0
  231. package/dist/kits/data/lib/server/result.ts +90 -0
  232. package/dist/kits/data/package-deps.json +11 -0
  233. package/dist/kits/data/scripts/seed-demo.mjs +41 -0
  234. package/dist/kits/forms/.gitkeep +0 -0
  235. package/dist/kits/forms/README.md +49 -0
  236. package/dist/kits/forms/app/.gitkeep +0 -0
  237. package/dist/kits/forms/app/api/wizard/route.ts +71 -0
  238. package/dist/kits/forms/app/examples/forms/basic/page.tsx +124 -0
  239. package/dist/kits/forms/app/examples/forms/server-action/form-client.tsx +28 -0
  240. package/dist/kits/forms/app/examples/forms/server-action/page.tsx +71 -0
  241. package/dist/kits/forms/app/examples/forms/wizard/page.tsx +15 -0
  242. package/dist/kits/forms/app/examples/forms/wizard/wizard-client.tsx +2 -0
  243. package/dist/kits/forms/components/.gitkeep +0 -0
  244. package/dist/kits/forms/components/examples/wizard-client.tsx +231 -0
  245. package/dist/kits/forms/components/hooks/useCheckUnique.ts +79 -0
  246. package/dist/kits/forms/components/ui/button.tsx +122 -0
  247. package/dist/kits/forms/components/ui/checkbox.tsx +30 -0
  248. package/dist/kits/forms/components/ui/form/context.ts +33 -0
  249. package/dist/kits/forms/components/ui/form/form-control.tsx +28 -0
  250. package/dist/kits/forms/components/ui/form/form-description.tsx +22 -0
  251. package/dist/kits/forms/components/ui/form/form-field.tsx +36 -0
  252. package/dist/kits/forms/components/ui/form/form-item.tsx +21 -0
  253. package/dist/kits/forms/components/ui/form/form-label.tsx +24 -0
  254. package/dist/kits/forms/components/ui/form/form-message.tsx +29 -0
  255. package/dist/kits/forms/components/ui/form/form.tsx +26 -0
  256. package/dist/kits/forms/components/ui/input.tsx +27 -0
  257. package/dist/kits/forms/components/ui/label.tsx +29 -0
  258. package/dist/kits/forms/components/ui/select.tsx +25 -0
  259. package/dist/kits/forms/components/ui/switch.tsx +78 -0
  260. package/dist/kits/forms/components/ui/textarea.tsx +26 -0
  261. package/dist/kits/forms/lib/.gitkeep +0 -0
  262. package/dist/kits/forms/lib/forms/map-errors.ts +29 -0
  263. package/dist/kits/forms/lib/prisma.ts +16 -0
  264. package/dist/kits/forms/lib/utils.ts +9 -0
  265. package/dist/kits/forms/lib/validation/forms.ts +88 -0
  266. package/dist/kits/forms/lib/validation/wizard.ts +32 -0
  267. package/dist/kits/forms/package-deps.json +17 -0
  268. package/dist/utils/file-operations.d.ts +18 -0
  269. package/dist/utils/file-operations.d.ts.map +1 -0
  270. package/dist/utils/file-operations.js +327 -0
  271. package/dist/utils/file-operations.js.map +1 -0
  272. package/dist/utils/installation-tracker.d.ts +26 -0
  273. package/dist/utils/installation-tracker.d.ts.map +1 -0
  274. package/dist/utils/installation-tracker.js +98 -0
  275. package/dist/utils/installation-tracker.js.map +1 -0
  276. package/package.json +51 -21
  277. package/index.js +0 -1
@@ -0,0 +1,63 @@
1
+ import { NextResponse } from "next/server";
2
+ import { prisma } from "@/lib/prisma";
3
+ import { resetPasswordSchema } from "@/lib/validation/forms";
4
+ import { hash } from "bcryptjs";
5
+ import { createHash } from "crypto";
6
+ import { z } from "zod";
7
+
8
+ export async function POST(req: Request) {
9
+ if (process.env.NEXTWORKS_ENABLE_PASSWORD_RESET !== "1") {
10
+ return NextResponse.json({ message: "Not found" }, { status: 404 });
11
+ }
12
+ let body: unknown;
13
+ try {
14
+ body = await req.json();
15
+ } catch {
16
+ return NextResponse.json({ message: "Invalid request" }, { status: 400 });
17
+ }
18
+
19
+ try {
20
+ const parsed = resetPasswordSchema.parse(body);
21
+ const { token, password } = parsed;
22
+
23
+ const tokenHash = createHash("sha256").update(token).digest("hex");
24
+ const pr = await prisma.passwordReset.findFirst({ where: { tokenHash } });
25
+ if (!pr) {
26
+ return NextResponse.json({ message: "Invalid or expired token" }, { status: 400 });
27
+ }
28
+ if ((pr as any).used) {
29
+ return NextResponse.json({ message: "Token already used" }, { status: 400 });
30
+ }
31
+ if (pr.expires < new Date()) {
32
+ return NextResponse.json({ message: "Token expired" }, { status: 400 });
33
+ }
34
+
35
+ const hashed = await hash(password, 10);
36
+ await prisma.user.update({ where: { id: pr.userId }, data: { password: hashed } });
37
+ await prisma.passwordReset.update({ where: { id: pr.id }, data: { used: true } });
38
+
39
+ return NextResponse.json({ message: "Password updated" });
40
+ } catch (err: any) {
41
+ if (err instanceof z.ZodError) {
42
+ return NextResponse.json({ message: "Validation failed", errors: (err as z.ZodError).issues }, { status: 400 });
43
+ }
44
+ return NextResponse.json({ message: "Failed" }, { status: 400 });
45
+ }
46
+ }
47
+
48
+ export async function GET(req: Request) {
49
+ if (process.env.NEXTWORKS_ENABLE_PASSWORD_RESET !== "1") {
50
+ return NextResponse.json({ message: "Not found" }, { status: 404 });
51
+ }
52
+ const url = new URL(req.url);
53
+ const token = url.searchParams.get("token");
54
+ if (!token) return NextResponse.json({ valid: false }, { status: 400 });
55
+
56
+ const tokenHash = createHash("sha256").update(token).digest("hex");
57
+ const pr = await prisma.passwordReset.findFirst({ where: { tokenHash } });
58
+ if (!pr) return NextResponse.json({ valid: false });
59
+ if ((pr as any).used) return NextResponse.json({ valid: false });
60
+ if (pr.expires < new Date()) return NextResponse.json({ valid: false });
61
+
62
+ return NextResponse.json({ valid: true });
63
+ }
@@ -0,0 +1,6 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ export async function POST(req: Request) {
4
+ // Placeholder endpoint: future work may implement email verification tokens.
5
+ return NextResponse.json({ message: "Not implemented" }, { status: 501 });
6
+ }
@@ -0,0 +1,41 @@
1
+ import { NextRequest } from "next/server";
2
+ import { hash } from "bcryptjs";
3
+ import { prisma } from "@/lib/prisma";
4
+ import { signupSchema } from "@/lib/validation/forms";
5
+ import { ZodError } from "zod";
6
+ import { jsonOk, jsonFail, jsonFromZod } from "@/lib/server/result";
7
+
8
+ export const runtime = "nodejs";
9
+
10
+ export async function POST(req: NextRequest) {
11
+ try {
12
+ const body = await req.json();
13
+ const data = signupSchema.parse(body);
14
+
15
+ const existing = await prisma.user.findUnique({
16
+ where: { email: data.email },
17
+ });
18
+ if (existing) {
19
+ return jsonFail("Email already in use", {
20
+ status: 409,
21
+ errors: { email: "Email already in use" },
22
+ code: "EMAIL_EXISTS",
23
+ });
24
+ }
25
+
26
+ const hashed = await hash(data.password, 10);
27
+ await prisma.user.create({
28
+ data: { name: data.name, email: data.email, password: hashed },
29
+ });
30
+
31
+ return jsonOk(undefined, {
32
+ status: 201,
33
+ message: "User created successfully",
34
+ });
35
+ } catch (error: unknown) {
36
+ if (error instanceof ZodError) {
37
+ return jsonFromZod(error, { status: 400, message: "Validation failed" });
38
+ }
39
+ return jsonFail("Error during signup", { status: 500 });
40
+ }
41
+ }
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import ForgotPasswordForm from "@/components/auth/forgot-password-form";
3
+
4
+ export default function ForgotPasswordPage() {
5
+ // Guard rendered server-side: the feature is opt-in via NEXTWORKS_ENABLE_PASSWORD_RESET=1
6
+ if (process.env.NEXTWORKS_ENABLE_PASSWORD_RESET !== "1") {
7
+ return (
8
+ <div className="mx-auto w-full max-w-md pt-6">
9
+ <h2 className="text-foreground text-center text-2xl font-bold">
10
+ Not found
11
+ </h2>
12
+ <p className="text-muted-foreground mt-1 text-center text-sm">
13
+ Password reset is disabled.
14
+ </p>
15
+ </div>
16
+ );
17
+ }
18
+
19
+ // Render the client component (the form)
20
+ return <ForgotPasswordForm />;
21
+ }
@@ -0,0 +1,5 @@
1
+ import LoginForm from "@/components/auth/login-form";
2
+
3
+ export default function LoginPage() {
4
+ return <LoginForm />;
5
+ }
@@ -0,0 +1,187 @@
1
+ "use client";
2
+
3
+ import React, { useEffect, useState } from "react";
4
+ import { useSearchParams, useRouter } from "next/navigation";
5
+ import { useForm } from "react-hook-form";
6
+ import { zodResolver } from "@hookform/resolvers/zod";
7
+ import { resetPasswordSchema } from "@/lib/validation/forms";
8
+ import { Input } from "@/components/ui/input";
9
+ import { Button } from "@/components/ui/button";
10
+ import { Form } from "@/components/ui/form/form";
11
+ import { FormField } from "@/components/ui/form/form-field";
12
+ import { FormItem } from "@/components/ui/form/form-item";
13
+ import { FormLabel } from "@/components/ui/form/form-label";
14
+ import { FormControl } from "@/components/ui/form/form-control";
15
+ import { FormMessage } from "@/components/ui/form/form-message";
16
+ import { toast } from "sonner";
17
+ import { signOut } from "next-auth/react";
18
+
19
+ export default function ResetPasswordPage() {
20
+ // Do not gate rendering on process.env here — rendering must be consistent
21
+ // between server and client to avoid hydration mismatches. The API routes
22
+ // already enforce the feature guard (returning 404) so we let the client
23
+ // always render and surface the API's response.
24
+ const searchParams = useSearchParams();
25
+ const router = useRouter();
26
+ const token = searchParams.get("token") || "";
27
+ const [valid, setValid] = useState<boolean | null>(null);
28
+
29
+ const methods = useForm({
30
+ resolver: zodResolver(resetPasswordSchema) as any,
31
+ defaultValues: { token, password: "", confirmPassword: "" },
32
+ });
33
+ const {
34
+ handleSubmit,
35
+ control,
36
+ formState: { isSubmitting },
37
+ } = methods;
38
+
39
+ useEffect(() => {
40
+ (async () => {
41
+ if (!token) return setValid(false);
42
+ try {
43
+ const res = await fetch(
44
+ `/api/auth/reset-password?token=${encodeURIComponent(token)}`,
45
+ );
46
+ if (res.ok) {
47
+ const json = await res.json();
48
+ setValid(!!json.valid);
49
+ } else {
50
+ setValid(false);
51
+ }
52
+ } catch {
53
+ setValid(false);
54
+ }
55
+ })();
56
+ }, [token]);
57
+
58
+ const onSubmit = async (data: any) => {
59
+ try {
60
+ const res = await fetch("/api/auth/reset-password", {
61
+ method: "POST",
62
+ body: JSON.stringify(data),
63
+ headers: { "Content-Type": "application/json" },
64
+ });
65
+ if (res.ok) {
66
+ toast.success("Password updated. You can now sign in.");
67
+ try {
68
+ // Ensure any existing session is cleared so the login page doesn't
69
+ // immediately redirect away. This also avoids confusion during testing.
70
+ await signOut({ redirect: false });
71
+ } catch (e) {
72
+ // ignore signOut failures but log for debugging
73
+ // eslint-disable-next-line no-console
74
+ console.error("signOut failed:", e);
75
+ }
76
+ // Poll /api/auth/session until it returns null, or timeout after 2s.
77
+ // This avoids the race where signOut is accepted server-side but the
78
+ // client still believes it's authenticated due to caching or timing.
79
+ const waitForSignOut = async (timeout = 2000, interval = 200) => {
80
+ const start = Date.now();
81
+ while (Date.now() - start < timeout) {
82
+ try {
83
+ const r = await fetch("/api/auth/session");
84
+ if (r.ok) {
85
+ const json = await r.json();
86
+ if (!json) {
87
+ // session cleared
88
+ window.location.href = "/auth/login?signup=1";
89
+ return;
90
+ }
91
+ }
92
+ } catch (e) {
93
+ // ignore transient fetch errors
94
+ }
95
+ // eslint-disable-next-line no-await-in-loop
96
+ await new Promise((res) => setTimeout(res, interval));
97
+ }
98
+ // Timeout: navigate anyway to ensure user sees login
99
+ window.location.href = "/auth/login?signup=1";
100
+ };
101
+ waitForSignOut();
102
+ } else {
103
+ const json = await res.json();
104
+ toast.error(json?.message || "Failed to reset password");
105
+ }
106
+ } catch (e) {
107
+ toast.error("Failed to reset password");
108
+ }
109
+ };
110
+
111
+ if (valid === null)
112
+ return (
113
+ <div className="mx-auto w-full max-w-md pt-6">Checking token...</div>
114
+ );
115
+ if (valid === false)
116
+ return (
117
+ <div className="mx-auto w-full max-w-md pt-6">
118
+ Invalid or expired token.
119
+ </div>
120
+ );
121
+
122
+ return (
123
+ <div className="mx-auto w-full max-w-md pt-6">
124
+ <h2 className="text-foreground text-center text-2xl font-bold">
125
+ Reset password
126
+ </h2>
127
+ <p className="text-muted-foreground mt-1 text-center text-sm">
128
+ Set a new password for your account.
129
+ </p>
130
+
131
+ <Form methods={methods}>
132
+ <form
133
+ onSubmit={handleSubmit(onSubmit)}
134
+ className="border-border bg-card space-y-4 rounded-lg border p-6 shadow-sm"
135
+ >
136
+ <FormField
137
+ control={control}
138
+ name="password"
139
+ render={({ field: f }) => (
140
+ <FormItem>
141
+ <FormLabel>New password</FormLabel>
142
+ <FormControl>
143
+ <Input
144
+ id="password"
145
+ type="password"
146
+ placeholder="At least 6 characters"
147
+ {...f}
148
+ />
149
+ </FormControl>
150
+ <FormMessage />
151
+ </FormItem>
152
+ )}
153
+ />
154
+
155
+ <FormField
156
+ control={control}
157
+ name="confirmPassword"
158
+ render={({ field: f }) => (
159
+ <FormItem>
160
+ <FormLabel>Confirm password</FormLabel>
161
+ <FormControl>
162
+ <Input
163
+ id="confirmPassword"
164
+ type="password"
165
+ placeholder="Repeat password"
166
+ {...f}
167
+ />
168
+ </FormControl>
169
+ <FormMessage />
170
+ </FormItem>
171
+ )}
172
+ />
173
+
174
+ <FormField
175
+ control={control}
176
+ name="token"
177
+ render={({ field: f }) => <input type="hidden" {...f} />}
178
+ />
179
+
180
+ <Button type="submit" disabled={isSubmitting} className="w-full">
181
+ Set password
182
+ </Button>
183
+ </form>
184
+ </Form>
185
+ </div>
186
+ );
187
+ }
@@ -0,0 +1,5 @@
1
+ import SignupForm from "@/components/auth/signup-form";
2
+
3
+ export default function SignupPage() {
4
+ return <SignupForm />;
5
+ }
@@ -0,0 +1,11 @@
1
+ export default function VerifyEmailPage() {
2
+ return (
3
+ <div className="mx-auto max-w-sm py-10">
4
+ <h1 className="text-xl font-semibold">Verify your email</h1>
5
+ <p className="text-sm text-muted-foreground mt-2">
6
+ Placeholder page. In a production app you might show a message that a
7
+ verification email has been sent.
8
+ </p>
9
+ </div>
10
+ );
11
+ }
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import MinimalLogoutButton from "@/components/auth/minimal-logout-button";
6
+
7
+ /**
8
+ * Minimal admin header with basic links.
9
+ * - Uses only Next.js primitives and basic utility classes
10
+ * - Independent from any UI kit
11
+ */
12
+ export default function AdminHeader() {
13
+ const pathname = usePathname();
14
+ const isActive = (href: string) => {
15
+ if (!pathname) return false;
16
+ return pathname === href || pathname.startsWith(href + "/");
17
+ };
18
+
19
+ const linkClass = (href: string) =>
20
+ [
21
+ "no-underline border-b-2 pb-0.5 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 rounded-sm",
22
+ isActive(href)
23
+ ? "text-foreground border-foreground"
24
+ : "text-foreground/70 border-transparent",
25
+ ].join(" ");
26
+
27
+ return (
28
+ <header className="bg-background/50 supports-[backdrop-filter]:bg-background/60 border-b backdrop-blur">
29
+ <div className="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3">
30
+ <nav className="flex items-center gap-4 text-sm">
31
+ <Link
32
+ className={linkClass("/dashboard")}
33
+ href="/dashboard"
34
+ aria-current={isActive("/dashboard") ? "page" : undefined}
35
+ >
36
+ Dashboard
37
+ </Link>
38
+ <Link
39
+ className={linkClass("/admin/users")}
40
+ href="/admin/users"
41
+ aria-current={isActive("/admin/users") ? "page" : undefined}
42
+ >
43
+ Users
44
+ </Link>
45
+ <Link
46
+ className={linkClass("/admin/posts")}
47
+ href="/admin/posts"
48
+ aria-current={isActive("/admin/posts") ? "page" : undefined}
49
+ >
50
+ Posts
51
+ </Link>
52
+ </nav>
53
+ <MinimalLogoutButton />
54
+ </div>
55
+ </header>
56
+ );
57
+ }
@@ -0,0 +1,237 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import type { Session } from "next-auth";
5
+ import Link from "next/link";
6
+ import { cn } from "@/lib/utils";
7
+ import LogoutButton from "@/components/auth/logout-button";
8
+ import {
9
+ Settings,
10
+ Key,
11
+ FolderGit2,
12
+ BookOpen,
13
+ MessageCircle,
14
+ } from "lucide-react";
15
+
16
+ export interface DashboardProps {
17
+ id?: string;
18
+ className?: string;
19
+ session?: Session | null;
20
+
21
+ headingText?: { text?: string; className?: string };
22
+ subheadingText?: { text?: string; className?: string };
23
+
24
+ container?: { className?: string };
25
+ card?: { className?: string };
26
+ heading?: { className?: string };
27
+ subheading?: { className?: string };
28
+ actions?: { className?: string };
29
+
30
+ // MVP blocks
31
+ showProfileCard?: boolean;
32
+ quickLinks?: Array<{ label: string; href: string; icon?: React.ReactNode }>;
33
+ quickLinksTitle?: { text?: string; className?: string };
34
+
35
+ // Slots for blocks
36
+ profileCard?: { className?: string };
37
+ profileAvatar?: { className?: string };
38
+ profileContent?: { className?: string };
39
+ quickLinksCard?: { className?: string };
40
+ quickLinksGrid?: { className?: string };
41
+ quickLinkItem?: { className?: string };
42
+ quickLinkIcon?: { className?: string };
43
+
44
+ showLogout?: boolean;
45
+ ariaLabel?: string;
46
+ }
47
+
48
+ export default function Dashboard({
49
+ id,
50
+ className,
51
+ session,
52
+ headingText = {
53
+ text: "Welcome",
54
+ className: "text-2xl font-semibold text-foreground",
55
+ },
56
+ subheadingText,
57
+ container = { className: "mx-auto max-w-5xl p-6" },
58
+ card = {
59
+ className:
60
+ "rounded-lg border border-border bg-card p-6 shadow-sm text-card-foreground",
61
+ },
62
+ heading = { className: "" },
63
+ subheading = { className: "mt-2 text-sm text-muted-foreground" },
64
+ actions = { className: "flex items-center gap-3" },
65
+
66
+ // MVP blocks defaults
67
+ showProfileCard = true,
68
+ quickLinks = [
69
+ {
70
+ label: "Settings",
71
+ href: "/settings/profile/",
72
+ icon: <Settings className="h-4 w-4" />,
73
+ },
74
+ {
75
+ label: "API Keys",
76
+ href: "/settings/api-keys",
77
+ icon: <Key className="h-4 w-4" />,
78
+ },
79
+ {
80
+ label: "Projects",
81
+ href: "/projects",
82
+ icon: <FolderGit2 className="h-4 w-4" />,
83
+ },
84
+ { label: "Docs", href: "/docs", icon: <BookOpen className="h-4 w-4" /> },
85
+ {
86
+ label: "Support",
87
+ href: "/support",
88
+ icon: <MessageCircle className="h-4 w-4" />,
89
+ },
90
+ ],
91
+ quickLinksTitle = {
92
+ text: "Quick links",
93
+ className: "text-sm font-medium text-muted-foreground",
94
+ },
95
+
96
+ profileCard = {
97
+ className: "rounded-lg border border-border bg-card p-6 shadow-sm",
98
+ },
99
+ profileAvatar = {
100
+ className:
101
+ "h-12 w-12 rounded-full bg-primary text-primary-foreground flex items-center justify-center text-base font-semibold",
102
+ },
103
+ profileContent = { className: "mt-3" },
104
+ quickLinksCard = {
105
+ className: "rounded-lg border border-border bg-card p-6 shadow-sm",
106
+ },
107
+ quickLinksGrid = { className: "mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2" },
108
+ quickLinkItem = {
109
+ className:
110
+ "group flex items-center gap-3 rounded-md border border-border bg-secondary/30 p-3 text-sm transition-all duration-200 hover:-translate-y-0.5 hover:bg-secondary/50",
111
+ },
112
+ quickLinkIcon = {
113
+ className:
114
+ "text-muted-foreground group-hover:text-foreground transition-colors",
115
+ },
116
+
117
+ showLogout = true,
118
+ ariaLabel = "Dashboard",
119
+ }: DashboardProps) {
120
+ const displayName =
121
+ (session?.user?.name && session.user.name.trim().length > 0
122
+ ? session.user.name
123
+ : session?.user?.email) ?? "";
124
+
125
+ const computedSubheading =
126
+ subheadingText?.text ?? (displayName ? `Signed in as ${displayName}` : "");
127
+
128
+ const nameOrEmail = displayName || session?.user?.email || "";
129
+ const initials = nameOrEmail
130
+ .split(" ")
131
+ .map((s) => s[0])
132
+ .filter(Boolean)
133
+ .slice(0, 2)
134
+ .join("")
135
+ .toUpperCase();
136
+
137
+ const createdAtRaw =
138
+ (session as any)?.user?.createdAt ?? (session as any)?.createdAt;
139
+ const createdAt = createdAtRaw ? new Date(createdAtRaw) : null;
140
+
141
+ return (
142
+ <main
143
+ id={id}
144
+ aria-label={ariaLabel}
145
+ className={cn(container.className, className)}
146
+ >
147
+ {/* Header card */}
148
+ <div className={cn(card.className)}>
149
+ <div className="flex items-center justify-between">
150
+ {headingText?.text && (
151
+ <h1 className={cn(headingText.className, heading.className)}>
152
+ {headingText.text}
153
+ </h1>
154
+ )}
155
+ {showLogout && (
156
+ <div className={cn(actions.className)}>
157
+ <LogoutButton />
158
+ </div>
159
+ )}
160
+ </div>
161
+
162
+ {computedSubheading && (
163
+ <p className={cn(subheading.className, subheadingText?.className)}>
164
+ {computedSubheading}
165
+ </p>
166
+ )}
167
+ </div>
168
+
169
+ {/* Content grid */}
170
+ <div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-2">
171
+ {showProfileCard && (
172
+ <div className={cn(profileCard.className)}>
173
+ <div className="flex items-center gap-4">
174
+ {session?.user?.image ? (
175
+ // eslint-disable-next-line @next/next/no-img-element
176
+ <img
177
+ src={session.user.image}
178
+ alt="Avatar"
179
+ className={cn(
180
+ "h-12 w-12 rounded-full object-cover",
181
+ profileAvatar.className,
182
+ )}
183
+ />
184
+ ) : (
185
+ <div className={cn(profileAvatar.className)}>
186
+ {initials || "U"}
187
+ </div>
188
+ )}
189
+ <div className="min-w-0">
190
+ <div className="text-foreground truncate text-base font-medium">
191
+ {session?.user?.name || session?.user?.email || "User"}
192
+ </div>
193
+ {session?.user?.email && (
194
+ <div className="text-muted-foreground truncate text-sm">
195
+ {session.user.email}
196
+ </div>
197
+ )}
198
+ </div>
199
+ </div>
200
+ <div className={cn(profileContent.className)}>
201
+ {createdAt && !Number.isNaN(createdAt.getTime()) && (
202
+ <p className="text-muted-foreground text-sm">
203
+ Member since {createdAt.toLocaleDateString()}
204
+ </p>
205
+ )}
206
+ </div>
207
+ </div>
208
+ )}
209
+
210
+ {/* Quick links */}
211
+ <div className={cn(quickLinksCard.className)}>
212
+ {quickLinksTitle?.text && (
213
+ <div className={cn(quickLinksTitle.className)}>
214
+ {quickLinksTitle.text}
215
+ </div>
216
+ )}
217
+ <div className={cn(quickLinksGrid.className)}>
218
+ {quickLinks?.map((item, i) => (
219
+ <Link
220
+ key={`${item.label}-${i}`}
221
+ href={item.href || "#"}
222
+ className={cn(quickLinkItem.className)}
223
+ >
224
+ {item.icon && (
225
+ <span className={cn(quickLinkIcon.className)}>
226
+ {item.icon}
227
+ </span>
228
+ )}
229
+ <span className="text-foreground truncate">{item.label}</span>
230
+ </Link>
231
+ ))}
232
+ </div>
233
+ </div>
234
+ </div>
235
+ </main>
236
+ );
237
+ }