@imksh/create 1.0.31 → 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 (152) 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/templates/layers/next/javascript/package.json +8 -2
  6. package/templates/layers/next/typescript/.eslintrc.json +12 -0
  7. package/templates/layers/next/typescript/package.json +14 -3
  8. package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -1
  9. package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
  10. package/templates/layers/next/typescript/src/components/common/Table.tsx +4 -4
  11. package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +2 -1
  12. package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
  13. package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
  14. package/templates/layers/next/typescript/src/services/authService.ts +1 -1
  15. package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
  16. package/templates/layers/node/javascript/.env.example +3 -1
  17. package/templates/layers/node/javascript/package.json +12 -3
  18. package/templates/layers/node/javascript/src/app.js +2 -1
  19. package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
  20. package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
  21. package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
  22. package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
  23. package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
  24. package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
  25. package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
  26. package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
  27. package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
  28. package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
  29. package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
  30. package/templates/layers/node/typescript/.env.example +3 -1
  31. package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
  32. package/templates/layers/node/typescript/package.json +16 -2
  33. package/templates/layers/node/typescript/src/app.ts +3 -2
  34. package/templates/layers/node/typescript/src/config/db.ts +3 -2
  35. package/templates/layers/node/typescript/src/config/socket.ts +5 -4
  36. package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
  37. package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
  38. package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
  39. package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
  40. package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
  41. package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
  42. package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
  43. package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
  44. package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
  45. package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
  46. package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
  47. package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
  48. package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
  49. package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
  50. package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
  51. package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
  52. package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
  53. package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
  54. package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
  55. package/templates/layers/node-prisma/javascript/.env.example +3 -1
  56. package/templates/layers/node-prisma/javascript/package.json +11 -2
  57. package/templates/layers/node-prisma/javascript/src/app.js +2 -1
  58. package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
  59. package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
  60. package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
  61. package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
  62. package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
  63. package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
  64. package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
  65. package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
  66. package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
  67. package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
  68. package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
  69. package/templates/layers/node-prisma/typescript/.env.example +3 -1
  70. package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
  71. package/templates/layers/node-prisma/typescript/package.json +19 -5
  72. package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
  73. package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
  74. package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
  75. package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
  76. package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
  77. package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
  78. package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
  79. package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
  80. package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
  81. package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
  82. package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
  83. package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
  84. package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
  85. package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
  86. package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
  87. package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
  88. package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
  89. package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
  90. package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
  91. package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
  92. package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
  93. package/templates/layers/prisma/javascript/package.json +8 -0
  94. package/templates/layers/prisma/typescript/package.json +14 -1
  95. package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
  96. package/templates/layers/react/javascript/package.json +6 -0
  97. package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
  98. package/templates/layers/react/typescript/package.json +11 -2
  99. package/templates/layers/react/typescript/src/components/common/Table.tsx +4 -4
  100. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +1 -1
  101. package/templates/layers/react/typescript/src/main.tsx +11 -11
  102. package/templates/layers/react/typescript/src/services/authService.ts +1 -1
  103. package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
  104. package/templates/layers/react-native/base/app.json +9 -22
  105. package/templates/layers/react-native/base/eslint.config.js +5 -1
  106. package/templates/layers/react-native/base/package.json +11 -1
  107. package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
  108. package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
  109. package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
  110. package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
  111. package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
  112. package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
  113. package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
  114. package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
  115. package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
  116. package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
  117. package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
  118. package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
  119. package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
  120. package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
  121. package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
  122. package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
  123. package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
  124. package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
  125. package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
  126. package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
  127. package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
  128. package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
  129. package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
  130. package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
  131. package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
  132. package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
  133. package/templates/layers/react-native/base/src/config/api.ts +5 -5
  134. package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
  135. package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
  136. package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
  137. package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
  138. package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
  139. package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
  140. package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
  141. package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
  142. package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
  143. package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
  144. package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
  145. package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
  146. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
  147. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
  148. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
  149. package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
  150. package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
  151. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
  152. /package/templates/layers/react-native/base/assets/images/logo/{logoWithBg.png → icon.png} +0 -0
@@ -10,6 +10,7 @@ import Input from "./Input";
10
10
  import { toast } from "@/utils/toast";
11
11
  import { WebView, WebViewMessageEvent } from "react-native-webview";
12
12
  import { LocationLoader } from "../../common/LocationLoader";
13
+ import ScreenHeader from "@/components/common/ScreenHeader";
13
14
 
