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,142 @@
1
+ import type { NextAuthOptions } from "next-auth";
2
+ import NextAuth from "next-auth";
3
+ import GitHubProvider from "next-auth/providers/github";
4
+ import CredentialsProvider from "next-auth/providers/credentials";
5
+ import { PrismaAdapter } from "@next-auth/prisma-adapter";
6
+ import { prisma } from "@/lib/prisma";
7
+ import { compare } from "bcryptjs";
8
+ import { z } from "zod";
9
+ import { zodErrorToFieldErrors } from "@/lib/api/errors";
10
+
11
+ export const authOptions: NextAuthOptions = {
12
+ adapter: PrismaAdapter(prisma),
13
+ providers: [
14
+ CredentialsProvider({
15
+ name: "Credentials",
16
+ credentials: {
17
+ email: { label: "Email", type: "email" },
18
+ password: { label: "Password", type: "password" },
19
+ },
20
+ async authorize(credentials) {
21
+ const schema = z.object({
22
+ email: z.string().email("Invalid email address"),
23
+ password: z.string().min(1, "Password is required"),
24
+ });
25
+
26
+ try {
27
+ const { email, password } = schema.parse(credentials ?? {});
28
+
29
+ const user = await prisma.user.findFirst({
30
+ where: {
31
+ email: {
32
+ equals: email,
33
+ mode: "insensitive",
34
+ },
35
+ },
36
+ select: {
37
+ id: true,
38
+ name: true,
39
+ email: true,
40
+ password: true,
41
+ role: true,
42
+ },
43
+ });
44
+
45
+ if (!user || !user.password) {
46
+ throw new Error(
47
+ JSON.stringify({
48
+ message: "Invalid credentials",
49
+ errors: { email: "Email not found" },
50
+ }),
51
+ );
52
+ }
53
+
54
+ const isValid = await compare(password, user.password);
55
+ if (!isValid) {
56
+ throw new Error(
57
+ JSON.stringify({
58
+ message: "Invalid credentials",
59
+ errors: { password: "Incorrect password" },
60
+ }),
61
+ );
62
+ }
63
+
64
+ return {
65
+ id: user.id,
66
+ name: user.name,
67
+ email: user.email,
68
+ role: user.role,
69
+ } as any;
70
+ } catch (err: any) {
71
+ if (err instanceof z.ZodError) {
72
+ throw new Error(
73
+ JSON.stringify({
74
+ message: "Validation failed",
75
+ errors: zodErrorToFieldErrors(err),
76
+ }),
77
+ );
78
+ }
79
+ try {
80
+ JSON.parse(err?.message);
81
+ throw err; // already structured
82
+ } catch {
83
+ throw new Error(
84
+ JSON.stringify({
85
+ message: "Login failed",
86
+ errors: { email: "Invalid email or password" },
87
+ }),
88
+ );
89
+ }
90
+ }
91
+ },
92
+ }),
93
+ ...(process.env.GITHUB_ID && process.env.GITHUB_SECRET
94
+ ? [
95
+ GitHubProvider({
96
+ clientId: process.env.GITHUB_ID!,
97
+ clientSecret: process.env.GITHUB_SECRET!,
98
+ }),
99
+ ]
100
+ : []),
101
+ ],
102
+ session: { strategy: "jwt" },
103
+ callbacks: {
104
+ async jwt({ token, user, trigger, session }) {
105
+ if (user) {
106
+ (token as any).id = (user as any).id;
107
+ token.name = user.name ?? "";
108
+ token.email = user.email ?? "";
109
+ (token as any).role =
110
+ (user as any).role ?? (token as any).role ?? "user";
111
+ }
112
+ if (trigger === "update" && session) {
113
+ const s = session as any;
114
+ if (typeof s.name === "string") token.name = s.name;
115
+ if (typeof s.email === "string") token.email = s.email;
116
+ if (typeof s.role === "string") (token as any).role = s.role;
117
+ if (s.image === null || typeof s.image === "string")
118
+ (token as any).image = s.image ?? null;
119
+ }
120
+ return token;
121
+ },
122
+ async session({ session, token }) {
123
+ if (session.user) {
124
+ (session.user as any).id = (token as any).id as string;
125
+ session.user.name = token.name ?? null;
126
+ session.user.email = token.email ?? "";
127
+ (session.user as any).role = (token as any).role ?? "user";
128
+ }
129
+ return session;
130
+ },
131
+ async redirect({ url, baseUrl }) {
132
+ if (url.startsWith("/")) return `${baseUrl}${url}`;
133
+ if (new URL(url).origin === baseUrl) return url;
134
+ return baseUrl;
135
+ },
136
+ },
137
+ pages: { signIn: "/auth/login" },
138
+ secret: process.env.NEXTAUTH_SECRET,
139
+ };
140
+
141
+ const handler = NextAuth(authOptions);
142
+ export { handler as GET, handler as POST };
@@ -0,0 +1,42 @@
1
+ import nodemailer from "nodemailer";
2
+ import { sendSmtpEmail } from "@/lib/email/provider-smtp";
3
+
4
+ let cachedTransport: nodemailer.Transporter | null = null;
5
+
6
+ async function getDevTransport() {
7
+ if (cachedTransport) return cachedTransport;
8
+ const testAccount = await nodemailer.createTestAccount();
9
+ const transport = nodemailer.createTransport({
10
+ host: "smtp.ethereal.email",
11
+ port: 587,
12
+ auth: { user: testAccount.user, pass: testAccount.pass },
13
+ });
14
+ cachedTransport = transport;
15
+ return transport;
16
+ }
17
+
18
+ export async function sendDevEmail(opts: {
19
+ to: string;
20
+ subject: string;
21
+ text?: string;
22
+ html?: string;
23
+ from?: string;
24
+ }) {
25
+ const smtpConfigured = !!(
26
+ process.env.SMTP_HOST && process.env.SMTP_USER && process.env.SMTP_PASS
27
+ );
28
+ if (smtpConfigured && process.env.NODE_ENV === "production") {
29
+ return sendSmtpEmail(opts).then((r) => ({ info: r.info, previewUrl: undefined }));
30
+ }
31
+
32
+ const transport = await getDevTransport();
33
+ const info = await transport.sendMail({
34
+ from: opts.from ?? `Nextworks <${process.env.NOREPLY_EMAIL ?? "no-reply@example.com"}>`,
35
+ to: opts.to,
36
+ subject: opts.subject,
37
+ text: opts.text,
38
+ html: opts.html,
39
+ });
40
+ const previewUrl = nodemailer.getTestMessageUrl(info) || undefined;
41
+ return { info, previewUrl };
42
+ }
@@ -0,0 +1,28 @@
1
+ import { sendDevEmail } from "@/lib/email/dev-transport";
2
+ import { sendSmtpEmail } from "@/lib/email/provider-smtp";
3
+
4
+ export async function sendEmail(opts: {
5
+ to: string;
6
+ subject: string;
7
+ text?: string;
8
+ html?: string;
9
+ from?: string;
10
+ }) {
11
+ const smtpConfigured = !!(
12
+ process.env.SMTP_HOST && process.env.SMTP_USER && process.env.SMTP_PASS
13
+ );
14
+ if (smtpConfigured) {
15
+ return sendSmtpEmail(opts);
16
+ }
17
+ if (process.env.NEXTWORKS_USE_DEV_EMAIL === "1") {
18
+ return sendDevEmail(opts);
19
+ }
20
+ throw new Error("No email transport configured");
21
+ }
22
+
23
+ export function isEmailProviderConfigured() {
24
+ return !!(
25
+ (process.env.SMTP_HOST && process.env.SMTP_USER && process.env.SMTP_PASS) ||
26
+ process.env.MAILER_API_KEY
27
+ );
28
+ }
@@ -0,0 +1,36 @@
1
+ import nodemailer from "nodemailer";
2
+
3
+ export async function sendSmtpEmail(opts: {
4
+ to: string;
5
+ subject: string;
6
+ text?: string;
7
+ html?: string;
8
+ from?: string;
9
+ }) {
10
+ const host = process.env.SMTP_HOST;
11
+ const port = parseInt(process.env.SMTP_PORT || "587", 10);
12
+ const secure = port === 465;
13
+ const user = process.env.SMTP_USER;
14
+ const pass = process.env.SMTP_PASS;
15
+
16
+ if (!host || !user || !pass) throw new Error("SMTP not configured");
17
+
18
+ const transporter = nodemailer.createTransport({
19
+ host,
20
+ port,
21
+ secure,
22
+ auth: { user, pass },
23
+ });
24
+
25
+ const info = await transporter.sendMail({
26
+ from:
27
+ opts.from ??
28
+ `No Reply <${process.env.NOREPLY_EMAIL ?? "no-reply@example.com"}>`,
29
+ to: opts.to,
30
+ subject: opts.subject,
31
+ text: opts.text,
32
+ html: opts.html,
33
+ });
34
+
35
+ return { info };
36
+ }
@@ -0,0 +1,11 @@
1
+ export function mapApiErrorsToForm(methods: any, payload: any) {
2
+ if (!payload || typeof payload !== "object") return null;
3
+ const errors = payload.errors || null;
4
+ const message = payload.message || null;
5
+ if (errors && typeof errors === "object") {
6
+ Object.entries(errors).forEach(([field, msg]) => {
7
+ methods.setError(field as any, { type: "server", message: String(msg) });
8
+ });
9
+ }
10
+ return message;
11
+ }
@@ -0,0 +1,6 @@
1
+ import { hash } from "bcryptjs";
2
+
3
+ export async function hashPassword(password: string) {
4
+ const saltRounds = 10;
5
+ return hash(password, saltRounds);
6
+ }
@@ -0,0 +1,15 @@
1
+ import "server-only";
2
+
3
+ import { PrismaClient } from "@prisma/client";
4
+
5
+ const globalForPrisma = globalThis as unknown as {
6
+ prisma: PrismaClient | undefined;
7
+ };
8
+
9
+ export const prisma =
10
+ globalForPrisma.prisma ??
11
+ new PrismaClient({
12
+ log: ["error", "warn"],
13
+ });
14
+
15
+ if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
@@ -0,0 +1,45 @@
1
+ export function jsonOk(data?: any, opts?: { status?: number; message?: string }) {
2
+ return Response.json({ success: true, data: data ?? null, message: opts?.message ?? null }, { status: opts?.status ?? 200 });
3
+ }
4
+
5
+ export function jsonFail(
6
+ message: string,
7
+ opts?: { status?: number; code?: string | number; errors?: Record<string, string> | null },
8
+ ) {
9
+ return Response.json(
10
+ { success: false, data: null, message, code: opts?.code ?? null, errors: opts?.errors ?? null },
11
+ { status: opts?.status ?? 400 },
12
+ );
13
+ }
14
+
15
+ export function jsonFromZod(err: any, opts?: { status?: number; message?: string }) {
16
+ const fieldErrors: Record<string, string> = {};
17
+ if (err?.issues && Array.isArray(err.issues)) {
18
+ for (const issue of err.issues) {
19
+ if (issue.path && issue.path.length > 0) {
20
+ fieldErrors[String(issue.path[0])] = issue.message;
21
+ }
22
+ }
23
+ }
24
+ return jsonFail(opts?.message || "Validation failed", {
25
+ status: opts?.status ?? 400,
26
+ errors: Object.keys(fieldErrors).length > 0 ? fieldErrors : null,
27
+ code: "VALIDATION_ERROR",
28
+ });
29
+ }
30
+
31
+ export function jsonFromPrisma(err: any) {
32
+ // Basic unique violation mapping (P2002)
33
+ const code = (err && err.code) || "PRISMA_ERROR";
34
+ if (code === "P2002") {
35
+ const meta = err.meta || {};
36
+ const target = Array.isArray(meta.target) ? meta.target[0] : meta.target;
37
+ const field = typeof target === "string" ? target : "field";
38
+ return jsonFail("Unique constraint violation", {
39
+ status: 409,
40
+ errors: { [field]: `${field} already in use` },
41
+ code: "UNIQUE_CONSTRAINT",
42
+ });
43
+ }
44
+ return jsonFail("Database error", { status: 500, code });
45
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,88 @@
1
+ import { z } from "zod";
2
+
3
+ export const signupSchema = z.object({
4
+ name: z.string().min(2, "Name must be at least 2 characters"),
5
+ email: z.string().email("Invalid email address"),
6
+ password: z.string().min(6, "Password must be at least 6 characters"),
7
+ });
8
+
9
+ export const loginSchema = z.object({
10
+ email: z.string().email("Invalid email address"),
11
+ password: z.string().min(6, "Password must be at least 6 characters"),
12
+ });
13
+
14
+ // Request body for forgot-password endpoint
15
+ export const forgotPasswordSchema = z.object({
16
+ email: z.string().email("Invalid email address"),
17
+ });
18
+
19
+ // Reset password schema (token + new password + confirm)
20
+ export const resetPasswordSchema = z
21
+ .object({
22
+ token: z.string().min(1, "Token is required"),
23
+ password: z.string().min(6, "Password must be at least 6 characters"),
24
+ confirmPassword: z.string().min(6, "Confirm password is required"),
25
+ })
26
+ .refine((data) => data.password === data.confirmPassword, {
27
+ path: ["confirmPassword"],
28
+ message: "Passwords do not match",
29
+ });
30
+
31
+ export const contactSchema = z.object({
32
+ name: z.string().min(2, "Name must be at least 2 characters"),
33
+ email: z.string().email("Invalid email address"),
34
+ subject: z.string().min(5, "Subject must be at least 5 characters"),
35
+ message: z.string().min(10, "Message must be at least 10 characters"),
36
+ });
37
+
38
+ export const newsletterSchema = z.object({
39
+ name: z.string().optional(),
40
+ email: z.string().email("Invalid email address"),
41
+ });
42
+
43
+ export const userSchema = z.object({
44
+ email: z.string().email("Invalid email address"),
45
+ name: z
46
+ .string()
47
+ .min(2, "Name must be at least 2 characters")
48
+ .optional()
49
+ .or(z.literal("")),
50
+ });
51
+
52
+ export const userUpdateSchema = z.object({
53
+ email: z.string().email("Invalid email address").optional(),
54
+ name: z
55
+ .string()
56
+ .min(2, "Name must be at least 2 characters")
57
+ .optional()
58
+ .or(z.literal("")),
59
+ image: z.string().optional().or(z.literal("")),
60
+ password: z
61
+ .string()
62
+ .min(6, "Password must be at least 6 characters")
63
+ .optional()
64
+ .or(z.literal("")),
65
+ emailVerified: z
66
+ .union([z.date(), z.string().transform((s) => new Date(s)), z.null()])
67
+ .optional(),
68
+ });
69
+
70
+ export const postSchema = z.object({
71
+ title: z
72
+ .string()
73
+ .min(1, "Title is required")
74
+ .min(3, "Title must be at least 3 characters"),
75
+ content: z.string().optional().or(z.literal("")),
76
+ authorId: z.string().min(1, "Author ID is required"),
77
+ // published is optional in forms/API - defaults to false in the DB
78
+ published: z.boolean().optional(),
79
+ });
80
+
81
+ export type NewsletterFormValues = z.infer<typeof newsletterSchema>;
82
+ export type ContactFormValues = z.infer<typeof contactSchema>;
83
+ export type SignupFormValues = z.infer<typeof signupSchema>;
84
+ export type LoginFormValues = z.infer<typeof loginSchema>;
85
+ export type UserFormValues = z.infer<typeof userSchema>;
86
+ export type PostFormValues = z.infer<typeof postSchema>;
87
+ export type ForgotPasswordFormValues = z.infer<typeof forgotPasswordSchema>;
88
+ export type ResetPasswordFormValues = z.infer<typeof resetPasswordSchema>;
@@ -0,0 +1,19 @@
1
+ {
2
+ "dependencies": {
3
+ "@next-auth/prisma-adapter": "^1.0.7",
4
+ "@prisma/client": "^6.16.1",
5
+ "@radix-ui/react-label": "^2.1.7",
6
+ "@radix-ui/react-slot": "^1.2.3",
7
+ "bcryptjs": "^3.0.2",
8
+ "class-variance-authority": "^0.7.1",
9
+ "clsx": "^2.1.1",
10
+ "lucide-react": "^0.542.0",
11
+ "next-auth": "^4.24.13",
12
+ "tailwind-merge": "^3.3.1",
13
+ "nodemailer": "^7.0.7",
14
+ "@nextworks/blocks-core": "*"
15
+ },
16
+ "devDependencies": {
17
+ "prisma": "^6.16.1"
18
+ }
19
+ }
@@ -0,0 +1,81 @@
1
+ // ========================================
2
+ // AUTH MODELS FOR NEXT-AUTH + PRISMA
3
+ // ========================================
4
+ //
5
+ // Copy these models to your prisma/schema.prisma file
6
+ //
7
+ // Required models for NextAuth.js with Prisma adapter:
8
+ // - User: Main user model
9
+ // - Account: OAuth provider accounts (GitHub, Google, etc.)
10
+ // - Session: User sessions
11
+ // - VerificationToken: Email verification tokens
12
+ //
13
+ // Optional models:
14
+ // - Post: Example model showing user relationships
15
+ //
16
+ // ========================================
17
+
18
+ model User {
19
+ id String @id @default(cuid())
20
+ email String @unique
21
+ name String?
22
+ image String?
23
+ emailVerified DateTime?
24
+ password String? // For credentials provider
25
+ createdAt DateTime @default(now())
26
+
27
+ // Relations
28
+ posts Post[]
29
+ accounts Account[]
30
+ sessions Session[]
31
+ }
32
+
33
+ model Account {
34
+ id String @id @default(cuid())
35
+ userId String
36
+ type String
37
+ provider String
38
+ providerAccountId String
39
+ access_token String?
40
+ token_type String?
41
+ scope String?
42
+
43
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
44
+
45
+ @@unique([provider, providerAccountId])
46
+ }
47
+
48
+ model Session {
49
+ id String @id @default(cuid())
50
+ sessionToken String @unique
51
+ userId String
52
+ expires DateTime
53
+
54
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
55
+ }
56
+
57
+ model VerificationToken {
58
+ identifier String
59
+ token String @unique
60
+ expires DateTime
61
+
62
+ @@unique([identifier, token])
63
+ }
64
+
65
+ // ========================================
66
+ // EXAMPLE MODEL (OPTIONAL)
67
+ // ========================================
68
+ // This is an example model showing how to create
69
+ // relationships with the User model. You can remove
70
+ // this if you don't need posts in your app.
71
+
72
+ model Post {
73
+ id String @id @default(cuid())
74
+ title String
75
+ content String?
76
+ published Boolean @default(false)
77
+ createdAt DateTime @default(now())
78
+ updatedAt DateTime @updatedAt
79
+ authorId String
80
+ author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
81
+ }
@@ -0,0 +1,81 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ model User {
11
+ id String @id @default(cuid())
12
+ email String @unique
13
+ name String?
14
+ image String?
15
+ emailVerified DateTime?
16
+ password String?
17
+ role String @default("user")
18
+ createdAt DateTime @default(now())
19
+ posts Post[]
20
+ accounts Account[]
21
+ sessions Session[]
22
+ // Relation for password reset tokens
23
+ passwordResets PasswordReset[]
24
+ }
25
+
26
+ model Account {
27
+ id String @id @default(cuid())
28
+ userId String
29
+ type String
30
+ provider String
31
+ providerAccountId String
32
+ access_token String?
33
+ token_type String?
34
+ scope String?
35
+
36
+ user User @relation(fields: [userId], references: [id])
37
+
38
+ @@unique([provider, providerAccountId])
39
+ }
40
+
41
+ model Session {
42
+ id String @id @default(cuid())
43
+ sessionToken String @unique
44
+ userId String
45
+ expires DateTime
46
+
47
+ user User @relation(fields: [userId], references: [id])
48
+ }
49
+
50
+ model VerificationToken {
51
+ identifier String
52
+ token String @unique
53
+ expires DateTime
54
+
55
+ @@unique([identifier, token])
56
+ }
57
+
58
+ model Post {
59
+ id String @id @default(cuid())
60
+ title String
61
+ slug String? @unique
62
+ content String?
63
+ excerpt String?
64
+ tags String?
65
+ published Boolean @default(false)
66
+ createdAt DateTime @default(now())
67
+ updatedAt DateTime @updatedAt
68
+ authorId String
69
+ author User @relation(fields: [authorId], references: [id])
70
+ }
71
+
72
+ model PasswordReset {
73
+ id String @id @default(cuid())
74
+ token String?
75
+ tokenHash String? @unique
76
+ userId String
77
+ expires DateTime
78
+ used Boolean @default(false)
79
+
80
+ user User @relation(fields: [userId], references: [id])
81
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { PrismaClient } from "@prisma/client";
4
+ import { createHash } from "crypto";
5
+
6
+ const prisma = new PrismaClient();
7
+
8
+ async function main() {
9
+ console.log("Reading existing PasswordReset rows with token column...");
10
+ const rows = await prisma.$queryRaw`SELECT id, token FROM "PasswordReset" WHERE token IS NOT NULL`;
11
+ console.log(`Found ${rows.length} rows`);
12
+ for (const row of rows) {
13
+ const id = row.id;
14
+ const token = row.token;
15
+ if (!token) continue;
16
+ const hash = createHash("sha256").update(token).digest("hex");
17
+ await prisma.passwordReset.update({ where: { id }, data: { tokenHash: hash } });
18
+ console.log(`Updated ${id}`);
19
+ }
20
+ console.log("Done.");
21
+ }
22
+
23
+ main().catch((e) => {
24
+ console.error(e);
25
+ process.exit(1);
26
+ }).finally(async () => await prisma.$disconnect());
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { PrismaClient } from "@prisma/client";
4
+
5
+ const prisma = new PrismaClient();
6
+
7
+ async function promote(email) {
8
+ if (!email) {
9
+ console.error("Usage: node scripts/promote-admin.mjs <email>");
10
+ process.exit(1);
11
+ }
12
+
13
+ const user = await prisma.user.findUnique({ where: { email } });
14
+ if (!user) {
15
+ console.error(`User not found: ${email}`);
16
+ process.exit(2);
17
+ }
18
+
19
+ if (user.role === "admin") {
20
+ console.log(`User ${email} is already an admin`);
21
+ process.exit(0);
22
+ }
23
+
24
+ await prisma.user.update({ where: { email }, data: { role: "admin" } });
25
+ console.log(`Promoted ${email} to admin`);
26
+ process.exit(0);
27
+ }
28
+
29
+ const email = process.argv[2];
30
+ promote(email).catch((err) => {
31
+ console.error(err);
32
+ process.exit(1);
33
+ });