@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.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/commands/create.js +2 -0
- package/src/index.js +1 -1
- package/templates/layers/next/javascript/package.json +8 -2
- package/templates/layers/next/typescript/.eslintrc.json +12 -0
- package/templates/layers/next/typescript/package.json +14 -3
- package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -1
- package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
- package/templates/layers/next/typescript/src/components/common/Table.tsx +4 -4
- package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +2 -1
- package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
- package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
- package/templates/layers/next/typescript/src/services/authService.ts +1 -1
- package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
- package/templates/layers/node/javascript/.env.example +3 -1
- package/templates/layers/node/javascript/package.json +12 -3
- package/templates/layers/node/javascript/src/app.js +2 -1
- package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
- package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
- package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
- package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
- package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
- package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
- package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
- package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
- package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
- package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
- package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
- package/templates/layers/node/typescript/.env.example +3 -1
- package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
- package/templates/layers/node/typescript/package.json +16 -2
- package/templates/layers/node/typescript/src/app.ts +3 -2
- package/templates/layers/node/typescript/src/config/db.ts +3 -2
- package/templates/layers/node/typescript/src/config/socket.ts +5 -4
- package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
- package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
- package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
- package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
- package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
- package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
- package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
- package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
- package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
- package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
- package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
- package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
- package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
- package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
- package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
- package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
- package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
- package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
- package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
- package/templates/layers/node-prisma/javascript/.env.example +3 -1
- package/templates/layers/node-prisma/javascript/package.json +11 -2
- package/templates/layers/node-prisma/javascript/src/app.js +2 -1
- package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
- package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
- package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
- package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
- package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
- package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
- package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
- package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
- package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
- package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
- package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
- package/templates/layers/node-prisma/typescript/.env.example +3 -1
- package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
- package/templates/layers/node-prisma/typescript/package.json +19 -5
- package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
- package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
- package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
- package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
- package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
- package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
- package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
- package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
- package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
- package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
- package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
- package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
- package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
- package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
- package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
- package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
- package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
- package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
- package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
- package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
- package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
- package/templates/layers/prisma/javascript/package.json +8 -0
- package/templates/layers/prisma/typescript/package.json +14 -1
- package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
- package/templates/layers/react/javascript/package.json +6 -0
- package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
- package/templates/layers/react/typescript/package.json +11 -2
- package/templates/layers/react/typescript/src/components/common/Table.tsx +4 -4
- package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +1 -1
- package/templates/layers/react/typescript/src/main.tsx +11 -11
- package/templates/layers/react/typescript/src/services/authService.ts +1 -1
- package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
- package/templates/layers/react-native/base/app.json +9 -22
- package/templates/layers/react-native/base/eslint.config.js +5 -1
- package/templates/layers/react-native/base/package.json +11 -1
- package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
- package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
- package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
- package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
- package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
- package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
- package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
- package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
- package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
- package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
- package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
- package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
- package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
- package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
- package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
- package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
- package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
- package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
- package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
- package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
- package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
- package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
- package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
- package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
- package/templates/layers/react-native/base/src/config/api.ts +5 -5
- package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
- package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
- package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
- package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
- package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
- package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
- package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
- package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
- package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
- package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
- package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
- package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
- package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
- /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({
|
|
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
|
-
|
|
124
|
-
|
|
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.
|
|
187
|
+
toast.error("Location not found");
|
|
146
188
|
}
|
|
147
|
-
} catch
|
|
148
|
-
|
|
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}.
|
|
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
|
-
`,
|
|
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
|
|
227
|
-
: "Pick from
|
|
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
|
|
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
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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 =
|
|
8
|
-
delete<T =
|
|
9
|
-
post<T =
|
|
10
|
-
put<T =
|
|
11
|
-
patch<T =
|
|
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:
|
|
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:
|
|
129
|
+
} catch (err: unknown) {
|
|
115
130
|
set({
|
|
116
|
-
error: err
|
|
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:
|
|
172
|
+
} catch (err: unknown) {
|
|
158
173
|
set({
|
|
159
|
-
error: err
|
|
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:
|
|
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
|
|
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:
|
|
279
|
+
} catch (err: unknown) {
|
|
235
280
|
set({
|
|
236
|
-
error: err
|
|
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:
|
|
268
|
-
set({ error: err
|
|
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:
|
|
282
|
-
set({ error: err
|
|
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:
|
|
295
|
-
set({ error: err
|
|
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:
|
|
308
|
-
set({ error: err
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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 ===
|
|
98
|
-
if (isExpoGo && Platform.OS ===
|
|
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 ===
|
|
110
|
-
await Notifications.setNotificationChannelAsync(
|
|
111
|
-
name:
|
|
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:
|
|
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 !==
|
|
118
|
+
if (existingStatus !== "granted") {
|
|
123
119
|
const { status } = await Notifications.requestPermissionsAsync();
|
|
124
120
|
finalStatus = status;
|
|
125
121
|
}
|
|
126
122
|
|
|
127
|
-
if (finalStatus !==
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
149
|
-
|
|
142
|
+
} catch {
|
|
143
|
+
// Notification setup failed silently
|
|
150
144
|
}
|
|
151
145
|
|
|
152
146
|
return token;
|