14
15
  export interface LocationDetails {
15
16
  addressLine1: string;
@@ -40,11 +41,12 @@ export default function WebLocationPicker({
40
41
  const [loading, setLoading] = useState(false);
41
42
  const [searchQuery, setSearchQuery] = useState("");
42
43
  const [searching, setSearching] = useState(false);
43
-
44
+
44
45
  const [loadingLocation, setLoadingLocation] = useState(false);
45
46
  const [animationDone, setAnimationDone] = useState(false);
46
47
  const [positionFetched, setPositionFetched] = useState(false);
47
-
48
+ const [showWebView, setShowWebView] = useState(true);
49
+
48
50
  const [mapCenter, setMapCenter] = useState({
49
51
  latitude: initialCoordinate?.latitude || 28.6139,
50
52
  longitude: initialCoordinate?.longitude || 77.209,
@@ -56,7 +58,6 @@ export default function WebLocationPicker({
56
58
 
57
59
  useEffect(() => {
58
60
  if (initialCoordinate?.latitude && initialCoordinate?.longitude) {
59
-
60
61
  setMapCenter(initialCoordinate);
61
62
  setCurrentCenter(initialCoordinate);
62
63
  }
@@ -69,6 +70,7 @@ export default function WebLocationPicker({
69
70
  }, [animationDone, positionFetched]);
70
71
 
71
72
  const requestLocationAndOpenMap = async () => {
73
+ setShowWebView(true);
72
74
  setModalVisible(true);
73
75
  setLoadingLocation(true);
74
76
  setAnimationDone(false);
@@ -84,7 +86,9 @@ export default function WebLocationPicker({
84
86
  }
85
87
 
86
88
  if (!initialCoordinate?.latitude) {
87
- let location = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.Balanced });
89
+ let location = await Location.getCurrentPositionAsync({
90
+ accuracy: Location.Accuracy.Balanced,
91
+ });
88
92
  setMapCenter({
89
93
  latitude: location.coords.latitude,
90
94
  longitude: location.coords.longitude,
@@ -120,8 +124,15 @@ export default function WebLocationPicker({
120
124
  latitude: currentCenter.latitude,
121
125
  longitude: currentCenter.longitude,
122
126
  };
123
- onLocationSelected(details);
124
- setModalVisible(false);
127
+
128
+ // Hide WebView first to prevent Android crash when Modal closes
129
+ setShowWebView(false);
130
+ setTimeout(() => {
131
+ onLocationSelected(details);
132
+ setModalVisible(false);
133
+ setLoading(false);
134
+ }, 150);
135
+ return;
125
136
  }
126
137
  } catch (error) {
127
138
  console.warn("Geocoding failed", error);
@@ -129,6 +140,37 @@ export default function WebLocationPicker({
129
140
  setLoading(false);
130
141
  };
131
142
 
143
+ const [fetchingCurrentLocation, setFetchingCurrentLocation] = useState(false);
144
+
145
+ const handleCurrentLocation = async () => {
146
+ setFetchingCurrentLocation(true);
147
+ try {
148
+ let { status } = await Location.requestForegroundPermissionsAsync();
149
+ if (status !== "granted") {
150
+ toast.error("Permission to access location was denied");
151
+ return;
152
+ }
153
+
154
+ let location = await Location.getCurrentPositionAsync({
155
+ accuracy: Location.Accuracy.Balanced,
156
+ });
157
+
158
+ const lat = location.coords.latitude;
159
+ const lng = location.coords.longitude;
160
+
161
+ setCurrentCenter({ latitude: lat, longitude: lng });
162
+ webViewRef.current?.injectJavaScript(`
163
+ map.setView([${lat}, ${lng}], 15);
164
+ true;
165
+ `);
166
+ } catch (error) {
167
+ console.warn("Failed to get current location", error);
168
+ toast.error("Failed to get current location");
169
+ } finally {
170
+ setFetchingCurrentLocation(false);
171
+ }
172
+ };
173
+
132
174
  const handleSearch = async () => {
133
175
  if (!searchQuery.trim()) return;
134
176
  setSearching(true);
@@ -142,11 +184,10 @@ export default function WebLocationPicker({
142
184
  true;
143
185
  `);
144
186
  } else {
145
- toast.success("Location not found");
187
+ toast.error("Location not found");
146
188
  }
147
- } catch (error) {
148
- console.warn("Search failed", error);
149
- toast.error("Failed to search location");
189
+ } catch {
190
+ toast.error("Failed to search");
150
191
  }
151
192
  setSearching(false);
152
193
  };
@@ -163,7 +204,8 @@ export default function WebLocationPicker({
163
204
  } catch {}
164
205
  };
165
206
 
166
- const htmlContent = useMemo(() => `
207
+ const htmlContent = useMemo(
208
+ () => `
167
209
  <!DOCTYPE html>
168
210
  <html>
169
211
  <head>
@@ -188,7 +230,7 @@ export default function WebLocationPicker({
188
230
  <div class="center-marker">📍</div>
189
231
  <script>
190
232
  var map = L.map('map', { zoomControl: false, attributionControl: false }).setView([${mapCenter.latitude}, ${mapCenter.longitude}], 15);
191
- L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
233
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
192
234
  maxZoom: 19
193
235
  }).addTo(map);
194
236
 
@@ -203,7 +245,9 @@ export default function WebLocationPicker({
203
245
  </script>
204
246
  </body>
205
247
  </html>
206
- `, [mapCenter.latitude, mapCenter.longitude]);
248
+ `,
249
+ [mapCenter.latitude, mapCenter.longitude],
250
+ );
207
251
 
208
252
  return (
209
253
  <>
@@ -223,14 +267,18 @@ export default function WebLocationPicker({
223
267
  />
224
268
  <Txt color={theme.baseContent} weight="semibold">
225
269
  {initialCoordinate?.latitude
226
- ? "Update Location on Map"
227
- : "Pick from Map"}
270
+ ? "Update Location"
271
+ : "Pick from map"}
228
272
  </Txt>
229
273
  </View>
230
274
  {loading && !modalVisible ? (
231
275
  <ActivityIndicator color={theme.primary} />
232
276
  ) : (
233
- <Ionicons name="chevron-forward" size={20} color={theme.secondary} />
277
+ <Ionicons
278
+ name="chevron-forward"
279
+ size={20}
280
+ color={theme.secondary}
281
+ />
234
282
  )}
235
283
  </TouchableOpacity>
236
284
  )}
@@ -251,68 +299,94 @@ export default function WebLocationPicker({
251
299
  </View>
252
300
  ) : (
253
301
  <View className="flex-1 bg-base-100">
254
- <View
255
- className="h-16 flex-row items-center justify-between px-4 border-b border-base-300 pt-4 bg-base-100"
256
- style={{ zIndex: 10 }}
257
- >
258
- <TouchableOpacity onPress={() => setModalVisible(false)}>
259
- <Ionicons name="close" size={28} color={theme.baseContent} />
260
- </TouchableOpacity>
261
- <Txt weight="bold" style={{ fontSize: 18 }}>
262
- Select Location
263
- </Txt>
264
- <View style={{ width: 28 }} />
265
- </View>
302
+ <ScreenHeader
303
+ icon="close"
304
+ onBack={() => setModalVisible(false)}
305
+ title="Select Location"
306
+ />
266
307
 
267
- <View className="px-4 py-3 bg-base-100 flex-row items-center border-b border-base-200 gap-4">
268
- <View className="flex-1" style={{ marginBottom: -16 }}>
269
- <Input
270
- placeholder="Search city, area, etc."
271
- value={searchQuery}
272
- onChangeText={setSearchQuery}
273
- onSubmitEditing={handleSearch}
274
- returnKeyType="search"
275
- containerStyle={{ marginBottom: 0 }}
276
- leftIcon="search"
308
+ <View className="px-4 py-3 bg-base-100 flex-row items-center border-b border-base-200 gap-4">
309
+ <View className="flex-1" style={{ marginBottom: -8 }}>
310
+ <Input
311
+ placeholder="Search Location"
312
+ value={searchQuery}
313
+ onChangeText={setSearchQuery}
314
+ onSubmitEditing={handleSearch}
315
+ returnKeyType="search"
316
+ containerStyle={{ marginBottom: 0 }}
317
+ leftIcon="search"
318
+ />
319
+ </View>
320
+ <Button
321
+ onPress={handleSearch}
322
+ isLoading={searching}
323
+ label="Go"
324
+ variant="primary"
325
+ soft
326
+ size="md"
327
+ className=""
277
328
  />
278
329
  </View>
279
- <Button
280
- onPress={handleSearch}
281
- isLoading={searching}
282
- label="Go"
283
- variant="primary"
284
- soft
285
- size="md"
286
- className="my-auto"
287
- />
288
- </View>
289
330
 
290
- <View className="flex-1 relative">
291
- <WebView
292
- ref={webViewRef}
293
- source={{ html: htmlContent }}
294
- onMessage={handleMessage}
295
- style={{ flex: 1 }}
296
- javaScriptEnabled={true}
297
- scrollEnabled={false}
298
- bounces={false}
299
- startInLoadingState={true}
300
- renderLoading={() => (
301
- <View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f0f0f0' }}>
302
- <ActivityIndicator size="large" color={theme.primary} />
303
- </View>
331
+ <View className="flex-1 relative">
332
+ {showWebView && (
333
+ <WebView
334
+ ref={webViewRef}
335
+ source={{ html: htmlContent }}
336
+ onMessage={handleMessage}
337
+ style={{ flex: 1 }}
338
+ javaScriptEnabled={true}
339
+ scrollEnabled={false}
340
+ bounces={false}
341
+ startInLoadingState={true}
342
+ renderLoading={() => (
343
+ <View
344
+ style={{
345
+ position: "absolute",
346
+ top: 0,
347
+ left: 0,
348
+ right: 0,
349
+ bottom: 0,
350
+ justifyContent: "center",
351
+ alignItems: "center",
352
+ backgroundColor: "#f0f0f0",
353
+ }}
354
+ >
355
+ <ActivityIndicator size="large" color={theme.primary} />
356
+ </View>
357
+ )}
358
+ />
304
359
  )}
305
- />
306
- </View>
307
360
 
308
- <View className="p-4 bg-base-100 border-t border-base-300 pb-8">
309
- <Button
310
- label="Confirm Location"
311
- onPress={handleConfirmLocation}
312
- isLoading={loading}
313
- variant="primary"
314
- />
315
- </View>
361
+ <TouchableOpacity
362
+ onPress={handleCurrentLocation}
363
+ disabled={fetchingCurrentLocation}
364
+ className="absolute bottom-6 right-4 w-12 h-12 rounded-full items-center justify-center shadow-lg border border-base-200"
365
+ style={{
366
+ backgroundColor: theme.base100,
367
+ elevation: 5,
368
+ shadowColor: "#000",
369
+ shadowOffset: { width: 0, height: 2 },
370
+ shadowOpacity: 0.25,
371
+ shadowRadius: 3.84,
372
+ }}
373
+ >
374
+ {fetchingCurrentLocation ? (
375
+ <ActivityIndicator size="small" color={theme.primary} />
376
+ ) : (
377
+ <Ionicons name="locate" size={24} color={theme.primary} />
378
+ )}
379
+ </TouchableOpacity>
380
+ </View>
381
+
382
+ <View className="p-4 bg-base-100 border-t border-base-300 pb-8">
383
+ <Button
384
+ label="Confirm Location"
385
+ onPress={handleConfirmLocation}
386
+ isLoading={loading}
387
+ variant="primary"
388
+ />
389
+ </View>
316
390
  </View>
317
391
  )}
318
392
  </Modal>
@@ -4,11 +4,11 @@ import { ApiResponse } from "../types/common.type";
4
4
 
5
5
  declare module 'axios' {
6
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>;
7
+ get<T = unknown, R = AxiosResponse<ApiResponse<T>>, D = unknown>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
8
+ delete<T = unknown, R = AxiosResponse<ApiResponse<T>>, D = unknown>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
9
+ post<T = unknown, R = AxiosResponse<ApiResponse<T>>, D = unknown>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
10
+ put<T = unknown, R = AxiosResponse<ApiResponse<T>>, D = unknown>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
11
+ patch<T = unknown, R = AxiosResponse<ApiResponse<T>>, D = unknown>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
12
12
  }
13
13
  }
14
14
 
@@ -14,9 +14,11 @@ export const authService = {
14
14
  login: (email: string, password: string) =>
15
15
  api.post("/auth/login", { email, password }),
16
16
 
17
+ googleLogin: (idToken: string) => api.post("/auth/google", { idToken }),
18
+
17
19
  getMe: () => api.get("/auth/me"),
18
20
 
19
- updateMe: (data: any) => api.put("/auth/me", data),
21
+ updateMe: (data: FormData | Record<string, unknown>) => api.put("/auth/me", data),
20
22
 
21
23
  logout: () => api.post("/auth/logout"),
22
24
 
@@ -175,13 +175,11 @@ class PermissionService {
175
175
  const Notifications = require("expo-notifications");
176
176
 
177
177
  if (!Device.isDevice) {
178
- console.log("Must use physical device for Push Notifications");
179
178
  return null;
180
179
  }
181
180
 
182
181
  const hasPermission = await this.requirePermission("notification");
183
182
  if (!hasPermission) {
184
- console.log("User denied push notification permissions");
185
183
  return null;
186
184
  }
187
185
 
@@ -17,13 +17,6 @@ export const initializeSocket = async () => {
17
17
  transports: ['websocket'],
18
18
  });
19
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
20
  };
28
21
 
29
22
  export const getSocket = () => socket;
@@ -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;