nexstruct 1.0.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 (229) hide show
  1. package/AGENTS.md +122 -0
  2. package/LICENSE +21 -0
  3. package/README.md +103 -0
  4. package/package.json +99 -0
  5. package/scaffold/generator.js +409 -0
  6. package/scaffold/index.js +20 -0
  7. package/scaffold/prompts.js +108 -0
  8. package/templates/api/axios/src/api/axios/client.api.ts +30 -0
  9. package/templates/api/axios/src/api/axios/users.api.ts +15 -0
  10. package/templates/api/fetch/src/api/fetch/client.api.ts +68 -0
  11. package/templates/api/fetch/src/api/fetch/users.api.ts +15 -0
  12. package/templates/api/trpc/src/api/trpc/client.api.ts +4 -0
  13. package/templates/api/trpc/src/api/trpc/router.api.ts +15 -0
  14. package/templates/api/trpc/src/api/trpc/server.client.api.ts +4 -0
  15. package/templates/api/trpc/src/providers/trpc.provider.tsx +24 -0
  16. package/templates/auth/clerk/src/auth/clerk/auth.service.ts +4 -0
  17. package/templates/auth/clerk/src/hooks/use-auth.hook.ts +13 -0
  18. package/templates/auth/clerk/src/middleware.ts +7 -0
  19. package/templates/auth/clerk/src/providers/auth.provider.tsx +6 -0
  20. package/templates/auth/next-auth/src/app/api/auth/[...nextauth]/route.ts +5 -0
  21. package/templates/auth/next-auth/src/auth/next-auth/auth.service.ts +45 -0
  22. package/templates/auth/next-auth/src/hooks/use-session.hook.ts +13 -0
  23. package/templates/auth/next-auth/src/providers/session.provider.tsx +6 -0
  24. package/templates/forms/formik/src/components/forms/login-form.component.tsx +30 -0
  25. package/templates/forms/formik/src/forms/formik/hooks/use-form-config.hook.ts +7 -0
  26. package/templates/forms/formik/src/forms/formik/schemas/example.schema.ts +8 -0
  27. package/templates/forms/react-hook-form/src/components/forms/login-form.component.tsx +27 -0
  28. package/templates/forms/react-hook-form/src/forms/react-hook-form/hooks/use-form.hook.ts +13 -0
  29. package/templates/forms/react-hook-form/src/forms/react-hook-form/schemas/example.schema.ts +15 -0
  30. package/templates/nextjs-base/next.config.ts +5 -0
  31. package/templates/nextjs-base/postcss.config.mjs +9 -0
  32. package/templates/nextjs-base/src/app/_components/navbar.tsx +88 -0
  33. package/templates/nextjs-base/src/app/_components/sidebar.tsx +223 -0
  34. package/templates/nextjs-base/src/app/error.tsx +39 -0
  35. package/templates/nextjs-base/src/app/globals.css +71 -0
  36. package/templates/nextjs-base/src/app/layout.tsx +21 -0
  37. package/templates/nextjs-base/src/app/loading.tsx +13 -0
  38. package/templates/nextjs-base/src/app/not-found.tsx +22 -0
  39. package/templates/nextjs-base/src/app/page.tsx +10 -0
  40. package/templates/nextjs-base/tailwind.config.ts +69 -0
  41. package/templates/shared/src/components/common/theme-toggle.component.tsx +31 -0
  42. package/templates/shared/src/components/common/toast/custom-message.component.tsx +18 -0
  43. package/templates/shared/src/components/common/toast/index.ts +8 -0
  44. package/templates/shared/src/components/common/toast/toast-message.component.tsx +112 -0
  45. package/templates/shared/src/hooks/use-debounce.hook.ts +12 -0
  46. package/templates/shared/src/hooks/use-fetch.hook.ts +42 -0
  47. package/templates/shared/src/hooks/use-intersection-observer.hook.ts +39 -0
  48. package/templates/shared/src/hooks/use-local-storage.hook.ts +30 -0
  49. package/templates/shared/src/hooks/use-media-query.hook.ts +26 -0
  50. package/templates/shared/src/hooks/use-toggle.hook.ts +12 -0
  51. package/templates/shared/src/lib/utils.util.ts +361 -0
  52. package/templates/shared/src/providers/theme.provider.tsx +17 -0
  53. package/templates/shared/src/providers/toast.provider.tsx +32 -0
  54. package/templates/shared/src/types/common.type.ts +34 -0
  55. package/templates/state/context/src/store/context/auth.context.tsx +47 -0
  56. package/templates/state/context/src/store/context/counter.context.tsx +41 -0
  57. package/templates/state/context/src/store/context/index.ts +2 -0
  58. package/templates/state/redux/src/providers/redux.provider.tsx +7 -0
  59. package/templates/state/redux/src/store/redux/hooks.store.ts +5 -0
  60. package/templates/state/redux/src/store/redux/index.ts +4 -0
  61. package/templates/state/redux/src/store/redux/slices/api.slice.ts +8 -0
  62. package/templates/state/redux/src/store/redux/slices/counter.slice.ts +24 -0
  63. package/templates/state/redux/src/store/redux/store.store.ts +13 -0
  64. package/templates/state/zustand/src/store/zustand/counter.store.ts +15 -0
  65. package/templates/state/zustand/src/store/zustand/index.ts +2 -0
  66. package/templates/state/zustand/src/store/zustand/user.store.ts +32 -0
  67. package/templates/ui/antd/COMPONENT_GUIDE.md +326 -0
  68. package/templates/ui/antd/src/app/examples/dialog/page.tsx +205 -0
  69. package/templates/ui/antd/src/app/examples/form/page.tsx +160 -0
  70. package/templates/ui/antd/src/app/examples/layout.tsx +125 -0
  71. package/templates/ui/antd/src/app/examples/page.tsx +64 -0
  72. package/templates/ui/antd/src/app/examples/table/page.tsx +118 -0
  73. package/templates/ui/antd/src/app/page.tsx +283 -0
  74. package/templates/ui/antd/src/components/common/DynamicTable/dynamic-table.component.tsx +79 -0
  75. package/templates/ui/antd/src/components/common/button/action-button.component.tsx +63 -0
  76. package/templates/ui/antd/src/components/common/dialog/dialog-wrapper.component.tsx +63 -0
  77. package/templates/ui/antd/src/components/common/fields/assets/components/check-field.component.tsx +55 -0
  78. package/templates/ui/antd/src/components/common/fields/assets/components/date-picker-field.component.tsx +80 -0
  79. package/templates/ui/antd/src/components/common/fields/assets/components/limit-field.component.tsx +26 -0
  80. package/templates/ui/antd/src/components/common/fields/assets/components/multi-check-field.component.tsx +56 -0
  81. package/templates/ui/antd/src/components/common/fields/assets/components/number-field.component.tsx +100 -0
  82. package/templates/ui/antd/src/components/common/fields/assets/components/otp-field.component.tsx +63 -0
  83. package/templates/ui/antd/src/components/common/fields/assets/components/password-field.component.tsx +106 -0
  84. package/templates/ui/antd/src/components/common/fields/assets/components/phone-number-field.component.tsx +78 -0
  85. package/templates/ui/antd/src/components/common/fields/assets/components/radio-field.component.tsx +55 -0
  86. package/templates/ui/antd/src/components/common/fields/assets/components/range-date-picker.component.tsx +66 -0
  87. package/templates/ui/antd/src/components/common/fields/assets/components/search-field.component.tsx +24 -0
  88. package/templates/ui/antd/src/components/common/fields/assets/components/select-field.component.tsx +82 -0
  89. package/templates/ui/antd/src/components/common/fields/assets/components/single-check-field.component.tsx +50 -0
  90. package/templates/ui/antd/src/components/common/fields/assets/components/single-select-field.component.tsx +86 -0
  91. package/templates/ui/antd/src/components/common/fields/assets/components/string-number-field.component.tsx +80 -0
  92. package/templates/ui/antd/src/components/common/fields/assets/components/switch-field.component.tsx +62 -0
  93. package/templates/ui/antd/src/components/common/fields/assets/components/text-area-field.component.tsx +85 -0
  94. package/templates/ui/antd/src/components/common/fields/assets/components/text-field.component.tsx +88 -0
  95. package/templates/ui/antd/src/components/common/fields/assets/interface/input-props.type.ts +233 -0
  96. package/templates/ui/antd/src/components/common/fields/cusInputField.component.tsx +40 -0
  97. package/templates/ui/antd/src/components/common/pagination/pagination.component.tsx +27 -0
  98. package/templates/ui/antd/src/components/ui/avatar.component.tsx +8 -0
  99. package/templates/ui/antd/src/components/ui/badge.component.tsx +8 -0
  100. package/templates/ui/antd/src/components/ui/button.component.tsx +8 -0
  101. package/templates/ui/antd/src/components/ui/card.component.tsx +8 -0
  102. package/templates/ui/antd/src/components/ui/checkbox.component.tsx +8 -0
  103. package/templates/ui/antd/src/components/ui/dialog.component.tsx +9 -0
  104. package/templates/ui/antd/src/components/ui/dropdown-menu.component.tsx +10 -0
  105. package/templates/ui/antd/src/components/ui/form.component.tsx +12 -0
  106. package/templates/ui/antd/src/components/ui/input.component.tsx +13 -0
  107. package/templates/ui/antd/src/components/ui/label.component.tsx +18 -0
  108. package/templates/ui/antd/src/components/ui/popover.component.tsx +8 -0
  109. package/templates/ui/antd/src/components/ui/progress.component.tsx +8 -0
  110. package/templates/ui/antd/src/components/ui/radio-group.component.tsx +10 -0
  111. package/templates/ui/antd/src/components/ui/scroll-area.component.tsx +25 -0
  112. package/templates/ui/antd/src/components/ui/select.component.tsx +8 -0
  113. package/templates/ui/antd/src/components/ui/separator.component.tsx +8 -0
  114. package/templates/ui/antd/src/components/ui/sheet.component.tsx +8 -0
  115. package/templates/ui/antd/src/components/ui/switch.component.tsx +8 -0
  116. package/templates/ui/antd/src/components/ui/table.component.tsx +8 -0
  117. package/templates/ui/antd/src/components/ui/tabs.component.tsx +8 -0
  118. package/templates/ui/antd/src/components/ui/textarea.component.tsx +9 -0
  119. package/templates/ui/antd/src/components/ui/tooltip.component.tsx +8 -0
  120. package/templates/ui/antd/src/lib/theme.util.ts +40 -0
  121. package/templates/ui/antd/src/providers/antd.provider.tsx +13 -0
  122. package/templates/ui/mui/src/app/examples/layout.tsx +113 -0
  123. package/templates/ui/mui/src/app/examples/page.tsx +716 -0
  124. package/templates/ui/mui/src/app/page.tsx +298 -0
  125. package/templates/ui/mui/src/components/common/DynamicTable/dynamic-table.component.tsx +131 -0
  126. package/templates/ui/mui/src/components/common/button/action-button.component.tsx +57 -0
  127. package/templates/ui/mui/src/components/common/dialog/dialog-wrapper.component.tsx +55 -0
  128. package/templates/ui/mui/src/components/common/fields/assets/components/check-field.component.tsx +51 -0
  129. package/templates/ui/mui/src/components/common/fields/assets/components/date-picker-field.component.tsx +50 -0
  130. package/templates/ui/mui/src/components/common/fields/assets/components/multi-check-field.component.tsx +14 -0
  131. package/templates/ui/mui/src/components/common/fields/assets/components/number-field.component.tsx +59 -0
  132. package/templates/ui/mui/src/components/common/fields/assets/components/password-field.component.tsx +87 -0
  133. package/templates/ui/mui/src/components/common/fields/assets/components/phone-number-field.component.tsx +48 -0
  134. package/templates/ui/mui/src/components/common/fields/assets/components/radio-field.component.tsx +37 -0
  135. package/templates/ui/mui/src/components/common/fields/assets/components/search-field.component.tsx +41 -0
  136. package/templates/ui/mui/src/components/common/fields/assets/components/select-field.component.tsx +77 -0
  137. package/templates/ui/mui/src/components/common/fields/assets/components/single-check-field.component.tsx +39 -0
  138. package/templates/ui/mui/src/components/common/fields/assets/components/single-select-field.component.tsx +56 -0
  139. package/templates/ui/mui/src/components/common/fields/assets/components/string-number-field.component.tsx +52 -0
  140. package/templates/ui/mui/src/components/common/fields/assets/components/switch-field.component.tsx +35 -0
  141. package/templates/ui/mui/src/components/common/fields/assets/components/text-area-field.component.tsx +46 -0
  142. package/templates/ui/mui/src/components/common/fields/assets/components/text-field.component.tsx +51 -0
  143. package/templates/ui/mui/src/components/common/fields/assets/interface/input-props.type.ts +193 -0
  144. package/templates/ui/mui/src/components/common/fields/cusInputField.component.tsx +34 -0
  145. package/templates/ui/mui/src/components/common/pagination/pagination.component.tsx +59 -0
  146. package/templates/ui/mui/src/components/ui/avatar.component.tsx +19 -0
  147. package/templates/ui/mui/src/components/ui/badge.component.tsx +18 -0
  148. package/templates/ui/mui/src/components/ui/button.component.tsx +22 -0
  149. package/templates/ui/mui/src/components/ui/card.component.tsx +39 -0
  150. package/templates/ui/mui/src/components/ui/checkbox.component.tsx +21 -0
  151. package/templates/ui/mui/src/components/ui/dialog.component.tsx +38 -0
  152. package/templates/ui/mui/src/components/ui/dropdown-menu.component.tsx +43 -0
  153. package/templates/ui/mui/src/components/ui/form.component.tsx +98 -0
  154. package/templates/ui/mui/src/components/ui/input.component.tsx +15 -0
  155. package/templates/ui/mui/src/components/ui/label.component.tsx +15 -0
  156. package/templates/ui/mui/src/components/ui/popover.component.tsx +20 -0
  157. package/templates/ui/mui/src/components/ui/progress.component.tsx +19 -0
  158. package/templates/ui/mui/src/components/ui/radio-group.component.tsx +25 -0
  159. package/templates/ui/mui/src/components/ui/scroll-area.component.tsx +27 -0
  160. package/templates/ui/mui/src/components/ui/select.component.tsx +26 -0
  161. package/templates/ui/mui/src/components/ui/separator.component.tsx +11 -0
  162. package/templates/ui/mui/src/components/ui/sheet.component.tsx +44 -0
  163. package/templates/ui/mui/src/components/ui/switch.component.tsx +23 -0
  164. package/templates/ui/mui/src/components/ui/table.component.tsx +34 -0
  165. package/templates/ui/mui/src/components/ui/tabs.component.tsx +38 -0
  166. package/templates/ui/mui/src/components/ui/textarea.component.tsx +18 -0
  167. package/templates/ui/mui/src/components/ui/tooltip.component.tsx +24 -0
  168. package/templates/ui/mui/src/lib/theme.util.ts +73 -0
  169. package/templates/ui/mui/src/providers/mui.provider.tsx +13 -0
  170. package/templates/ui/shadcn/COMPONENT_GUIDE.md +306 -0
  171. package/templates/ui/shadcn/src/app/examples/dialog/page.tsx +122 -0
  172. package/templates/ui/shadcn/src/app/examples/form/page.tsx +107 -0
  173. package/templates/ui/shadcn/src/app/examples/layout.tsx +24 -0
  174. package/templates/ui/shadcn/src/app/examples/page.tsx +30 -0
  175. package/templates/ui/shadcn/src/app/examples/table/page.tsx +77 -0
  176. package/templates/ui/shadcn/src/app/page.tsx +20 -0
  177. package/templates/ui/shadcn/src/components/common/DynamicTable/dynamic-table.component.tsx +136 -0
  178. package/templates/ui/shadcn/src/components/common/button/action-button.component.tsx +68 -0
  179. package/templates/ui/shadcn/src/components/common/dialog/dialog-wrapper.component.tsx +58 -0
  180. package/templates/ui/shadcn/src/components/common/fields/assets/components/check-field.component.tsx +52 -0
  181. package/templates/ui/shadcn/src/components/common/fields/assets/components/date-picker-field.component.tsx +62 -0
  182. package/templates/ui/shadcn/src/components/common/fields/assets/components/dynamic-file-upload-field.component.tsx +152 -0
  183. package/templates/ui/shadcn/src/components/common/fields/assets/components/limit-field.component.tsx +73 -0
  184. package/templates/ui/shadcn/src/components/common/fields/assets/components/multi-check-field.component.tsx +46 -0
  185. package/templates/ui/shadcn/src/components/common/fields/assets/components/number-field.component.tsx +124 -0
  186. package/templates/ui/shadcn/src/components/common/fields/assets/components/otp-field.component.tsx +61 -0
  187. package/templates/ui/shadcn/src/components/common/fields/assets/components/password-field.component.tsx +110 -0
  188. package/templates/ui/shadcn/src/components/common/fields/assets/components/phone-number-field.component.tsx +90 -0
  189. package/templates/ui/shadcn/src/components/common/fields/assets/components/radio-field.component.tsx +41 -0
  190. package/templates/ui/shadcn/src/components/common/fields/assets/components/range-date-picker.component.tsx +71 -0
  191. package/templates/ui/shadcn/src/components/common/fields/assets/components/rich-text-editor.component.tsx +91 -0
  192. package/templates/ui/shadcn/src/components/common/fields/assets/components/search-field.component.tsx +34 -0
  193. package/templates/ui/shadcn/src/components/common/fields/assets/components/select-field.component.tsx +231 -0
  194. package/templates/ui/shadcn/src/components/common/fields/assets/components/single-check-field.component.tsx +42 -0
  195. package/templates/ui/shadcn/src/components/common/fields/assets/components/single-select-field.component.tsx +82 -0
  196. package/templates/ui/shadcn/src/components/common/fields/assets/components/string-number-field.component.tsx +68 -0
  197. package/templates/ui/shadcn/src/components/common/fields/assets/components/switch-field.component.tsx +61 -0
  198. package/templates/ui/shadcn/src/components/common/fields/assets/components/text-area-field.component.tsx +62 -0
  199. package/templates/ui/shadcn/src/components/common/fields/assets/components/text-area-with-file.component.tsx +142 -0
  200. package/templates/ui/shadcn/src/components/common/fields/assets/components/text-field.component.tsx +80 -0
  201. package/templates/ui/shadcn/src/components/common/fields/assets/components/tiny-editor.component.tsx +51 -0
  202. package/templates/ui/shadcn/src/components/common/fields/assets/components/upload-profile-picture.component.tsx +103 -0
  203. package/templates/ui/shadcn/src/components/common/fields/assets/components/upload-video-file.component.tsx +86 -0
  204. package/templates/ui/shadcn/src/components/common/fields/assets/interface/input-props.type.ts +198 -0
  205. package/templates/ui/shadcn/src/components/common/fields/cusInputField.component.tsx +52 -0
  206. package/templates/ui/shadcn/src/components/common/pagination/pagination.component.tsx +68 -0
  207. package/templates/ui/shadcn/src/components/ui/avatar.component.tsx +37 -0
  208. package/templates/ui/shadcn/src/components/ui/badge.component.tsx +28 -0
  209. package/templates/ui/shadcn/src/components/ui/button.component.tsx +52 -0
  210. package/templates/ui/shadcn/src/components/ui/card.component.tsx +46 -0
  211. package/templates/ui/shadcn/src/components/ui/checkbox.component.tsx +25 -0
  212. package/templates/ui/shadcn/src/components/ui/dialog.component.tsx +98 -0
  213. package/templates/ui/shadcn/src/components/ui/dropdown-menu.component.tsx +163 -0
  214. package/templates/ui/shadcn/src/components/ui/form.component.tsx +110 -0
  215. package/templates/ui/shadcn/src/components/ui/input-otp.component.tsx +64 -0
  216. package/templates/ui/shadcn/src/components/ui/input.component.tsx +23 -0
  217. package/templates/ui/shadcn/src/components/ui/label.component.tsx +23 -0
  218. package/templates/ui/shadcn/src/components/ui/popover.component.tsx +27 -0
  219. package/templates/ui/shadcn/src/components/ui/progress.component.tsx +22 -0
  220. package/templates/ui/shadcn/src/components/ui/radio-group.component.tsx +33 -0
  221. package/templates/ui/shadcn/src/components/ui/scroll-area.component.tsx +37 -0
  222. package/templates/ui/shadcn/src/components/ui/select.component.tsx +139 -0
  223. package/templates/ui/shadcn/src/components/ui/separator.component.tsx +23 -0
  224. package/templates/ui/shadcn/src/components/ui/sheet.component.tsx +89 -0
  225. package/templates/ui/shadcn/src/components/ui/switch.component.tsx +26 -0
  226. package/templates/ui/shadcn/src/components/ui/table.component.tsx +71 -0
  227. package/templates/ui/shadcn/src/components/ui/tabs.component.tsx +52 -0
  228. package/templates/ui/shadcn/src/components/ui/textarea.component.tsx +20 -0
  229. package/templates/ui/shadcn/src/components/ui/tooltip.component.tsx +25 -0
