@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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { TouchableOpacity, View, ActivityIndicator } from "react-native";
|
|
3
|
+
import Constants from "expo-constants";
|
|
4
|
+
import { useColorScheme } from "nativewind";
|
|
5
|
+
import { Colors } from "../../../constants/Colors";
|
|
6
|
+
import { Txt } from "./Typography";
|
|
7
|
+
import { useAuthStore } from "../../../store/useAuthStore";
|
|
8
|
+
import { toast } from "../../../utils/toast";
|
|
9
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
10
|
+
|
|
11
|
+
const isExpoGo = Constants.appOwnership === "expo";
|
|
12
|
+
|
|
13
|
+
interface GoogleSigninType {
|
|
14
|
+
configure: (options: Record<string, unknown>) => void;
|
|
15
|
+
hasPlayServices: (options: { showPlayServicesUpdateDialog: boolean }) => Promise<boolean>;
|
|
16
|
+
signIn: () => Promise<{ data?: { idToken?: string }; idToken?: string }>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface StatusCodesType {
|
|
20
|
+
SIGN_IN_CANCELLED: string;
|
|
21
|
+
IN_PROGRESS: string;
|
|
22
|
+
PLAY_SERVICES_NOT_AVAILABLE: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let GoogleSignin: GoogleSigninType | undefined;
|
|
26
|
+
let statusCodes: StatusCodesType | undefined;
|
|
27
|
+
|
|
28
|
+
if (!isExpoGo) {
|
|
29
|
+
try {
|
|
30
|
+
const googleModule = require("@react-native-google-signin/google-signin");
|
|
31
|
+
GoogleSignin = googleModule.GoogleSignin;
|
|
32
|
+
statusCodes = googleModule.statusCodes;
|
|
33
|
+
} catch {
|
|
34
|
+
// Module not found or not supported in current environment
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface GoogleAuthButtonProps {
|
|
39
|
+
onSuccess?: () => void;
|
|
40
|
+
label?: string;
|
|
41
|
+
isLogin?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default function GoogleAuthButton({
|
|
45
|
+
onSuccess,
|
|
46
|
+
label,
|
|
47
|
+
isLogin = true,
|
|
48
|
+
}: GoogleAuthButtonProps) {
|
|
49
|
+
const { colorScheme } = useColorScheme();
|
|
50
|
+
const colors = Colors[colorScheme ?? "light"];
|
|
51
|
+
const { googleLogin } = useAuthStore();
|
|
52
|
+
const [loading, setLoading] = useState(false);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!isExpoGo && GoogleSignin) {
|
|
56
|
+
GoogleSignin.configure({
|
|
57
|
+
webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID || "",
|
|
58
|
+
iosClientId: process.env.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID || "",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
const handlePress = async () => {
|
|
64
|
+
if (isExpoGo) {
|
|
65
|
+
toast.info(
|
|
66
|
+
"Expo Go",
|
|
67
|
+
"Google Sign-In is not supported in Expo Go. Please use a custom dev client."
|
|
68
|
+
);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!GoogleSignin) {
|
|
73
|
+
toast.error("Error", "Google Sign-In module is not loaded.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
setLoading(true);
|
|
79
|
+
await GoogleSignin.hasPlayServices();
|
|
80
|
+
|
|
81
|
+
// Force account selection every time
|
|
82
|
+
try {
|
|
83
|
+
await GoogleSignin.signOut();
|
|
84
|
+
} catch {
|
|
85
|
+
// Ignore if already signed out
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const userInfo = await GoogleSignin.signIn();
|
|
89
|
+
const idToken = userInfo.data?.idToken;
|
|
90
|
+
|
|
91
|
+
if (!idToken) {
|
|
92
|
+
throw new Error("No ID token returned from Google");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const ok = await googleLogin(idToken);
|
|
96
|
+
if (ok) {
|
|
97
|
+
const currentUser = useAuthStore.getState().user;
|
|
98
|
+
toast.success("Success", `Welcome back, ${currentUser?.name || "User"}`);
|
|
99
|
+
onSuccess?.();
|
|
100
|
+
} else {
|
|
101
|
+
toast.error("Error", "Google authentication failed");
|
|
102
|
+
}
|
|
103
|
+
} catch (error: unknown) {
|
|
104
|
+
const err = error as { code?: string; message?: string };
|
|
105
|
+
if (err.code === statusCodes?.SIGN_IN_CANCELLED) {
|
|
106
|
+
// user cancelled the login flow
|
|
107
|
+
} else if (err.code === statusCodes?.IN_PROGRESS) {
|
|
108
|
+
// operation (e.g. sign in) is in progress already
|
|
109
|
+
} else if (err.code === statusCodes?.PLAY_SERVICES_NOT_AVAILABLE) {
|
|
110
|
+
// play services not available or outdated
|
|
111
|
+
toast.error("Error", "Google Play Services not available");
|
|
112
|
+
} else {
|
|
113
|
+
// some other error happened
|
|
114
|
+
toast.error("Error", err.message || "An unexpected error occurred");
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
setLoading(false);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<TouchableOpacity
|
|
123
|
+
activeOpacity={0.8}
|
|
124
|
+
onPress={handlePress}
|
|
125
|
+
disabled={loading}
|
|
126
|
+
className="w-full flex-row items-center justify-center py-4 rounded-xl mt-4 border"
|
|
127
|
+
style={{
|
|
128
|
+
backgroundColor: colors.base100,
|
|
129
|
+
borderColor: colors.base300,
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
{loading ? (
|
|
133
|
+
<ActivityIndicator color={colors.primary} />
|
|
134
|
+
) : (
|
|
135
|
+
<View className="flex-row items-center justify-center gap-2">
|
|
136
|
+
<Ionicons name="logo-google" size={20} color={colors.baseContent} />
|
|
137
|
+
<Txt className="font-semibold text-base text-base-content">
|
|
138
|
+
{label || (isLogin ? "Continue with Google" : "Sign up with Google")}
|
|
139
|
+
</Txt>
|
|
140
|
+
</View>
|
|
141
|
+
)}
|
|
142
|
+
</TouchableOpacity>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, ReactElement, useState } from "react";
|
|
2
|
-
import { View, Dimensions, StyleSheet } from "react-native";
|
|
2
|
+
import { View, Dimensions, StyleSheet, DimensionValue } from "react-native";
|
|
3
3
|
import { Ionicons } from "@expo/vector-icons";
|
|
4
4
|
import { Txt } from "./Typography";
|
|
5
5
|
import { useColorScheme } from "nativewind";
|
|
@@ -49,21 +49,41 @@ const DARK_MAP = {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
// ─── Map Primitives ───
|
|
52
|
-
|
|
52
|
+
interface MapShapeProps {
|
|
53
|
+
top: number;
|
|
54
|
+
left: number;
|
|
55
|
+
w: number;
|
|
56
|
+
h: number;
|
|
57
|
+
rx?: number;
|
|
58
|
+
dark?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const Water = ({ top, left, w, h, rx = 8, dark }: MapShapeProps) => (
|
|
53
62
|
<View style={{
|
|
54
63
|
position: "absolute", top, left, width: w, height: h,
|
|
55
64
|
backgroundColor: dark ? DARK_MAP.water : LIGHT_MAP.water,
|
|
56
65
|
borderRadius: rx, opacity: 0.9,
|
|
57
66
|
}} />
|
|
58
67
|
);
|
|
59
|
-
|
|
68
|
+
|
|
69
|
+
const Park = ({ top, left, w, h, rx = 6, dark }: MapShapeProps) => (
|
|
60
70
|
<View style={{
|
|
61
71
|
position: "absolute", top, left, width: w, height: h,
|
|
62
72
|
backgroundColor: dark ? DARK_MAP.park : LIGHT_MAP.park,
|
|
63
73
|
borderRadius: rx, opacity: dark ? 0.6 : 0.55,
|
|
64
74
|
}} />
|
|
65
75
|
);
|
|
66
|
-
|
|
76
|
+
|
|
77
|
+
interface BlockProps {
|
|
78
|
+
top: number;
|
|
79
|
+
left: number;
|
|
80
|
+
w: number;
|
|
81
|
+
h: number;
|
|
82
|
+
shade?: number;
|
|
83
|
+
dark?: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const Block = ({ top, left, w, h, shade = 0, dark }: BlockProps) => {
|
|
67
87
|
const palette = dark ? DARK_MAP.block : LIGHT_MAP.block;
|
|
68
88
|
return (
|
|
69
89
|
<View style={{
|
|
@@ -73,7 +93,17 @@ const Block = ({ top, left, w, h, shade = 0, dark }: any) => {
|
|
|
73
93
|
}} />
|
|
74
94
|
);
|
|
75
95
|
};
|
|
76
|
-
|
|
96
|
+
|
|
97
|
+
interface RoadProps {
|
|
98
|
+
top: number;
|
|
99
|
+
left: number;
|
|
100
|
+
w: number;
|
|
101
|
+
h: number;
|
|
102
|
+
major?: boolean;
|
|
103
|
+
dark?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const Road = ({ top, left, w, h, major = false, dark }: RoadProps) => (
|
|
77
107
|
<View style={{
|
|
78
108
|
position: "absolute", top, left, width: w, height: h,
|
|
79
109
|
backgroundColor: major ? (dark ? DARK_MAP.road : LIGHT_MAP.road)
|
|
@@ -81,7 +111,15 @@ const Road = ({ top, left, w, h, major = false, dark }: any) => (
|
|
|
81
111
|
opacity: major ? 0.95 : 0.75,
|
|
82
112
|
}} />
|
|
83
113
|
);
|
|
84
|
-
|
|
114
|
+
|
|
115
|
+
interface LaneDashProps {
|
|
116
|
+
top: number;
|
|
117
|
+
left: number;
|
|
118
|
+
vertical?: boolean;
|
|
119
|
+
dark?: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const LaneDash = ({ top, left, vertical = false, dark }: LaneDashProps) => (
|
|
85
123
|
<View style={{
|
|
86
124
|
position: "absolute", top, left,
|
|
87
125
|
width: vertical ? 2 : 12, height: vertical ? 12 : 2,
|
|
@@ -154,7 +192,16 @@ const MapWorld = ({ dark }: { dark: boolean }) => {
|
|
|
154
192
|
};
|
|
155
193
|
|
|
156
194
|
// ─── MOVING TRAFFIC DOT ───
|
|
157
|
-
|
|
195
|
+
interface TrafficDotProps {
|
|
196
|
+
startX: number;
|
|
197
|
+
startY: number;
|
|
198
|
+
endX: number;
|
|
199
|
+
endY: number;
|
|
200
|
+
delay: number;
|
|
201
|
+
color: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const TrafficDot = ({ startX, startY, endX, endY, delay, color }: TrafficDotProps) => {
|
|
158
205
|
const x = useSharedValue(startX);
|
|
159
206
|
const y = useSharedValue(startY);
|
|
160
207
|
|
|
@@ -181,7 +228,15 @@ const TrafficDot = ({ startX, startY, endX, endY, delay, color }: any) => {
|
|
|
181
228
|
};
|
|
182
229
|
|
|
183
230
|
// ─── PROPERTY PRICE PIN ───
|
|
184
|
-
|
|
231
|
+
interface PropertyPinProps {
|
|
232
|
+
top: number;
|
|
233
|
+
left: number;
|
|
234
|
+
delay: number;
|
|
235
|
+
price: string;
|
|
236
|
+
color: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const PropertyPin = ({ top, left, delay, price, color }: PropertyPinProps) => {
|
|
185
240
|
const scale = useSharedValue(0);
|
|
186
241
|
const opacity = useSharedValue(0);
|
|
187
242
|
const bounce = useSharedValue(0);
|
|
@@ -328,7 +383,7 @@ const AltitudeBar = ({ primary }: { primary: string }) => {
|
|
|
328
383
|
}, []);
|
|
329
384
|
|
|
330
385
|
const barStyle = useAnimatedStyle(() => ({
|
|
331
|
-
height: `${prog.value * 100}%` as
|
|
386
|
+
height: `${prog.value * 100}%` as DimensionValue,
|
|
332
387
|
opacity: interpolate(prog.value, [0, 0.1], [0, 1], Extrapolation.CLAMP),
|
|
333
388
|
}));
|
|
334
389
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Text, TextProps } from "react-native";
|
|
1
|
+
import { Text, TextProps, TextStyle } from "react-native";
|
|
3
2
|
export const FontFamily = {
|
|
4
3
|
regular: "InterRegular",
|
|
5
4
|
medium: "InterMedium",
|
|
@@ -80,30 +79,65 @@ export const Txt = ({
|
|
|
80
79
|
const hasColorClass = className.split(" ").some((c) => {
|
|
81
80
|
if (!c.startsWith("text-")) return false;
|
|
82
81
|
const val = c.substring(5).split("/")[0]; // handle potential opacity suffix like text-white/80
|
|
83
|
-
const
|
|
84
|
-
|
|
82
|
+
const nonColors = [
|
|
83
|
+
"xxs",
|
|
84
|
+
"xs",
|
|
85
|
+
"sm",
|
|
86
|
+
"base",
|
|
87
|
+
"md",
|
|
88
|
+
"lg",
|
|
89
|
+
"xl",
|
|
90
|
+
"2xl",
|
|
91
|
+
"3xl",
|
|
92
|
+
"4xl",
|
|
93
|
+
"5xl",
|
|
94
|
+
"6xl",
|
|
95
|
+
"7xl",
|
|
96
|
+
"caption",
|
|
97
|
+
"center",
|
|
98
|
+
"left",
|
|
99
|
+
"right",
|
|
100
|
+
"justify",
|
|
101
|
+
"start",
|
|
102
|
+
"end",
|
|
103
|
+
"wrap",
|
|
104
|
+
"nowrap",
|
|
105
|
+
"balance",
|
|
106
|
+
"pretty",
|
|
107
|
+
"clip",
|
|
108
|
+
"ellipsis",
|
|
109
|
+
"truncate",
|
|
110
|
+
];
|
|
111
|
+
return !nonColors.includes(val);
|
|
85
112
|
});
|
|
86
|
-
const defaultColorClass =
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
113
|
+
const defaultColorClass =
|
|
114
|
+
hasColorClass || color
|
|
115
|
+
? ""
|
|
116
|
+
: variant === "xs" || variant === "caption"
|
|
117
|
+
? "text-secondary"
|
|
118
|
+
: "text-base-content";
|
|
119
|
+
|
|
120
|
+
const customStyles: TextStyle = {};
|
|
93
121
|
if (color) customStyles.color = color;
|
|
94
122
|
if (align) customStyles.textAlign = align;
|
|
95
123
|
|
|
96
124
|
// Intercept font weight classes from Nativewind or weight prop
|
|
97
125
|
let finalClassName = className;
|
|
98
|
-
|
|
99
|
-
const resolvedWeight = weight || (
|
|
100
|
-
finalClassName.includes("font-black") || finalClassName.includes("font-extrabold") ? "black" :
|
|
101
|
-
finalClassName.includes("font-bold") ? "bold" :
|
|
102
|
-
finalClassName.includes("font-semibold") ? "semibold" :
|
|
103
|
-
finalClassName.includes("font-medium") ? "medium" : null
|
|
104
|
-
);
|
|
105
126
|
|
|
106
|
-
|
|
127
|
+
const resolvedWeight =
|
|
128
|
+
weight ||
|
|
129
|
+
(finalClassName.includes("font-black") ||
|
|
130
|
+
finalClassName.includes("font-extrabold")
|
|
131
|
+
? "black"
|
|
132
|
+
: finalClassName.includes("font-bold")
|
|
133
|
+
? "bold"
|
|
134
|
+
: finalClassName.includes("font-semibold")
|
|
135
|
+
? "semibold"
|
|
136
|
+
: finalClassName.includes("font-medium")
|
|
137
|
+
? "medium"
|
|
138
|
+
: null);
|
|
139
|
+
|
|
140
|
+
if (resolvedWeight === "black") {
|
|
107
141
|
customStyles.fontFamily = FontFamily.extrabold;
|
|
108
142
|
customStyles.fontWeight = "normal"; // Prevent Android fallback
|
|
109
143
|
} else if (resolvedWeight === "bold") {
|
|
@@ -132,6 +166,7 @@ export const Txt = ({
|
|
|
132
166
|
<Text
|
|
133
167
|
className={`${defaultColorClass} ${finalClassName}`}
|
|
134
168
|
style={[baseStyle, customStyles, style]}
|
|
169
|
+
textBreakStrategy="simple"
|
|
135
170
|
{...props}
|
|
136
171
|
>
|
|
137
172
|
{children}
|
|
@@ -5,8 +5,17 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
5
5
|
import { useColorScheme } from 'nativewind';
|
|
6
6
|
import { Colors } from '../../constants/Colors';
|
|
7
7
|
import { getTabConfig } from '../../config/tabNavigation';
|
|
8
|
+
import type { BottomTabBarProps } from "@react-navigation/bottom-tabs";
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
interface TabItemProps {
|
|
11
|
+
isFocused: boolean;
|
|
12
|
+
onPress: () => void;
|
|
13
|
+
onLongPress: () => void;
|
|
14
|
+
iconName: keyof typeof Ionicons.glyphMap;
|
|
15
|
+
colors: (typeof Colors)[keyof typeof Colors];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const TabItem = ({ isFocused, onPress, onLongPress, iconName, colors }: TabItemProps) => {
|
|
10
19
|
return (
|
|
11
20
|
<TouchableOpacity
|
|
12
21
|
accessibilityRole="button"
|
|
@@ -18,7 +27,7 @@ const TabItem = ({ isFocused, onPress, onLongPress, iconName, colors }: any) =>
|
|
|
18
27
|
>
|
|
19
28
|
<View style={styles.tabItem}>
|
|
20
29
|
<Ionicons
|
|
21
|
-
name={iconName
|
|
30
|
+
name={iconName}
|
|
22
31
|
size={28}
|
|
23
32
|
color={isFocused ? colors.primary : colors.baseContent + '60'}
|
|
24
33
|
/>
|
|
@@ -27,7 +36,7 @@ const TabItem = ({ isFocused, onPress, onLongPress, iconName, colors }: any) =>
|
|
|
27
36
|
);
|
|
28
37
|
};
|
|
29
38
|
|
|
30
|
-
export default function ClassicTabBar({ state, descriptors, navigation }:
|
|
39
|
+
export default function ClassicTabBar({ state, descriptors, navigation }: BottomTabBarProps) {
|
|
31
40
|
const insets = useSafeAreaInsets();
|
|
32
41
|
const { colorScheme } = useColorScheme();
|
|
33
42
|
const colors = Colors[colorScheme ?? 'light'];
|
|
@@ -43,15 +52,9 @@ export default function ClassicTabBar({ state, descriptors, navigation }: any) {
|
|
|
43
52
|
elevation:1
|
|
44
53
|
}
|
|
45
54
|
]}>
|
|
46
|
-
{state.routes.map((route
|
|
55
|
+
{state.routes.map((route, index: number) => {
|
|
47
56
|
const { options } = descriptors[route.key];
|
|
48
57
|
const tabConfig = getTabConfig(route.name);
|
|
49
|
-
const label = options.tabBarLabel !== undefined
|
|
50
|
-
? options.tabBarLabel
|
|
51
|
-
: options.title !== undefined
|
|
52
|
-
? options.title
|
|
53
|
-
: tabConfig?.title || route.name;
|
|
54
|
-
|
|
55
58
|
const isFocused = state.index === index;
|
|
56
59
|
|
|
57
60
|
const onPress = () => {
|
|
@@ -73,8 +76,8 @@ export default function ClassicTabBar({ state, descriptors, navigation }: any) {
|
|
|
73
76
|
});
|
|
74
77
|
};
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
iconName = isFocused ?
|
|
79
|
+
const baseIcon = tabConfig?.baseIcon || "home";
|
|
80
|
+
const iconName = (isFocused ? baseIcon : `${baseIcon}-outline`) as keyof typeof Ionicons.glyphMap;
|
|
78
81
|
|
|
79
82
|
return (
|
|
80
83
|
<TabItem
|
|
@@ -6,15 +6,23 @@ import { useColorScheme } from "nativewind";
|
|
|
6
6
|
import { Colors } from "../../constants/Colors";
|
|
7
7
|
import { getTabConfig } from "../../config/tabNavigation";
|
|
8
8
|
import { LinearGradient } from "expo-linear-gradient";
|
|
9
|
+
import type { BottomTabBarProps } from "@react-navigation/bottom-tabs";
|
|
10
|
+
|
|
11
|
+
interface TabItemProps {
|
|
12
|
+
isFocused: boolean;
|
|
13
|
+
onPress: () => void;
|
|
14
|
+
onLongPress: () => void;
|
|
15
|
+
iconName: keyof typeof Ionicons.glyphMap;
|
|
16
|
+
colors: (typeof Colors)[keyof typeof Colors];
|
|
17
|
+
}
|
|
9
18
|
|
|
10
19
|
const TabItem = ({
|
|
11
20
|
isFocused,
|
|
12
21
|
onPress,
|
|
13
22
|
onLongPress,
|
|
14
23
|
iconName,
|
|
15
|
-
label,
|
|
16
24
|
colors,
|
|
17
|
-
}:
|
|
25
|
+
}: TabItemProps) => {
|
|
18
26
|
return (
|
|
19
27
|
<TouchableOpacity
|
|
20
28
|
accessibilityRole="button"
|
|
@@ -39,7 +47,7 @@ const TabItem = ({
|
|
|
39
47
|
]}
|
|
40
48
|
>
|
|
41
49
|
<Ionicons
|
|
42
|
-
name={iconName
|
|
50
|
+
name={iconName}
|
|
43
51
|
size={isFocused ? 20 : 24}
|
|
44
52
|
color={isFocused ? colors.primaryContent : colors.secondary}
|
|
45
53
|
/>
|
|
@@ -49,25 +57,10 @@ const TabItem = ({
|
|
|
49
57
|
);
|
|
50
58
|
};
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
activeOpacity={0.9}
|
|
57
|
-
onPress={onPress}
|
|
58
|
-
style={[
|
|
59
|
-
styles.centerButton,
|
|
60
|
-
{
|
|
61
|
-
backgroundColor: colors.primary,
|
|
62
|
-
shadowColor: colors.primary,
|
|
63
|
-
},
|
|
64
|
-
]}
|
|
65
|
-
>
|
|
66
|
-
<Ionicons name="add" size={32} color={colors.primaryContent} />
|
|
67
|
-
</TouchableOpacity>
|
|
68
|
-
</View>
|
|
69
|
-
);
|
|
70
|
-
};
|
|
60
|
+
export interface CustomTabBarProps extends BottomTabBarProps {
|
|
61
|
+
showCenterButton?: boolean;
|
|
62
|
+
onCenterButtonPress?: () => void;
|
|
63
|
+
}
|
|
71
64
|
|
|
72
65
|
export default function CustomTabBar({
|
|
73
66
|
state,
|
|
@@ -75,7 +68,7 @@ export default function CustomTabBar({
|
|
|
75
68
|
navigation,
|
|
76
69
|
showCenterButton = true,
|
|
77
70
|
onCenterButtonPress,
|
|
78
|
-
}:
|
|
71
|
+
}: CustomTabBarProps) {
|
|
79
72
|
const insets = useSafeAreaInsets();
|
|
80
73
|
const { colorScheme } = useColorScheme();
|
|
81
74
|
const colors = Colors[colorScheme ?? "light"];
|
|
@@ -86,7 +79,8 @@ export default function CustomTabBar({
|
|
|
86
79
|
return null;
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
|
|
82
|
+
// Since we are using a global SafeAreaView, we don't need to manually add bottom safe area
|
|
83
|
+
const bottomOffset = insets.bottom > 0 ? 5 : 20; // Just a small spacing above the global safe area
|
|
90
84
|
const totalTabBarHeight = bottomOffset + 70; // 70 is the height of the tab bar container
|
|
91
85
|
|
|
92
86
|
return (
|
|
@@ -113,63 +107,80 @@ export default function CustomTabBar({
|
|
|
113
107
|
},
|
|
114
108
|
]}
|
|
115
109
|
>
|
|
116
|
-
{
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
options
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
110
|
+
{(() => {
|
|
111
|
+
const children: React.ReactNode[] = [];
|
|
112
|
+
|
|
113
|
+
state.routes.forEach((route, index: number) => {
|
|
114
|
+
const { options } = descriptors[route.key];
|
|
115
|
+
if (
|
|
116
|
+
options.href === null ||
|
|
117
|
+
options.tabBarItemStyle?.display === "none"
|
|
118
|
+
) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const tabConfig = getTabConfig(route.name);
|
|
123
|
+
const label =
|
|
124
|
+
options.tabBarLabel !== undefined
|
|
125
|
+
? options.tabBarLabel
|
|
126
|
+
: options.title !== undefined
|
|
127
|
+
? options.title
|
|
128
|
+
: tabConfig?.title || route.name;
|
|
129
|
+
|
|
130
|
+
const isFocused = state.index === index;
|
|
131
|
+
|
|
132
|
+
const onPress = () => {
|
|
133
|
+
const event = navigation.emit({
|
|
134
|
+
type: "tabPress",
|
|
135
|
+
target: route.key,
|
|
136
|
+
canPreventDefault: true,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!isFocused && !event.defaultPrevented) {
|
|
140
|
+
navigation.navigate(route.name, route.params);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const onLongPress = () => {
|
|
145
|
+
navigation.emit({
|
|
146
|
+
type: "tabLongPress",
|
|
147
|
+
target: route.key,
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const baseIcon = tabConfig?.baseIcon || "home";
|
|
152
|
+
const iconName = (isFocused ? baseIcon : `${baseIcon}-outline`) as keyof typeof Ionicons.glyphMap;
|
|
153
|
+
|
|
154
|
+
if (
|
|
155
|
+
showCenterButton &&
|
|
156
|
+
index === Math.floor(state.routes.length / 2)
|
|
157
|
+
) {
|
|
158
|
+
children.push(
|
|
159
|
+
<TabItem
|
|
160
|
+
key="center-btn"
|
|
161
|
+
isFocused={false}
|
|
162
|
+
onPress={onCenterButtonPress}
|
|
163
|
+
iconName="add"
|
|
164
|
+
colors={colors}
|
|
165
|
+
/>,
|
|
166
|
+
);
|
|
143
167
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const onLongPress = () => {
|
|
147
|
-
navigation.emit({
|
|
148
|
-
type: "tabLongPress",
|
|
149
|
-
target: route.key,
|
|
150
|
-
});
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
let iconName: any = tabConfig?.baseIcon || "home";
|
|
154
|
-
iconName = isFocused ? iconName : `${iconName}-outline`;
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<React.Fragment key={route.key}>
|
|
158
|
-
{showCenterButton &&
|
|
159
|
-
index === Math.floor(state.routes.length / 2) && (
|
|
160
|
-
<CenterButton colors={colors} onPress={onCenterButtonPress} />
|
|
161
|
-
)}
|
|
168
|
+
|
|
169
|
+
children.push(
|
|
162
170
|
<TabItem
|
|
171
|
+
key={route.key}
|
|
163
172
|
isFocused={isFocused}
|
|
164
173
|
onPress={onPress}
|
|
165
174
|
onLongPress={onLongPress}
|
|
166
175
|
iconName={iconName}
|
|
167
176
|
label={label as string}
|
|
168
177
|
colors={colors}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
172
|
-
|
|
178
|
+
/>,
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return children;
|
|
183
|
+
})()}
|
|
173
184
|
</View>
|
|
174
185
|
</View>
|
|
175
186
|
);
|
|
@@ -218,21 +229,4 @@ const styles = StyleSheet.create({
|
|
|
218
229
|
position: "absolute",
|
|
219
230
|
bottom: -6,
|
|
220
231
|
},
|
|
221
|
-
centerButtonWrapper: {
|
|
222
|
-
flex: 1,
|
|
223
|
-
alignItems: "center",
|
|
224
|
-
justifyContent: "center",
|
|
225
|
-
},
|
|
226
|
-
centerButton: {
|
|
227
|
-
width: 56,
|
|
228
|
-
height: 56,
|
|
229
|
-
borderRadius: 28,
|
|
230
|
-
alignItems: "center",
|
|
231
|
-
justifyContent: "center",
|
|
232
|
-
marginTop: -40,
|
|
233
|
-
shadowOffset: { width: 0, height: 8 },
|
|
234
|
-
shadowOpacity: 0.4,
|
|
235
|
-
shadowRadius: 15,
|
|
236
|
-
elevation: 15,
|
|
237
|
-
},
|
|
238
232
|
});
|