@imksh/create 1.0.30 → 1.0.32

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 (247) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/src/commands/create.js +2 -0
  4. package/src/index.js +1 -1
  5. package/src/utils/askTemplate.js +2 -1
  6. package/templates/layers/electron/base/electron/main.js +262 -0
  7. package/templates/layers/electron/base/electron/preload.js +37 -0
  8. package/templates/layers/electron/base/electron-builder.json5 +68 -0
  9. package/templates/layers/electron/javascript/_gitignore +38 -0
  10. package/templates/layers/electron/javascript/package.json +49 -0
  11. package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
  12. package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
  13. package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
  14. package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
  15. package/templates/layers/electron/javascript/src/main.jsx +17 -0
  16. package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
  17. package/templates/layers/electron/javascript/vite.config.js +29 -0
  18. package/templates/layers/electron/typescript/_gitignore +38 -0
  19. package/templates/layers/electron/typescript/electron/main.ts +213 -0
  20. package/templates/layers/electron/typescript/electron/preload.ts +52 -0
  21. package/templates/layers/electron/typescript/package.json +52 -0
  22. package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
  23. package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
  24. package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
  25. package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
  26. package/templates/layers/electron/typescript/src/main.tsx +17 -0
  27. package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
  28. package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
  29. package/templates/layers/electron/typescript/tsconfig.json +25 -0
  30. package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
  31. package/templates/layers/electron/typescript/vite.config.ts +29 -0
  32. package/templates/layers/next/javascript/package.json +8 -2
  33. package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
  34. package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
  35. package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
  36. package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
  37. package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
  38. package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
  39. package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
  40. package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
  41. package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
  42. package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
  43. package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
  44. package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
  45. package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
  46. package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
  47. package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
  48. package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
  49. package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
  50. package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
  51. package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
  52. package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
  53. package/templates/layers/next/typescript/.eslintrc.json +12 -0
  54. package/templates/layers/next/typescript/package.json +14 -3
  55. package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
  56. package/templates/layers/next/typescript/src/components/common/Loading.tsx +2 -1
  57. package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
  58. package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
  59. package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
  60. package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
  61. package/templates/layers/next/typescript/src/components/common/Table.tsx +205 -39
  62. package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
  63. package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
  64. package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
  65. package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
  66. package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +3 -1
  67. package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
  68. package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
  69. package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
  70. package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
  71. package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
  72. package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
  73. package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
  74. package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
  75. package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
  76. package/templates/layers/next/typescript/src/services/authService.ts +1 -1
  77. package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
  78. package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
  79. package/templates/layers/node/javascript/.env.example +3 -1
  80. package/templates/layers/node/javascript/package.json +12 -3
  81. package/templates/layers/node/javascript/src/app.js +2 -1
  82. package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
  83. package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
  84. package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
  85. package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
  86. package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
  87. package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
  88. package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
  89. package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
  90. package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
  91. package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
  92. package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
  93. package/templates/layers/node/typescript/.env.example +3 -1
  94. package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
  95. package/templates/layers/node/typescript/package-lock.json +2 -2
  96. package/templates/layers/node/typescript/package.json +17 -3
  97. package/templates/layers/node/typescript/src/app.ts +3 -2
  98. package/templates/layers/node/typescript/src/config/db.ts +3 -2
  99. package/templates/layers/node/typescript/src/config/socket.ts +5 -4
  100. package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
  101. package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
  102. package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
  103. package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
  104. package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
  105. package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
  106. package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
  107. package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
  108. package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
  109. package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
  110. package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
  111. package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
  112. package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
  113. package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
  114. package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
  115. package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
  116. package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
  117. package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
  118. package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
  119. package/templates/layers/node-prisma/javascript/.env.example +3 -1
  120. package/templates/layers/node-prisma/javascript/package.json +11 -2
  121. package/templates/layers/node-prisma/javascript/src/app.js +2 -1
  122. package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
  123. package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
  124. package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
  125. package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
  126. package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
  127. package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
  128. package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
  129. package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
  130. package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
  131. package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
  132. package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
  133. package/templates/layers/node-prisma/typescript/.env.example +3 -1
  134. package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
  135. package/templates/layers/node-prisma/typescript/package.json +19 -5
  136. package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
  137. package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
  138. package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
  139. package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
  140. package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
  141. package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
  142. package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
  143. package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
  144. package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
  145. package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
  146. package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
  147. package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
  148. package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
  149. package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
  150. package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
  151. package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
  152. package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
  153. package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
  154. package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
  155. package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
  156. package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
  157. package/templates/layers/prisma/javascript/package.json +8 -0
  158. package/templates/layers/prisma/typescript/package.json +14 -1
  159. package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
  160. package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
  161. package/templates/layers/react/javascript/package.json +6 -0
  162. package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
  163. package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
  164. package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
  165. package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
  166. package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
  167. package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
  168. package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
  169. package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
  170. package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
  171. package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
  172. package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
  173. package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
  174. package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
  175. package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
  176. package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
  177. package/templates/layers/react/typescript/package.json +11 -2
  178. package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
  179. package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
  180. package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
  181. package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
  182. package/templates/layers/react/typescript/src/components/common/Table.tsx +201 -38
  183. package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
  184. package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
  185. package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
  186. package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
  187. package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
  188. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
  189. package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
  190. package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
  191. package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
  192. package/templates/layers/react/typescript/src/main.tsx +11 -11
  193. package/templates/layers/react/typescript/src/services/authService.ts +1 -1
  194. package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
  195. package/templates/layers/react-native/base/app.json +9 -22
  196. package/templates/layers/react-native/base/eslint.config.js +5 -1
  197. package/templates/layers/react-native/base/package.json +11 -1
  198. package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
  199. package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
  200. package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
  201. package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
  202. package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
  203. package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
  204. package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
  205. package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
  206. package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
  207. package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
  208. package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
  209. package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
  210. package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
  211. package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
  212. package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
  213. package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
  214. package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
  215. package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
  216. package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
  217. package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
  218. package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
  219. package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
  220. package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
  221. package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
  222. package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
  223. package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
  224. package/templates/layers/react-native/base/src/config/api.ts +5 -5
  225. package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
  226. package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
  227. package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
  228. package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
  229. package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
  230. package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
  231. package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
  232. package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
  233. package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
  234. package/templates/presets/electron/preset.json +9 -0
  235. package/templates/presets/electron-ts/preset.json +9 -0
  236. package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
  237. package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
  238. package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
  239. package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
  240. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
  241. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
  242. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
  243. package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
  244. package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
  245. package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
  246. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
  247. /package/templates/layers/react-native/base/assets/images/logo/{logoWithBg.png → icon.png} +0 -0
