@imksh/create 1.0.24 → 1.0.26

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 (415) hide show
  1. package/package.json +1 -1
  2. package/src/commands/create.js +2 -1
  3. package/src/index.js +1 -1
  4. package/src/utils/askTemplate.js +1 -0
  5. package/templates/mern-react-native/client/src/vite-env.d.ts +1 -0
  6. package/templates/mern-react-native/mobile/_gitignore +43 -0
  7. package/templates/mern-react-native/mobile/babel.config.js +1 -0
  8. package/templates/mern-react-native/mobile/drizzle.config.ts +8 -0
  9. package/templates/mern-react-native/mobile/expo-env.d.ts +3 -0
  10. package/templates/mern-react-native/mobile/package.json +8 -1
  11. package/templates/mern-react-native/mobile/src/app/(auth)/_layout.tsx +12 -0
  12. package/templates/mern-react-native/mobile/src/app/(auth)/change-password.tsx +112 -0
  13. package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +156 -0
  14. package/templates/mern-react-native/mobile/src/app/(auth)/forgot-password.tsx +191 -0
  15. package/templates/mern-react-native/mobile/src/app/{login.tsx → (auth)/login.tsx} +69 -21
  16. package/templates/mern-react-native/mobile/src/app/(tabs)/dashboard.tsx +2 -2
  17. package/templates/mern-react-native/mobile/src/app/(tabs)/index.tsx +18 -17
  18. package/templates/mern-react-native/mobile/src/app/(tabs)/profile.tsx +23 -60
  19. package/templates/mern-react-native/mobile/src/app/(tabs)/search.tsx +2 -2
  20. package/templates/mern-react-native/mobile/src/app/_layout.tsx +22 -4
  21. package/templates/mern-react-native/mobile/src/components/common/AnimatedHeader.tsx +328 -0
  22. package/templates/mern-react-native/mobile/src/components/common/LocationLoader.tsx +591 -0
  23. package/templates/mern-react-native/mobile/src/components/common/ScreenHeader.tsx +41 -17
  24. package/templates/mern-react-native/mobile/src/components/common/TabHeader.tsx +58 -0
  25. package/templates/mern-react-native/mobile/src/components/common/Typography.tsx +49 -30
  26. package/templates/mern-react-native/mobile/src/components/common/Welcome.tsx +2 -2
  27. package/templates/mern-react-native/mobile/src/components/ui/BottomModal.tsx +31 -20
  28. package/templates/mern-react-native/mobile/src/components/ui/ClassicTabBar.tsx +4 -3
  29. package/templates/mern-react-native/mobile/src/components/ui/CustomTabBar.tsx +108 -71
  30. package/templates/mern-react-native/mobile/src/components/ui/Sidebar.tsx +81 -56
  31. package/templates/mern-react-native/mobile/src/components/ui/buttons/Button.tsx +106 -37
  32. package/templates/mern-react-native/mobile/src/components/ui/buttons/IconButton.tsx +4 -2
  33. package/templates/mern-react-native/mobile/src/components/ui/data-display/Accordion.tsx +3 -2
  34. package/templates/mern-react-native/mobile/src/components/ui/data-display/Avatar.tsx +4 -3
  35. package/templates/mern-react-native/mobile/src/components/ui/data-display/Badge.tsx +45 -17
  36. package/templates/mern-react-native/mobile/src/components/ui/data-display/ImageViewer.tsx +19 -9
  37. package/templates/mern-react-native/mobile/src/components/ui/feedback/CircularProgress.tsx +5 -4
  38. package/templates/mern-react-native/mobile/src/components/ui/feedback/ProgressBar.tsx +4 -3
  39. package/templates/mern-react-native/mobile/src/components/ui/feedback/Skeleton.tsx +3 -0
  40. package/templates/mern-react-native/mobile/src/components/ui/feedback/Spinner.tsx +11 -3
  41. package/templates/mern-react-native/mobile/src/components/ui/inputs/Checkbox.tsx +3 -2
  42. package/templates/mern-react-native/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
  43. package/templates/mern-react-native/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  44. package/templates/mern-react-native/mobile/src/components/ui/inputs/Input.tsx +54 -14
  45. package/templates/mern-react-native/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
  46. package/templates/mern-react-native/mobile/src/components/ui/inputs/OTPInput.tsx +32 -9
  47. package/templates/mern-react-native/mobile/src/components/ui/inputs/PasswordInput.tsx +2 -1
  48. package/templates/mern-react-native/mobile/src/components/ui/inputs/Radio.tsx +3 -2
  49. package/templates/mern-react-native/mobile/src/components/ui/inputs/SearchBar.tsx +41 -4
  50. package/templates/mern-react-native/mobile/src/components/ui/inputs/Slider.tsx +4 -3
  51. package/templates/mern-react-native/mobile/src/components/ui/inputs/Switch.tsx +3 -2
  52. package/templates/mern-react-native/mobile/src/components/ui/inputs/TextArea.tsx +2 -1
  53. package/templates/mern-react-native/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  54. package/templates/mern-react-native/mobile/src/components/ui/layout/Grid.tsx +52 -10
  55. package/templates/mern-react-native/mobile/src/components/ui/layout/Section.tsx +7 -8
  56. package/templates/mern-react-native/mobile/src/components/ui/navigation/Pagination.tsx +4 -3
  57. package/templates/mern-react-native/mobile/src/components/ui/navigation/ProgressStepper.tsx +6 -5
  58. package/templates/mern-react-native/mobile/src/components/ui/navigation/Tabs.tsx +30 -4
  59. package/templates/mern-react-native/mobile/src/config/api.ts +12 -1
  60. package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +12 -0
  61. package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +1 -1
  62. package/templates/mern-react-native/mobile/src/db/index.ts +9 -0
  63. package/templates/mern-react-native/mobile/src/db/schema.ts +14 -0
  64. package/templates/mern-react-native/mobile/src/hooks/useDebounce.ts +17 -0
  65. package/templates/mern-react-native/mobile/src/services/notification.service.ts +22 -0
  66. package/templates/mern-react-native/mobile/src/services/socket.service.ts +36 -0
  67. package/templates/mern-react-native/mobile/src/store/useAuthStore.ts +122 -27
  68. package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +178 -0
  69. package/templates/mern-react-native/mobile/src/types/auth.type.ts +14 -0
  70. package/templates/mern-react-native/mobile/tsconfig.json +1 -0
  71. package/templates/mern-ts/client/src/vite-env.d.ts +1 -0
  72. package/templates/next/next.config.mjs +1 -0
  73. package/templates/next/package.json +10 -9
  74. package/templates/next/postcss.config.js +5 -0
  75. package/templates/next/src/app/layout.js +1 -0
  76. package/templates/next/src/app/page.js +1 -1
  77. package/templates/next/src/components/layout/ProtectedRoute.jsx +3 -3
  78. package/templates/next/src/index.css +1 -0
  79. package/templates/next/src/store/useUiStore.js +13 -7
  80. package/templates/next-ts/.env.example +0 -0
  81. package/templates/next-ts/AGENTS.md +37 -0
  82. package/templates/next-ts/CLAUDE.md +1 -0
  83. package/templates/next-ts/_gitignore +0 -24
  84. package/templates/next-ts/next-env.d.ts +3 -1
  85. package/templates/next-ts/next.config.mjs +1 -0
  86. package/templates/next-ts/package.json +13 -12
  87. package/templates/next-ts/postcss.config.js +5 -0
  88. package/templates/next-ts/public/logo.png +0 -0
  89. package/templates/next-ts/src/app/error.tsx +41 -0
  90. package/templates/next-ts/src/app/layout.tsx +2 -1
  91. package/templates/next-ts/src/app/not-found.tsx +5 -0
  92. package/templates/next-ts/src/app/page.tsx +1 -1
  93. package/templates/next-ts/src/components/layout/ProtectedRoute.tsx +3 -3
  94. package/templates/next-ts/src/components/ui/Button.tsx +63 -0
  95. package/templates/next-ts/src/components/ui/Input.tsx +79 -0
  96. package/templates/next-ts/src/index.css +1 -0
  97. package/templates/next-ts/src/store/useUiStore.ts +13 -7
  98. package/templates/next-ts/tsconfig.json +19 -5
  99. package/templates/production-fullstack/client/index.html +14 -0
  100. package/templates/production-fullstack/client/package.json +47 -0
  101. package/templates/production-fullstack/client/public/_redirects +1 -0
  102. package/templates/production-fullstack/client/public/favicon.ico +0 -0
  103. package/templates/production-fullstack/client/public/favicon.svg +1 -0
  104. package/templates/production-fullstack/client/public/logo.png +0 -0
  105. package/templates/production-fullstack/client/public/manifest.json +24 -0
  106. package/templates/production-fullstack/client/public/sw.js +56 -0
  107. package/templates/production-fullstack/client/src/App.tsx +68 -0
  108. package/templates/production-fullstack/client/src/assets/animations/loading.json +1 -0
  109. package/templates/production-fullstack/client/src/assets/data/navigation.ts +29 -0
  110. package/templates/production-fullstack/client/src/assets/images/logo.png +0 -0
  111. package/templates/production-fullstack/client/src/components/layout/ProtectedRoute.tsx +23 -0
  112. package/templates/production-fullstack/client/src/components/layout/PublicLayout.tsx +13 -0
  113. package/templates/production-fullstack/client/src/components/layout/SidebarLayout.tsx +37 -0
  114. package/templates/production-fullstack/client/src/components/shared/Breadcrumbs.tsx +27 -0
  115. package/templates/production-fullstack/client/src/components/shared/LottieIcon.tsx +36 -0
  116. package/templates/production-fullstack/client/src/components/shared/NotFound.tsx +78 -0
  117. package/templates/production-fullstack/client/src/components/shared/PwaInstallPrompt.tsx +72 -0
  118. package/templates/production-fullstack/client/src/components/shared/SEO.tsx +67 -0
  119. package/templates/production-fullstack/client/src/components/shared/Scroll.tsx +25 -0
  120. package/templates/production-fullstack/client/src/components/shared/Unauthorized.tsx +76 -0
  121. package/templates/production-fullstack/client/src/components/shared/skeletons/common/CardSkeleton.tsx +23 -0
  122. package/templates/production-fullstack/client/src/components/shared/skeletons/common/DashboardSkeleton.tsx +19 -0
  123. package/templates/production-fullstack/client/src/components/shared/skeletons/common/ListSkeleton.tsx +24 -0
  124. package/templates/production-fullstack/client/src/components/shared/skeletons/common/PageSkeleton.tsx +26 -0
  125. package/templates/production-fullstack/client/src/components/shared/skeletons/common/ProfileSkeleton.tsx +23 -0
  126. package/templates/production-fullstack/client/src/components/shared/skeletons/common/Skeleton.tsx +18 -0
  127. package/templates/production-fullstack/client/src/components/shared/skeletons/common/TableSkeleton.tsx +33 -0
  128. package/templates/production-fullstack/client/src/components/ui/ConfirmModal.tsx +152 -0
  129. package/templates/production-fullstack/client/src/components/ui/Header.tsx +230 -0
  130. package/templates/production-fullstack/client/src/components/ui/Loading.tsx +19 -0
  131. package/templates/production-fullstack/client/src/components/ui/Modal.tsx +106 -0
  132. package/templates/production-fullstack/client/src/components/ui/SidebarHeader.tsx +34 -0
  133. package/templates/production-fullstack/client/src/components/ui/Table.tsx +118 -0
  134. package/templates/production-fullstack/client/src/components/ui/ToggleSwitch.tsx +38 -0
  135. package/templates/production-fullstack/client/src/components/ui/sidebar.tsx +152 -0
  136. package/templates/production-fullstack/client/src/config/index.ts +44 -0
  137. package/templates/production-fullstack/client/src/constants/navigation.ts +14 -0
  138. package/templates/production-fullstack/client/src/features/auth/api/authService.ts +19 -0
  139. package/templates/production-fullstack/client/src/features/auth/pages/Login.tsx +233 -0
  140. package/templates/production-fullstack/client/src/features/auth/store/useAuthStore.ts +143 -0
  141. package/templates/production-fullstack/client/src/features/core/pages/Home.tsx +150 -0
  142. package/templates/production-fullstack/client/src/features/core/pages/Profile.tsx +9 -0
  143. package/templates/production-fullstack/client/src/hooks/useLenis.ts +28 -0
  144. package/templates/production-fullstack/client/src/hooks/usePwaInstall.ts +45 -0
  145. package/templates/production-fullstack/client/src/hooks/useWindowSize.ts +25 -0
  146. package/templates/production-fullstack/client/src/index.css +158 -0
  147. package/templates/production-fullstack/client/src/lib/axios.ts +22 -0
  148. package/templates/production-fullstack/client/src/lib/dayjs.ts +12 -0
  149. package/templates/production-fullstack/client/src/lib/query-client.ts +11 -0
  150. package/templates/production-fullstack/client/src/lib/socket.ts +6 -0
  151. package/templates/production-fullstack/client/src/main.tsx +28 -0
  152. package/templates/production-fullstack/client/src/store/useUiStore.ts +74 -0
  153. package/templates/production-fullstack/client/src/utils/formatDate.ts +19 -0
  154. package/templates/production-fullstack/client/src/utils/getInitials.ts +10 -0
  155. package/templates/production-fullstack/client/src/vite-env.d.ts +1 -0
  156. package/templates/production-fullstack/client/tsconfig.json +25 -0
  157. package/templates/production-fullstack/client/tsconfig.node.json +10 -0
  158. package/templates/production-fullstack/client/vite.config.ts +8 -0
  159. package/templates/production-fullstack/mobile/LICENSE +21 -0
  160. package/templates/production-fullstack/mobile/app.json +84 -0
  161. package/templates/production-fullstack/mobile/assets/animations/celebrate.json +1 -0
  162. package/templates/production-fullstack/mobile/assets/animations/infinity.json +1 -0
  163. package/templates/production-fullstack/mobile/assets/animations/loading.json +1 -0
  164. package/templates/production-fullstack/mobile/assets/fonts/Inter_Bold.ttf +0 -0
  165. package/templates/production-fullstack/mobile/assets/fonts/Inter_ExtraBold.ttf +0 -0
  166. package/templates/production-fullstack/mobile/assets/fonts/Inter_Medium.ttf +0 -0
  167. package/templates/production-fullstack/mobile/assets/fonts/Inter_Regular.ttf +0 -0
  168. package/templates/production-fullstack/mobile/assets/fonts/Inter_SemiBold.ttf +0 -0
  169. package/templates/production-fullstack/mobile/assets/images/logo/logo.png +0 -0
  170. package/templates/production-fullstack/mobile/assets/images/logo/logo.svg +1 -0
  171. package/templates/production-fullstack/mobile/assets/images/logo/logoWithBg.png +0 -0
  172. package/templates/production-fullstack/mobile/babel.config.js +13 -0
  173. package/templates/production-fullstack/mobile/drizzle.config.ts +8 -0
  174. package/templates/production-fullstack/mobile/eas.json +24 -0
  175. package/templates/production-fullstack/mobile/eslint.config.js +10 -0
  176. package/templates/production-fullstack/mobile/expo-env.d.ts +3 -0
  177. package/templates/production-fullstack/mobile/metro.config.js +6 -0
  178. package/templates/production-fullstack/mobile/nativewind-env.d.ts +3 -0
  179. package/templates/production-fullstack/mobile/package.json +89 -0
  180. package/templates/production-fullstack/mobile/src/app/(auth)/_layout.tsx +12 -0
  181. package/templates/production-fullstack/mobile/src/app/(auth)/change-password.tsx +112 -0
  182. package/templates/production-fullstack/mobile/src/app/(auth)/edit-profile.tsx +160 -0
  183. package/templates/production-fullstack/mobile/src/app/(auth)/forgot-password.tsx +191 -0
  184. package/templates/production-fullstack/mobile/src/app/(auth)/login.tsx +253 -0
  185. package/templates/production-fullstack/mobile/src/app/_layout.tsx +102 -0
  186. package/templates/production-fullstack/mobile/src/app/global.css +82 -0
  187. package/templates/production-fullstack/mobile/src/components/common/AnimatedHeader.tsx +328 -0
  188. package/templates/production-fullstack/mobile/src/components/common/ConfirmationToast.tsx +67 -0
  189. package/templates/production-fullstack/mobile/src/components/common/Loading.tsx +26 -0
  190. package/templates/production-fullstack/mobile/src/components/common/LocationLoader.tsx +591 -0
  191. package/templates/production-fullstack/mobile/src/components/common/ScreenHeader.tsx +68 -0
  192. package/templates/production-fullstack/mobile/src/components/common/TabHeader.tsx +58 -0
  193. package/templates/production-fullstack/mobile/src/components/common/Typography.tsx +113 -0
  194. package/templates/production-fullstack/mobile/src/components/common/Welcome.tsx +55 -0
  195. package/templates/production-fullstack/mobile/src/components/ui/BottomModal.tsx +159 -0
  196. package/templates/production-fullstack/mobile/src/components/ui/ClassicTabBar.tsx +129 -0
  197. package/templates/production-fullstack/mobile/src/components/ui/CustomTabBar.tsx +238 -0
  198. package/templates/production-fullstack/mobile/src/components/ui/Sidebar.tsx +258 -0
  199. package/templates/production-fullstack/mobile/src/components/ui/buttons/Button.tsx +180 -0
  200. package/templates/production-fullstack/mobile/src/components/ui/buttons/ButtonGroup.tsx +32 -0
  201. package/templates/production-fullstack/mobile/src/components/ui/buttons/FloatingActionButton.tsx +72 -0
  202. package/templates/production-fullstack/mobile/src/components/ui/buttons/IconButton.tsx +84 -0
  203. package/templates/production-fullstack/mobile/src/components/ui/data-display/Accordion.tsx +82 -0
  204. package/templates/production-fullstack/mobile/src/components/ui/data-display/Avatar.tsx +47 -0
  205. package/templates/production-fullstack/mobile/src/components/ui/data-display/Badge.tsx +104 -0
  206. package/templates/production-fullstack/mobile/src/components/ui/data-display/Card.tsx +36 -0
  207. package/templates/production-fullstack/mobile/src/components/ui/data-display/Image.tsx +43 -0
  208. package/templates/production-fullstack/mobile/src/components/ui/data-display/ImageViewer.tsx +33 -0
  209. package/templates/production-fullstack/mobile/src/components/ui/data-display/SwipeableRow.tsx +94 -0
  210. package/templates/production-fullstack/mobile/src/components/ui/feedback/CircularProgress.tsx +38 -0
  211. package/templates/production-fullstack/mobile/src/components/ui/feedback/GestureWrapper.tsx +114 -0
  212. package/templates/production-fullstack/mobile/src/components/ui/feedback/ProgressBar.tsx +51 -0
  213. package/templates/production-fullstack/mobile/src/components/ui/feedback/Skeleton.tsx +84 -0
  214. package/templates/production-fullstack/mobile/src/components/ui/feedback/Spinner.tsx +50 -0
  215. package/templates/production-fullstack/mobile/src/components/ui/inputs/Checkbox.tsx +54 -0
  216. package/templates/production-fullstack/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
  217. package/templates/production-fullstack/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  218. package/templates/production-fullstack/mobile/src/components/ui/inputs/Input.tsx +158 -0
  219. package/templates/production-fullstack/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
  220. package/templates/production-fullstack/mobile/src/components/ui/inputs/OTPInput.tsx +83 -0
  221. package/templates/production-fullstack/mobile/src/components/ui/inputs/PasswordInput.tsx +16 -0
  222. package/templates/production-fullstack/mobile/src/components/ui/inputs/Radio.tsx +54 -0
  223. package/templates/production-fullstack/mobile/src/components/ui/inputs/SearchBar.tsx +53 -0
  224. package/templates/production-fullstack/mobile/src/components/ui/inputs/Slider.tsx +49 -0
  225. package/templates/production-fullstack/mobile/src/components/ui/inputs/Switch.tsx +75 -0
  226. package/templates/production-fullstack/mobile/src/components/ui/inputs/TextArea.tsx +15 -0
  227. package/templates/production-fullstack/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  228. package/templates/production-fullstack/mobile/src/components/ui/layout/Container.tsx +23 -0
  229. package/templates/production-fullstack/mobile/src/components/ui/layout/Divider.tsx +20 -0
  230. package/templates/production-fullstack/mobile/src/components/ui/layout/Grid.tsx +62 -0
  231. package/templates/production-fullstack/mobile/src/components/ui/layout/KeyboardAvoidingViewWrapper.tsx +16 -0
  232. package/templates/production-fullstack/mobile/src/components/ui/layout/ScrollContainer.tsx +21 -0
  233. package/templates/production-fullstack/mobile/src/components/ui/layout/Section.tsx +51 -0
  234. package/templates/production-fullstack/mobile/src/components/ui/layout/Spacer.tsx +6 -0
  235. package/templates/production-fullstack/mobile/src/components/ui/layout/Stack.tsx +34 -0
  236. package/templates/production-fullstack/mobile/src/components/ui/navigation/Pagination.tsx +61 -0
  237. package/templates/production-fullstack/mobile/src/components/ui/navigation/ProgressStepper.tsx +66 -0
  238. package/templates/production-fullstack/mobile/src/components/ui/navigation/Tabs.tsx +173 -0
  239. package/templates/production-fullstack/mobile/src/config/sidebar.config.ts +72 -0
  240. package/templates/production-fullstack/mobile/src/config/tabNavigation.ts +64 -0
  241. package/templates/production-fullstack/mobile/src/constants/Colors.ts +71 -0
  242. package/templates/production-fullstack/mobile/src/db/index.ts +9 -0
  243. package/templates/production-fullstack/mobile/src/db/schema.ts +14 -0
  244. package/templates/production-fullstack/mobile/src/features/auth/api/auth.service.ts +35 -0
  245. package/templates/production-fullstack/mobile/src/features/auth/store/useAuthStore.ts +273 -0
  246. package/templates/production-fullstack/mobile/src/features/auth/types/auth.type.ts +14 -0
  247. package/templates/production-fullstack/mobile/src/hooks/useDebounce.ts +17 -0
  248. package/templates/production-fullstack/mobile/src/lib/axios.ts +27 -0
  249. package/templates/production-fullstack/mobile/src/lib/query-client.ts +11 -0
  250. package/templates/production-fullstack/mobile/src/lib/socket.ts +36 -0
  251. package/templates/production-fullstack/mobile/src/lib/storage.ts +55 -0
  252. package/templates/production-fullstack/mobile/src/services/permission.service.ts +214 -0
  253. package/templates/production-fullstack/mobile/src/store/localStore.ts +7 -0
  254. package/templates/production-fullstack/mobile/src/store/useNotificationStore.ts +53 -0
  255. package/templates/production-fullstack/mobile/src/types/common.type.ts +6 -0
  256. package/templates/production-fullstack/mobile/src/types/images.d.ts +24 -0
  257. package/templates/production-fullstack/mobile/src/utils/toast.ts +26 -0
  258. package/templates/production-fullstack/mobile/tailwind.config.js +71 -0
  259. package/templates/production-fullstack/mobile/tsconfig.json +23 -0
  260. package/templates/production-fullstack/server/.env.example +63 -0
  261. package/templates/production-fullstack/server/README.md +75 -0
  262. package/templates/production-fullstack/server/eslint.config.js +13 -0
  263. package/templates/production-fullstack/server/package.json +58 -0
  264. package/templates/production-fullstack/server/prisma/schema/core/attachment.prisma +14 -0
  265. package/templates/production-fullstack/server/prisma/schema/core/notification.prisma +18 -0
  266. package/templates/production-fullstack/server/prisma/schema/core/permission.prisma +26 -0
  267. package/templates/production-fullstack/server/prisma/schema/core/role.prisma +25 -0
  268. package/templates/production-fullstack/server/prisma/schema/core/user.prisma +56 -0
  269. package/templates/production-fullstack/server/prisma/schema/enums/common.prisma +13 -0
  270. package/templates/production-fullstack/server/prisma/schema/enums/notification.prisma +6 -0
  271. package/templates/production-fullstack/server/prisma/schema/enums/user.prisma +11 -0
  272. package/templates/production-fullstack/server/prisma/schema/schema.prisma +7 -0
  273. package/templates/production-fullstack/server/prisma.config.ts +14 -0
  274. package/templates/production-fullstack/server/src/app.ts +36 -0
  275. package/templates/production-fullstack/server/src/common/middlewares/auth.middleware.ts +45 -0
  276. package/templates/production-fullstack/server/src/common/middlewares/errorHandler.ts +16 -0
  277. package/templates/production-fullstack/server/src/common/middlewares/multer.ts +20 -0
  278. package/templates/production-fullstack/server/src/common/middlewares/rateLimiter.ts +53 -0
  279. package/templates/production-fullstack/server/src/common/services/email.service.ts +28 -0
  280. package/templates/production-fullstack/server/src/common/services/push.service.ts +65 -0
  281. package/templates/production-fullstack/server/src/common/services/storage.service.ts +9 -0
  282. package/templates/production-fullstack/server/src/common/types/express.d.ts +10 -0
  283. package/templates/production-fullstack/server/src/common/utils/cloudinaryUpload.ts +44 -0
  284. package/templates/production-fullstack/server/src/common/utils/emailOtp.ts +30 -0
  285. package/templates/production-fullstack/server/src/common/utils/genOtpToken.ts +22 -0
  286. package/templates/production-fullstack/server/src/common/utils/generateVapidKeys.ts +12 -0
  287. package/templates/production-fullstack/server/src/common/utils/jwt.ts +27 -0
  288. package/templates/production-fullstack/server/src/common/utils/pushHelper.ts +22 -0
  289. package/templates/production-fullstack/server/src/config/env.ts +2 -0
  290. package/templates/production-fullstack/server/src/config/logger.ts +58 -0
  291. package/templates/production-fullstack/server/src/config/prisma.ts +26 -0
  292. package/templates/production-fullstack/server/src/features/attachments/upload.controller.ts +22 -0
  293. package/templates/production-fullstack/server/src/features/attachments/upload.routes.ts +11 -0
  294. package/templates/production-fullstack/server/src/features/auth/auth.controller.ts +124 -0
  295. package/templates/production-fullstack/server/src/features/auth/auth.routes.ts +22 -0
  296. package/templates/production-fullstack/server/src/features/auth/auth.service.ts +155 -0
  297. package/templates/production-fullstack/server/src/features/notifications/push.controller.ts +55 -0
  298. package/templates/production-fullstack/server/src/features/notifications/push.routes.ts +11 -0
  299. package/templates/production-fullstack/server/src/index.ts +17 -0
  300. package/templates/production-fullstack/server/src/routes/index.ts +12 -0
  301. package/templates/production-fullstack/server/src/sockets/auth.ts +0 -0
  302. package/templates/production-fullstack/server/src/sockets/connection.ts +31 -0
  303. package/templates/production-fullstack/server/src/sockets/emitters/assignment.emitter.ts +0 -0
  304. package/templates/production-fullstack/server/src/sockets/emitters/attendance.emitter.ts +0 -0
  305. package/templates/production-fullstack/server/src/sockets/emitters/exam.emitter.ts +0 -0
  306. package/templates/production-fullstack/server/src/sockets/emitters/notification.emitter.ts +17 -0
  307. package/templates/production-fullstack/server/src/sockets/events.ts +27 -0
  308. package/templates/production-fullstack/server/src/sockets/handlers/attendance.handler.ts +0 -0
  309. package/templates/production-fullstack/server/src/sockets/handlers/chat.handler.ts +0 -0
  310. package/templates/production-fullstack/server/src/sockets/handlers/index.ts +15 -0
  311. package/templates/production-fullstack/server/src/sockets/handlers/lms.handler.ts +0 -0
  312. package/templates/production-fullstack/server/src/sockets/handlers/notification.handler.ts +39 -0
  313. package/templates/production-fullstack/server/src/sockets/index.ts +39 -0
  314. package/templates/production-fullstack/server/src/sockets/middleware.ts +54 -0
  315. package/templates/production-fullstack/server/src/sockets/rooms.ts +11 -0
  316. package/templates/production-fullstack/server/src/sockets/types.ts +32 -0
  317. package/templates/production-fullstack/server/tsconfig.json +14 -0
  318. package/templates/react-native/babel.config.js +1 -0
  319. package/templates/react-native/drizzle.config.ts +8 -0
  320. package/templates/react-native/package.json +8 -1
  321. package/templates/react-native/src/app/(auth)/_layout.tsx +12 -0
  322. package/templates/react-native/src/app/(auth)/change-password.tsx +112 -0
  323. package/templates/react-native/src/app/(auth)/edit-profile.tsx +156 -0
  324. package/templates/react-native/src/app/(auth)/forgot-password.tsx +191 -0
  325. package/templates/react-native/src/app/{login.tsx → (auth)/login.tsx} +69 -21
  326. package/templates/react-native/src/app/(tabs)/dashboard.tsx +2 -2
  327. package/templates/react-native/src/app/(tabs)/index.tsx +18 -17
  328. package/templates/react-native/src/app/(tabs)/profile.tsx +23 -60
  329. package/templates/react-native/src/app/(tabs)/search.tsx +2 -2
  330. package/templates/react-native/src/app/_layout.tsx +22 -4
  331. package/templates/react-native/src/components/common/AnimatedHeader.tsx +328 -0
  332. package/templates/react-native/src/components/common/LocationLoader.tsx +591 -0
  333. package/templates/react-native/src/components/common/ScreenHeader.tsx +41 -17
  334. package/templates/react-native/src/components/common/TabHeader.tsx +58 -0
  335. package/templates/react-native/src/components/common/Typography.tsx +49 -30
  336. package/templates/react-native/src/components/common/Welcome.tsx +2 -2
  337. package/templates/react-native/src/components/ui/BottomModal.tsx +31 -20
  338. package/templates/react-native/src/components/ui/ClassicTabBar.tsx +4 -3
  339. package/templates/react-native/src/components/ui/CustomTabBar.tsx +108 -71
  340. package/templates/react-native/src/components/ui/Sidebar.tsx +81 -56
  341. package/templates/react-native/src/components/ui/buttons/Button.tsx +106 -37
  342. package/templates/react-native/src/components/ui/buttons/IconButton.tsx +4 -2
  343. package/templates/react-native/src/components/ui/data-display/Accordion.tsx +3 -2
  344. package/templates/react-native/src/components/ui/data-display/Avatar.tsx +4 -3
  345. package/templates/react-native/src/components/ui/data-display/Badge.tsx +45 -17
  346. package/templates/react-native/src/components/ui/data-display/ImageViewer.tsx +19 -9
  347. package/templates/react-native/src/components/ui/feedback/CircularProgress.tsx +5 -4
  348. package/templates/react-native/src/components/ui/feedback/ProgressBar.tsx +4 -3
  349. package/templates/react-native/src/components/ui/feedback/Skeleton.tsx +3 -0
  350. package/templates/react-native/src/components/ui/feedback/Spinner.tsx +11 -3
  351. package/templates/react-native/src/components/ui/inputs/Checkbox.tsx +3 -2
  352. package/templates/react-native/src/components/ui/inputs/DatePicker.tsx +186 -0
  353. package/templates/react-native/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  354. package/templates/react-native/src/components/ui/inputs/Input.tsx +54 -14
  355. package/templates/react-native/src/components/ui/inputs/LocationPicker.tsx +211 -0
  356. package/templates/react-native/src/components/ui/inputs/OTPInput.tsx +32 -9
  357. package/templates/react-native/src/components/ui/inputs/PasswordInput.tsx +2 -1
  358. package/templates/react-native/src/components/ui/inputs/Radio.tsx +3 -2
  359. package/templates/react-native/src/components/ui/inputs/SearchBar.tsx +41 -4
  360. package/templates/react-native/src/components/ui/inputs/Slider.tsx +4 -3
  361. package/templates/react-native/src/components/ui/inputs/Switch.tsx +3 -2
  362. package/templates/react-native/src/components/ui/inputs/TextArea.tsx +2 -1
  363. package/templates/react-native/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  364. package/templates/react-native/src/components/ui/layout/Grid.tsx +52 -10
  365. package/templates/react-native/src/components/ui/layout/Section.tsx +7 -8
  366. package/templates/react-native/src/components/ui/navigation/Pagination.tsx +4 -3
  367. package/templates/react-native/src/components/ui/navigation/ProgressStepper.tsx +6 -5
  368. package/templates/react-native/src/components/ui/navigation/Tabs.tsx +30 -4
  369. package/templates/react-native/src/config/api.ts +12 -1
  370. package/templates/react-native/src/config/sidebar.config.ts +12 -0
  371. package/templates/react-native/src/config/tabNavigation.ts +1 -1
  372. package/templates/react-native/src/db/index.ts +9 -0
  373. package/templates/react-native/src/db/schema.ts +14 -0
  374. package/templates/react-native/src/hooks/useDebounce.ts +17 -0
  375. package/templates/react-native/src/services/notification.service.ts +22 -0
  376. package/templates/react-native/src/services/socket.service.ts +36 -0
  377. package/templates/react-native/src/store/useAuthStore.ts +122 -27
  378. package/templates/react-native/src/store/useNotificationStore.ts +178 -0
  379. package/templates/react-native/src/types/auth.type.ts +14 -0
  380. package/templates/react-native/tsconfig.json +1 -0
  381. package/templates/react-ts/src/vite-env.d.ts +1 -0
  382. package/templates/mern-react-native/mobile/.env.example +0 -2
  383. package/templates/mern-react-native/mobile/.vscode/extensions.json +0 -1
  384. package/templates/mern-react-native/mobile/.vscode/settings.json +0 -7
  385. package/templates/mern-react-native/mobile/README.md +0 -159
  386. package/templates/mern-react-native/mobile/docs/README.md +0 -17
  387. package/templates/mern-react-native/mobile/docs/components/buttons.md +0 -40
  388. package/templates/mern-react-native/mobile/docs/components/data-display.md +0 -44
  389. package/templates/mern-react-native/mobile/docs/components/feedback.md +0 -36
  390. package/templates/mern-react-native/mobile/docs/components/gestures.md +0 -60
  391. package/templates/mern-react-native/mobile/docs/components/inputs.md +0 -41
  392. package/templates/mern-react-native/mobile/docs/components/layout.md +0 -34
  393. package/templates/mern-react-native/mobile/docs/components/navigation.md +0 -31
  394. package/templates/mern-react-native/mobile/docs/features.md +0 -85
  395. package/templates/mern-react-native/mobile/docs/getting-started.md +0 -47
  396. package/templates/mern-react-native/mobile/src/components/common/HomeHeader.tsx +0 -43
  397. package/templates/mern-react-native/mobile/src/components/settings/SettingToggle.tsx +0 -26
  398. package/templates/mern-react-native/mobile/src/components/settings/SettingsOptions.tsx +0 -47
  399. package/templates/react-native/.env.example +0 -2
  400. package/templates/react-native/.vscode/extensions.json +0 -1
  401. package/templates/react-native/.vscode/settings.json +0 -7
  402. package/templates/react-native/README.md +0 -159
  403. package/templates/react-native/docs/README.md +0 -17
  404. package/templates/react-native/docs/components/buttons.md +0 -40
  405. package/templates/react-native/docs/components/data-display.md +0 -44
  406. package/templates/react-native/docs/components/feedback.md +0 -36
  407. package/templates/react-native/docs/components/gestures.md +0 -60
  408. package/templates/react-native/docs/components/inputs.md +0 -41
  409. package/templates/react-native/docs/components/layout.md +0 -34
  410. package/templates/react-native/docs/components/navigation.md +0 -31
  411. package/templates/react-native/docs/features.md +0 -85
  412. package/templates/react-native/docs/getting-started.md +0 -47
  413. package/templates/react-native/src/components/common/HomeHeader.tsx +0 -43
  414. package/templates/react-native/src/components/settings/SettingToggle.tsx +0 -26
  415. package/templates/react-native/src/components/settings/SettingsOptions.tsx +0 -47
