@imksh/create 1.0.25 → 1.0.27

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 (271) 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/mobile/_gitignore +43 -0
  6. package/templates/mern-react-native/mobile/package.json +2 -1
  7. package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +11 -15
  8. package/templates/mern-react-native/mobile/src/app/(tabs)/dashboard.tsx +2 -2
  9. package/templates/mern-react-native/mobile/src/app/(tabs)/search.tsx +2 -2
  10. package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +0 -60
  11. package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +6 -36
  12. package/templates/mern-react-native/mobile/src/services/notification.service.ts +22 -0
  13. package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +158 -33
  14. package/templates/next/next.config.mjs +1 -0
  15. package/templates/next/package.json +10 -9
  16. package/templates/next/postcss.config.js +5 -0
  17. package/templates/next/src/app/layout.js +1 -0
  18. package/templates/next/src/app/page.js +1 -1
  19. package/templates/next/src/components/layout/ProtectedRoute.jsx +3 -3
  20. package/templates/next/src/index.css +1 -0
  21. package/templates/next/src/store/useUiStore.js +13 -7
  22. package/templates/next-ts/.env.example +0 -0
  23. package/templates/next-ts/AGENTS.md +37 -0
  24. package/templates/next-ts/CLAUDE.md +1 -0
  25. package/templates/next-ts/next-env.d.ts +3 -1
  26. package/templates/next-ts/next.config.mjs +1 -0
  27. package/templates/next-ts/package.json +13 -12
  28. package/templates/next-ts/postcss.config.js +5 -0
  29. package/templates/next-ts/public/logo.png +0 -0
  30. package/templates/next-ts/src/app/error.tsx +41 -0
  31. package/templates/next-ts/src/app/layout.tsx +2 -1
  32. package/templates/next-ts/src/app/not-found.tsx +5 -0
  33. package/templates/next-ts/src/app/page.tsx +1 -1
  34. package/templates/next-ts/src/components/layout/ProtectedRoute.tsx +3 -3
  35. package/templates/next-ts/src/components/ui/Button.tsx +63 -0
  36. package/templates/next-ts/src/components/ui/Input.tsx +79 -0
  37. package/templates/next-ts/src/index.css +1 -0
  38. package/templates/next-ts/src/store/useUiStore.ts +13 -7
  39. package/templates/next-ts/tsconfig.json +19 -5
  40. package/templates/production-fullstack/client/_gitignore +24 -0
  41. package/templates/production-fullstack/client/index.html +14 -0
  42. package/templates/production-fullstack/client/package.json +47 -0
  43. package/templates/production-fullstack/client/public/_redirects +1 -0
  44. package/templates/production-fullstack/client/public/favicon.ico +0 -0
  45. package/templates/production-fullstack/client/public/favicon.svg +1 -0
  46. package/templates/production-fullstack/client/public/logo.png +0 -0
  47. package/templates/production-fullstack/client/public/manifest.json +24 -0
  48. package/templates/production-fullstack/client/public/sw.js +56 -0
  49. package/templates/production-fullstack/client/src/App.tsx +68 -0
  50. package/templates/production-fullstack/client/src/assets/animations/loading.json +1 -0
  51. package/templates/production-fullstack/client/src/assets/data/navigation.ts +29 -0
  52. package/templates/production-fullstack/client/src/assets/images/logo.png +0 -0
  53. package/templates/production-fullstack/client/src/components/layout/ProtectedRoute.tsx +23 -0
  54. package/templates/production-fullstack/client/src/components/layout/PublicLayout.tsx +13 -0
  55. package/templates/production-fullstack/client/src/components/layout/SidebarLayout.tsx +37 -0
  56. package/templates/production-fullstack/client/src/components/shared/Breadcrumbs.tsx +27 -0
  57. package/templates/production-fullstack/client/src/components/shared/LottieIcon.tsx +36 -0
  58. package/templates/production-fullstack/client/src/components/shared/NotFound.tsx +78 -0
  59. package/templates/production-fullstack/client/src/components/shared/PwaInstallPrompt.tsx +72 -0
  60. package/templates/production-fullstack/client/src/components/shared/SEO.tsx +67 -0
  61. package/templates/production-fullstack/client/src/components/shared/Scroll.tsx +25 -0
  62. package/templates/production-fullstack/client/src/components/shared/Unauthorized.tsx +76 -0
  63. package/templates/production-fullstack/client/src/components/shared/skeletons/common/CardSkeleton.tsx +23 -0
  64. package/templates/production-fullstack/client/src/components/shared/skeletons/common/DashboardSkeleton.tsx +19 -0
  65. package/templates/production-fullstack/client/src/components/shared/skeletons/common/ListSkeleton.tsx +24 -0
  66. package/templates/production-fullstack/client/src/components/shared/skeletons/common/PageSkeleton.tsx +26 -0
  67. package/templates/production-fullstack/client/src/components/shared/skeletons/common/ProfileSkeleton.tsx +23 -0
  68. package/templates/production-fullstack/client/src/components/shared/skeletons/common/Skeleton.tsx +18 -0
  69. package/templates/production-fullstack/client/src/components/shared/skeletons/common/TableSkeleton.tsx +33 -0
  70. package/templates/production-fullstack/client/src/components/ui/ConfirmModal.tsx +152 -0
  71. package/templates/production-fullstack/client/src/components/ui/Header.tsx +230 -0
  72. package/templates/production-fullstack/client/src/components/ui/Loading.tsx +19 -0
  73. package/templates/production-fullstack/client/src/components/ui/Modal.tsx +106 -0
  74. package/templates/production-fullstack/client/src/components/ui/SidebarHeader.tsx +34 -0
  75. package/templates/production-fullstack/client/src/components/ui/Table.tsx +118 -0
  76. package/templates/production-fullstack/client/src/components/ui/ToggleSwitch.tsx +38 -0
  77. package/templates/production-fullstack/client/src/components/ui/sidebar.tsx +152 -0
  78. package/templates/production-fullstack/client/src/config/index.ts +44 -0
  79. package/templates/production-fullstack/client/src/constants/navigation.ts +14 -0
  80. package/templates/production-fullstack/client/src/features/auth/api/authService.ts +19 -0
  81. package/templates/production-fullstack/client/src/features/auth/pages/Login.tsx +233 -0
  82. package/templates/production-fullstack/client/src/features/auth/store/useAuthStore.ts +143 -0
  83. package/templates/production-fullstack/client/src/features/core/pages/Home.tsx +150 -0
  84. package/templates/production-fullstack/client/src/features/core/pages/Profile.tsx +9 -0
  85. package/templates/production-fullstack/client/src/hooks/useLenis.ts +28 -0
  86. package/templates/production-fullstack/client/src/hooks/usePwaInstall.ts +45 -0
  87. package/templates/production-fullstack/client/src/hooks/useWindowSize.ts +25 -0
  88. package/templates/production-fullstack/client/src/index.css +158 -0
  89. package/templates/production-fullstack/client/src/lib/axios.ts +22 -0
  90. package/templates/production-fullstack/client/src/lib/dayjs.ts +12 -0
  91. package/templates/production-fullstack/client/src/lib/query-client.ts +11 -0
  92. package/templates/production-fullstack/client/src/lib/socket.ts +6 -0
  93. package/templates/production-fullstack/client/src/main.tsx +28 -0
  94. package/templates/production-fullstack/client/src/store/useUiStore.ts +74 -0
  95. package/templates/production-fullstack/client/src/utils/formatDate.ts +19 -0
  96. package/templates/production-fullstack/client/src/utils/getInitials.ts +10 -0
  97. package/templates/production-fullstack/client/src/vite-env.d.ts +1 -0
  98. package/templates/production-fullstack/client/tsconfig.json +25 -0
  99. package/templates/production-fullstack/client/tsconfig.node.json +10 -0
  100. package/templates/production-fullstack/client/vite.config.ts +8 -0
  101. package/templates/production-fullstack/mobile/LICENSE +21 -0
  102. package/templates/production-fullstack/mobile/_gitignore +43 -0
  103. package/templates/production-fullstack/mobile/app.json +84 -0
  104. package/templates/production-fullstack/mobile/assets/animations/celebrate.json +1 -0
  105. package/templates/production-fullstack/mobile/assets/animations/infinity.json +1 -0
  106. package/templates/production-fullstack/mobile/assets/animations/loading.json +1 -0
  107. package/templates/production-fullstack/mobile/assets/fonts/Inter_Bold.ttf +0 -0
  108. package/templates/production-fullstack/mobile/assets/fonts/Inter_ExtraBold.ttf +0 -0
  109. package/templates/production-fullstack/mobile/assets/fonts/Inter_Medium.ttf +0 -0
  110. package/templates/production-fullstack/mobile/assets/fonts/Inter_Regular.ttf +0 -0
  111. package/templates/production-fullstack/mobile/assets/fonts/Inter_SemiBold.ttf +0 -0
  112. package/templates/production-fullstack/mobile/assets/images/logo/logo.png +0 -0
  113. package/templates/production-fullstack/mobile/assets/images/logo/logo.svg +1 -0
  114. package/templates/production-fullstack/mobile/assets/images/logo/logoWithBg.png +0 -0
  115. package/templates/production-fullstack/mobile/babel.config.js +13 -0
  116. package/templates/production-fullstack/mobile/drizzle.config.ts +8 -0
  117. package/templates/production-fullstack/mobile/eas.json +24 -0
  118. package/templates/production-fullstack/mobile/eslint.config.js +10 -0
  119. package/templates/production-fullstack/mobile/expo-env.d.ts +3 -0
  120. package/templates/production-fullstack/mobile/metro.config.js +6 -0
  121. package/templates/production-fullstack/mobile/nativewind-env.d.ts +3 -0
  122. package/templates/production-fullstack/mobile/package.json +89 -0
  123. package/templates/production-fullstack/mobile/src/app/(auth)/_layout.tsx +12 -0
  124. package/templates/production-fullstack/mobile/src/app/(auth)/change-password.tsx +112 -0
  125. package/templates/production-fullstack/mobile/src/app/(auth)/edit-profile.tsx +160 -0
  126. package/templates/production-fullstack/mobile/src/app/(auth)/forgot-password.tsx +191 -0
  127. package/templates/production-fullstack/mobile/src/app/(auth)/login.tsx +253 -0
  128. package/templates/production-fullstack/mobile/src/app/_layout.tsx +102 -0
  129. package/templates/production-fullstack/mobile/src/app/global.css +82 -0
  130. package/templates/production-fullstack/mobile/src/components/common/AnimatedHeader.tsx +328 -0
  131. package/templates/production-fullstack/mobile/src/components/common/ConfirmationToast.tsx +67 -0
  132. package/templates/production-fullstack/mobile/src/components/common/Loading.tsx +26 -0
  133. package/templates/production-fullstack/mobile/src/components/common/LocationLoader.tsx +591 -0
  134. package/templates/production-fullstack/mobile/src/components/common/ScreenHeader.tsx +68 -0
  135. package/templates/production-fullstack/mobile/src/components/common/TabHeader.tsx +58 -0
  136. package/templates/production-fullstack/mobile/src/components/common/Typography.tsx +113 -0
  137. package/templates/production-fullstack/mobile/src/components/common/Welcome.tsx +55 -0
  138. package/templates/production-fullstack/mobile/src/components/ui/BottomModal.tsx +159 -0
  139. package/templates/production-fullstack/mobile/src/components/ui/ClassicTabBar.tsx +129 -0
  140. package/templates/production-fullstack/mobile/src/components/ui/CustomTabBar.tsx +238 -0
  141. package/templates/production-fullstack/mobile/src/components/ui/Sidebar.tsx +258 -0
  142. package/templates/production-fullstack/mobile/src/components/ui/buttons/Button.tsx +180 -0
  143. package/templates/production-fullstack/mobile/src/components/ui/buttons/ButtonGroup.tsx +32 -0
  144. package/templates/production-fullstack/mobile/src/components/ui/buttons/FloatingActionButton.tsx +72 -0
  145. package/templates/production-fullstack/mobile/src/components/ui/buttons/IconButton.tsx +84 -0
  146. package/templates/production-fullstack/mobile/src/components/ui/data-display/Accordion.tsx +82 -0
  147. package/templates/production-fullstack/mobile/src/components/ui/data-display/Avatar.tsx +47 -0
  148. package/templates/production-fullstack/mobile/src/components/ui/data-display/Badge.tsx +104 -0
  149. package/templates/production-fullstack/mobile/src/components/ui/data-display/Card.tsx +36 -0
  150. package/templates/production-fullstack/mobile/src/components/ui/data-display/Image.tsx +43 -0
  151. package/templates/production-fullstack/mobile/src/components/ui/data-display/ImageViewer.tsx +33 -0
  152. package/templates/production-fullstack/mobile/src/components/ui/data-display/SwipeableRow.tsx +94 -0
  153. package/templates/production-fullstack/mobile/src/components/ui/feedback/CircularProgress.tsx +38 -0
  154. package/templates/production-fullstack/mobile/src/components/ui/feedback/GestureWrapper.tsx +114 -0
  155. package/templates/production-fullstack/mobile/src/components/ui/feedback/ProgressBar.tsx +51 -0
  156. package/templates/production-fullstack/mobile/src/components/ui/feedback/Skeleton.tsx +84 -0
  157. package/templates/production-fullstack/mobile/src/components/ui/feedback/Spinner.tsx +50 -0
  158. package/templates/production-fullstack/mobile/src/components/ui/inputs/Checkbox.tsx +54 -0
  159. package/templates/production-fullstack/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
  160. package/templates/production-fullstack/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  161. package/templates/production-fullstack/mobile/src/components/ui/inputs/Input.tsx +158 -0
  162. package/templates/production-fullstack/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
  163. package/templates/production-fullstack/mobile/src/components/ui/inputs/OTPInput.tsx +83 -0
  164. package/templates/production-fullstack/mobile/src/components/ui/inputs/PasswordInput.tsx +16 -0
  165. package/templates/production-fullstack/mobile/src/components/ui/inputs/Radio.tsx +54 -0
  166. package/templates/production-fullstack/mobile/src/components/ui/inputs/SearchBar.tsx +53 -0
  167. package/templates/production-fullstack/mobile/src/components/ui/inputs/Slider.tsx +49 -0
  168. package/templates/production-fullstack/mobile/src/components/ui/inputs/Switch.tsx +75 -0
  169. package/templates/production-fullstack/mobile/src/components/ui/inputs/TextArea.tsx +15 -0
  170. package/templates/production-fullstack/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  171. package/templates/production-fullstack/mobile/src/components/ui/layout/Container.tsx +23 -0
  172. package/templates/production-fullstack/mobile/src/components/ui/layout/Divider.tsx +20 -0
  173. package/templates/production-fullstack/mobile/src/components/ui/layout/Grid.tsx +62 -0
  174. package/templates/production-fullstack/mobile/src/components/ui/layout/KeyboardAvoidingViewWrapper.tsx +16 -0
  175. package/templates/production-fullstack/mobile/src/components/ui/layout/ScrollContainer.tsx +21 -0
  176. package/templates/production-fullstack/mobile/src/components/ui/layout/Section.tsx +51 -0
  177. package/templates/production-fullstack/mobile/src/components/ui/layout/Spacer.tsx +6 -0
  178. package/templates/production-fullstack/mobile/src/components/ui/layout/Stack.tsx +34 -0
  179. package/templates/production-fullstack/mobile/src/components/ui/navigation/Pagination.tsx +61 -0
  180. package/templates/production-fullstack/mobile/src/components/ui/navigation/ProgressStepper.tsx +66 -0
  181. package/templates/production-fullstack/mobile/src/components/ui/navigation/Tabs.tsx +173 -0
  182. package/templates/production-fullstack/mobile/src/config/sidebar.config.ts +72 -0
  183. package/templates/production-fullstack/mobile/src/config/tabNavigation.ts +64 -0
  184. package/templates/production-fullstack/mobile/src/constants/Colors.ts +71 -0
  185. package/templates/production-fullstack/mobile/src/db/index.ts +9 -0
  186. package/templates/production-fullstack/mobile/src/db/schema.ts +14 -0
  187. package/templates/production-fullstack/mobile/src/features/auth/api/auth.service.ts +35 -0
  188. package/templates/production-fullstack/mobile/src/features/auth/store/useAuthStore.ts +273 -0
  189. package/templates/production-fullstack/mobile/src/features/auth/types/auth.type.ts +14 -0
  190. package/templates/production-fullstack/mobile/src/hooks/useDebounce.ts +17 -0
  191. package/templates/production-fullstack/mobile/src/lib/axios.ts +27 -0
  192. package/templates/production-fullstack/mobile/src/lib/query-client.ts +11 -0
  193. package/templates/production-fullstack/mobile/src/lib/socket.ts +36 -0
  194. package/templates/production-fullstack/mobile/src/lib/storage.ts +55 -0
  195. package/templates/production-fullstack/mobile/src/services/permission.service.ts +214 -0
  196. package/templates/production-fullstack/mobile/src/store/localStore.ts +7 -0
  197. package/templates/production-fullstack/mobile/src/store/useNotificationStore.ts +53 -0
  198. package/templates/production-fullstack/mobile/src/types/common.type.ts +6 -0
  199. package/templates/production-fullstack/mobile/src/types/images.d.ts +24 -0
  200. package/templates/production-fullstack/mobile/src/utils/toast.ts +26 -0
  201. package/templates/production-fullstack/mobile/tailwind.config.js +71 -0
  202. package/templates/production-fullstack/mobile/tsconfig.json +23 -0
  203. package/templates/production-fullstack/server/.env.example +63 -0
  204. package/templates/production-fullstack/server/README.md +75 -0
  205. package/templates/production-fullstack/server/_gitignore +24 -0
  206. package/templates/production-fullstack/server/eslint.config.js +13 -0
  207. package/templates/production-fullstack/server/package.json +58 -0
  208. package/templates/production-fullstack/server/prisma/schema/core/attachment.prisma +14 -0
  209. package/templates/production-fullstack/server/prisma/schema/core/notification.prisma +18 -0
  210. package/templates/production-fullstack/server/prisma/schema/core/permission.prisma +26 -0
  211. package/templates/production-fullstack/server/prisma/schema/core/role.prisma +25 -0
  212. package/templates/production-fullstack/server/prisma/schema/core/user.prisma +56 -0
  213. package/templates/production-fullstack/server/prisma/schema/enums/common.prisma +13 -0
  214. package/templates/production-fullstack/server/prisma/schema/enums/notification.prisma +6 -0
  215. package/templates/production-fullstack/server/prisma/schema/enums/user.prisma +11 -0
  216. package/templates/production-fullstack/server/prisma/schema/schema.prisma +7 -0
  217. package/templates/production-fullstack/server/prisma.config.ts +14 -0
  218. package/templates/production-fullstack/server/src/app.ts +36 -0
  219. package/templates/production-fullstack/server/src/common/middlewares/auth.middleware.ts +45 -0
  220. package/templates/production-fullstack/server/src/common/middlewares/errorHandler.ts +16 -0
  221. package/templates/production-fullstack/server/src/common/middlewares/multer.ts +20 -0
  222. package/templates/production-fullstack/server/src/common/middlewares/rateLimiter.ts +53 -0
  223. package/templates/production-fullstack/server/src/common/services/email.service.ts +28 -0
  224. package/templates/production-fullstack/server/src/common/services/push.service.ts +65 -0
  225. package/templates/production-fullstack/server/src/common/services/storage.service.ts +9 -0
  226. package/templates/production-fullstack/server/src/common/types/express.d.ts +10 -0
  227. package/templates/production-fullstack/server/src/common/utils/cloudinaryUpload.ts +44 -0
  228. package/templates/production-fullstack/server/src/common/utils/emailOtp.ts +30 -0
  229. package/templates/production-fullstack/server/src/common/utils/genOtpToken.ts +22 -0
  230. package/templates/production-fullstack/server/src/common/utils/generateVapidKeys.ts +12 -0
  231. package/templates/production-fullstack/server/src/common/utils/jwt.ts +27 -0
  232. package/templates/production-fullstack/server/src/common/utils/pushHelper.ts +22 -0
  233. package/templates/production-fullstack/server/src/config/env.ts +2 -0
  234. package/templates/production-fullstack/server/src/config/logger.ts +58 -0
  235. package/templates/production-fullstack/server/src/config/prisma.ts +26 -0
  236. package/templates/production-fullstack/server/src/features/attachments/upload.controller.ts +22 -0
  237. package/templates/production-fullstack/server/src/features/attachments/upload.routes.ts +11 -0
  238. package/templates/production-fullstack/server/src/features/auth/auth.controller.ts +124 -0
  239. package/templates/production-fullstack/server/src/features/auth/auth.routes.ts +22 -0
  240. package/templates/production-fullstack/server/src/features/auth/auth.service.ts +155 -0
  241. package/templates/production-fullstack/server/src/features/notifications/push.controller.ts +55 -0
  242. package/templates/production-fullstack/server/src/features/notifications/push.routes.ts +11 -0
  243. package/templates/production-fullstack/server/src/index.ts +17 -0
  244. package/templates/production-fullstack/server/src/routes/index.ts +12 -0
  245. package/templates/production-fullstack/server/src/sockets/auth.ts +0 -0
  246. package/templates/production-fullstack/server/src/sockets/connection.ts +31 -0
  247. package/templates/production-fullstack/server/src/sockets/emitters/assignment.emitter.ts +0 -0
  248. package/templates/production-fullstack/server/src/sockets/emitters/attendance.emitter.ts +0 -0
  249. package/templates/production-fullstack/server/src/sockets/emitters/exam.emitter.ts +0 -0
  250. package/templates/production-fullstack/server/src/sockets/emitters/notification.emitter.ts +17 -0
  251. package/templates/production-fullstack/server/src/sockets/events.ts +27 -0
  252. package/templates/production-fullstack/server/src/sockets/handlers/attendance.handler.ts +0 -0
  253. package/templates/production-fullstack/server/src/sockets/handlers/chat.handler.ts +0 -0
  254. package/templates/production-fullstack/server/src/sockets/handlers/index.ts +15 -0
  255. package/templates/production-fullstack/server/src/sockets/handlers/lms.handler.ts +0 -0
  256. package/templates/production-fullstack/server/src/sockets/handlers/notification.handler.ts +39 -0
  257. package/templates/production-fullstack/server/src/sockets/index.ts +39 -0
  258. package/templates/production-fullstack/server/src/sockets/middleware.ts +54 -0
  259. package/templates/production-fullstack/server/src/sockets/rooms.ts +11 -0
  260. package/templates/production-fullstack/server/src/sockets/types.ts +32 -0
  261. package/templates/production-fullstack/server/tsconfig.json +14 -0
  262. package/templates/react/src/assets/animations/loading.json +1 -0
  263. package/templates/react-native/_gitignore +43 -0
  264. package/templates/react-native/package.json +2 -1
  265. package/templates/react-native/src/app/(auth)/edit-profile.tsx +11 -15
  266. package/templates/react-native/src/app/(tabs)/dashboard.tsx +2 -2
  267. package/templates/react-native/src/app/(tabs)/search.tsx +2 -2
  268. package/templates/react-native/src/config/sidebar.config.ts +0 -60
  269. package/templates/react-native/src/config/tabNavigation.ts +6 -36
  270. package/templates/react-native/src/services/notification.service.ts +22 -0
  271. package/templates/react-native/src/store/useNotificationStore.ts +158 -33