@@ -0,0 +1,71 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ html {
7
+ @apply transition-colors duration-300;
8
+ }
9
+ }
10
+
11
+ @layer base {
12
+ :root {
13
+ --background: 0 0% 100%;
14
+ --foreground: 222.2 84% 4.9%;
15
+ --card: 0 0% 100%;
16
+ --card-foreground: 222.2 84% 4.9%;
17
+ --primary: 221.2 83.2% 53.3%;
18
+ --primary-foreground: 210 40% 98%;
19
+ --secondary: 210 40% 96.1%;
20
+ --secondary-foreground: 222.2 47.4% 11.2%;
21
+ --muted: 210 40% 96.1%;
22
+ --muted-foreground: 215.4 16.3% 46.9%;
23
+ --accent: 210 40% 96.1%;
24
+ --accent-foreground: 222.2 47.4% 11.2%;
25
+ --destructive: 0 84.2% 60.2%;
26
+ --destructive-foreground: 210 40% 98%;
27
+ --border: 214.3 31.8% 91.4%;
28
+ --input: 214.3 31.8% 91.4%;
29
+ --ring: 221.2 83.2% 53.3%;
30
+ --radius: 0.5rem;
31
+ }
32
+
33
+ .dark {
34
+ --background: 222.2 84% 4.9%;
35
+ --foreground: 210 40% 98%;
36
+ --card: 222.2 84% 4.9%;
37
+ --card-foreground: 210 40% 98%;
38
+ --primary: 217.2 91.2% 59.8%;
39
+ --primary-foreground: 222.2 47.4% 11.2%;
40
+ --secondary: 217.2 32.6% 17.5%;
41
+ --secondary-foreground: 210 40% 98%;
42
+ --muted: 217.2 32.6% 17.5%;
43
+ --muted-foreground: 215 20.2% 65.1%;
44
+ --accent: 217.2 32.6% 17.5%;
45
+ --accent-foreground: 210 40% 98%;
46
+ --destructive: 0 62.8% 30.6%;
47
+ --destructive-foreground: 210 40% 98%;
48
+ --border: 217.2 32.6% 17.5%;
49
+ --input: 217.2 32.6% 17.5%;
50
+ --ring: 224.3 76.3% 48%;
51
+ }
52
+ }
53
+
54
+ @layer base {
55
+ * {
56
+ @apply border-border;
57
+ }
58
+ body {
59
+ @apply bg-background text-foreground;
60
+ }
61
+ }
62
+
63
+ @layer utilities {
64
+ .scrollbar-none {
65
+ -ms-overflow-style: none;
66
+ scrollbar-width: none;
67
+ }
68
+ .scrollbar-none::-webkit-scrollbar {
69
+ display: none;
70
+ }
71
+ }
@@ -0,0 +1,21 @@
1
+ import type { Metadata } from 'next';
2
+ import { Inter } from 'next/font/google';
3
+ import './globals.css';
4
+ import Sidebar from './_components/sidebar';
5
+
6
+ const inter = Inter({ subsets: ['latin'] });
7
+
8
+ export const metadata: Metadata = {
9
+ title: 'Nexstruct',
10
+ description: 'Scaffolded with Nexstruct',
11
+ };
12
+
13
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
14
+ return (
15
+ <html lang="en" suppressHydrationWarning>
16
+ <body className={inter.className} suppressHydrationWarning>
17
+ <Sidebar>{children}</Sidebar>
18
+ </body>
19
+ </html>
20
+ );
21
+ }
@@ -0,0 +1,13 @@
1
+ export default function Loading() {
2
+ return (
3
+ <div className="min-h-screen flex flex-col items-center justify-center bg-background">
4
+ <div className="relative">
5
+ <div className="h-16 w-16 animate-spin rounded-full border-4 border-muted border-t-primary" />
6
+ <div className="absolute inset-0 flex items-center justify-center">
7
+ <div className="h-4 w-4 rounded-full bg-primary animate-pulse" />
8
+ </div>
9
+ </div>
10
+ <p className="mt-6 text-sm text-muted-foreground animate-pulse">Loading…</p>
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,22 @@
1
+ import Link from 'next/link';
2
+
3
+ export default function NotFound() {
4
+ return (
5
+ <div className="min-h-screen flex flex-col items-center justify-center bg-background px-4">
6
+ <div className="text-8xl font-bold text-primary/20 select-none">404</div>
7
+ <h1 className="text-2xl font-semibold text-foreground mt-4 mb-2">Page not found</h1>
8
+ <p className="text-sm text-muted-foreground text-center max-w-md mb-8">
9
+ The page you&apos;re looking for doesn&apos;t exist or has been moved.
10
+ </p>
11
+ <Link
12
+ href="/"
13
+ className="inline-flex items-center justify-center gap-2 rounded-md bg-primary px-6 py-2 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90 transition-colors"
14
+ >
15
+ <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
16
+ <path strokeLinecap="round" strokeLinejoin="round" d="M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3" />
17
+ </svg>
18
+ Back to home
19
+ </Link>
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,10 @@
1
+ export default function Home() {
2
+ return (
3
+ <main className="flex min-h-screen flex-col items-center justify-center p-24">
4
+ <h1 className="text-4xl font-bold">Nexstruct</h1>
5
+ <p className="mt-4 text-lg text-muted-foreground">
6
+ Your project is ready. Start building.
7
+ </p>
8
+ </main>
9
+ );
10
+ }
@@ -0,0 +1,69 @@
1
+ import type { Config } from 'tailwindcss';
2
+ import animate from 'tailwindcss-animate';
3
+
4
+ const config: Config = {
5
+ darkMode: 'class',
6
+ content: ['./src/**/*.{ts,tsx}'],
7
+ theme: {
8
+ container: {
9
+ center: true,
10
+ padding: '2rem',
11
+ screens: { '2xl': '1400px' },
12
+ },
13
+ extend: {
14
+ colors: {
15
+ border: 'hsl(var(--border))',
16
+ input: 'hsl(var(--input))',
17
+ ring: 'hsl(var(--ring))',
18
+ background: 'hsl(var(--background))',
19
+ foreground: 'hsl(var(--foreground))',
20
+ primary: {
21
+ DEFAULT: 'hsl(var(--primary))',
22
+ foreground: 'hsl(var(--primary-foreground))',
23
+ },
24
+ secondary: {
25
+ DEFAULT: 'hsl(var(--secondary))',
26
+ foreground: 'hsl(var(--secondary-foreground))',
27
+ },
28
+ destructive: {
29
+ DEFAULT: 'hsl(var(--destructive))',
30
+ foreground: 'hsl(var(--destructive-foreground))',
31
+ },
32
+ muted: {
33
+ DEFAULT: 'hsl(var(--muted))',
34
+ foreground: 'hsl(var(--muted-foreground))',
35
+ },
36
+ accent: {
37
+ DEFAULT: 'hsl(var(--accent))',
38
+ foreground: 'hsl(var(--accent-foreground))',
39
+ },
40
+ card: {
41
+ DEFAULT: 'hsl(var(--card))',
42
+ foreground: 'hsl(var(--card-foreground))',
43
+ },
44
+ },
45
+ borderRadius: {
46
+ lg: 'var(--radius)',
47
+ md: 'calc(var(--radius) - 2px)',
48
+ sm: 'calc(var(--radius) - 4px)',
49
+ },
50
+ keyframes: {
51
+ 'accordion-down': {
52
+ from: { height: '0' },
53
+ to: { height: 'var(--radix-accordion-content-height)' },
54
+ },
55
+ 'accordion-up': {
56
+ from: { height: 'var(--radix-accordion-content-height)' },
57
+ to: { height: '0' },
58
+ },
59
+ },
60
+ animation: {
61
+ 'accordion-down': 'accordion-down 0.2s ease-out',
62
+ 'accordion-up': 'accordion-up 0.2s ease-out',
63
+ },
64
+ },
65
+ },
66
+ plugins: [animate],
67
+ };
68
+
69
+ export default config;
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ import { useTheme } from 'next-themes';
4
+ import { useEffect, useState } from 'react';
5
+
6
+ export function ThemeToggle() {
7
+ const { theme, setTheme } = useTheme();
8
+ const [mounted, setMounted] = useState(false);
9
+
10
+ useEffect(() => setMounted(true), []);
11
+
12
+ if (!mounted) return <div className="h-9 w-9" />;
13
+
14
+ return (
15
+ <button
16
+ onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
17
+ className="rounded-md p-2 text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
18
+ aria-label="Toggle theme"
19
+ >
20
+ {theme === 'dark' ? (
21
+ <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
22
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
23
+ </svg>
24
+ ) : (
25
+ <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
26
+ <path strokeLinecap="round" strokeLinejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
27
+ </svg>
28
+ )}
29
+ </button>
30
+ );
31
+ }
@@ -0,0 +1,18 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ /**
4
+ * ToastMessageChange — Transform common error/response messages into
5
+ * human-friendly text. Register known patterns here.
6
+ */
7
+ export const ToastMessageChange = (message: string | any): string => {
8
+ switch (message) {
9
+ case `Prisma: Unique constraint failed on field "name"`:
10
+ return 'Duplicate data found. Please change the title and try again.';
11
+ case `Prisma: Unique constraint failed on field "courseType"`:
12
+ return 'Duplicate data found. Please change the course type and try again.';
13
+ case 'Course total credits is less than modules total credits':
14
+ return 'Please ensure the assigned module credit is within the course credit.';
15
+ default:
16
+ return message;
17
+ }
18
+ };
@@ -0,0 +1,8 @@
1
+ export { ToastMessageShow } from './toast-message.component';
2
+ export { ToastMessageChange } from './custom-message.component';
3
+ export {
4
+ toastSuccessMessage,
5
+ toastErrorMessage,
6
+ toastLoadingMessage,
7
+ toastCustomMessage,
8
+ } from './toast-message.component';
@@ -0,0 +1,112 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ 'use client';
3
+
4
+ import toast, { ToastOptions } from 'react-hot-toast';
5
+ import { LabelAndPlaceholderTextFormat } from '@/lib/utils.util';
6
+ import { ToastMessageChange } from './custom-message.component';
7
+
8
+ /**
9
+ * Available toast notification types
10
+ */
11
+ type ToastType = 'success' | 'error' | 'loading' | 'custom';
12
+
13
+ /**
14
+ * Extended toast options including custom flags
15
+ */
16
+ type ExtendedToastOptions = ToastOptions & {
17
+ textFormat?: boolean;
18
+ changeMessage?: boolean;
19
+ };
20
+
21
+ /**
22
+ * Default toast configuration
23
+ */
24
+ const defaultOptions: ExtendedToastOptions = {
25
+ duration: 3000,
26
+ position: 'top-center',
27
+ textFormat: true,
28
+ changeMessage: false,
29
+ };
30
+
31
+ /**
32
+ * ToastMessageShow — Unified toast notification system.
33
+ *
34
+ * Supports:
35
+ * - Success / Error / Loading / Custom types
36
+ * - Extracts messages from API error responses
37
+ * - Optional message transformation via ToastMessageChange
38
+ * - Optional text case formatting
39
+ *
40
+ * @example
41
+ * ToastMessageShow('success', 'Profile updated successfully');
42
+ * ToastMessageShow('error', apiErrorResponse);
43
+ * ToastMessageShow('loading', 'Saving...', { textFormat: false });
44
+ */
45
+ export const ToastMessageShow = (
46
+ type: ToastType,
47
+ message: any,
48
+ options: ExtendedToastOptions = {},
49
+ ) => {
50
+ const { textFormat, changeMessage, ...toastOptions } = {
51
+ ...defaultOptions,
52
+ ...options,
53
+ };
54
+
55
+ const MessageHandler = (msg: string) => {
56
+ let finalMessage = msg;
57
+ if (changeMessage) finalMessage = ToastMessageChange(finalMessage);
58
+ if (textFormat) finalMessage = LabelAndPlaceholderTextFormat(finalMessage);
59
+ return finalMessage;
60
+ };
61
+
62
+ switch (type) {
63
+ case 'success': {
64
+ if (typeof message !== 'string') {
65
+ message = message?.data?.message || message?.message || 'Successfully updated';
66
+ }
67
+ message = MessageHandler(message);
68
+ toast.success(message, toastOptions);
69
+ break;
70
+ }
71
+ case 'error': {
72
+ if (typeof message !== 'string') {
73
+ message =
74
+ message?.response?.data?.message ||
75
+ message?.response?.message ||
76
+ message?.data?.message ||
77
+ message?.message ||
78
+ 'Something went wrong';
79
+ }
80
+ message = MessageHandler(message);
81
+ toast.error(message, toastOptions);
82
+ break;
83
+ }
84
+ case 'loading':
85
+ message = MessageHandler(message);
86
+ toast.loading(message, toastOptions);
87
+ break;
88
+ case 'custom':
89
+ message = MessageHandler(message);
90
+ toast(message, toastOptions);
91
+ break;
92
+ default:
93
+ message = MessageHandler(message);
94
+ toast(message, toastOptions);
95
+ }
96
+ };
97
+
98
+ /** Shortcut: Show a success toast */
99
+ export const toastSuccessMessage = (msg: any, options?: ExtendedToastOptions) =>
100
+ ToastMessageShow('success', msg, options);
101
+
102
+ /** Shortcut: Show an error toast */
103
+ export const toastErrorMessage = (msg: any, options?: ExtendedToastOptions) =>
104
+ ToastMessageShow('error', msg, options);
105
+
106
+ /** Shortcut: Show a loading toast */
107
+ export const toastLoadingMessage = (msg: any, options?: ExtendedToastOptions) =>
108
+ ToastMessageShow('loading', msg, options);
109
+
110
+ /** Shortcut: Show a custom toast */
111
+ export const toastCustomMessage = (msg: any, options?: ExtendedToastOptions) =>
112
+ ToastMessageShow('custom', msg, options);
@@ -0,0 +1,12 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ export function useDebounce<T>(value: T, delayMs: number = 300): T {
4
+ const [debouncedValue, setDebouncedValue] = useState(value);
5
+
6
+ useEffect(() => {
7
+ const timer = setTimeout(() => setDebouncedValue(value), delayMs);
8
+ return () => clearTimeout(timer);
9
+ }, [value, delayMs]);
10
+
11
+ return debouncedValue;
12
+ }
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ import { useState, useEffect, useCallback } from 'react';
3
+
4
+ interface UseFetchResult<T> {
5
+ data: T | null;
6
+ isLoading: boolean;
7
+ error: Error | null;
8
+ refetch: () => void;
9
+ }
10
+
11
+ export function useFetch<T>(url: string | null): UseFetchResult<T> {
12
+ const [data, setData] = useState<T | null>(null);
13
+ const [isLoading, setIsLoading] = useState(false);
14
+ const [error, setError] = useState<Error | null>(null);
15
+ const [refetchTrigger, setRefetchTrigger] = useState(0);
16
+
17
+ const refetch = useCallback(() => setRefetchTrigger((n) => n + 1), []);
18
+
19
+ useEffect(() => {
20
+ if (!url) return;
21
+
22
+ let cancelled = false;
23
+ setIsLoading(true);
24
+ setError(null);
25
+
26
+ fetch(url)
27
+ .then((res) => {
28
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
29
+ return res.json();
30
+ })
31
+ .then((json) => {
32
+ if (!cancelled) { setData(json); setIsLoading(false); }
33
+ })
34
+ .catch((err) => {
35
+ if (!cancelled) { setError(err); setIsLoading(false); }
36
+ });
37
+
38
+ return () => { cancelled = true; };
39
+ }, [url, refetchTrigger]);
40
+
41
+ return { data, isLoading, error, refetch };
42
+ }
@@ -0,0 +1,39 @@
1
+ 'use client';
2
+ import { useEffect, useRef, useState, type RefObject } from 'react';
3
+
4
+ interface UseIntersectionObserverOptions {
5
+ threshold?: number;
6
+ root?: Element | null;
7
+ rootMargin?: string;
8
+ triggerOnce?: boolean;
9
+ }
10
+
11
+ export function useIntersectionObserver<T extends Element>(
12
+ options: UseIntersectionObserverOptions = {}
13
+ ): [RefObject<T | null>, boolean] {
14
+ const { threshold = 0, root = null, rootMargin = '0px', triggerOnce = false } = options;
15
+ const ref = useRef<T | null>(null);
16
+ const [isIntersecting, setIsIntersecting] = useState(false);
17
+
18
+ useEffect(() => {
19
+ const element = ref.current;
20
+ if (!element) return;
21
+
22
+ const observer = new IntersectionObserver(
23
+ ([entry]) => {
24
+ if (entry.isIntersecting) {
25
+ setIsIntersecting(true);
26
+ if (triggerOnce) observer.unobserve(element);
27
+ } else if (!triggerOnce) {
28
+ setIsIntersecting(false);
29
+ }
30
+ },
31
+ { threshold, root, rootMargin }
32
+ );
33
+
34
+ observer.observe(element);
35
+ return () => observer.disconnect();
36
+ }, [threshold, root, rootMargin, triggerOnce]);
37
+
38
+ return [ref, isIntersecting];
39
+ }
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { useState, useEffect, useCallback } from 'react';
3
+
4
+ export function useLocalStorage<T>(key: string, initialValue: T) {
5
+ const [storedValue, setStoredValue] = useState<T>(initialValue);
6
+
7
+ useEffect(() => {
8
+ try {
9
+ const item = window.localStorage.getItem(key);
10
+ if (item) setStoredValue(JSON.parse(item));
11
+ } catch {
12
+ console.warn(`Error reading localStorage key "${key}":`);
13
+ }
14
+ }, [key]);
15
+
16
+ const setValue = useCallback(
17
+ (value: T | ((val: T) => T)) => {
18
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
19
+ setStoredValue(valueToStore);
20
+ try {
21
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
22
+ } catch {
23
+ console.warn(`Error setting localStorage key "${key}":`);
24
+ }
25
+ },
26
+ [key, storedValue]
27
+ );
28
+
29
+ return [storedValue, setValue] as const;
30
+ }
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+ import { useState, useEffect } from 'react';
3
+
4
+ export function useMediaQuery(query: string): boolean {
5
+ const [matches, setMatches] = useState(false);
6
+
7
+ useEffect(() => {
8
+ const media = window.matchMedia(query);
9
+ setMatches(media.matches);
10
+
11
+ const listener = (event: MediaQueryListEvent) => setMatches(event.matches);
12
+ media.addEventListener('change', listener);
13
+ return () => media.removeEventListener('change', listener);
14
+ }, [query]);
15
+
16
+ return matches;
17
+ }
18
+
19
+ export function useIsMobile(): boolean {
20
+ return useMediaQuery('(max-width: 768px)');
21
+ }
22
+
23
+ export function useIsDarkMode(): boolean {
24
+ const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
25
+ return prefersDark;
26
+ }
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+ import { useState, useCallback } from 'react';
3
+
4
+ export function useToggle(initialValue: boolean = false) {
5
+ const [value, setValue] = useState(initialValue);
6
+
7
+ const toggle = useCallback(() => setValue((v) => !v), []);
8
+ const setTrue = useCallback(() => setValue(true), []);
9
+ const setFalse = useCallback(() => setValue(false), []);
10
+
11
+ return { value, toggle, setTrue, setFalse, setValue } as const;
12
+ }