nextworks 0.0.1 → 0.1.0-alpha.1

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 +145 -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 +106 -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 +104 -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,71 @@
1
+ import { NextRequest } from "next/server";
2
+ import {
3
+ jsonOk,
4
+ jsonFromZod,
5
+ jsonFromPrisma,
6
+ jsonFail,
7
+ } from "@/lib/server/result";
8
+ import { wizardServerSchema } from "@/lib/validation/wizard";
9
+ import { prisma } from "@/lib/prisma";
10
+ import type { Prisma } from "@prisma/client";
11
+ import { getServerSession } from "next-auth";
12
+ import { authOptions } from "@/lib/auth";
13
+
14
+ export const runtime = "nodejs";
15
+
16
+ export async function POST(req: NextRequest) {
17
+ try {
18
+ const body = await req.json();
19
+ const parsed = wizardServerSchema.parse(body);
20
+ // coerce to typed server input
21
+ const parsedTyped: import("@/lib/validation/wizard").WizardServerValues =
22
+ parsed;
23
+ // require session to attach author
24
+ const session = await getServerSession(authOptions);
25
+ if (!session?.user?.id)
26
+ return jsonFail("Not authenticated", { status: 401 });
27
+ const userId = session.user.id;
28
+ // map visibility to published flag
29
+ const published = parsed.visibility === "public";
30
+ // create Post record (persist full set of fields)
31
+ const data: Prisma.PostCreateInput = {
32
+ title: parsed.title,
33
+ slug: parsed.slug || undefined,
34
+ content: parsed.content || undefined,
35
+ excerpt: parsed.excerpt || undefined,
36
+ tags: parsed.tags || undefined,
37
+ published,
38
+ author: { connect: { id: userId } },
39
+ };
40
+ const created = await prisma.post.create({ data });
41
+ // return a cleaned, typed result object (maps published -> visibility)
42
+ const result: import("@/lib/validation/wizard").WizardServerValues & {
43
+ id: string;
44
+ createdAt: Date;
45
+ } = {
46
+ title: created.title,
47
+ slug: created.slug ?? "",
48
+ content: created.content ?? "",
49
+ excerpt: created.excerpt ?? "",
50
+ visibility: created.published ? "public" : "private",
51
+ tags: created.tags ?? "",
52
+ id: created.id,
53
+ createdAt: created.createdAt,
54
+ };
55
+ return jsonOk(result, { status: 201, message: "Created" });
56
+ } catch (e: unknown) {
57
+ // zod errors
58
+ // prisma errors
59
+ try {
60
+ // detect zod error
61
+ // @ts-ignore
62
+ if (e?.issues) return jsonFromZod(e);
63
+ } catch {}
64
+ try {
65
+ return jsonFromPrisma(e);
66
+ } catch {
67
+ console.error(e);
68
+ return jsonOk(null, { message: "Unknown error" });
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,124 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { useForm } from "react-hook-form";
5
+ import { zodResolver } from "@hookform/resolvers/zod";
6
+ import { Button } from "@/components/ui/button";
7
+ import { Form } from "@/components/ui/form/form";
8
+ import { FormField } from "@/components/ui/form/form-field";
9
+ import { FormItem } from "@/components/ui/form/form-item";
10
+ import { FormLabel } from "@/components/ui/form/form-label";
11
+ import { FormControl } from "@/components/ui/form/form-control";
12
+ import { FormMessage } from "@/components/ui/form/form-message";
13
+ import { Input } from "@/components/ui/input";
14
+ import { Select } from "@/components/ui/select";
15
+ import { Checkbox } from "@/components/ui/checkbox";
16
+ import { Switch } from "@/components/ui/switch";
17
+ import { z } from "zod";
18
+
19
+ const schema = z.object({
20
+ name: z.string().min(1, "Name is required"),
21
+ role: z.string().min(1, "Role is required"),
22
+ agree: z.boolean(),
23
+ notifications: z.boolean(),
24
+ });
25
+
26
+ type FormValues = z.infer<typeof schema>;
27
+
28
+ export default function BasicFormExample() {
29
+ const methods = useForm<FormValues>({
30
+ resolver: zodResolver(schema),
31
+ defaultValues: { name: "", role: "user", agree: false, notifications: true },
32
+ });
33
+ const { handleSubmit, control } = methods;
34
+
35
+ const onSubmit = (data: FormValues) => {
36
+ // For the example we just log. In a real app you'd submit to your API.
37
+ console.log("Form submit:", data);
38
+ alert(JSON.stringify(data, null, 2));
39
+ };
40
+
41
+ return (
42
+ <div className="mx-auto max-w-xl p-6">
43
+ <h1 className="mb-4 text-2xl font-bold">
44
+ Forms: Select / Checkbox / Switch example
45
+ </h1>
46
+ <Form methods={methods}>
47
+ <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
48
+ <FormField
49
+ control={control}
50
+ name="name"
51
+ render={({ field: f }) => (
52
+ <FormItem>
53
+ <FormLabel>Name</FormLabel>
54
+ <FormControl>
55
+ <Input {...f} placeholder="Your name" />
56
+ </FormControl>
57
+ <FormMessage />
58
+ </FormItem>
59
+ )}
60
+ />
61
+
62
+ <FormField
63
+ control={control}
64
+ name="role"
65
+ render={({ field: f }) => (
66
+ <FormItem>
67
+ <FormLabel>Role</FormLabel>
68
+ <FormControl>
69
+ <Select {...f}>
70
+ <option value="">Select role</option>
71
+ <option value="user">User</option>
72
+ <option value="admin">Admin</option>
73
+ </Select>
74
+ </FormControl>
75
+ <FormMessage />
76
+ </FormItem>
77
+ )}
78
+ />
79
+
80
+ <FormField
81
+ control={control}
82
+ name="agree"
83
+ render={({ field: f }) => (
84
+ <FormItem>
85
+ <FormLabel>Agree to terms</FormLabel>
86
+ <FormControl>
87
+ <div className="flex items-center gap-2">
88
+ <Checkbox
89
+ checked={!!f.value}
90
+ onChange={(e) => f.onChange(e.target.checked)}
91
+ />
92
+ <span className="text-sm text-muted-foreground">
93
+ I agree
94
+ </span>
95
+ </div>
96
+ </FormControl>
97
+ <FormMessage />
98
+ </FormItem>
99
+ )}
100
+ />
101
+
102
+ <FormField
103
+ control={control}
104
+ name="notifications"
105
+ render={({ field: f }) => (
106
+ <FormItem>
107
+ <FormLabel>Notifications</FormLabel>
108
+ <FormControl>
109
+ <Switch
110
+ checked={!!f.value}
111
+ onChange={(e) => f.onChange(e.target.checked)}
112
+ />
113
+ </FormControl>
114
+ <FormMessage />
115
+ </FormItem>
116
+ )}
117
+ />
118
+
119
+ <Button type="submit">Submit</Button>
120
+ </form>
121
+ </Form>
122
+ </div>
123
+ );
124
+ }
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { Input } from "@/components/ui/input";
6
+
7
+ export default function ServerActionForm({
8
+ action,
9
+ }: {
10
+ action: (formData: FormData) => Promise<void>;
11
+ }) {
12
+ return (
13
+ <form action={action} className="space-y-4">
14
+ <div>
15
+ <label className="mb-1 block text-sm font-medium">Title</label>
16
+ <Input name="title" placeholder="Post title" />
17
+ </div>
18
+ <div>
19
+ <label className="mb-1 block text-sm font-medium">Content</label>
20
+ <textarea
21
+ name="content"
22
+ className="h-28 w-full rounded-md border px-3 py-2 text-sm"
23
+ />
24
+ </div>
25
+ <Button type="submit">Create (server action)</Button>
26
+ </form>
27
+ );
28
+ }
@@ -0,0 +1,71 @@
1
+ import React from "react";
2
+ import ServerActionForm from "./form-client";
3
+ import { prisma } from "@/lib/prisma";
4
+ import { z } from "zod";
5
+ import { redirect } from "next/navigation";
6
+
7
+ const schema = z.object({
8
+ title: z.string().min(1, "Title is required"),
9
+ content: z.string().optional(),
10
+ });
11
+
12
+ export default async function Page({ searchParams }: any) {
13
+ async function createPost(formData: FormData) {
14
+ "use server";
15
+ const title = formData.get("title")?.toString() ?? "";
16
+ const content = formData.get("content")?.toString() ?? "";
17
+
18
+ const parsed = schema.safeParse({ title, content });
19
+ if (!parsed.success) {
20
+ // For simplicity in this example, throw an error which will surface in the server logs / dev overlay.
21
+ // In a real app, you'd return structured errors or use progressive enhancement with client-side validation.
22
+ throw new Error("Validation failed: " + parsed.error.message);
23
+ }
24
+
25
+ // NOTE: The Post model requires authorId. For this example we upsert a demo author so the FK is satisfied.
26
+ // In real apps you should resolve the current user via NextAuth session.
27
+ const fallbackAuthorId = "__demo_author__";
28
+
29
+ await prisma.user.upsert({
30
+ where: { id: fallbackAuthorId },
31
+ update: {},
32
+ create: {
33
+ id: fallbackAuthorId,
34
+ email: "demo@example.com",
35
+ name: "Demo User",
36
+ // No password for demo account
37
+ },
38
+ });
39
+
40
+ await prisma.post.create({
41
+ data: {
42
+ title: parsed.data.title,
43
+ content: parsed.data.content ?? null,
44
+ authorId: fallbackAuthorId,
45
+ },
46
+ });
47
+
48
+ redirect("/examples/forms/server-action?created=1");
49
+ }
50
+
51
+ const params = await searchParams;
52
+ const created = (params && (params as any).created) || false;
53
+
54
+ return (
55
+ <div className="mx-auto max-w-xl p-6">
56
+ <h1 className="mb-4 text-2xl font-bold">Server Action Example</h1>
57
+ <p className="mb-4 text-sm text-muted-foreground">
58
+ This demo shows a simple server action that validates with zod and
59
+ creates a Post via prisma (uses a demo fallback authorId).
60
+ </p>
61
+ {/* Pass the server action down to the client form */}
62
+ {/* @ts-ignore server action passed to client */}
63
+ <ServerActionForm action={createPost} />
64
+ {created && (
65
+ <p className="mt-4 text-sm text-green-600">
66
+ Post created successfully.
67
+ </p>
68
+ )}
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import WizardClient from "@/components/examples/wizard-client";
3
+
4
+ export default function WizardPage() {
5
+ return (
6
+ <div className="mx-auto max-w-3xl p-6">
7
+ <h1 className="mb-4 text-2xl font-bold">3-step Wizard (Forms)</h1>
8
+ <p className="mb-4 text-sm text-muted-foreground">
9
+ A minimal example showing per-step validation, state persistence, and
10
+ final submit to an API route that returns ApiResult field errors.
11
+ </p>
12
+ <WizardClient />
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,2 @@
1
+ // removed — component moved to components/examples/wizard-client.tsx
2
+ // This file intentionally left blank to avoid duplicate exports.
File without changes
@@ -0,0 +1,231 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { useForm, type Resolver } from "react-hook-form";
5
+ import { zodResolver } from "@hookform/resolvers/zod";
6
+ import { wizardClientSchema } from "@/lib/validation/wizard";
7
+ import type { WizardClientValues } from "@/lib/validation/wizard";
8
+ import { Form } from "@/components/ui/form/form";
9
+ import { FormField } from "@/components/ui/form/form-field";
10
+ import { FormItem } from "@/components/ui/form/form-item";
11
+ import { FormLabel } from "@/components/ui/form/form-label";
12
+ import { FormControl } from "@/components/ui/form/form-control";
13
+ import { FormMessage } from "@/components/ui/form/form-message";
14
+ import { Input } from "@/components/ui/input";
15
+ import { Textarea } from "@/components/ui/textarea";
16
+ import { Button } from "@/components/ui/button";
17
+ import { mapApiErrorsToForm } from "@/lib/forms/map-errors";
18
+ import { toast } from "sonner";
19
+
20
+ export default function WizardClient() {
21
+ const [step, setStep] = React.useState(0);
22
+ const methods = useForm<WizardClientValues>({
23
+ resolver: zodResolver(
24
+ wizardClientSchema,
25
+ ) as unknown as Resolver<WizardClientValues>,
26
+ defaultValues: {
27
+ title: "",
28
+ slug: "",
29
+ content: "",
30
+ excerpt: "",
31
+ visibility: "public",
32
+ tags: "",
33
+ },
34
+ mode: "onTouched",
35
+ });
36
+
37
+ const { control, handleSubmit, trigger, reset } = methods;
38
+
39
+ const canNext = async () => {
40
+ // Validate only current step fields
41
+ if (step === 0) {
42
+ const ok = await trigger(["title", "slug"]);
43
+ return ok;
44
+ }
45
+ if (step === 1) {
46
+ const ok = await trigger(["content", "excerpt"]);
47
+ return ok;
48
+ }
49
+ if (step === 2) {
50
+ const ok = await trigger(["visibility", "tags"]);
51
+ return ok;
52
+ }
53
+ return true;
54
+ };
55
+
56
+ const next = async () => {
57
+ const ok = await canNext();
58
+ if (!ok) return;
59
+ setStep((s) => Math.min(s + 1, 2));
60
+ };
61
+
62
+ const back = () => setStep((s) => Math.max(s - 1, 0));
63
+
64
+ const onSubmit = async (values: WizardClientValues) => {
65
+ try {
66
+ const res = await fetch("/api/wizard", {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/json" },
69
+ body: JSON.stringify(values),
70
+ });
71
+ const payload = await res.json().catch(() => null);
72
+ if (!res.ok || !payload?.success) {
73
+ const msg = payload ? mapApiErrorsToForm(methods, payload) : undefined;
74
+ toast.error(msg || payload?.message || "Submit failed");
75
+ return;
76
+ }
77
+ toast.success("Wizard submit succeeded");
78
+ reset();
79
+ setStep(0);
80
+ } catch (e) {
81
+ console.error(e);
82
+ toast.error("Submit failed");
83
+ }
84
+ };
85
+
86
+ return (
87
+ <div className="bg-card rounded-md p-6">
88
+ <Form methods={methods}>
89
+ <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
90
+ <div className="flex items-center gap-3">
91
+ <div
92
+ className={`rounded px-3 py-1 ${step === 0 ? "bg-primary text-white" : "bg-muted-foreground text-muted-foreground text-sm"}`}
93
+ >
94
+ Step 1
95
+ </div>
96
+ <div
97
+ className={`rounded px-3 py-1 ${step === 1 ? "bg-primary text-white" : "bg-muted-foreground text-muted-foreground text-sm"}`}
98
+ >
99
+ Step 2
100
+ </div>
101
+ <div
102
+ className={`rounded px-3 py-1 ${step === 2 ? "bg-primary text-white" : "bg-muted-foreground text-muted-foreground text-sm"}`}
103
+ >
104
+ Step 3
105
+ </div>
106
+ </div>
107
+
108
+ {step === 0 && (
109
+ <>
110
+ <FormField
111
+ control={control}
112
+ name="title"
113
+ render={({ field }) => (
114
+ <FormItem>
115
+ <FormLabel>Title</FormLabel>
116
+ <FormControl>
117
+ <Input {...field} />
118
+ </FormControl>
119
+ <FormMessage />
120
+ </FormItem>
121
+ )}
122
+ />
123
+
124
+ <FormField
125
+ control={control}
126
+ name="slug"
127
+ render={({ field }) => (
128
+ <FormItem>
129
+ <FormLabel>Slug (optional)</FormLabel>
130
+ <FormControl>
131
+ <Input {...field} />
132
+ </FormControl>
133
+ <FormMessage />
134
+ </FormItem>
135
+ )}
136
+ />
137
+ </>
138
+ )}
139
+
140
+ {step === 1 && (
141
+ <>
142
+ <FormField
143
+ control={control}
144
+ name="content"
145
+ render={({ field }) => (
146
+ <FormItem>
147
+ <FormLabel>Content</FormLabel>
148
+ <FormControl>
149
+ <Textarea {...field} rows={6} />
150
+ </FormControl>
151
+ <FormMessage />
152
+ </FormItem>
153
+ )}
154
+ />
155
+
156
+ <FormField
157
+ control={control}
158
+ name="excerpt"
159
+ render={({ field }) => (
160
+ <FormItem>
161
+ <FormLabel>Excerpt (optional)</FormLabel>
162
+ <FormControl>
163
+ <Textarea {...field} rows={2} />
164
+ </FormControl>
165
+ <FormMessage />
166
+ </FormItem>
167
+ )}
168
+ />
169
+ </>
170
+ )}
171
+
172
+ {step === 2 && (
173
+ <>
174
+ <FormField
175
+ control={control}
176
+ name="visibility"
177
+ render={({ field }) => (
178
+ <FormItem>
179
+ <FormLabel>Visibility</FormLabel>
180
+ <FormControl>
181
+ <select
182
+ {...field}
183
+ className="form-select w-full rounded border p-2"
184
+ >
185
+ <option value="public">Public</option>
186
+ <option value="private">Private</option>
187
+ </select>
188
+ </FormControl>
189
+ <FormMessage />
190
+ </FormItem>
191
+ )}
192
+ />
193
+
194
+ <FormField
195
+ control={control}
196
+ name="tags"
197
+ render={({ field }) => (
198
+ <FormItem>
199
+ <FormLabel>Tags (comma separated)</FormLabel>
200
+ <FormControl>
201
+ <Input {...field} />
202
+ </FormControl>
203
+ <FormMessage />
204
+ </FormItem>
205
+ )}
206
+ />
207
+ </>
208
+ )}
209
+
210
+ <div className="flex items-center gap-3">
211
+ {step > 0 ? (
212
+ <Button type="button" variant="ghost" onClick={back}>
213
+ Back
214
+ </Button>
215
+ ) : null}
216
+
217
+ {step < 2 ? (
218
+ <Button type="button" onClick={next}>
219
+ Next
220
+ </Button>
221
+ ) : (
222
+ <Button type="button" onClick={handleSubmit(onSubmit)}>
223
+ Submit
224
+ </Button>
225
+ )}
226
+ </div>
227
+ </form>
228
+ </Form>
229
+ </div>
230
+ );
231
+ }
@@ -0,0 +1,79 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, useRef } from "react";
4
+
5
+ type UseCheckUniqueResult = {
6
+ loading: boolean;
7
+ unique: boolean | null; // null = unknown / not checked
8
+ error: string | null;
9
+ };
10
+
11
+ export default function useCheckUnique(
12
+ field: string,
13
+ value: string | undefined | null,
14
+ delay = 500,
15
+ ): UseCheckUniqueResult {
16
+ const [loading, setLoading] = useState(false);
17
+ const [unique, setUnique] = useState<boolean | null>(null);
18
+ const [error, setError] = useState<string | null>(null);
19
+ const timer = useRef<number | undefined>(undefined);
20
+ const controllerRef = useRef<AbortController | null>(null);
21
+
22
+ useEffect(() => {
23
+ // Clear previous timer / request
24
+ if (timer.current) window.clearTimeout(timer.current);
25
+ if (controllerRef.current) controllerRef.current.abort();
26
+
27
+ // Reset state for empty values
28
+ if (!value) {
29
+ setLoading(false);
30
+ setUnique(null);
31
+ setError(null);
32
+ return;
33
+ }
34
+
35
+ setLoading(true);
36
+ setError(null);
37
+
38
+ timer.current = window.setTimeout(() => {
39
+ const controller = new AbortController();
40
+ controllerRef.current = controller;
41
+
42
+ (async () => {
43
+ try {
44
+ const res = await fetch("/api/users/check-unique", {
45
+ method: "POST",
46
+ headers: { "Content-Type": "application/json" },
47
+ body: JSON.stringify({ field, value }),
48
+ signal: controller.signal,
49
+ });
50
+ const payload = await res.json().catch(() => null);
51
+ if (!res.ok || !payload) {
52
+ setError("Failed to validate");
53
+ setUnique(null);
54
+ } else if (payload?.success) {
55
+ // Expecting { success: true, data: { unique: boolean } }
56
+ setUnique(Boolean(payload.data?.unique));
57
+ } else {
58
+ setError(payload?.message || "Validation failed");
59
+ setUnique(null);
60
+ }
61
+ } catch (e: unknown) {
62
+ if ((e as any)?.name === "AbortError") return;
63
+ setError("Validation failed");
64
+ setUnique(null);
65
+ } finally {
66
+ setLoading(false);
67
+ controllerRef.current = null;
68
+ }
69
+ })();
70
+ }, delay);
71
+
72
+ return () => {
73
+ if (timer.current) window.clearTimeout(timer.current);
74
+ if (controllerRef.current) controllerRef.current.abort();
75
+ };
76
+ }, [field, value, delay]);
77
+
78
+ return { loading, unique, error };
79
+ }