@@ -0,0 +1,273 @@
1
+ import { create } from "zustand";
2
+ import { authService } from "../api/auth.service";
3
+ import { save, remove, get } from "../../../lib/storage";
4
+ import { User } from "../types/auth.type";
5
+ import { initializeSocket, disconnectSocket } from "../../../lib/socket";
6
+ import { useNotificationStore } from "../../../store/useNotificationStore";
7
+
8
+ // ── Module-level prefetch ──────────────────────────────────────────────────
9
+ // Kick off the AsyncStorage read the instant this module is imported
10
+ // (before any React component mounts). By the time loadCachedUser() is
11
+ // called from a useEffect, the promise is already resolved or very close.
12
+ const _prefetchedUser: Promise<User | null> = get("user");
13
+
14
+ export interface AuthState {
15
+ user: User | null;
16
+ loading: boolean;
17
+ isCheckingAuth: boolean;
18
+ error: string | null;
19
+ sendOtp: (email: string) => Promise<boolean>;
20
+ signup: (
21
+ name: string,
22
+ email: string,
23
+ password: string,
24
+ otp: string,
25
+ role?: string,
26
+ ) => Promise<boolean>;
27
+ login: (email: string, password: string) => Promise<boolean>;
28
+ demoLogin: () => Promise<void>;
29
+ getMe: () => Promise<void>;
30
+ updateProfile: (updates: Partial<User> | FormData) => Promise<boolean>;
31
+ logout: () => Promise<void>;
32
+ genOtp: (email: string) => Promise<boolean>;
33
+ verifyOtp: (email: string, otp: string) => Promise<boolean>;
34
+ resetPassword: (newPassword: string) => Promise<boolean>;
35
+ changePassword: (oldPassword: string, newPassword: string) => Promise<boolean>;
36
+ setUser: (user: User) => void;
37
+ clearError: () => void;
38
+ loadCachedUser: () => Promise<void>;
39
+ }
40
+
41
+ export const useAuthStore = create<AuthState>((set) => ({
42
+ user: null,
43
+ loading: false,
44
+ isCheckingAuth: true,
45
+ error: null,
46
+
47
+ setUser: (user: User) => set({ user }),
48
+
49
+ clearError: () => set({ error: null }),
50
+
51
+ loadCachedUser: async () => {
52
+ // Await the already-in-flight prefetch — typically resolves in <1ms here
53
+ const cached = await _prefetchedUser;
54
+ if (cached) {
55
+ set({ user: cached, isCheckingAuth: false });
56
+ // Re-establish socket + push on app resume with a cached session
57
+ initializeSocket();
58
+ const notifStore = useNotificationStore.getState();
59
+ notifStore.listenForSocketEvents();
60
+ notifStore.registerForPushNotificationsAsync();
61
+ notifStore.fetchNotifications();
62
+ } else {
63
+ // No cache — mark auth check done so routing doesn't hang
64
+ set({ isCheckingAuth: false });
65
+ }
66
+ },
67
+
68
+ sendOtp: async (email: string) => {
69
+ set({ loading: true, error: null });
70
+ try {
71
+ await authService.sendOtp(email);
72
+ return true;
73
+ } catch (err: any) {
74
+ set({
75
+ error: err.response?.data?.message || err.message,
76
+ loading: false,
77
+ });
78
+ return false;
79
+ } finally {
80
+ set({ loading: false });
81
+ }
82
+ },
83
+
84
+ signup: async (
85
+ name: string,
86
+ email: string,
87
+ password: string,
88
+ otp: string,
89
+ role = "user",
90
+ ) => {
91
+ set({ loading: true, error: null });
92
+ try {
93
+ const { data } = await authService.signup(
94
+ name,
95
+ email,
96
+ password,
97
+ otp,
98
+ role,
99
+ );
100
+ if (data.data?.token) {
101
+ save("token", data.data.token);
102
+ }
103
+ const user = data.data?.user;
104
+ if (user) save("user", user);
105
+ set({
106
+ user,
107
+ loading: false,
108
+ });
109
+ // Initialise socket + push after signup
110
+ initializeSocket();
111
+ const notifStore = useNotificationStore.getState();
112
+ notifStore.listenForSocketEvents();
113
+ notifStore.registerForPushNotificationsAsync();
114
+ notifStore.fetchNotifications();
115
+ return true;
116
+ } catch (err: any) {
117
+ set({
118
+ error: err.response?.data?.message || err.message,
119
+ loading: false,
120
+ });
121
+ return false;
122
+ } finally {
123
+ set({ loading: false });
124
+ }
125
+ },
126
+
127
+ login: async (email: string, password: string) => {
128
+ set({ loading: true, error: null });
129
+ try {
130
+ const { data } = await authService.login(email, password);
131
+ if (data.data?.token) {
132
+ save("token", data.data.token);
133
+ }
134
+ const user = data.data?.user;
135
+ if (user) save("user", user);
136
+ set({
137
+ user,
138
+ loading: false,
139
+ });
140
+ // Initialise socket + push after login
141
+ initializeSocket();
142
+ const notifStore = useNotificationStore.getState();
143
+ notifStore.listenForSocketEvents();
144
+ notifStore.registerForPushNotificationsAsync();
145
+ notifStore.fetchNotifications();
146
+ return true;
147
+ } catch (err: any) {
148
+ set({
149
+ error: err.response?.data?.message || err.message,
150
+ loading: false,
151
+ });
152
+ return false;
153
+ } finally {
154
+ set({ loading: false });
155
+ }
156
+ },
157
+
158
+ demoLogin: async () => {
159
+ set({ loading: true, error: null });
160
+ // Simulate network delay
161
+ await new Promise(resolve => setTimeout(resolve, 800));
162
+ save("token", "demo-token-123");
163
+ set({
164
+ user: {
165
+ id: "demo_user_123",
166
+ name: "Demo User",
167
+ email: "demo@imksh.com",
168
+ role: "admin",
169
+ },
170
+ loading: false,
171
+ });
172
+ },
173
+
174
+ getMe: async () => {
175
+ try {
176
+ const { data } = await authService.getMe();
177
+ const user = data.data;
178
+ // Always update cache with fresh server data
179
+ if (user) save("user", user);
180
+ set({ user, isCheckingAuth: false });
181
+ // Network failed — cached user (already loaded) stays in place
182
+ } finally {
183
+ set({ isCheckingAuth: false });
184
+ }
185
+ },
186
+
187
+ updateProfile: async (updates: Partial<User> | FormData) => {
188
+ set({ loading: true, error: null });
189
+ try {
190
+ const { data } = await authService.updateMe(updates);
191
+ set({ user: data.data, loading: false });
192
+ return true;
193
+ } catch (err: any) {
194
+ set({
195
+ error: err.response?.data?.message || err.message,
196
+ loading: false,
197
+ });
198
+ return false;
199
+ } finally {
200
+ set({ loading: false });
201
+ }
202
+ },
203
+
204
+ logout: async () => {
205
+ try {
206
+ await authService.logout();
207
+ } catch {
208
+ // ignore errors — cookie may already be expired
209
+ } finally {
210
+ await remove("token");
211
+ await remove("user"); // clear cached user on logout
212
+ disconnectSocket();
213
+ set({ loading: false });
214
+ }
215
+ set({
216
+ user: null,
217
+ error: null,
218
+ });
219
+ },
220
+
221
+ genOtp: async (email: string) => {
222
+ set({ loading: true, error: null });
223
+ try {
224
+ await authService.genOtp(email);
225
+ return true;
226
+ } catch (err: any) {
227
+ set({ error: err.response?.data?.message || err.message, loading: false });
228
+ return false;
229
+ } finally {
230
+ set({ loading: false });
231
+ }
232
+ },
233
+
234
+ verifyOtp: async (email: string, otp: string) => {
235
+ set({ loading: true, error: null });
236
+ try {
237
+ const { data } = await authService.verifyOtp(email, otp);
238
+ if (data.token) save("token", data.token);
239
+ return true;
240
+ } catch (err: any) {
241
+ set({ error: err.response?.data?.message || err.message, loading: false });
242
+ return false;
243
+ } finally {
244
+ set({ loading: false });
245
+ }
246
+ },
247
+
248
+ resetPassword: async (newPassword: string) => {
249
+ set({ loading: true, error: null });
250
+ try {
251
+ await authService.resetPassword(newPassword);
252
+ return true;
253
+ } catch (err: any) {
254
+ set({ error: err.response?.data?.message || err.message, loading: false });
255
+ return false;
256
+ } finally {
257
+ set({ loading: false });
258
+ }
259
+ },
260
+
261
+ changePassword: async (oldPassword: string, newPassword: string) => {
262
+ set({ loading: true, error: null });
263
+ try {
264
+ await authService.changePassword(oldPassword, newPassword);
265
+ return true;
266
+ } catch (err: any) {
267
+ set({ error: err.response?.data?.message || err.message, loading: false });
268
+ return false;
269
+ } finally {
270
+ set({ loading: false });
271
+ }
272
+ },
273
+ }));
@@ -0,0 +1,14 @@
1
+ export interface User {
2
+ id: string; // Updated from _id for Prisma consistency
3
+ _id?: string; // Fallback for mongo legacy if any
4
+ name: string;
5
+ email: string;
6
+ phone?: string;
7
+ role: string;
8
+ avatar?: {
9
+ url?: string;
10
+ publicId?: string;
11
+ };
12
+ createdAt?: string;
13
+ updatedAt?: string;
14
+ }
@@ -0,0 +1,17 @@
1
+ import { useState, useEffect } from "react";
2
+
3
+ export function useDebounce<T>(value: T, delay: number): T {
4
+ const [debouncedValue, setDebouncedValue] = useState<T>(value);
5
+
6
+ useEffect(() => {
7
+ const handler = setTimeout(() => {
8
+ setDebouncedValue(value);
9
+ }, delay);
10
+
11
+ return () => {
12
+ clearTimeout(handler);
13
+ };
14
+ }, [value, delay]);
15
+
16
+ return debouncedValue;
17
+ }
@@ -0,0 +1,27 @@
1
+ import axios, { AxiosResponse, AxiosRequestConfig } from "axios";
2
+ import { get } from "./storage";
3
+ import { ApiResponse } from "../types/common.type";
4
+
5
+ declare module 'axios' {
6
+ export interface AxiosInstance {
7
+ get<T = any, R = AxiosResponse<ApiResponse<T>>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
8
+ delete<T = any, R = AxiosResponse<ApiResponse<T>>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
9
+ post<T = any, R = AxiosResponse<ApiResponse<T>>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
10
+ put<T = any, R = AxiosResponse<ApiResponse<T>>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
11
+ patch<T = any, R = AxiosResponse<ApiResponse<T>>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
12
+ }
13
+ }
14
+
15
+ // eslint-disable-next-line import/no-named-as-default-member
16
+ export const api = axios.create({
17
+ baseURL: process.env.EXPO_PUBLIC_API_URL,
18
+ withCredentials: true,
19
+ });
20
+
21
+ api.interceptors.request.use(async (config) => {
22
+ const token = await get("token");
23
+ if (token) {
24
+ config.headers.Authorization = `Bearer ${token}`;
25
+ }
26
+ return config;
27
+ });
@@ -0,0 +1,11 @@
1
+ import { QueryClient } from "@tanstack/react-query";
2
+
3
+ export const queryClient = new QueryClient({
4
+ defaultOptions: {
5
+ queries: {
6
+ refetchOnWindowFocus: false, // React Native doesn't have traditional window focus
7
+ retry: 1,
8
+ staleTime: 5 * 60 * 1000,
9
+ },
10
+ },
11
+ });
@@ -0,0 +1,36 @@
1
+ import { io, Socket } from 'socket.io-client';
2
+ import { get } from './storage';
3
+ import Constants from 'expo-constants';
4
+
5
+ let socket: Socket | null = null;
6
+
7
+ // Set your backend socket URL
8
+ const SOCKET_URL = Constants.expoConfig?.extra?.apiUrl || 'http://localhost:3000';
9
+
10
+ export const initializeSocket = async () => {
11
+ if (socket) return;
12
+ const token = await get('token');
13
+ if (!token) return;
14
+
15
+ socket = io(SOCKET_URL, {
16
+ auth: { token },
17
+ transports: ['websocket'],
18
+ });
19
+
20
+ socket.on('connect', () => {
21
+ console.log('Connected to socket server');
22
+ });
23
+
24
+ socket.on('disconnect', () => {
25
+ console.log('Disconnected from socket server');
26
+ });
27
+ };
28
+
29
+ export const getSocket = () => socket;
30
+
31
+ export const disconnectSocket = () => {
32
+ if (socket) {
33
+ socket.disconnect();
34
+ socket = null;
35
+ }
36
+ };
@@ -0,0 +1,55 @@
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+
3
+ // Save data
4
+ export const save = async (key: string, value: any) => {
5
+ if (!key || !value) return;
6
+ try {
7
+ const jsonValue = JSON.stringify(value);
8
+ await AsyncStorage.setItem(key, jsonValue);
9
+ } catch (error) {
10
+ console.warn("AsyncStorage save error:", error);
11
+ }
12
+ };
13
+
14
+ // Get data
15
+ export const get = async (key: string) => {
16
+ try {
17
+ if (!AsyncStorage) return null;
18
+ const value = await AsyncStorage.getItem(key);
19
+ return value ? JSON.parse(value) : null;
20
+ } catch (error) {
21
+ console.warn('AsyncStorage get error:', error);
22
+ return null;
23
+ }
24
+ };
25
+
26
+ export const getData = async (key: string) => {
27
+ try {
28
+ if (!AsyncStorage) return null;
29
+ const value = await AsyncStorage.getItem(key);
30
+ return value ? JSON.parse(value) : null;
31
+ } catch (error) {
32
+ console.warn('AsyncStorage get error:', error);
33
+ return null;
34
+ }
35
+ };
36
+
37
+ // Remove specific key
38
+ export const remove = async (key: string) => {
39
+ try {
40
+ if (!AsyncStorage) return;
41
+ await AsyncStorage.removeItem(key);
42
+ } catch (error) {
43
+ console.warn('AsyncStorage remove error:', error);
44
+ }
45
+ };
46
+
47
+ // Clear entire storage
48
+ export const clear = async () => {
49
+ try {
50
+ if (!AsyncStorage) return;
51
+ await AsyncStorage.clear();
52
+ } catch (error) {
53
+ console.warn('AsyncStorage clear error:', error);
54
+ }
55
+ };
@@ -0,0 +1,214 @@
1
+ import { Platform, Alert } from "react-native";
2
+ import {
3
+ check,
4
+ request,
5
+ PERMISSIONS,
6
+ RESULTS,
7
+ Permission,
8
+ PermissionStatus,
9
+ openSettings,
10
+ } from "react-native-permissions";
11
+
12
+ export type AppPermissionType =
13
+ | "camera"
14
+ | "audio" // Microphone
15
+ | "notification" // Notifications (Push)
16
+ | "image" // Photo Library / Read External Storage
17
+ | "file" // Write External Storage
18
+ | "location" // Location When In Use
19
+ | "locationAlways" // Location Always
20
+ | "contacts" // Contacts
21
+ | "phone" // Call Phone
22
+ | "nearbydevice" // Bluetooth
23
+ | "calendar"; // Calendar
24
+
25
+ class PermissionService {
26
+ /**
27
+ * Helper to determine the correct native Permission object based on platform
28
+ */
29
+ private getNativePermission(type: AppPermissionType): Permission | null {
30
+ if (Platform.OS === "ios") {
31
+ switch (type) {
32
+ case "camera":
33
+ return PERMISSIONS.IOS.CAMERA;
34
+ case "audio":
35
+ return PERMISSIONS.IOS.MICROPHONE;
36
+ case "image":
37
+ return PERMISSIONS.IOS.PHOTO_LIBRARY;
38
+ case "file":
39
+ // iOS doesn't have a broad "file system" permission for the app sandbox
40
+ // Usually handled via DocumentPicker or PhotoLibrary depending on use case.
41
+ return PERMISSIONS.IOS.PHOTO_LIBRARY;
42
+ case "location":
43
+ return PERMISSIONS.IOS.LOCATION_WHEN_IN_USE;
44
+ case "locationAlways":
45
+ return PERMISSIONS.IOS.LOCATION_ALWAYS;
46
+ case "contacts":
47
+ return PERMISSIONS.IOS.CONTACTS;
48
+ case "calendar":
49
+ return PERMISSIONS.IOS.CALENDARS;
50
+ case "nearbydevice":
51
+ return PERMISSIONS.IOS.BLUETOOTH;
52
+ case "notification":
53
+ // handled uniquely by react-native-permissions using requestNotifications
54
+ return null;
55
+ case "phone":
56
+ // iOS doesn't require explicit permission to use Linking.openURL('tel:...')
57
+ return null;
58
+ default:
59
+ return null;
60
+ }
61
+ } else if (Platform.OS === "android") {
62
+ switch (type) {
63
+ case "camera":
64
+ return PERMISSIONS.ANDROID.CAMERA;
65
+ case "audio":
66
+ return PERMISSIONS.ANDROID.RECORD_AUDIO;
67
+ case "image":
68
+ return PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
69
+ case "file":
70
+ return PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE;
71
+ case "location":
72
+ return PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION;
73
+ case "locationAlways":
74
+ return PERMISSIONS.ANDROID.ACCESS_BACKGROUND_LOCATION;
75
+ case "contacts":
76
+ return PERMISSIONS.ANDROID.READ_CONTACTS;
77
+ case "calendar":
78
+ return PERMISSIONS.ANDROID.READ_CALENDAR;
79
+ case "nearbydevice":
80
+ // Required for Android 12+
81
+ return PERMISSIONS.ANDROID.BLUETOOTH_CONNECT;
82
+ case "phone":
83
+ return PERMISSIONS.ANDROID.CALL_PHONE;
84
+ case "notification":
85
+ // handled uniquely by checkNotifications/requestNotifications
86
+ return null;
87
+ default:
88
+ return null;
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+
94
+ /**
95
+ * Check the current status of a permission without asking the user
96
+ */
97
+ async checkPermission(type: AppPermissionType): Promise<PermissionStatus> {
98
+ if (type === "notification") {
99
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
100
+ const { checkNotifications } = require("react-native-permissions");
101
+ const { status } = await checkNotifications();
102
+ return status;
103
+ }
104
+
105
+ const nativePerm = this.getNativePermission(type);
106
+ if (!nativePerm) {
107
+ // If the permission doesn't exist on this platform (e.g. Phone on iOS), we consider it granted
108
+ return RESULTS.GRANTED;
109
+ }
110
+ try {
111
+ return await check(nativePerm);
112
+ } catch (error) {
113
+ console.error(`Error checking permission ${type}:`, error);
114
+ return RESULTS.UNAVAILABLE;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Request a permission from the user
120
+ */
121
+ async requestPermission(type: AppPermissionType): Promise<PermissionStatus> {
122
+ if (type === "notification") {
123
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
124
+ const { requestNotifications } = require("react-native-permissions");
125
+ const { status } = await requestNotifications(["alert", "sound", "badge"]);
126
+ return status;
127
+ }
128
+
129
+ const nativePerm = this.getNativePermission(type);
130
+ if (!nativePerm) {
131
+ return RESULTS.GRANTED;
132
+ }
133
+ try {
134
+ return await request(nativePerm);
135
+ } catch (error) {
136
+ console.error(`Error requesting permission ${type}:`, error);
137
+ return RESULTS.UNAVAILABLE;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Ensures a permission is granted. If not, requests it.
143
+ */
144
+ async requirePermission(type: AppPermissionType): Promise<boolean> {
145
+ let status = await this.checkPermission(type);
146
+
147
+ if (status === RESULTS.DENIED) {
148
+ status = await this.requestPermission(type);
149
+ }
150
+
151
+ return status === RESULTS.GRANTED || status === RESULTS.LIMITED;
152
+ }
153
+
154
+ /**
155
+ * Helper to open the OS Settings screen for the app if permission is blocked
156
+ */
157
+ async openSettingsForApp(
158
+ title = "Permission Required",
159
+ message = "Please enable this permission in your device settings."
160
+ ) {
161
+ Alert.alert(title, message, [
162
+ { text: "Cancel", style: "cancel" },
163
+ { text: "Open Settings", onPress: () => openSettings() },
164
+ ]);
165
+ }
166
+ /**
167
+ * Utility to register and fetch the Expo Push Token for this device.
168
+ * This uses the unified PermissionService to request the notification permission.
169
+ * NOTE: This will only return a token on physical devices.
170
+ */
171
+ async registerForPushNotificationsAsync(): Promise<string | null> {
172
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
173
+ const Device = require("expo-device");
174
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
175
+ const Notifications = require("expo-notifications");
176
+
177
+ if (!Device.isDevice) {
178
+ console.log("Must use physical device for Push Notifications");
179
+ return null;
180
+ }
181
+
182
+ const hasPermission = await this.requirePermission("notification");
183
+ if (!hasPermission) {
184
+ console.log("User denied push notification permissions");
185
+ return null;
186
+ }
187
+
188
+ // Set channel for Android
189
+ if (Platform.OS === "android") {
190
+ Notifications.setNotificationChannelAsync("default", {
191
+ name: "default",
192
+ importance: Notifications.AndroidImportance.MAX,
193
+ vibrationPattern: [0, 250, 250, 250],
194
+ lightColor: "#FF231F7C",
195
+ });
196
+ }
197
+
198
+ try {
199
+ const projectId =
200
+ require("../../app.json")?.expo?.extra?.eas?.projectId;
201
+
202
+ const tokenData = await Notifications.getExpoPushTokenAsync({
203
+ projectId,
204
+ });
205
+
206
+ return tokenData.data;
207
+ } catch (e) {
208
+ console.error("Failed to get push token", e);
209
+ return null;
210
+ }
211
+ }
212
+ }
213
+
214
+ export const permissionService = new PermissionService();
@@ -0,0 +1,7 @@
1
+ import { create } from "zustand";
2
+
3
+ const useLocalStore = create((set, get) => ({
4
+
5
+ }));
6
+
7
+ export default useLocalStore;