@@ -11,6 +11,20 @@ import { useNotificationStore } from "./useNotificationStore";
11
11
  // called from a useEffect, the promise is already resolved or very close.
12
12
  const _prefetchedUser: Promise<User | null> = get("user");
13
13
 
14
+ interface ApiErrorResponse {
15
+ response?: {
16
+ data?: {
17
+ message?: string;
18
+ };
19
+ };
20
+ message?: string;
21
+ }
22
+
23
+ const getErrorMessage = (err: unknown): string => {
24
+ const apiErr = err as ApiErrorResponse;
25
+ return apiErr.response?.data?.message || apiErr.message || "An unexpected error occurred";
26
+ };
27
+
14
28
  export interface AuthState {
15
29
  user: User | null;
16
30
  loading: boolean;
@@ -31,6 +45,7 @@ export interface AuthState {
31
45
  role?: string,
32
46
  ) => Promise<boolean>;
33
47
  login: (email: string, password: string) => Promise<boolean>;
48
+ googleLogin: (idToken: string) => Promise<boolean>;
34
49
  demoLogin: () => Promise<void>;
35
50
  getMe: () => Promise<void>;
36
51
  updateProfile: (updates: Partial<User> | FormData) => Promise<boolean>;
@@ -111,9 +126,9 @@ export const useAuthStore = create<AuthState>((set) => ({
111
126
  try {
112
127
  await authService.sendOtp(email);
113
128
  return true;
114
- } catch (err: any) {
129
+ } catch (err: unknown) {
115
130
  set({
116
- error: err.response?.data?.message || err.message,
131
+ error: getErrorMessage(err),
117
132
  loading: false,
118
133
  });
119
134
  return false;
@@ -154,9 +169,9 @@ export const useAuthStore = create<AuthState>((set) => ({
154
169
  notifStore.registerForPushNotificationsAsync();
155
170
  notifStore.fetchNotifications();
156
171
  return true;
157
- } catch (err: any) {
172
+ } catch (err: unknown) {
158
173
  set({
159
- error: err.response?.data?.message || err.message,
174
+ error: getErrorMessage(err),
160
175
  loading: false,
161
176
  });
162
177
  return false;
@@ -185,9 +200,39 @@ export const useAuthStore = create<AuthState>((set) => ({
185
200
  notifStore.registerForPushNotificationsAsync();
186
201
  notifStore.fetchNotifications();
187
202
  return true;
188
- } catch (err: any) {
203
+ } catch (err: unknown) {
204
+ set({
205
+ error: getErrorMessage(err),
206
+ loading: false,
207
+ });
208
+ return false;
209
+ } finally {
210
+ set({ loading: false });
211
+ }
212
+ },
213
+
214
+ googleLogin: async (idToken: string) => {
215
+ set({ loading: true, error: null });
216
+ try {
217
+ const { data } = await authService.googleLogin(idToken);
218
+ if (data.data?.token) {
219
+ save("token", data.data.token);
220
+ }
221
+ const user = data.data?.user || data.data;
222
+ if (user) save("user", user);
223
+ set({
224
+ user,
225
+ loading: false,
226
+ });
227
+ initializeSocket();
228
+ const notifStore = useNotificationStore.getState();
229
+ notifStore.listenForSocketEvents();
230
+ notifStore.registerForPushNotificationsAsync();
231
+ notifStore.fetchNotifications();
232
+ return true;
233
+ } catch (err: unknown) {
189
234
  set({
190
- error: err.response?.data?.message || err.message,
235
+ error: getErrorMessage(err),
191
236
  loading: false,
192
237
  });
193
238
  return false;
@@ -231,9 +276,9 @@ export const useAuthStore = create<AuthState>((set) => ({
231
276
  const { data } = await authService.updateMe(updates);
232
277
  set({ user: data.data, loading: false });
233
278
  return true;
234
- } catch (err: any) {
279
+ } catch (err: unknown) {
235
280
  set({
236
- error: err.response?.data?.message || err.message,
281
+ error: getErrorMessage(err),
237
282
  loading: false,
238
283
  });
239
284
  return false;
@@ -264,8 +309,8 @@ export const useAuthStore = create<AuthState>((set) => ({
264
309
  try {
265
310
  await authService.genOtp(email);
266
311
  return true;
267
- } catch (err: any) {
268
- set({ error: err.response?.data?.message || err.message, loading: false });
312
+ } catch (err: unknown) {
313
+ set({ error: getErrorMessage(err), loading: false });
269
314
  return false;
270
315
  } finally {
271
316
  set({ loading: false });
@@ -278,8 +323,8 @@ export const useAuthStore = create<AuthState>((set) => ({
278
323
  const { data } = await authService.verifyOtp(email, otp);
279
324
  if (data.token) save("token", data.token);
280
325
  return true;
281
- } catch (err: any) {
282
- set({ error: err.response?.data?.message || err.message, loading: false });
326
+ } catch (err: unknown) {
327
+ set({ error: getErrorMessage(err), loading: false });
283
328
  return false;
284
329
  } finally {
285
330
  set({ loading: false });
@@ -291,8 +336,8 @@ export const useAuthStore = create<AuthState>((set) => ({
291
336
  try {
292
337
  await authService.resetPassword(newPassword);
293
338
  return true;
294
- } catch (err: any) {
295
- set({ error: err.response?.data?.message || err.message, loading: false });
339
+ } catch (err: unknown) {
340
+ set({ error: getErrorMessage(err), loading: false });
296
341
  return false;
297
342
  } finally {
298
343
  set({ loading: false });
@@ -304,8 +349,8 @@ export const useAuthStore = create<AuthState>((set) => ({
304
349
  try {
305
350
  await authService.changePassword(oldPassword, newPassword);
306
351
  return true;
307
- } catch (err: any) {
308
- set({ error: err.response?.data?.message || err.message, loading: false });
352
+ } catch (err: unknown) {
353
+ set({ error: getErrorMessage(err), loading: false });
309
354
  return false;
310
355
  } finally {
311
356
  set({ loading: false });
@@ -12,7 +12,7 @@ export interface AppNotification {
12
12
  type: string;
13
13
  url: string | null;
14
14
  isRead: boolean;
15
- metadata: any | null;
15
+ metadata: Record<string, unknown> | null;
16
16
  createdAt: string;
17
17
  }
18
18
 
@@ -47,9 +47,12 @@ export const useNotificationStore = create<NotificationState>((set, get) => ({
47
47
  unreadCount,
48
48
  loading: false,
49
49
  });
50
- } catch (error: any) {
50
+ } catch (error: unknown) {
51
+ const message =
52
+ (error as { response?: { data?: { message?: string } } })?.response
53
+ ?.data?.message || "Failed to fetch notifications";
51
54
  set({
52
- error: error.response?.data?.message || "Failed to fetch notifications",
55
+ error: message,
53
56
  loading: false,
54
57
  });
55
58
  }
@@ -58,45 +61,38 @@ export const useNotificationStore = create<NotificationState>((set, get) => ({
58
61
  markAsRead: async (id: string) => {
59
62
  try {
60
63
  // Optimistic update
61
- const { notifications, unreadCount } = get();
62
- const updated = notifications.map(n =>
63
- n.id === id ? { ...n, isRead: true } : n
64
- );
65
-
66
- set({
67
- notifications: updated,
68
- unreadCount: Math.max(0, unreadCount - 1)
69
- });
64
+ set((state) => ({
65
+ notifications: state.notifications.map((n) =>
66
+ n.id === id ? { ...n, isRead: true } : n
67
+ ),
68
+ unreadCount: Math.max(0, state.unreadCount - 1),
69
+ }));
70
70
 
71
71
  await notificationService.markAsRead(id);
72
- } catch{
73
- // Revert if failed
72
+ } catch {
73
+ // Revert if API fails
74
74
  get().fetchNotifications();
75
75
  }
76
76
  },
77
77
 
78
78
  markAllAsRead: async () => {
79
79
  try {
80
- const { notifications } = get();
81
- const updated = notifications.map(n => ({ ...n, isRead: true }));
82
-
83
- set({
84
- notifications: updated,
85
- unreadCount: 0
86
- });
80
+ set((state) => ({
81
+ notifications: state.notifications.map((n) => ({ ...n, isRead: true })),
82
+ unreadCount: 0,
83
+ }));
87
84
 
88
85
  await notificationService.markAllAsRead();
89
- } catch{
86
+ } catch {
90
87
  get().fetchNotifications();
91
88
  }
92
89
  },
93
90
 
94
91
  registerForPushNotificationsAsync: async () => {
95
- let token;
92
+ let token: string | undefined;
96
93
 
97
- const isExpoGo = Constants.appOwnership === 'expo';
98
- if (isExpoGo && Platform.OS === 'android') {
99
- console.log('Skipping push notification registration: not supported in Expo Go on Android (SDK 53+).');
94
+ const isExpoGo = Constants.appOwnership === "expo";
95
+ if (isExpoGo && Platform.OS === "android") {
100
96
  return;
101
97
  }
102
98
 
@@ -106,12 +102,12 @@ export const useNotificationStore = create<NotificationState>((set, get) => ({
106
102
  // eslint-disable-next-line @typescript-eslint/no-require-imports
107
103
  const Device = require("expo-device");
108
104
 
109
- if (Platform.OS === 'android') {
110
- await Notifications.setNotificationChannelAsync('default', {
111
- name: 'default',
105
+ if (Platform.OS === "android") {
106
+ await Notifications.setNotificationChannelAsync("default", {
107
+ name: "default",
112
108
  importance: Notifications.AndroidImportance.MAX,
113
109
  vibrationPattern: [0, 250, 250, 250],
114
- lightColor: '#FF231F7C',
110
+ lightColor: "#FF231F7C",
115
111
  });
116
112
  }
117
113
 
@@ -119,13 +115,12 @@ export const useNotificationStore = create<NotificationState>((set, get) => ({
119
115
  const { status: existingStatus } = await Notifications.getPermissionsAsync();
120
116
  let finalStatus = existingStatus;
121
117
 
122
- if (existingStatus !== 'granted') {
118
+ if (existingStatus !== "granted") {
123
119
  const { status } = await Notifications.requestPermissionsAsync();
124
120
  finalStatus = status;
125
121
  }
126
122
 
127
- if (finalStatus !== 'granted') {
128
- console.log('Failed to get push token for push notification!');
123
+ if (finalStatus !== "granted") {
129
124
  return;
130
125
  }
131
126
 
@@ -136,17 +131,16 @@ export const useNotificationStore = create<NotificationState>((set, get) => ({
136
131
 
137
132
  token = (await Notifications.getExpoPushTokenAsync({ projectId })).data;
138
133
 
139
- // Send token to backend
140
- try {
141
- await notificationService.registerPushToken(token);
142
- } catch (e) {
143
- console.error("Failed to save push token to server", e);
134
+ if (token) {
135
+ try {
136
+ await notificationService.registerPushToken(token);
137
+ } catch {
138
+ // Token registration failed silently
139
+ }
144
140
  }
145
- } else {
146
- console.log('Must use physical device for Push Notifications');
147
141
  }
148
- } catch (e) {
149
- console.warn("Failed to setup push notifications dynamically:", e);
142
+ } catch {
143
+ // Notification setup failed silently
150
144
  }
151
145
 
152
146
  return token;
@@ -1,24 +1,26 @@
1
+ import type { ImageSourcePropType } from "react-native";
2
+
1
3
  declare module "*.png" {
2
- const value: any;
4
+ const value: ImageSourcePropType;
3
5
  export default value;
4
6
  }
5
7
 
6
8
  declare module "*.jpg" {
7
- const value: any;
9
+ const value: ImageSourcePropType;
8
10
  export default value;
9
11
  }
10
12
 
11
13
  declare module "*.jpeg" {
12
- const value: any;
14
+ const value: ImageSourcePropType;
13
15
  export default value;
14
16
  }
15
17
 
16
18
  declare module "*.gif" {
17
- const value: any;
19
+ const value: ImageSourcePropType;
18
20
  export default value;
19
21
  }
20
22
 
21
23
  declare module "*.svg" {
22
- const value: any;
24
+ const value: ImageSourcePropType;
23
25
  export default value;
24
26
  }
@@ -0,0 +1,206 @@
1
+ import { Platform } from "react-native";
2
+ import { toast } from "./toast";
3
+ import * as FileSystem from "expo-file-system/legacy";
4
+ import * as Sharing from "expo-sharing";
5
+
6
+ /**
7
+ * Downloads a string or base64 data to a file.
8
+ * - On Web: triggers browser download via an anchor element and Blob.
9
+ * - On Android: saves to user-selected folder via StorageAccessFramework.
10
+ * - On iOS: writes to app document directory and opens native iOS Share/Save sheet.
11
+ */
12
+ export const downloadFileString = async (
13
+ content: string,
14
+ filename: string,
15
+ mimeType: string,
16
+ isBase64: boolean = false
17
+ ): Promise<boolean> => {
18
+ if (Platform.OS === "web") {
19
+ try {
20
+ let blob: Blob;
21
+ if (isBase64) {
22
+ const byteCharacters = atob(content);
23
+ const byteArrays: Uint8Array[] = [];
24
+ for (let offset = 0; offset < byteCharacters.length; offset += 512) {
25
+ const slice = byteCharacters.slice(offset, offset + 512);
26
+ const byteNumbers = new Array(slice.length);
27
+ for (let i = 0; i < slice.length; i++) {
28
+ byteNumbers[i] = slice.charCodeAt(i);
29
+ }
30
+ const byteArray = new Uint8Array(byteNumbers);
31
+ byteArrays.push(byteArray);
32
+ }
33
+ blob = new Blob(byteArrays, { type: mimeType });
34
+ } else {
35
+ blob = new Blob([content], { type: mimeType });
36
+ }
37
+ const link = document.createElement("a");
38
+ const url = URL.createObjectURL(blob);
39
+ link.setAttribute("href", url);
40
+ link.setAttribute("download", filename);
41
+ link.style.visibility = "hidden";
42
+ document.body.appendChild(link);
43
+ link.click();
44
+ document.body.removeChild(link);
45
+ URL.revokeObjectURL(url);
46
+ return true;
47
+ } catch (error: unknown) {
48
+ const err = error as Error;
49
+ toast.error("Error", err.message || `Failed to download ${filename}`);
50
+ return false;
51
+ }
52
+ }
53
+
54
+ try {
55
+ const encoding = isBase64
56
+ ? FileSystem.EncodingType.Base64
57
+ : FileSystem.EncodingType.UTF8;
58
+
59
+ if (Platform.OS === "android") {
60
+ const permissions =
61
+ await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync();
62
+ if (!permissions.granted) {
63
+ toast.info("Cancelled", "Directory permission was not granted.");
64
+ return false;
65
+ }
66
+
67
+ const createdFileUri =
68
+ await FileSystem.StorageAccessFramework.createFileAsync(
69
+ permissions.directoryUri,
70
+ filename,
71
+ mimeType
72
+ );
73
+ await FileSystem.writeAsStringAsync(createdFileUri, content, {
74
+ encoding,
75
+ });
76
+ toast.success("Success", `${filename} saved successfully!`);
77
+ return true;
78
+ }
79
+
80
+ // iOS and other native platforms
81
+ const baseDir =
82
+ FileSystem.documentDirectory ?? FileSystem.cacheDirectory ?? "";
83
+ const fileUri = `${baseDir}${filename}`;
84
+ await FileSystem.writeAsStringAsync(fileUri, content, { encoding });
85
+
86
+ const isSharingAvailable = await Sharing.isAvailableAsync();
87
+ if (isSharingAvailable) {
88
+ await Sharing.shareAsync(fileUri, {
89
+ mimeType,
90
+ dialogTitle: `Save ${filename}`,
91
+ UTI: mimeType,
92
+ });
93
+ toast.success("Success", "File ready to save or share.");
94
+ return true;
95
+ } else {
96
+ toast.error("Error", "Sharing is not available on this device.");
97
+ return false;
98
+ }
99
+ } catch (error: unknown) {
100
+ const err = error as Error;
101
+ toast.error("Error", err.message || `Failed to download ${filename}`);
102
+ return false;
103
+ }
104
+ };
105
+
106
+ /**
107
+ * Downloads a CSV string as a .csv file.
108
+ */
109
+ export const downloadCsvString = async (
110
+ csvString: string,
111
+ filename: string = "export.csv"
112
+ ): Promise<boolean> => {
113
+ return downloadFileString(csvString, filename, "text/csv;charset=utf-8;");
114
+ };
115
+
116
+ /**
117
+ * Downloads JSON data as a formatted .json file.
118
+ */
119
+ export const downloadJson = async (
120
+ data: unknown,
121
+ filename: string = "export.json"
122
+ ): Promise<boolean> => {
123
+ const jsonString = JSON.stringify(data, null, 2);
124
+ return downloadFileString(jsonString, filename, "application/json");
125
+ };
126
+
127
+ /**
128
+ * Downloads a file from a remote URL.
129
+ * - On Web: triggers browser download.
130
+ * - On Mobile: downloads to local filesystem, then prompts to save on Android
131
+ * via StorageAccessFramework or opens iOS Share sheet.
132
+ */
133
+ export const downloadFileFromUrl = async (
134
+ url: string,
135
+ filename: string,
136
+ mimeType: string = "application/octet-stream"
137
+ ): Promise<boolean> => {
138
+ if (Platform.OS === "web") {
139
+ try {
140
+ const link = document.createElement("a");
141
+ link.href = url;
142
+ link.download = filename;
143
+ link.target = "_blank";
144
+ link.style.visibility = "hidden";
145
+ document.body.appendChild(link);
146
+ link.click();
147
+ document.body.removeChild(link);
148
+ return true;
149
+ } catch (error: unknown) {
150
+ const err = error as Error;
151
+ toast.error("Error", err.message || `Failed to download ${filename}`);
152
+ return false;
153
+ }
154
+ }
155
+
156
+ try {
157
+ const baseDir =
158
+ FileSystem.cacheDirectory ?? FileSystem.documentDirectory ?? "";
159
+ const localUri = `${baseDir}${filename}`;
160
+ const result = await FileSystem.downloadAsync(url, localUri);
161
+
162
+ if (Platform.OS === "android") {
163
+ const permissions =
164
+ await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync();
165
+ if (!permissions.granted) {
166
+ toast.info("Cancelled", "Directory permission was not granted.");
167
+ return false;
168
+ }
169
+
170
+ const fileBase64 = await FileSystem.readAsStringAsync(result.uri, {
171
+ encoding: FileSystem.EncodingType.Base64,
172
+ });
173
+
174
+ const createdFileUri =
175
+ await FileSystem.StorageAccessFramework.createFileAsync(
176
+ permissions.directoryUri,
177
+ filename,
178
+ mimeType
179
+ );
180
+
181
+ await FileSystem.writeAsStringAsync(createdFileUri, fileBase64, {
182
+ encoding: FileSystem.EncodingType.Base64,
183
+ });
184
+
185
+ toast.success("Success", `${filename} saved successfully!`);
186
+ return true;
187
+ }
188
+
189
+ const isSharingAvailable = await Sharing.isAvailableAsync();
190
+ if (isSharingAvailable) {
191
+ await Sharing.shareAsync(result.uri, {
192
+ mimeType,
193
+ dialogTitle: `Save ${filename}`,
194
+ });
195
+ toast.success("Success", "File ready to save or share.");
196
+ return true;
197
+ } else {
198
+ toast.error("Error", "Sharing is not available on this device.");
199
+ return false;
200
+ }
201
+ } catch (error: unknown) {
202
+ const err = error as Error;
203
+ toast.error("Error", err.message || `Failed to download ${filename}`);
204
+ return false;
205
+ }
206
+ };
@@ -1,7 +1,7 @@
1
1
  import AsyncStorage from '@react-native-async-storage/async-storage';
2
2
 
3
3
  // Save data
4
- export const save = async (key: string, value: any) => {
4
+ export const save = async (key: string, value: unknown) => {
5
5
  if (!key || !value) return;
6
6
  try {
7
7
  const jsonValue = JSON.stringify(value);
@@ -0,0 +1,9 @@
1
+ {
2
+ "description": "Electron + React (JavaScript) with Vite",
3
+ "layers": [
4
+ "react/base",
5
+ "react/javascript",
6
+ "electron/base",
7
+ "electron/javascript"
8
+ ]
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "description": "Electron + React (TypeScript) with Vite",
3
+ "layers": [
4
+ "react/base",
5
+ "react/typescript",
6
+ "electron/base",
7
+ "electron/typescript"
8
+ ]
9
+ }
@@ -7,6 +7,12 @@
7
7
  "dev": "vite",
8
8
  "build": "tsc && vite build",
9
9
  "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10
+ "lint:fix": "eslint . --ext ts,tsx --fix",
11
+ "format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"",
12
+ "format:check": "prettier --check \"src/**/*.{ts,tsx,css,json}\"",
13
+ "circular-check": "dpdm --circular --no-warning --no-tree --exit-code circular:1 \"src/**/*.{ts,tsx}\"",
14
+ "ts-prune": "ts-prune",
15
+ "ts-purns": "ts-prune",
10
16
  "preview": "vite preview"
11
17
  },
12
18
  "dependencies": {
@@ -37,10 +43,13 @@
37
43
  "@typescript-eslint/eslint-plugin": "^6.10.0",
38
44
  "@typescript-eslint/parser": "^6.10.0",
39
45
  "@vitejs/plugin-react": "^4.2.0",
46
+ "dpdm": "^3.14.0",
40
47
  "eslint": "^8.53.0",
41
48
  "eslint-plugin-react": "^7.33.2",
42
49
  "eslint-plugin-react-hooks": "^4.6.0",
43
50
  "eslint-plugin-react-refresh": "^0.4.4",
51
+ "prettier": "^3.4.2",
52
+ "ts-prune": "^0.10.3",
44
53
  "typescript": "^5.2.2",
45
54
  "vite": "^5.0.0"
46
55
  }
@@ -22,16 +22,8 @@
22
22
  "package": "com.anonymous.mobile",
23
23
  "permissions": [
24
24
  "android.permission.CAMERA",
25
- "android.permission.RECORD_AUDIO",
26
25
  "android.permission.ACCESS_FINE_LOCATION",
27
- "android.permission.ACCESS_COARSE_LOCATION",
28
- "android.permission.READ_CONTACTS",
29
- "android.permission.READ_CALENDAR",
30
- "android.permission.READ_EXTERNAL_STORAGE",
31
- "android.permission.WRITE_EXTERNAL_STORAGE",
32
- "android.permission.BLUETOOTH_CONNECT",
33
- "android.permission.BLUETOOTH_SCAN",
34
- "android.permission.CALL_PHONE"
26
+ "android.permission.ACCESS_COARSE_LOCATION"
35
27
  ]
36
28
  },
37
29
  "web": {
@@ -58,14 +50,9 @@
58
50
  {
59
51
  "iosPermissions": [
60
52
  "Camera",
61
- "Microphone",
62
- "LocationAlways",
63
53
  "LocationWhenInUse",
64
54
  "Notifications",
65
- "PhotoLibrary",
66
- "Contacts",
67
- "Calendars",
68
- "BluetoothPeripheral"
55
+ "PhotoLibrary"
69
56
  ]
70
57
  }
71
58
  ]
@@ -18,6 +18,8 @@
18
18
  "expo-blur": "~57.0.2",
19
19
  "expo-constants": "~57.0.6",
20
20
  "expo-device": "~57.0.1",
21
+ "expo-file-system": "~18.0.7",
22
+ "expo-sharing": "~13.0.1",
21
23
  "expo-font": "~57.0.1",
22
24
  "expo-glass-effect": "~57.0.1",
23
25
  "expo-image": "~57.0.1",
@@ -61,9 +63,12 @@
61
63
  "devDependencies": {
62
64
  "@types/react": "~19.2.2",
63
65
  "babel-plugin-inline-import": "^3.0.0",
66
+ "dpdm": "^3.14.0",
64
67
  "drizzle-kit": "^0.31.10",
65
68
  "eslint": "^9.0.0",
66
69
  "eslint-config-expo": "~57.0.0",
70
+ "prettier": "^3.4.2",
71
+ "ts-prune": "^0.10.3",
67
72
  "typescript": "~6.0.3"
68
73
  },
69
74
  "scripts": {
@@ -74,7 +79,12 @@
74
79
  "ios": "expo run:ios",
75
80
  "web": "expo start --web",
76
81
  "lint": "expo lint",
77
- "format": "expo lint --fix",
82
+ "lint:fix": "expo lint --fix",
83
+ "format": "prettier --write \"src/**/*.{ts,tsx,json}\"",
84
+ "format:check": "prettier --check \"src/**/*.{ts,tsx,json}\"",
85
+ "circular-check": "dpdm --circular --no-warning --no-tree --exit-code circular:1 \"src/**/*.{ts,tsx}\"",
86
+ "ts-prune": "ts-prune",
87
+ "ts-purns": "ts-prune",
78
88
  "ts:check": "tsc --noEmit",
79
89
  "build": "eas build",
80
90
  "build:android": "eas build -p android",
@@ -63,8 +63,8 @@ export default function RootLayout() {
63
63
  if (!user && !inAuthGroup) {
64
64
  // Redirect to the auth group
65
65
  router.replace("/(auth)/login");
66
- } else if (user && inAuthGroup) {
67
- // Redirect away from auth
66
+ } else if (user && (inAuthGroup || !segments[0])) {
67
+ // Redirect away from auth or root to main app tabs
68
68
  router.replace("/(tabs)");
69
69
  }
70
70
  }, [user, isCheckingAuth, segments]);
@@ -94,8 +94,8 @@ export default function RootLayout() {
94
94
  options={{ headerShown: false, animation: "fade" }}
95
95
  />
96
96
  </Stack>
97
- <Toast />
98
97
  </PaperProvider>
98
+ <Toast />
99
99
  </GestureHandlerRootView>
100
100
  </QueryClientProvider>
101
101
  );