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,468 @@
1
+ "use client";
2
+
3
+ import React, {
4
+ useEffect,
5
+ useState,
6
+ useRef,
7
+ useCallback,
8
+ type JSX,
9
+ } from "react";
10
+ import { useRouter, useSearchParams } from "next/navigation";
11
+ import { signIn, useSession, getProviders } from "next-auth/react";
12
+
13
+ import Link from "next/link";
14
+ import { useForm } from "react-hook-form";
15
+ import { zodResolver } from "@hookform/resolvers/zod";
16
+ import { Button } from "@/components/ui/button";
17
+ import { Input } from "@/components/ui/input";
18
+ import { cn } from "@/lib/utils";
19
+ import { signupSchema, type SignupFormValues } from "@/lib/validation/forms";
20
+ import { Form } from "@/components/ui/form/form";
21
+ import { FormField } from "@/components/ui/form/form-field";
22
+ import { FormItem } from "@/components/ui/form/form-item";
23
+ import { FormLabel } from "@/components/ui/form/form-label";
24
+ import { FormMessage } from "@/components/ui/form/form-message";
25
+ import { FormControl } from "@/components/ui/form/form-control";
26
+ import { toast } from "sonner";
27
+ import { mapApiErrorsToForm } from "@/lib/forms/map-errors";
28
+ import useCheckUnique from "@/components/hooks/useCheckUnique";
29
+
30
+ export interface SignupFormProps {
31
+ id?: string;
32
+ className?: string;
33
+
34
+ /** Where to redirect if the user is already authenticated. Default: "/dashboard" */
35
+ defaultRedirect?: string;
36
+
37
+ /** Show the OAuth (GitHub) provider button. Default: true */
38
+ showGithub?: boolean;
39
+
40
+ /** Show the divider between providers and form fields. Default: true */
41
+ showDivider?: boolean;
42
+
43
+ /** Optional header text above the form */
44
+ headingText?: { text?: string; className?: string } | null;
45
+ /** Optional subheading text under the header */
46
+ subheadingText?: { text?: string; className?: string } | null;
47
+
48
+ /** Text labels you might want to override */
49
+ labels?: {
50
+ name?: string;
51
+ email?: string;
52
+ password?: string;
53
+ submit?: string;
54
+ submitting?: string;
55
+ github?: string;
56
+ success?: string;
57
+ };
58
+
59
+ /** Slots for styling overrides (similar to Navbar) */
60
+ container?: { className?: string };
61
+ headerWrapper?: { className?: string };
62
+ providerWrapper?: { className?: string };
63
+ providerButton?: {
64
+ variant?:
65
+ | "default"
66
+ | "destructive"
67
+ | "outline"
68
+ | "secondary"
69
+ | "ghost"
70
+ | "link";
71
+ size?: "default" | "sm" | "lg" | "icon";
72
+ className?: string;
73
+ };
74
+ divider?: {
75
+ wrapperClassName?: string;
76
+ lineClassName?: string;
77
+ textClassName?: string;
78
+ };
79
+ form?: { className?: string };
80
+ field?: { className?: string };
81
+ label?: { className?: string };
82
+ input?: { className?: string };
83
+ submitButton?: {
84
+ variant?:
85
+ | "default"
86
+ | "destructive"
87
+ | "outline"
88
+ | "secondary"
89
+ | "ghost"
90
+ | "link";
91
+ size?: "default" | "sm" | "lg" | "icon";
92
+ className?: string;
93
+ };
94
+ alerts?: {
95
+ errorClassName?: string;
96
+ successClassName?: string;
97
+ };
98
+
99
+ /** ARIA label for the form */
100
+ ariaLabel?: string;
101
+ }
102
+
103
+ export default function SignupForm({
104
+ id,
105
+ className,
106
+ defaultRedirect = "/dashboard",
107
+ showGithub = true,
108
+ showDivider = true,
109
+ headingText = {
110
+ text: "Create your account",
111
+ className: "text-2xl font-bold text-foreground text-center",
112
+ },
113
+ subheadingText = {
114
+ text: "Start your free trial in minutes",
115
+ className: "mt-1 text-sm text-muted-foreground text-center",
116
+ },
117
+ labels = {
118
+ name: "Name",
119
+ email: "Email",
120
+ password: "Password",
121
+ submit: "Create account",
122
+ submitting: "Creating account...",
123
+ github: "Continue with GitHub",
124
+ success: "Account created. You can now sign in.",
125
+ },
126
+ container = { className: "mx-auto w-full max-w-md pt-6" },
127
+ headerWrapper = { className: "mb-4" },
128
+ providerWrapper = { className: "mb-6" },
129
+ providerButton = {
130
+ variant: "outline",
131
+ size: "default",
132
+ className:
133
+ "w-full shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md",
134
+ },
135
+ divider = {
136
+ wrapperClassName: "relative mb-6",
137
+ lineClassName: "w-full border-t",
138
+ textClassName: "bg-background text-muted-foreground px-2",
139
+ },
140
+ form = {
141
+ className:
142
+ "space-y-4 rounded-lg border border-border bg-card p-6 shadow-sm",
143
+ },
144
+ field = { className: "space-y-2" },
145
+ label = { className: "" },
146
+ input = { className: "" },
147
+ submitButton = {
148
+ variant: "default",
149
+ size: "default",
150
+ className:
151
+ "w-full shadow-lg transition-all duration-200 hover:-translate-y-0.5 hover:shadow-xl",
152
+ },
153
+ alerts = {
154
+ errorClassName:
155
+ "mb-4 rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive",
156
+ successClassName:
157
+ "mb-4 rounded-md border border-primary/20 bg-primary/10 p-3 text-sm text-foreground",
158
+ },
159
+ ariaLabel = "Signup form",
160
+ }: SignupFormProps): JSX.Element {
161
+ const router = useRouter();
162
+ const searchParams = useSearchParams();
163
+ const { data: session, status } = useSession();
164
+ const redirectTo = searchParams.get("callbackUrl") || defaultRedirect;
165
+ const [formError, setFormError] = useState<string | null>(null);
166
+ const [success, setSuccess] = useState(false);
167
+ const [isGithubLoading, setIsGithubLoading] = useState(false);
168
+ const [githubAvailable, setGithubAvailable] = useState(false);
169
+
170
+ const formMethods = useForm<SignupFormValues>({
171
+ resolver: zodResolver(signupSchema),
172
+ defaultValues: {
173
+ name: "",
174
+ email: "",
175
+ password: "",
176
+ },
177
+ });
178
+ const {
179
+ control,
180
+ handleSubmit,
181
+ setError: setFieldError,
182
+ formState: { isSubmitting },
183
+ } = formMethods;
184
+
185
+ const emailValue = formMethods.watch("email");
186
+ const {
187
+ loading: checkingEmail,
188
+ unique: emailUnique,
189
+ error: emailUniqueError,
190
+ } = useCheckUnique("email", emailValue, 500);
191
+
192
+ const useDebouncedValidator = (
193
+ fn: (v: any) => Promise<boolean | string | undefined>,
194
+ delay = 500,
195
+ ) => {
196
+ const timer = useRef<number | undefined>(undefined);
197
+ return useCallback(
198
+ (value: any): Promise<boolean | string | undefined> => {
199
+ if (timer.current) window.clearTimeout(timer.current);
200
+ return new Promise((resolve) => {
201
+ timer.current = window.setTimeout(async () => {
202
+ try {
203
+ const res = await fn(value);
204
+ resolve(res);
205
+ } catch {
206
+ resolve(true);
207
+ }
208
+ }, delay);
209
+ });
210
+ },
211
+ [fn, delay],
212
+ );
213
+ };
214
+
215
+ const checkEmailAvailability = async (
216
+ email: string,
217
+ ): Promise<boolean | string | undefined> => {
218
+ if (!email) return true;
219
+ try {
220
+ const res = await fetch(
221
+ `/api/users/check-email?email=${encodeURIComponent(email)}`,
222
+ );
223
+ const payload = await res.json().catch(() => null);
224
+ if (!res.ok || !payload?.success) {
225
+ return true;
226
+ }
227
+ return payload.data?.available ? true : "Email already in use";
228
+ } catch {
229
+ return true;
230
+ }
231
+ };
232
+
233
+ const debouncedValidateEmail = useDebouncedValidator(
234
+ checkEmailAvailability,
235
+ 500,
236
+ );
237
+
238
+ useEffect(() => {
239
+ if (status === "authenticated" && session) {
240
+ router.push(redirectTo);
241
+ }
242
+ }, [status, session, router, redirectTo]);
243
+
244
+ useEffect(() => {
245
+ (async () => {
246
+ const providers = await getProviders();
247
+ setGithubAvailable(!!providers?.github);
248
+ })();
249
+ }, []);
250
+
251
+ const onSubmit = async (data: SignupFormValues) => {
252
+ setFormError(null);
253
+ setSuccess(false);
254
+ try {
255
+ const res = await fetch("/api/signup", {
256
+ method: "POST",
257
+ headers: { "Content-Type": "application/json" },
258
+ body: JSON.stringify(data),
259
+ });
260
+
261
+ const payload = await res.json().catch(() => null);
262
+ if (!res.ok || !payload?.success) {
263
+ const msg = payload
264
+ ? mapApiErrorsToForm(formMethods, payload)
265
+ : undefined;
266
+ setFormError(msg || payload?.message || "Signup failed");
267
+ return;
268
+ }
269
+
270
+ router.push(
271
+ `/auth/login?signup=1&callbackUrl=${encodeURIComponent(redirectTo)}`,
272
+ );
273
+ } catch (e: unknown) {
274
+ const err = e as Error;
275
+ setFormError(err.message || "Unknown error");
276
+ }
277
+ };
278
+
279
+ const handleGithubSignup = async () => {
280
+ setIsGithubLoading(true);
281
+ setFormError(null);
282
+ const result = await signIn("github", {
283
+ redirect: false,
284
+ callbackUrl: redirectTo,
285
+ });
286
+ if (result?.error) {
287
+ setFormError("GitHub login failed. Please try again.");
288
+ setIsGithubLoading(false);
289
+ }
290
+ };
291
+
292
+ return (
293
+ <div
294
+ id={id}
295
+ className={cn(container.className, className)}
296
+ aria-label={ariaLabel}
297
+ >
298
+ {(headingText?.text || subheadingText?.text) && (
299
+ <div className={cn(headerWrapper.className)}>
300
+ {headingText?.text && (
301
+ <h2 className={cn("font-poppins", headingText.className)}>
302
+ {headingText.text}
303
+ </h2>
304
+ )}
305
+ {subheadingText?.text && (
306
+ <p className={cn(subheadingText.className)}>
307
+ {subheadingText.text}
308
+ </p>
309
+ )}
310
+ </div>
311
+ )}
312
+
313
+ {formError && (
314
+ <div
315
+ className={cn(alerts.errorClassName)}
316
+ role="alert"
317
+ aria-live="polite"
318
+ >
319
+ {formError}
320
+ </div>
321
+ )}
322
+ {success && (
323
+ <div
324
+ className={cn(alerts.successClassName)}
325
+ role="status"
326
+ aria-live="polite"
327
+ >
328
+ {labels.success}
329
+ </div>
330
+ )}
331
+
332
+ {showGithub && githubAvailable && (
333
+ <>
334
+ <div className={cn(providerWrapper.className)}>
335
+ <Button
336
+ variant={providerButton.variant}
337
+ size={providerButton.size}
338
+ className={cn(providerButton.className)}
339
+ onClick={handleGithubSignup}
340
+ disabled={isGithubLoading}
341
+ aria-label={labels.github}
342
+ >
343
+ {labels.github}
344
+ </Button>
345
+ </div>
346
+ {showDivider && (
347
+ <div className={cn(divider.wrapperClassName)}>
348
+ <div className="absolute inset-0 flex items-center">
349
+ <span className={cn(divider.lineClassName)} />
350
+ </div>
351
+ <div className="relative flex justify-center text-xs">
352
+ <span className={cn(divider.textClassName)}>or</span>
353
+ </div>
354
+ </div>
355
+ )}
356
+ </>
357
+ )}
358
+
359
+ <Form methods={formMethods}>
360
+ <form onSubmit={handleSubmit(onSubmit)} className={cn(form.className)}>
361
+ <FormField
362
+ control={control}
363
+ name="name"
364
+ render={({ field: f }) => (
365
+ <FormItem className={cn(field.className)}>
366
+ <FormLabel className={cn(label.className)}>
367
+ {labels.name}
368
+ </FormLabel>
369
+ <FormControl>
370
+ <Input
371
+ placeholder="Your name"
372
+ autoComplete="name"
373
+ className={cn(input.className)}
374
+ {...f}
375
+ />
376
+ </FormControl>
377
+ <FormMessage />
378
+ </FormItem>
379
+ )}
380
+ />
381
+
382
+ <FormField
383
+ control={control}
384
+ name="email"
385
+ rules={{ validate: debouncedValidateEmail }}
386
+ render={({ field: f }) => (
387
+ <FormItem className={cn(field.className)}>
388
+ <FormLabel className={cn(label.className)}>
389
+ {labels.email}
390
+ </FormLabel>
391
+ <FormControl>
392
+ <Input
393
+ type="email"
394
+ inputMode="email"
395
+ autoComplete="email"
396
+ placeholder="you@example.com"
397
+ className={cn(input.className)}
398
+ {...f}
399
+ />
400
+ </FormControl>
401
+
402
+ <div aria-live="polite" className="mt-1">
403
+ {checkingEmail ? (
404
+ <span className="text-muted-foreground text-sm">
405
+ Checking…
406
+ </span>
407
+ ) : emailUnique === true ? (
408
+ <span className="text-sm text-green-600">
409
+ Email available
410
+ </span>
411
+ ) : emailUnique === false ? (
412
+ <span className="text-destructive text-sm">
413
+ Email already in use
414
+ </span>
415
+ ) : null}
416
+ </div>
417
+
418
+ <FormMessage />
419
+ </FormItem>
420
+ )}
421
+ />
422
+
423
+ <FormField
424
+ control={control}
425
+ name="password"
426
+ render={({ field: f }) => (
427
+ <FormItem className={cn(field.className)}>
428
+ <FormLabel className={cn(label.className)}>
429
+ {labels.password}
430
+ </FormLabel>
431
+ <FormControl>
432
+ <Input
433
+ type="password"
434
+ autoComplete="new-password"
435
+ placeholder="At least 6 characters"
436
+ className={cn(input.className)}
437
+ {...f}
438
+ />
439
+ </FormControl>
440
+ <FormMessage />
441
+ </FormItem>
442
+ )}
443
+ />
444
+
445
+ <Button
446
+ type="submit"
447
+ variant={submitButton.variant}
448
+ size={submitButton.size}
449
+ className={cn(submitButton.className)}
450
+ disabled={isSubmitting || emailUnique === false}
451
+ aria-label={isSubmitting ? labels.submitting : labels.submit}
452
+ >
453
+ {isSubmitting ? labels.submitting : labels.submit}
454
+ </Button>
455
+ </form>
456
+ </Form>
457
+ <p className="mt-3 text-center text-sm">
458
+ Already have an account?{" "}
459
+ <Link
460
+ href={`/auth/login?callbackUrl=${encodeURIComponent(searchParams.get("callbackUrl") || defaultRedirect)}`}
461
+ className="text-primary underline"
462
+ >
463
+ Log in
464
+ </Link>
465
+ </p>
466
+ </div>
467
+ );
468
+ }
@@ -0,0 +1,59 @@
1
+ import { ReactNode } from "react";
2
+ import { getServerSession } from "next-auth";
3
+ import { authOptions } from "@/lib/auth";
4
+ import { redirect } from "next/navigation";
5
+ import AdminHeader from "@/components/admin/admin-header";
6
+
7
+ export interface RequireAuthProps {
8
+ children: ReactNode;
9
+ returnTo?: string;
10
+ /**
11
+ * Controls whether a minimal admin header is shown.
12
+ * Defaults to true for DX. Consumers can disable per layout/page.
13
+ */
14
+ showAdminHeader?: boolean;
15
+ }
16
+
17
+ export default async function RequireAuth({
18
+ children,
19
+ returnTo = "/dashboard",
20
+ showAdminHeader = true,
21
+ }: RequireAuthProps) {
22
+ const session = await getServerSession(authOptions);
23
+ if (!session?.user) {
24
+ redirect(`/auth/login?callbackUrl=${encodeURIComponent(returnTo)}`);
25
+ }
26
+ return (
27
+ <>
28
+ {showAdminHeader && <AdminHeader />}
29
+ {children}
30
+ </>
31
+ );
32
+ }
33
+
34
+ // Named export to enforce admin-only access for server-rendered pages/layouts.
35
+ export async function RequireAdmin({
36
+ children,
37
+ returnTo = "/dashboard",
38
+ // By default don't render the AdminHeader here because ProtectedLayout already
39
+ // injects it via RequireAuth. Consumers can opt-in by passing showAdminHeader.
40
+ showAdminHeader = false,
41
+ }: RequireAuthProps) {
42
+ const session = await getServerSession(authOptions);
43
+ if (!session?.user) {
44
+ // Not signed in -> redirect to login
45
+ redirect(`/auth/login?callbackUrl=${encodeURIComponent(returnTo)}`);
46
+ }
47
+
48
+ // Signed in but not admin -> redirect away from admin area
49
+ if ((session.user as { role?: string }).role !== "admin") {
50
+ redirect(returnTo);
51
+ }
52
+
53
+ return (
54
+ <>
55
+ {showAdminHeader && <AdminHeader />}
56
+ {children}
57
+ </>
58
+ );
59
+ }
@@ -0,0 +1,11 @@
1
+ "use client";
2
+
3
+ import { SessionProvider } from "next-auth/react";
4
+
5
+ export default function AppSessionProvider({
6
+ children,
7
+ }: {
8
+ children: React.ReactNode;
9
+ }) {
10
+ return <SessionProvider>{children}</SessionProvider>;
11
+ }
@@ -0,0 +1 @@
1
+ This folder contains minimal UI primitives used by the Auth kit (Button, Input, Label). If your project already has equivalents, you can delete these and update imports.
@@ -0,0 +1,55 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
18
+ ghost: "hover:bg-accent hover:text-accent-foreground",
19
+ link: "text-primary underline-offset-4 hover:underline",
20
+ },
21
+ size: {
22
+ default: "h-10 px-4 py-2",
23
+ sm: "h-9 rounded-md px-3",
24
+ lg: "h-11 rounded-md px-8",
25
+ icon: "h-10 w-10",
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: "default",
30
+ size: "default",
31
+ },
32
+ },
33
+ );
34
+
35
+ export interface ButtonProps
36
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
37
+ VariantProps<typeof buttonVariants> {
38
+ asChild?: boolean;
39
+ }
40
+
41
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
42
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
43
+ const Comp = asChild ? Slot : "button";
44
+ return (
45
+ <Comp
46
+ className={cn(buttonVariants({ variant, size, className }))}
47
+ ref={ref}
48
+ {...props}
49
+ />
50
+ );
51
+ },
52
+ );
53
+ Button.displayName = "Button";
54
+
55
+ export { Button, buttonVariants };
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ export interface InputProps
6
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
7
+
8
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
+ ({ className, type, ...props }, ref) => {
10
+ return (
11
+ <input
12
+ type={type}
13
+ className={cn(
14
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
15
+ className,
16
+ )}
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ );
21
+ },
22
+ );
23
+ Input.displayName = "Input";
24
+
25
+ export { Input };
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as LabelPrimitive from "@radix-ui/react-label";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const Label = React.forwardRef<
9
+ React.ElementRef<typeof LabelPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <LabelPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ ));
21
+ Label.displayName = LabelPrimitive.Root.displayName;
22
+
23
+ export { Label };
@@ -0,0 +1,14 @@
1
+ import { ZodError } from "zod";
2
+
3
+ export type FieldErrors = Record<string, string>;
4
+
5
+ export function zodErrorToFieldErrors(error: ZodError): FieldErrors {
6
+ const fieldErrors: FieldErrors = {};
7
+ for (const issue of error.issues) {
8
+ const key = (issue.path?.[0]?.toString() || "form").toString();
9
+ if (!fieldErrors[key]) {
10
+ fieldErrors[key] = issue.message;
11
+ }
12
+ }
13
+ return fieldErrors;
14
+ }
@@ -0,0 +1,29 @@
1
+ import { authOptions } from "@/lib/auth";
2
+ import { getServerSession } from "next-auth";
3
+ import { redirect } from "next/navigation";
4
+
5
+ export async function requireAuth(returnTo: string = "/dashboard") {
6
+ const session = await getServerSession(authOptions);
7
+ if (!session?.user) {
8
+ redirect(`/auth/login?callbackUrl=${encodeURIComponent(returnTo)}`);
9
+ }
10
+ return session;
11
+ }
12
+
13
+ export async function requireAdmin(returnTo: string = "/dashboard") {
14
+ const session = await getServerSession(authOptions);
15
+ if (!session?.user) {
16
+ redirect(`/auth/login?callbackUrl=${encodeURIComponent(returnTo)}`);
17
+ }
18
+ if ((session.user as { role?: string }).role !== "admin") {
19
+ redirect(returnTo);
20
+ }
21
+ return session;
22
+ }
23
+
24
+ export async function requireAdminApi() {
25
+ const session = await getServerSession(authOptions);
26
+ if (!session?.user) return null;
27
+ if ((session.user as { role?: string }).role !== "admin") return null;
28
+ return session;
29
+ }