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,90 @@
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 {
7
+ forgotPasswordSchema,
8
+ type ForgotPasswordFormValues,
9
+ } from "@/lib/validation/forms";
10
+ import { Input } from "@/components/ui/input";
11
+ import { Button } from "@/components/ui/button";
12
+ import { Form } from "@/components/ui/form/form";
13
+ import { FormField } from "@/components/ui/form/form-field";
14
+ import { FormItem } from "@/components/ui/form/form-item";
15
+ import { FormLabel } from "@/components/ui/form/form-label";
16
+ import { FormControl } from "@/components/ui/form/form-control";
17
+ import { FormMessage } from "@/components/ui/form/form-message";
18
+ import { toast } from "sonner";
19
+
20
+ export default function ForgotPasswordForm() {
21
+ const methods = useForm<ForgotPasswordFormValues>({
22
+ resolver: zodResolver(forgotPasswordSchema) as any,
23
+ defaultValues: { email: "" },
24
+ });
25
+ const {
26
+ handleSubmit,
27
+ control,
28
+ formState: { isSubmitting },
29
+ } = methods;
30
+
31
+ const onSubmit = async (data: any) => {
32
+ try {
33
+ const res = await fetch("/api/auth/forgot-password", {
34
+ method: "POST",
35
+ body: JSON.stringify(data),
36
+ headers: { "Content-Type": "application/json" },
37
+ });
38
+ if (res.ok) {
39
+ toast.success(
40
+ "If an account exists, a reset link was sent (check server console in dev).",
41
+ );
42
+ } else {
43
+ toast.error("Failed to request password reset");
44
+ }
45
+ } catch (e) {
46
+ toast.error("Failed to request password reset");
47
+ }
48
+ };
49
+
50
+ return (
51
+ <div className="mx-auto w-full max-w-md pt-6">
52
+ <h2 className="text-foreground text-center text-2xl font-bold">
53
+ Forgot password
54
+ </h2>
55
+ <p className="text-muted-foreground mt-1 text-center text-sm">
56
+ Enter your email and we will send a reset link.
57
+ </p>
58
+
59
+ <Form methods={methods}>
60
+ <form
61
+ onSubmit={handleSubmit(onSubmit)}
62
+ className="border-border bg-card space-y-4 rounded-lg border p-6 shadow-sm"
63
+ >
64
+ <FormField
65
+ control={control}
66
+ name="email"
67
+ render={({ field: f }) => (
68
+ <FormItem>
69
+ <FormLabel>Email</FormLabel>
70
+ <FormControl>
71
+ <Input
72
+ id="email"
73
+ type="email"
74
+ placeholder="you@example.com"
75
+ {...f}
76
+ />
77
+ </FormControl>
78
+ <FormMessage />
79
+ </FormItem>
80
+ )}
81
+ />
82
+
83
+ <Button type="submit" disabled={isSubmitting} className="w-full">
84
+ Request reset
85
+ </Button>
86
+ </form>
87
+ </Form>
88
+ </div>
89
+ );
90
+ }
@@ -0,0 +1,467 @@
1
+ "use client";
2
+
3
+ import React, { useEffect, useState, type JSX } from "react";
4
+ import { useRouter, useSearchParams } from "next/navigation";
5
+ import { signIn, useSession } from "next-auth/react";
6
+
7
+ import Link from "next/link";
8
+ import { useForm } from "react-hook-form";
9
+ import { zodResolver } from "@hookform/resolvers/zod";
10
+ import { Button } from "@/components/ui/button";
11
+ import { Input } from "@/components/ui/input";
12
+ import { cn } from "@/lib/utils";
13
+ import { loginSchema, type LoginFormValues } from "@/lib/validation/forms";
14
+ import { Form } from "@/components/ui/form/form";
15
+ import { FormField } from "@/components/ui/form/form-field";
16
+ import { FormItem } from "@/components/ui/form/form-item";
17
+ import { FormLabel } from "@/components/ui/form/form-label";
18
+ import { FormMessage } from "@/components/ui/form/form-message";
19
+ import { FormControl } from "@/components/ui/form/form-control";
20
+ import { toast } from "sonner";
21
+ import { mapApiErrorsToForm } from "@/lib/forms/map-errors";
22
+
23
+ export interface LoginFormProps {
24
+ id?: string;
25
+ className?: string;
26
+
27
+ /** Fallback redirect when no callbackUrl is present in the URL. Default: "/dashboard" */
28
+ defaultRedirect?: string;
29
+
30
+ /** Show the OAuth (GitHub) provider button. Default: true */
31
+ showGithub?: boolean;
32
+
33
+ /** Show the divider between providers and form fields. Default: true */
34
+ showDivider?: boolean;
35
+
36
+ /** Optional header text above the form */
37
+ headingText?: { text?: string; className?: string } | null;
38
+ /** Optional subheading text under the header */
39
+ subheadingText?: { text?: string; className?: string } | null;
40
+
41
+ /** Text labels you might want to override */
42
+ labels?: {
43
+ email?: string;
44
+ password?: string;
45
+ submit?: string;
46
+ submitting?: string;
47
+ github?: string;
48
+ errorGeneric?: string;
49
+ };
50
+
51
+ /** Slots for styling overrides (similar to Navbar) */
52
+ container?: { className?: string };
53
+ headerWrapper?: { className?: string };
54
+ providerWrapper?: { className?: string };
55
+ providerButton?: {
56
+ variant?:
57
+ | "default"
58
+ | "destructive"
59
+ | "outline"
60
+ | "secondary"
61
+ | "ghost"
62
+ | "link";
63
+ size?: "default" | "sm" | "lg" | "icon";
64
+ className?: string;
65
+ };
66
+ divider?: {
67
+ wrapperClassName?: string;
68
+ lineClassName?: string;
69
+ textClassName?: string;
70
+ };
71
+ form?: { className?: string };
72
+ field?: { className?: string };
73
+ label?: { className?: string };
74
+ input?: { className?: string };
75
+ submitButton?: {
76
+ variant?:
77
+ | "default"
78
+ | "destructive"
79
+ | "outline"
80
+ | "secondary"
81
+ | "ghost"
82
+ | "link";
83
+ size?: "default" | "sm" | "lg" | "icon";
84
+ className?: string;
85
+ };
86
+ alerts?: {
87
+ errorClassName?: string;
88
+ successClassName?: string;
89
+ };
90
+
91
+ /** ARIA label for the form */
92
+ ariaLabel?: string;
93
+ }
94
+
95
+ export default function LoginForm({
96
+ id,
97
+ className,
98
+ defaultRedirect = "/dashboard",
99
+ showGithub = true,
100
+ showDivider = true,
101
+ headingText = {
102
+ text: "Welcome back",
103
+ className: "text-2xl font-bold text-foreground text-center",
104
+ },
105
+ subheadingText = {
106
+ text: "Enter your credentials to sign in",
107
+ className: "mt-1 text-sm text-muted-foreground text-center",
108
+ },
109
+ labels = {
110
+ email: "Email",
111
+ password: "Password",
112
+ submit: "Log In",
113
+ submitting: "Logging in...",
114
+ github: "Continue with GitHub",
115
+ errorGeneric: "Login failed. Please try again.",
116
+ },
117
+ container = { className: "mx-auto w-full max-w-md pt-6" },
118
+ headerWrapper = { className: "mb-4" },
119
+ providerWrapper = { className: "mb-6" },
120
+ providerButton = {
121
+ variant: "outline",
122
+ size: "default",
123
+ className:
124
+ "w-full shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md",
125
+ },
126
+ divider = {
127
+ wrapperClassName: "relative mb-6",
128
+ lineClassName: "w-full border-t",
129
+ textClassName: "bg-background text-muted-foreground px-2",
130
+ },
131
+ form = {
132
+ className:
133
+ "space-y-4 rounded-lg border border-border bg-card p-6 shadow-sm",
134
+ },
135
+ field = { className: "space-y-2" },
136
+ label = { className: "" },
137
+ input = { className: "" },
138
+ submitButton = {
139
+ variant: "default",
140
+ size: "default",
141
+ className:
142
+ "w-full shadow-lg transition-all duration-200 hover:-translate-y-0.5 hover:shadow-xl",
143
+ },
144
+ alerts = {
145
+ errorClassName:
146
+ "mb-4 rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive",
147
+ successClassName:
148
+ "mb-4 rounded-md border border-primary/20 bg-primary/10 p-3 text-sm text-foreground",
149
+ },
150
+ ariaLabel = "Login form",
151
+ }: LoginFormProps): JSX.Element {
152
+ const router = useRouter();
153
+ const searchParams = useSearchParams();
154
+ const { data: session, status } = useSession();
155
+ const callbackUrl = searchParams.get("callbackUrl") || defaultRedirect;
156
+
157
+ const [error, setError] = useState<string | null | undefined>(null);
158
+ const [success, setSuccess] = useState<string | null>(null);
159
+ const [isGithubLoading, setIsGithubLoading] = useState(false);
160
+ const [githubAvailable, setGithubAvailable] = useState(false);
161
+ const [githubConfigured, setGithubConfigured] = useState(false);
162
+
163
+ const formMethods = useForm<LoginFormValues>({
164
+ resolver: zodResolver(loginSchema),
165
+ defaultValues: { email: "", password: "" },
166
+ });
167
+ const {
168
+ control,
169
+ handleSubmit,
170
+ setError: setFieldError,
171
+ formState: { isSubmitting },
172
+ } = formMethods;
173
+
174
+ useEffect(() => {
175
+ (async () => {
176
+ try {
177
+ const res = await fetch("/api/auth/providers");
178
+ if (res.ok) {
179
+ const json = await res.json();
180
+ const github = json?.github ?? { configured: false, enabled: false };
181
+ setGithubConfigured(!!github.configured);
182
+ setGithubAvailable(!!github.enabled);
183
+ } else {
184
+ const providers = await (
185
+ await import("next-auth/react")
186
+ ).getProviders();
187
+ setGithubAvailable(!!providers?.github);
188
+ }
189
+ } catch {
190
+ try {
191
+ const providers = await (
192
+ await import("next-auth/react")
193
+ ).getProviders();
194
+ setGithubAvailable(!!providers?.github);
195
+ } catch {
196
+ setGithubAvailable(false);
197
+ }
198
+ }
199
+ })();
200
+ }, []);
201
+
202
+ useEffect(() => {
203
+ const STORAGE_KEY = "signup_toast_shown";
204
+ const isSignup = searchParams.get("signup") === "1";
205
+
206
+ if (isSignup) {
207
+ try {
208
+ if (!sessionStorage.getItem(STORAGE_KEY)) {
209
+ sessionStorage.setItem(STORAGE_KEY, "1");
210
+ setSuccess("Account created. You can now sign in.");
211
+ toast.success("Account created. You can now sign in.", {
212
+ id: "signup-success",
213
+ });
214
+
215
+ const params = new URLSearchParams(searchParams);
216
+ params.delete("signup");
217
+ router.replace(`/auth/login?${params.toString()}`, { scroll: false });
218
+ }
219
+ } catch {
220
+ setSuccess("Account created. You can now sign in.");
221
+ toast.success("Account created. You can now sign in.", {
222
+ id: "signup-success",
223
+ });
224
+ const params = new URLSearchParams(searchParams);
225
+ params.delete("signup");
226
+ router.replace(`/auth/login?${params.toString()}`, { scroll: false });
227
+ }
228
+ } else {
229
+ try {
230
+ sessionStorage.removeItem(STORAGE_KEY);
231
+ } catch {}
232
+ }
233
+
234
+ if (status === "authenticated" && session) {
235
+ const callbackUrl = searchParams.get("callbackUrl") || defaultRedirect;
236
+ const resolve = (cb: string) => {
237
+ try {
238
+ const u = new URL(cb);
239
+ if (u.origin === window.location.origin)
240
+ return `${u.pathname}${u.search}${u.hash}`;
241
+ } catch {}
242
+ return cb;
243
+ };
244
+ window.location.assign(resolve(callbackUrl));
245
+ }
246
+ }, [status, session, router, searchParams, defaultRedirect]);
247
+
248
+ const onSubmit = async (data: LoginFormValues) => {
249
+ setError(null);
250
+ const callbackUrl = searchParams.get("callbackUrl") || defaultRedirect;
251
+
252
+ try {
253
+ const csrfRes = await fetch("/api/auth/csrf", { credentials: "include" });
254
+ const csrfJson = await csrfRes.json().catch(() => null);
255
+ const csrfToken = csrfJson?.csrfToken;
256
+
257
+ const body = new URLSearchParams();
258
+ if (csrfToken) body.set("csrfToken", csrfToken);
259
+ body.set("callbackUrl", callbackUrl);
260
+ body.set("json", "true");
261
+ body.set("email", data.email);
262
+ body.set("password", data.password);
263
+
264
+ const res = await fetch("/api/auth/callback/credentials", {
265
+ method: "POST",
266
+ credentials: "include",
267
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
268
+ body: body.toString(),
269
+ redirect: "manual",
270
+ });
271
+
272
+ const text = await res.text().catch(() => null);
273
+ let parsed: any = null;
274
+ try {
275
+ parsed = text ? JSON.parse(text) : null;
276
+ } catch {}
277
+
278
+ if (!res.ok) {
279
+ if (parsed && typeof parsed === "object") {
280
+ const msg = mapApiErrorsToForm(formMethods, parsed);
281
+ setError(
282
+ msg ||
283
+ parsed.message ||
284
+ (labels.errorGeneric ?? "Login failed. Please try again."),
285
+ );
286
+ } else {
287
+ setError(labels.errorGeneric ?? "Login failed. Please try again.");
288
+ }
289
+ return;
290
+ }
291
+
292
+ toast.success("Logged in successfully");
293
+ const dest = (parsed && parsed.url) || callbackUrl;
294
+ const resolve = (cb: string) => {
295
+ try {
296
+ const u = new URL(cb);
297
+ if (u.origin === window.location.origin)
298
+ return `${u.pathname}${u.search}${u.hash}`;
299
+ } catch {}
300
+ return cb;
301
+ };
302
+
303
+ window.location.assign(resolve(dest));
304
+ } catch (err) {
305
+ setError(labels.errorGeneric ?? "Login failed. Please try again.");
306
+ }
307
+ };
308
+
309
+ const handleGithubLogin = async () => {
310
+ setIsGithubLoading(true);
311
+ setError(null);
312
+ const callbackUrl = searchParams.get("callbackUrl") || defaultRedirect;
313
+
314
+ if (!githubConfigured && githubAvailable) {
315
+ setError(
316
+ "GitHub provider is not configured on the server. Contact the site administrator.",
317
+ );
318
+ setIsGithubLoading(false);
319
+ return;
320
+ }
321
+
322
+ const result = await signIn("github", { redirect: false, callbackUrl });
323
+ if (result?.error) {
324
+ setError("GitHub login failed. Please try again.");
325
+ setIsGithubLoading(false);
326
+ }
327
+ };
328
+
329
+ return (
330
+ <div
331
+ id={id}
332
+ className={cn(container.className, className)}
333
+ aria-label={ariaLabel}
334
+ >
335
+ {(headingText?.text || subheadingText?.text) && (
336
+ <div className={cn(headerWrapper.className)}>
337
+ {headingText?.text && (
338
+ <h2 className={cn("font-poppins", headingText.className)}>
339
+ {headingText.text}
340
+ </h2>
341
+ )}
342
+ {subheadingText?.text && (
343
+ <p className={cn(subheadingText.className)}>
344
+ {subheadingText.text}
345
+ </p>
346
+ )}
347
+ </div>
348
+ )}
349
+
350
+ {error && (
351
+ <div
352
+ className={cn(alerts.errorClassName)}
353
+ role="alert"
354
+ aria-live="polite"
355
+ >
356
+ {error}
357
+ </div>
358
+ )}
359
+
360
+ {showGithub && githubAvailable && (
361
+ <>
362
+ <div className={cn(providerWrapper.className)}>
363
+ <Button
364
+ variant={providerButton.variant}
365
+ size={providerButton.size}
366
+ className={cn(providerButton.className)}
367
+ onClick={handleGithubLogin}
368
+ disabled={isGithubLoading || !githubConfigured}
369
+ aria-label={labels.github}
370
+ >
371
+ {labels.github}
372
+ </Button>
373
+ </div>
374
+
375
+ {!githubConfigured && githubAvailable && (
376
+ <div className="text-muted-foreground mb-4 text-center text-sm">
377
+ GitHub sign-in is visible for demos but not configured. Please set
378
+ GITHUB_ID and GITHUB_SECRET in your environment to enable it.
379
+ </div>
380
+ )}
381
+
382
+ {showDivider && (
383
+ <div className={cn(divider.wrapperClassName)}>
384
+ <div className="absolute inset-0 flex items-center">
385
+ <span className={cn(divider.lineClassName)} />
386
+ </div>
387
+ <div className="relative flex justify-center text-xs">
388
+ <span className={cn(divider.textClassName)}>or</span>
389
+ </div>
390
+ </div>
391
+ )}
392
+ </>
393
+ )}
394
+
395
+ <Form methods={formMethods}>
396
+ <form onSubmit={handleSubmit(onSubmit)} className={cn(form.className)}>
397
+ <FormField
398
+ control={control}
399
+ name="email"
400
+ render={({ field: f }) => (
401
+ <FormItem className={cn(field.className)}>
402
+ <FormLabel className={cn(label.className)}>
403
+ {labels.email}
404
+ </FormLabel>
405
+ <FormControl>
406
+ <Input
407
+ id="email"
408
+ type="email"
409
+ inputMode="email"
410
+ autoComplete="email"
411
+ placeholder="you@example.com"
412
+ className={cn(input.className)}
413
+ {...f}
414
+ />
415
+ </FormControl>
416
+ <FormMessage />
417
+ </FormItem>
418
+ )}
419
+ />
420
+
421
+ <FormField
422
+ control={control}
423
+ name="password"
424
+ render={({ field: f }) => (
425
+ <FormItem className={cn(field.className)}>
426
+ <FormLabel className={cn(label.className)}>
427
+ {labels.password}
428
+ </FormLabel>
429
+ <FormControl>
430
+ <Input
431
+ id="password"
432
+ type="password"
433
+ autoComplete="current-password"
434
+ placeholder="At least 6 characters"
435
+ className={cn(input.className)}
436
+ {...f}
437
+ />
438
+ </FormControl>
439
+ <FormMessage />
440
+ </FormItem>
441
+ )}
442
+ />
443
+
444
+ <Button
445
+ type="submit"
446
+ variant={submitButton.variant}
447
+ size={submitButton.size}
448
+ className={cn(submitButton.className)}
449
+ disabled={isSubmitting}
450
+ aria-label={isSubmitting ? labels.submitting : labels.submit}
451
+ >
452
+ {isSubmitting ? labels.submitting : labels.submit}
453
+ </Button>
454
+ </form>
455
+ </Form>
456
+ <p className="mt-3 text-center text-sm">
457
+ Don&apos;t have an account?{" "}
458
+ <Link
459
+ href={`/auth/signup?callbackUrl=${encodeURIComponent(callbackUrl)}`}
460
+ className="text-primary underline"
461
+ >
462
+ Sign up
463
+ </Link>
464
+ </p>
465
+ </div>
466
+ );
467
+ }
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { signOut } from "next-auth/react";
5
+ import { Button } from "@/components/ui/button";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ export interface LogoutButtonProps {
9
+ className?: string;
10
+ label?: string;
11
+ loggingOutLabel?: string;
12
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
13
+ size?: "default" | "sm" | "lg" | "icon";
14
+ callbackUrl?: string;
15
+ }
16
+
17
+ export default function LogoutButton({
18
+ className,
19
+ label = "Log out",
20
+ loggingOutLabel = "Logging out...",
21
+ variant = "outline",
22
+ size = "sm",
23
+ callbackUrl = "/auth/login",
24
+ }: LogoutButtonProps) {
25
+ const [loading, setLoading] = useState(false);
26
+
27
+ const handleSignOut = async () => {
28
+ try {
29
+ setLoading(true);
30
+ await signOut({ callbackUrl });
31
+ } finally {
32
+ // no-op; NextAuth will redirect
33
+ setLoading(false);
34
+ }
35
+ };
36
+
37
+ return (
38
+ <Button
39
+ type="button"
40
+ variant={variant}
41
+ size={size}
42
+ className={cn(className)}
43
+ onClick={handleSignOut}
44
+ disabled={loading}
45
+ aria-label={loading ? loggingOutLabel : label}
46
+ >
47
+ {loading ? loggingOutLabel : label}
48
+ </Button>
49
+ );
50
+ }
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ // Minimal, UI-kit-free logout button for admin header
4
+ // Uses only next-auth/react and basic classes
5
+ import { useState } from "react";
6
+ import { signOut } from "next-auth/react";
7
+
8
+ export default function MinimalLogoutButton({
9
+ label = "Log out",
10
+ loggingOutLabel = "Logging out...",
11
+ callbackUrl = "/auth/login",
12
+ }: {
13
+ label?: string;
14
+ loggingOutLabel?: string;
15
+ callbackUrl?: string;
16
+ }) {
17
+ const [loading, setLoading] = useState(false);
18
+
19
+ const onClick = async () => {
20
+ try {
21
+ setLoading(true);
22
+ await signOut({ callbackUrl });
23
+ } finally {
24
+ // NextAuth will redirect; keep a guard to avoid re-clicks
25
+ setLoading(false);
26
+ }
27
+ };
28
+
29
+ return (
30
+ <button
31
+ type="button"
32
+ onClick={onClick}
33
+ disabled={loading}
34
+ className="rounded border px-3 py-1 text-sm hover:bg-muted disabled:opacity-60"
35
+ aria-label={loading ? loggingOutLabel : label}
36
+ >
37
+ {loading ? loggingOutLabel : label}
38
+ </button>
39
+ );
40
+ }