@@ -0,0 +1,22 @@
1
+ import express from "express";
2
+ import {
3
+ signup,
4
+ login,
5
+ getMe,
6
+ logout,
7
+ updateMe,
8
+ } from "./auth.controller.js";
9
+ import { protect } from "../../common/middlewares/auth.middleware.js";
10
+ import { apiLimiter } from "../../common/middlewares/rateLimiter.js";
11
+
12
+ const router = express.Router();
13
+
14
+ router.use(apiLimiter);
15
+
16
+ router.post("/signup", signup);
17
+ router.post("/login", login);
18
+ router.post("/logout", logout);
19
+ router.get("/me", protect, getMe);
20
+ router.put("/me", protect, updateMe);
21
+
22
+ export default router;
@@ -0,0 +1,155 @@
1
+ import bcrypt from "bcrypt";
2
+ import { prisma } from "../../config/prisma.js";
3
+ import { sendOtpEmail } from "../../common/utils/emailOtp.js";
4
+ import { uploadSingleToCloudinary, deleteFromCloudinary } from "../../common/utils/cloudinaryUpload.js";
5
+ import logger from "../../config/logger.js";
6
+ export const signup = async ({ name, email, phone, password }: any) => {
7
+ const existingUser = await prisma.user.findUnique({
8
+ where: { email },
9
+ });
10
+ if (existingUser) {
11
+ const err: any = new Error("User already exists");
12
+ err.status = 409;
13
+ throw err;
14
+ }
15
+
16
+ const salt = await bcrypt.genSalt(10);
17
+ const hashedPassword = await bcrypt.hash(password, salt);
18
+ const photoURL = `https://placehold.co/600x400?text=${name.charAt(0).toUpperCase()}`;
19
+
20
+ const newUser = await prisma.user.create({
21
+ data: {
22
+ name,
23
+ email: email.toLowerCase(),
24
+ phone,
25
+ password: hashedPassword,
26
+ avatarUrl: photoURL,
27
+ avatarPublicId: "",
28
+ },
29
+ });
30
+
31
+ return newUser;
32
+ }
33
+ export const login = async ({ email, password }: any) => {
34
+ const user = await prisma.user.findUnique({
35
+ where: { email },
36
+ });
37
+ if (!user) {
38
+ const err: any = new Error("User not found");
39
+ err.status = 404;
40
+ throw err;
41
+ }
42
+
43
+ const isValid = await bcrypt.compare(password, user.password);
44
+ if (!isValid) {
45
+ const err: any = new Error("Invalid Credentials");
46
+ err.status = 401;
47
+ throw err;
48
+ }
49
+
50
+ return user;
51
+ }
52
+ export const generateOtp = async (email: string) => {
53
+ const user = await prisma.user.findUnique({
54
+ where: { email },
55
+ });
56
+ if (!user) {
57
+ const err: any = new Error("User not found");
58
+ err.status = 404;
59
+ throw err;
60
+ }
61
+
62
+ const existingOtp = await prisma.otp.findUnique({
63
+ where: { email },
64
+ });
65
+ if (existingOtp) {
66
+ await prisma.otp.delete({
67
+ where: { email },
68
+ });
69
+ }
70
+
71
+ const otp = Math.floor(100000 + Math.random() * 900000).toString();
72
+ const hashedOtp = await bcrypt.hash(otp, 10);
73
+
74
+ await prisma.otp.create({
75
+ data: {
76
+ email,
77
+ otp: hashedOtp,
78
+ },
79
+ });
80
+
81
+ await sendOtpEmail(email, otp);
82
+ return true;
83
+ }
84
+ export const verifyOtp = async ({ email, otp }: any) => {
85
+ const user = await prisma.user.findUnique({
86
+ where: { email },
87
+ });
88
+ if (!user) {
89
+ const err: any = new Error("User not found");
90
+ err.status = 404;
91
+ throw err;
92
+ }
93
+
94
+ const existingOtp = await prisma.otp.findUnique({
95
+ where: { email },
96
+ });
97
+ if (!existingOtp) {
98
+ const err: any = new Error("OTP match failed");
99
+ err.status = 400;
100
+ throw err;
101
+ }
102
+
103
+ const isMatched = await bcrypt.compare(otp, existingOtp.otp);
104
+ if (!isMatched) {
105
+ const err: any = new Error("OTP match failed");
106
+ err.status = 400;
107
+ throw err;
108
+ }
109
+
110
+ return user;
111
+ }
112
+ export const resetPassword = async (user: any, newPassword: any) => {
113
+ const hashedPassword = await bcrypt.hash(newPassword, 10);
114
+ await prisma.user.update({
115
+ where: { id: user.id },
116
+ data: { password: hashedPassword },
117
+ });
118
+ return true;
119
+ }
120
+ export const updateProfile = async (userId: any, { name, phone, file }: any) => {
121
+ const user = await prisma.user.findUnique({
122
+ where: { id: userId },
123
+ });
124
+ if (!user) {
125
+ const err: any = new Error("User not found");
126
+ err.status = 404;
127
+ throw err;
128
+ }
129
+
130
+ const updateData: any = {};
131
+ if (name) updateData.name = name;
132
+ if (phone) updateData.phone = phone;
133
+
134
+ if (file) {
135
+ const uploadResult = await uploadSingleToCloudinary(file, {
136
+ folder: "codelab/avatars",
137
+ });
138
+
139
+ if (user.avatarPublicId) {
140
+ await deleteFromCloudinary(user.avatarPublicId).catch((err) =>
141
+ logger.error("Old avatar delete error", err)
142
+ );
143
+ }
144
+
145
+ updateData.avatarUrl = uploadResult.url;
146
+ updateData.avatarPublicId = uploadResult.publicId;
147
+ }
148
+
149
+ const updatedUser = await prisma.user.update({
150
+ where: { id: userId },
151
+ data: updateData,
152
+ });
153
+
154
+ return updatedUser;
155
+ }
@@ -0,0 +1,55 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ import { prisma } from "../../config/prisma.js";
3
+ import logger from "../../config/logger.js";
4
+
5
+ export const subscribe = async (req: Request, res: Response, next: NextFunction) => {
6
+ try {
7
+ const subscription = req.body;
8
+ const userId = req.user?._id || req.user?.id; // supports both mongoose and prisma IDs
9
+
10
+ if (!subscription || !subscription.endpoint) {
11
+ return next({ status: 400, message: "Invalid subscription payload" });
12
+ }
13
+
14
+ const existingSub = await prisma.pushSubscription.findUnique({ where: { endpoint: subscription.endpoint } });
15
+
16
+ if (existingSub) {
17
+ if (userId) {
18
+ await prisma.pushSubscription.update({ where: { endpoint: subscription.endpoint }, data: { userId } })
19
+ }
20
+ return res.status(200).json({ success: true, message: "Already subscribed." });
21
+ }
22
+
23
+
24
+ await prisma.pushSubscription.create({
25
+ data: {
26
+ endpoint: subscription.endpoint,
27
+ p256dh: subscription.keys.p256dh,
28
+ auth: subscription.keys.auth,
29
+ userId: userId || null
30
+ }
31
+ });
32
+
33
+
34
+ res.status(201).json({ success: true, message: "Subscription created." });
35
+ } catch (error) {
36
+ logger.error("Error in push subscribe controller", error);
37
+ next(error);
38
+ }
39
+ };
40
+
41
+ export const unsubscribe = async (req: Request, res: Response, next: NextFunction) => {
42
+ try {
43
+ const { endpoint } = req.body;
44
+ if (!endpoint) {
45
+ return next({ status: 400, message: "Endpoint required" });
46
+ }
47
+
48
+ await prisma.pushSubscription.delete({ where: { endpoint } }).catch(() => {})
49
+
50
+ res.status(200).json({ success: true, message: "Unsubscribed." });
51
+ } catch (error) {
52
+ logger.error("Error in push unsubscribe controller", error);
53
+ next(error);
54
+ }
55
+ };
@@ -0,0 +1,11 @@
1
+ import express from "express";
2
+ import { subscribe, unsubscribe } from "./push.controller.js";
3
+ import { protect } from "../../common/middlewares/auth.middleware.js";
4
+
5
+ const router = express.Router();
6
+
7
+ // You can remove protect if you want anonymous users to be able to subscribe
8
+ router.post("/subscribe", protect, subscribe);
9
+ router.post("/unsubscribe", unsubscribe);
10
+
11
+ export default router;
@@ -0,0 +1,17 @@
1
+ import dotenv from "dotenv";
2
+ import http from "http";
3
+ import app from "./app.js";
4
+ import connectDB from "./config/prisma.js";
5
+ import logger from "./config/logger.js";
6
+
7
+ dotenv.config();
8
+
9
+ const PORT = process.env.PORT || 5000;
10
+
11
+ connectDB();
12
+
13
+ const server = http.createServer(app);
14
+
15
+ server.listen(PORT, () => {
16
+ logger.success(`Server is running on port ${PORT}`);
17
+ });
@@ -0,0 +1,12 @@
1
+ import express from "express";
2
+ import authRoutes from "../features/auth/auth.routes.js";
3
+ import uploadRoutes from "../features/attachments/upload.routes.js";
4
+ import pushRoutes from "../features/notifications/push.routes.js";
5
+
6
+ const router = express.Router();
7
+
8
+ router.use("/auth", authRoutes);
9
+ router.use("/upload", uploadRoutes);
10
+ router.use("/push", pushRoutes);
11
+
12
+ export default router;
@@ -0,0 +1,31 @@
1
+ import { Socket, Server } from "socket.io";
2
+ import logger from "../config/logger.js";
3
+ import { registerHandlers } from "./handlers/index.js";
4
+ import { getUserRoom, getInstitutionRoom } from "./rooms.js";
5
+ import { SocketEvents } from "./events.js";
6
+ import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "./types.js";
7
+
8
+ type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
9
+ type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
10
+
11
+ export const onConnection = (io: AppServer, socket: AppSocket) => {
12
+ const user = socket.data.user;
13
+
14
+ logger.info(`Socket connected: ${socket.id} (User: ${user.id})`);
15
+
16
+ // Auto-join user-specific room
17
+ socket.join(getUserRoom(user.id));
18
+
19
+ // Auto-join institution room if applicable
20
+ if (user.institutionId) {
21
+ socket.join(getInstitutionRoom(user.institutionId));
22
+ }
23
+
24
+ // Register all event handlers
25
+ registerHandlers(io, socket);
26
+
27
+ // Handle disconnect
28
+ socket.on(SocketEvents.DISCONNECT, (reason) => {
29
+ logger.info(`Socket disconnected: ${socket.id} (User: ${user.id}, Reason: ${reason})`);
30
+ });
31
+ };
@@ -0,0 +1,17 @@
1
+ import { getIO } from "../index.js";
2
+ import { getUserRoom } from "../rooms.js";
3
+ import { SocketEvents } from "../events.js";
4
+ import logger from "../../config/logger.js";
5
+
6
+ /**
7
+ * Emits a new notification to a specific user.
8
+ * This is meant to be called from business logic services.
9
+ */
10
+ export const emitNewNotification = (userId: string, payload: unknown) => {
11
+ try {
12
+ const io = getIO();
13
+ io.to(getUserRoom(userId)).emit(SocketEvents.NOTIFICATION_NEW, payload);
14
+ } catch (error) {
15
+ logger.error(`Failed to emit notification to user ${userId}`, error);
16
+ }
17
+ };
@@ -0,0 +1,27 @@
1
+ export const SocketEvents = {
2
+ // Connection Events
3
+ DISCONNECT: "disconnect",
4
+ CONNECT_ERROR: "connect_error",
5
+
6
+ // Room Events
7
+ SUBSCRIBE: "room:subscribe",
8
+ UNSUBSCRIBE: "room:unsubscribe",
9
+
10
+ // Notification Events
11
+ NOTIFICATION_NEW: "notification:new",
12
+ NOTIFICATION_MARK_AS_READ: "notification:markAsRead",
13
+
14
+ // Chat Events
15
+ CHAT_NEW_MESSAGE: "chat:newMessage",
16
+ CHAT_TYPING: "chat:typing",
17
+ CHAT_STOP_TYPING: "chat:stopTyping",
18
+
19
+ // LMS Events
20
+ LMS_ASSIGNMENT_CREATED: "lms:assignmentCreated",
21
+ LMS_GRADE_PUBLISHED: "lms:gradePublished",
22
+
23
+ // Attendance Events
24
+ ATTENDANCE_MARKED: "attendance:marked",
25
+ } as const;
26
+
27
+ export type SocketEvent = (typeof SocketEvents)[keyof typeof SocketEvents];
@@ -0,0 +1,15 @@
1
+ import { Socket, Server } from "socket.io";
2
+ import { registerNotificationHandlers } from "./notification.handler.js";
3
+ import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "../types.js";
4
+
5
+ type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
6
+ type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
7
+
8
+ export const registerHandlers = (io: AppServer, socket: AppSocket) => {
9
+ // Register domain specific handlers
10
+ registerNotificationHandlers(io, socket);
11
+
12
+ // Future handlers:
13
+ // registerChatHandlers(io, socket);
14
+ // registerLmsHandlers(io, socket);
15
+ };
@@ -0,0 +1,39 @@
1
+ import { Socket, Server } from "socket.io";
2
+ import logger from "../../config/logger.js";
3
+ import { SocketEvents } from "../events.js";
4
+ import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "../types.js";
5
+
6
+ type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
7
+ type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
8
+
9
+ export const registerNotificationHandlers = (_io: AppServer, socket: AppSocket) => {
10
+ socket.on(SocketEvents.NOTIFICATION_MARK_AS_READ, (payload, callback) => {
11
+ try {
12
+ const { notificationId } = payload;
13
+ logger.info(`Notification marked as read: ${notificationId} by user ${socket.data.user.id}`);
14
+
15
+ if (typeof callback === "function") {
16
+ callback({ success: true, notificationId });
17
+ }
18
+ } catch (error) {
19
+ logger.error("Error in notification:markAsRead handler", error);
20
+ if (typeof callback === "function") {
21
+ callback({ success: false, error: "Failed to mark as read" });
22
+ }
23
+ }
24
+ });
25
+
26
+ socket.on(SocketEvents.SUBSCRIBE, (payload) => {
27
+ if (payload?.topic) {
28
+ socket.join(`topic:${payload.topic}`);
29
+ logger.info(`User ${socket.data.user.id} subscribed to topic: ${payload.topic}`);
30
+ }
31
+ });
32
+
33
+ socket.on(SocketEvents.UNSUBSCRIBE, (payload) => {
34
+ if (payload?.topic) {
35
+ socket.leave(`topic:${payload.topic}`);
36
+ logger.info(`User ${socket.data.user.id} unsubscribed from topic: ${payload.topic}`);
37
+ }
38
+ });
39
+ };
@@ -0,0 +1,39 @@
1
+ import { Server } from "socket.io";
2
+ import { socketAuthMiddleware } from "./middleware.js";
3
+ import { onConnection } from "./connection.js";
4
+ import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "./types.js";
5
+
6
+ type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
7
+
8
+ let io: AppServer;
9
+
10
+ const initializeSocket = (server: import("http").Server) => {
11
+ io = new Server(server, {
12
+ cors: {
13
+ origin: ["http://localhost:5173"],
14
+ credentials: true,
15
+ },
16
+ // To support Redis in the future, you would add the adapter here:
17
+ // adapter: createAdapter(pubClient, subClient)
18
+ });
19
+
20
+ // Apply Authentication Middleware
21
+ io.use(socketAuthMiddleware);
22
+
23
+ // Handle Connections
24
+ io.on("connection", (socket) => {
25
+ onConnection(io, socket);
26
+ });
27
+
28
+ return io;
29
+ };
30
+
31
+ export const getIO = (): AppServer => {
32
+ if (!io) {
33
+ throw new Error("Socket.IO has not been initialized yet");
34
+ }
35
+ return io;
36
+ };
37
+
38
+ export { io };
39
+ export default initializeSocket;
@@ -0,0 +1,54 @@
1
+ import { Socket } from "socket.io";
2
+ import { verifyToken } from "../common/utils/jwt.js";
3
+ import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "./types.js";
4
+ import { prisma } from "../config/prisma.js";
5
+
6
+ type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
7
+
8
+ const parseCookie = (cookieString: string, name: string) => {
9
+ const match = cookieString.match(new RegExp("(^| )" + name + "=([^;]+)"));
10
+ if (match) return match[2];
11
+ return null;
12
+ };
13
+
14
+ export const socketAuthMiddleware = async (socket: AppSocket, next: (err?: Error) => void) => {
15
+ try {
16
+ let token = socket.handshake.auth.token;
17
+
18
+ // Fallback to cookie if token is not explicitly passed in handshake
19
+ if (!token && socket.handshake.headers.cookie) {
20
+ token = parseCookie(socket.handshake.headers.cookie, "token");
21
+ }
22
+
23
+ if (!token) {
24
+ return next(new Error("Authentication error: No token provided"));
25
+ }
26
+
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ const decoded: any = verifyToken(token);
29
+
30
+ if (!decoded || !decoded.id) {
31
+ return next(new Error("Authentication error: Invalid token"));
32
+ }
33
+
34
+ const user = await prisma.user.findUnique({
35
+ where: { id: decoded.id },
36
+ select: { id: true, roles: true, institutionId: true },
37
+ });
38
+
39
+ if (!user) {
40
+ return next(new Error("Authentication error: User not found"));
41
+ }
42
+
43
+ // Attach user to socket data
44
+ socket.data.user = {
45
+ id: user.id,
46
+ roles: user.roles,
47
+ institutionId: user.institutionId,
48
+ };
49
+
50
+ next();
51
+ } catch {
52
+ next(new Error("Authentication error: Token verification failed"));
53
+ }
54
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Room generator functions.
3
+ * Using a consistent namespace pattern (e.g. user:<id>) ensures
4
+ * that rooms don't overlap and can be easily managed or targeted.
5
+ */
6
+
7
+ export const getUserRoom = (userId: string) => `user:${userId}`;
8
+ export const getInstitutionRoom = (institutionId: string) => `institution:${institutionId}`;
9
+ export const getClassRoom = (classId: string) => `class:${classId}`;
10
+ export const getSectionRoom = (sectionId: string) => `section:${sectionId}`;
11
+ export const getTopicRoom = (topic: string) => `topic:${topic}`;
@@ -0,0 +1,32 @@
1
+ import { SocketEvents } from "./events.js";
2
+
3
+ // Basic user info injected into socket.data by the auth middleware
4
+ export interface SocketUser {
5
+ id: string;
6
+ roles?: { role: { name: string } }[] | unknown;
7
+ institutionId?: string | null;
8
+ }
9
+
10
+ export interface SocketData {
11
+ user: SocketUser;
12
+ }
13
+
14
+ export interface ServerToClientEvents {
15
+ [SocketEvents.NOTIFICATION_NEW]: (payload: unknown) => void;
16
+ [SocketEvents.CHAT_NEW_MESSAGE]: (payload: unknown) => void;
17
+ [SocketEvents.LMS_ASSIGNMENT_CREATED]: (payload: unknown) => void;
18
+ [SocketEvents.ATTENDANCE_MARKED]: (payload: unknown) => void;
19
+ }
20
+
21
+ export interface ClientToServerEvents {
22
+ [SocketEvents.SUBSCRIBE]: (payload: { topic: string }) => void;
23
+ [SocketEvents.UNSUBSCRIBE]: (payload: { topic: string }) => void;
24
+ [SocketEvents.NOTIFICATION_MARK_AS_READ]: (
25
+ payload: { notificationId: string },
26
+ callback: (response: { success: boolean; notificationId?: string; error?: string }) => void
27
+ ) => void;
28
+ }
29
+
30
+ export interface InterServerEvents {
31
+ ping: () => void;
32
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src"
12
+ },
13
+ "include": ["src/**/*"]
14
+ }
@@ -6,6 +6,7 @@ module.exports = function (api) {
6
6
  "nativewind/babel",
7
7
  ],
