@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
@@ -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
- const Water = ({ top, left, w, h, rx = 8, dark }: any) => (
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
- const Park = ({ top, left, w, h, rx = 6, dark }: any) => (
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
- const Block = ({ top, left, w, h, shade = 0, dark }: any) => {
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
- const Road = ({ top, left, w, h, major = false, dark }: any) => (
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
- const LaneDash = ({ top, left, vertical = false, dark }: any) => (
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
- const TrafficDot = ({ startX, startY, endX, endY, delay, color }: any) => {
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
- const PropertyPin = ({ top, left, delay, price, color }: any) => {
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 any,
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 React from "react";
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 isSize = ["xxs", "xs", "sm", "base", "md", "lg", "xl", "2xl", "3xl", "4xl", "5xl", "6xl", "7xl", "caption"].includes(val);
84
- return !isSize;
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 = hasColorClass || color
87
- ? ""
88
- : variant === "xs"
89
- ? "text-secondary"
90
- : "text-base-content";
91
-
92
- const customStyles: any = {};
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
- if (resolvedWeight === "black" || resolvedWeight === "extrabold") {
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
- const TabItem = ({ isFocused, onPress, onLongPress, iconName, colors }: any) => {
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 as any}
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 }: any) {
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: any, index: number) => {
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
- let iconName: any = tabConfig?.baseIcon || "home";
77
- iconName = isFocused ? iconName : `${iconName}-outline`;
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
- }: any) => {
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 as any}
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
- const CenterButton = ({ colors, onPress }: any) => {
53
- return (
54
- <View style={styles.centerButtonWrapper}>
55
- <TouchableOpacity
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
- }: any) {
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
- const bottomOffset = insets.bottom > 0 ? insets.bottom : 20;
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
- {state.routes.map((route: any, index: number) => {
117
- const { options } = descriptors[route.key];
118
- if (
119
- options.href === null ||
120
- options.tabBarItemStyle?.display === "none"
121
- )
122
- return null;
123
-
124
- const tabConfig = getTabConfig(route.name);
125
- const label =
126
- options.tabBarLabel !== undefined
127
- ? options.tabBarLabel
128
- : options.title !== undefined
129
- ? options.title
130
- : tabConfig?.title || route.name;
131
-
132
- const isFocused = state.index === index;
133
-
134
- const onPress = () => {
135
- const event = navigation.emit({
136
- type: "tabPress",
137
- target: route.key,
138
- canPreventDefault: true,
139
- });
140
-
141
- if (!isFocused && !event.defaultPrevented) {
142
- navigation.navigate(route.name, route.params);
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
- </React.Fragment>
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
  });