8
8
  plugins: [
9
+ ["inline-import", { "extensions": [".sql"] }],
9
10
  "react-native-reanimated/plugin"
10
11
  ],
11
12
  };
@@ -0,0 +1,8 @@
1
+ import type { Config } from 'drizzle-kit';
2
+
3
+ export default {
4
+ schema: './src/db/schema.ts',
5
+ out: './drizzle',
6
+ dialect: 'sqlite',
7
+ driver: 'expo',
8
+ } satisfies Config;
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "mobile",
2
+ "name": "shivAi",
3
3
  "main": "expo-router/entry",
4
4
  "version": "1.0.0",
5
5
  "dependencies": {
@@ -11,6 +11,7 @@
11
11
  "@react-navigation/bottom-tabs": "^7.18.10",
12
12
  "axios": "^1.18.1",
13
13
  "babel-preset-expo": "^57.0.3",
14
+ "drizzle-orm": "^0.45.2",
14
15
  "expo": "~57.0.7",
15
16
  "expo-blur": "~57.0.2",
16
17
  "expo-constants": "~57.0.6",
@@ -18,10 +19,13 @@
18
19
  "expo-font": "~57.0.1",
19
20
  "expo-glass-effect": "~57.0.1",
20
21
  "expo-image": "~57.0.1",
22
+ "expo-image-picker": "^57.0.6",
23
+ "expo-linear-gradient": "^57.0.1",
21
24
  "expo-linking": "~57.0.3",
22
25
  "expo-notifications": "~57.0.6",
23
26
  "expo-router": "~57.0.7",
24
27
  "expo-splash-screen": "~57.0.4",
28
+ "expo-sqlite": "^57.0.1",
25
29
  "expo-status-bar": "~57.0.1",
26
30
  "expo-symbols": "~57.0.1",
27
31
  "expo-system-ui": "~57.0.1",
@@ -45,11 +49,14 @@
45
49
  "react-native-toast-message": "^2.4.0",
46
50
  "react-native-web": "~0.21.0",
47
51
  "react-native-worklets": "0.10.0",
52
+ "socket.io-client": "^4.8.3",
48
53
  "tailwindcss": "^3.4.19",
49
54
  "zustand": "^5.0.14"
50
55
  },
51
56
  "devDependencies": {
52
57
  "@types/react": "~19.2.2",
58
+ "babel-plugin-inline-import": "^3.0.0",
59
+ "drizzle-kit": "^0.31.10",
53
60
  "eslint": "^9.0.0",
54
61
  "eslint-config-expo": "~57.0.0",
55
62
  "typescript": "~6.0.3"
@@ -0,0 +1,12 @@
1
+ import { Stack } from 'expo-router';
2
+
3
+ export default function AuthLayout() {
4
+ return (
5
+ <Stack screenOptions={{ headerShown: false }}>
6
+ <Stack.Screen name="login" />
7
+ <Stack.Screen name="forgot-password" />
8
+ <Stack.Screen name="edit-profile" options={{ presentation: 'modal' }} />
9
+ <Stack.Screen name="change-password" options={{ presentation: 'modal' }} />
10
+ </Stack>
11
+ );
12
+ }