@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,344 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import {
3
+ View,
4
+ TouchableWithoutFeedback,
5
+ Dimensions,
6
+ StyleSheet,
7
+ KeyboardAvoidingView,
8
+ Platform,
9
+ BackHandler,
10
+ ViewStyle,
11
+ } from "react-native";
12
+ import Animated, {
13
+ useSharedValue,
14
+ useAnimatedStyle,
15
+ withTiming,
16
+ Easing,
17
+ runOnJS,
18
+ } from "react-native-reanimated";
19
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
20
+ import { Portal } from "react-native-paper";
21
+ import { useColorScheme } from "nativewind";
22
+ import { Colors } from "../../constants/Colors";
23
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
24
+
25
+ const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get("window");
26
+
27
+ export type DrawerPosition = "top" | "bottom" | "left" | "right";
28
+
29
+ export interface DrawerProps {
30
+ isOpen: boolean;
31
+ onClose: () => void;
32
+ children: React.ReactNode;
33
+ position?: DrawerPosition;
34
+ sizePercent?: number; // e.g. 0.5 for 50% of the screen height (top/bottom) or width (left/right)
35
+ heightPercent?: number; // backwards compatibility alias for top/bottom
36
+ widthPercent?: number; // backwards compatibility alias for left/right
37
+ showHandle?: boolean;
38
+ zIndex?: number;
39
+ }
40
+
41
+ export default function Drawer({
42
+ isOpen,
43
+ onClose,
44
+ children,
45
+ position = "bottom",
46
+ sizePercent,
47
+ heightPercent,
48
+ widthPercent,
49
+ showHandle = true,
50
+ zIndex = 50,
51
+ }: DrawerProps) {
52
+ const { colorScheme } = useColorScheme();
53
+ const colors = Colors[colorScheme ?? "light"];
54
+ const insets = useSafeAreaInsets();
55
+
56
+ const isVertical = position === "top" || position === "bottom";
57
+ const defaultPercent = isVertical ? 0.5 : 0.75;
58
+ const resolvedPercent =
59
+ sizePercent ??
60
+ (isVertical ? heightPercent : widthPercent) ??
61
+ defaultPercent;
62
+
63
+ const DRAWER_SIZE = isVertical
64
+ ? SCREEN_HEIGHT * resolvedPercent
65
+ : SCREEN_WIDTH * resolvedPercent;
66
+
67
+ const getClosedOffset = () => {
68
+ switch (position) {
69
+ case "top":
70
+ case "left":
71
+ return -DRAWER_SIZE;
72
+ case "bottom":
73
+ case "right":
74
+ return DRAWER_SIZE;
75
+ }
76
+ };
77
+
78
+ const translate = useSharedValue(getClosedOffset());
79
+ const backdropOpacity = useSharedValue(0);
80
+ const [isRendered, setIsRendered] = useState(false);
81
+
82
+ if (isOpen && !isRendered) {
83
+ setIsRendered(true);
84
+ }
85
+
86
+ useEffect(() => {
87
+ const closedOffset = getClosedOffset();
88
+ if (isOpen) {
89
+ translate.value = withTiming(0, {
90
+ duration: 300,
91
+ easing: Easing.out(Easing.ease),
92
+ });
93
+ backdropOpacity.value = withTiming(0.4, { duration: 300 });
94
+ } else {
95
+ translate.value = withTiming(closedOffset, {
96
+ duration: 300,
97
+ easing: Easing.in(Easing.ease),
98
+ });
99
+ backdropOpacity.value = withTiming(0, { duration: 300 }, (finished) => {
100
+ if (finished) {
101
+ runOnJS(setIsRendered)(false);
102
+ }
103
+ });
104
+ }
105
+ }, [isOpen, DRAWER_SIZE, position]);
106
+
107
+ useEffect(() => {
108
+ if (!isOpen) return;
109
+
110
+ const backAction = () => {
111
+ onClose();
112
+ return true;
113
+ };
114
+
115
+ const backHandler = BackHandler.addEventListener(
116
+ "hardwareBackPress",
117
+ backAction
118
+ );
119
+
120
+ return () => backHandler.remove();
121
+ }, [isOpen, onClose]);
122
+
123
+ const panGesture = Gesture.Pan()
124
+ .onUpdate((event) => {
125
+ if (position === "bottom") {
126
+ if (event.translationY > 0) {
127
+ translate.value = event.translationY;
128
+ }
129
+ } else if (position === "top") {
130
+ if (event.translationY < 0) {
131
+ translate.value = event.translationY;
132
+ }
133
+ } else if (position === "left") {
134
+ if (event.translationX < 0) {
135
+ translate.value = event.translationX;
136
+ }
137
+ } else if (position === "right") {
138
+ if (event.translationX > 0) {
139
+ translate.value = event.translationX;
140
+ }
141
+ }
142
+ })
143
+ .onEnd((event) => {
144
+ let shouldClose = false;
145
+ if (position === "bottom") {
146
+ shouldClose =
147
+ event.translationY > DRAWER_SIZE * 0.3 || event.velocityY > 500;
148
+ } else if (position === "top") {
149
+ shouldClose =
150
+ event.translationY < -DRAWER_SIZE * 0.3 || event.velocityY < -500;
151
+ } else if (position === "left") {
152
+ shouldClose =
153
+ event.translationX < -DRAWER_SIZE * 0.3 || event.velocityX < -500;
154
+ } else if (position === "right") {
155
+ shouldClose =
156
+ event.translationX > DRAWER_SIZE * 0.3 || event.velocityX > 500;
157
+ }
158
+
159
+ if (shouldClose) {
160
+ runOnJS(onClose)();
161
+ } else {
162
+ translate.value = withTiming(0, {
163
+ duration: 300,
164
+ easing: Easing.out(Easing.ease),
165
+ });
166
+ }
167
+ });
168
+
169
+ const drawerAnimatedStyle = useAnimatedStyle(() => {
170
+ if (position === "left" || position === "right") {
171
+ return {
172
+ transform: [{ translateX: translate.value }],
173
+ };
174
+ }
175
+ return {
176
+ transform: [{ translateY: translate.value }],
177
+ };
178
+ });
179
+
180
+ const backdropAnimatedStyle = useAnimatedStyle(() => ({
181
+ opacity: backdropOpacity.value,
182
+ }));
183
+
184
+ const getContainerStyle = (): ViewStyle => {
185
+ const baseShadow: ViewStyle = {
186
+ backgroundColor: colors.base100,
187
+ elevation: 24,
188
+ shadowColor: "#000",
189
+ shadowOpacity: 0.15,
190
+ shadowRadius: 15,
191
+ };
192
+
193
+ switch (position) {
194
+ case "top":
195
+ return {
196
+ ...baseShadow,
197
+ position: "absolute",
198
+ top: 0,
199
+ left: 0,
200
+ right: 0,
201
+ height: DRAWER_SIZE,
202
+ borderBottomLeftRadius: 30,
203
+ borderBottomRightRadius: 30,
204
+ shadowOffset: { width: 0, height: 5 },
205
+ paddingTop: insets.top > 0 ? insets.top : 20,
206
+ };
207
+ case "bottom":
208
+ return {
209
+ ...baseShadow,
210
+ position: "absolute",
211
+ bottom: 0,
212
+ left: 0,
213
+ right: 0,
214
+ height: DRAWER_SIZE,
215
+ borderTopLeftRadius: 30,
216
+ borderTopRightRadius: 30,
217
+ shadowOffset: { width: 0, height: -5 },
218
+ paddingBottom: insets.bottom > 0 ? insets.bottom : 20,
219
+ };
220
+ case "left":
221
+ return {
222
+ ...baseShadow,
223
+ position: "absolute",
224
+ top: 0,
225
+ bottom: 0,
226
+ left: 0,
227
+ width: DRAWER_SIZE,
228
+ borderTopRightRadius: 30,
229
+ borderBottomRightRadius: 30,
230
+ shadowOffset: { width: 5, height: 0 },
231
+ paddingTop: insets.top > 0 ? insets.top : 20,
232
+ paddingBottom: insets.bottom > 0 ? insets.bottom : 20,
233
+ paddingLeft: insets.left > 0 ? insets.left : 0,
234
+ };
235
+ case "right":
236
+ return {
237
+ ...baseShadow,
238
+ position: "absolute",
239
+ top: 0,
240
+ bottom: 0,
241
+ right: 0,
242
+ width: DRAWER_SIZE,
243
+ borderTopLeftRadius: 30,
244
+ borderBottomLeftRadius: 30,
245
+ shadowOffset: { width: -5, height: 0 },
246
+ paddingTop: insets.top > 0 ? insets.top : 20,
247
+ paddingBottom: insets.bottom > 0 ? insets.bottom : 20,
248
+ paddingRight: insets.right > 0 ? insets.right : 0,
249
+ };
250
+ }
251
+ };
252
+
253
+ if (!isRendered && !isOpen) return null;
254
+
255
+ return (
256
+ <Portal>
257
+ <KeyboardAvoidingView
258
+ behavior={Platform.OS === "ios" ? "padding" : undefined}
259
+ style={StyleSheet.absoluteFill}
260
+ pointerEvents="box-none"
261
+ >
262
+ <View style={[StyleSheet.absoluteFill, { zIndex }]}>
263
+ {/* Backdrop Overlay */}
264
+ <TouchableWithoutFeedback onPress={onClose}>
265
+ <Animated.View
266
+ style={[
267
+ StyleSheet.absoluteFill,
268
+ { backgroundColor: "#000" },
269
+ backdropAnimatedStyle,
270
+ ]}
271
+ />
272
+ </TouchableWithoutFeedback>
273
+
274
+ {/* Sliding Drawer Panel */}
275
+ <Animated.View style={[getContainerStyle(), drawerAnimatedStyle]}>
276
+ {position === "bottom" && (
277
+ <View className="flex-1">
278
+ {showHandle && (
279
+ <GestureDetector gesture={panGesture}>
280
+ <View
281
+ className="items-center pt-4 pb-6 bg-base-100 rounded-t-[30px]"
282
+ style={{ zIndex: 10 }}
283
+ >
284
+ <View className="w-16 h-1.5 rounded-full bg-base-300" />
285
+ </View>
286
+ </GestureDetector>
287
+ )}
288
+ <View className="flex-1 px-6">{children}</View>
289
+ </View>
290
+ )}
291
+
292
+ {position === "top" && (
293
+ <View className="flex-1">
294
+ <View className="flex-1 px-6">{children}</View>
295
+ {showHandle && (
296
+ <GestureDetector gesture={panGesture}>
297
+ <View
298
+ className="items-center pt-4 pb-6 bg-base-100 rounded-b-[30px]"
299
+ style={{ zIndex: 10 }}
300
+ >
301
+ <View className="w-16 h-1.5 rounded-full bg-base-300" />
302
+ </View>
303
+ </GestureDetector>
304
+ )}
305
+ </View>
306
+ )}
307
+
308
+ {position === "left" && (
309
+ <View className="flex-1 flex-row">
310
+ <View className="flex-1 px-6">{children}</View>
311
+ {showHandle && (
312
+ <GestureDetector gesture={panGesture}>
313
+ <View
314
+ className="justify-center items-center px-3 bg-base-100 rounded-r-[30px]"
315
+ style={{ zIndex: 10 }}
316
+ >
317
+ <View className="w-1.5 h-16 rounded-full bg-base-300" />
318
+ </View>
319
+ </GestureDetector>
320
+ )}
321
+ </View>
322
+ )}
323
+
324
+ {position === "right" && (
325
+ <View className="flex-1 flex-row">
326
+ {showHandle && (
327
+ <GestureDetector gesture={panGesture}>
328
+ <View
329
+ className="justify-center items-center px-3 bg-base-100 rounded-l-[30px]"
330
+ style={{ zIndex: 10 }}
331
+ >
332
+ <View className="w-1.5 h-16 rounded-full bg-base-300" />
333
+ </View>
334
+ </GestureDetector>
335
+ )}
336
+ <View className="flex-1 px-6">{children}</View>
337
+ </View>
338
+ )}
339
+ </Animated.View>
340
+ </View>
341
+ </KeyboardAvoidingView>
342
+ </Portal>
343
+ );
344
+ }
@@ -4,7 +4,7 @@ import { Txt } from "../../common/Typography";
4
4
  import { Ionicons } from "@expo/vector-icons";
5
5
  import { useColorScheme } from "nativewind";
6
6
  import { Colors } from "../../../constants/Colors";
7
- import BottomModal from "../BottomModal";
7
+ import Drawer from "../Drawer";
8
8
  import Button from "../buttons/Button";
9
9
  import dayjs from "dayjs";
10
10
 
@@ -104,7 +104,7 @@ export default function DatePicker({
104
104
  <Ionicons name="calendar-outline" size={18} color={colors.primary} />
105
105
  </TouchableOpacity>
106
106
 
107
- <BottomModal isOpen={isOpen} onClose={() => setIsOpen(false)} heightPercent={0.65}>
107
+ <Drawer isOpen={isOpen} onClose={() => setIsOpen(false)} position="bottom" sizePercent={0.65}>
108
108
  <View className="p-4 flex-1">
109
109
  <Txt variant="lg" className="font-bold text-center mb-4">
110
110
  {label}
@@ -180,7 +180,7 @@ export default function DatePicker({
180
180
  <Button label="Close" variant="outline" onPress={() => setIsOpen(false)} className="w-full" />
181
181
  </View>
182
182
  </View>
183
- </BottomModal>
183
+ </Drawer>
184
184
  </View>
185
185
  );
186
186
  }
@@ -2,7 +2,7 @@ import React from "react";
2
2
  import { View } from "react-native";
3
3
  import * as ImagePicker from "expo-image-picker";
4
4
  import { Txt } from "@/components/common/Typography";
5
- import BottomModal from "@/components/ui/BottomModal";
5
+ import Drawer from "@/components/ui/Drawer";
6
6
  import Button from "@/components/ui/buttons/Button";
7
7
 
8
8
  export type ImagePickerSource = "camera" | "gallery" | "both";
@@ -93,7 +93,7 @@ export default function ImagePickerModal({
93
93
  };
94
94
 
95
95
  return (
96
- <BottomModal isOpen={isOpen} onClose={onClose}>
96
+ <Drawer isOpen={isOpen} onClose={onClose} position="bottom">
97
97
  <View className="p-6">
98
98
  <Txt variant="lg" className="font-bold mb-1 text-center">
99
99
  {title}
@@ -124,6 +124,6 @@ export default function ImagePickerModal({
124
124
  )}
125
125
  </View>
126
126
  </View>
127
- </BottomModal>
127
+ </Drawer>
128
128
  );
129
129
  }
@@ -7,10 +7,16 @@
7
7
  "start": "tsx src/index.ts",
8
8
  "dev": "tsx watch src/index.ts",
9
9
  "build": "tsc",
10
+ "lint": "eslint src/**/*.ts",
11
+ "lint:fix": "eslint src/**/*.ts --fix",
12
+ "format": "prettier --write \"src/**/*.{ts,json}\"",
13
+ "format:check": "prettier --check \"src/**/*.{ts,json}\"",
14
+ "circular-check": "dpdm --circular --no-warning --no-tree --exit-code circular:1 \"src/**/*.ts\"",
15
+ "ts-prune": "ts-prune",
16
+ "ts-purns": "ts-prune",
10
17
  "db:push": "prisma db push",
11
18
  "db:generate": "prisma generate",
12
- "generate:vapid": "node ./src/utils/generateVapidKeys.js",
13
- "lint": "eslint src/**/*.ts"
19
+ "generate:vapid": "node ./src/utils/generateVapidKeys.js"
14
20
  },
15
21
  "dependencies": {
16
22
  "@prisma/adapter-pg": "^7.9.1",
@@ -49,8 +55,11 @@
49
55
  "@types/web-push": "^3.6.3",
50
56
  "@typescript-eslint/eslint-plugin": "^8.66.0",
51
57
  "@typescript-eslint/parser": "^8.66.0",
58
+ "dpdm": "^3.14.0",
52
59
  "eslint": "^10.8.0",
60
+ "prettier": "^3.4.2",
53
61
  "prisma": "^7.9.1",
62
+ "ts-prune": "^0.10.3",
54
63
  "tsx": "^4.23.1",
55
64
  "typescript": "^5.2.2",
56
65
  "typescript-eslint": "^8.66.0"
@@ -1,176 +0,0 @@
1
- import React, { useEffect, useState } from "react";
2
- import {
3
- View,
4
- TouchableWithoutFeedback,
5
- Dimensions,
6
- StyleSheet,
7
- KeyboardAvoidingView,
8
- Platform,
9
- BackHandler,
10
- } from "react-native";
11
- import Animated, {
12
- useSharedValue,
13
- useAnimatedStyle,
14
- withTiming,
15
- Easing,
16
- runOnJS,
17
- } from "react-native-reanimated";
18
- import { Gesture, GestureDetector } from "react-native-gesture-handler";
19
- import { Portal } from "react-native-paper";
20
- import { useColorScheme } from "nativewind";
21
- import { Colors } from "../../constants/Colors";
22
- import { useSafeAreaInsets } from "react-native-safe-area-context";
23
-
24
- const { height } = Dimensions.get("window");
25
-
26
- interface BottomModalProps {
27
- isOpen: boolean;
28
- onClose: () => void;
29
- children: React.ReactNode;
30
- heightPercent?: number; // e.g. 0.5 for 50% of the screen height
31
- }
32
-
33
- export default function BottomModal({
34
- isOpen,
35
- onClose,
36
- children,
37
- heightPercent = 0.5,
38
- }: BottomModalProps) {
39
- const { colorScheme } = useColorScheme();
40
- const colors = Colors[colorScheme ?? "light"];
41
- const insets = useSafeAreaInsets();
42
-
43
- const MODAL_HEIGHT = height * heightPercent;
44
- const translateY = useSharedValue(MODAL_HEIGHT);
45
- const backdropOpacity = useSharedValue(0);
46
- const [isRendered, setIsRendered] = useState(false);
47
-
48
- if (isOpen && !isRendered) {
49
- setIsRendered(true);
50
- }
51
-
52
- useEffect(() => {
53
- if (isOpen) {
54
- translateY.value = withTiming(0, {
55
- duration: 300,
56
- easing: Easing.out(Easing.ease),
57
- });
58
- backdropOpacity.value = withTiming(0.4, { duration: 300 });
59
- } else {
60
- translateY.value = withTiming(MODAL_HEIGHT, {
61
- duration: 300,
62
- easing: Easing.in(Easing.ease),
63
- });
64
- backdropOpacity.value = withTiming(0, { duration: 300 }, (finished) => {
65
- if (finished) {
66
- runOnJS(setIsRendered)(false);
67
- }
68
- });
69
- }
70
-
71
- }, [isOpen, MODAL_HEIGHT]);
72
-
73
- useEffect(() => {
74
- if (!isOpen) return;
75
-
76
- const backAction = () => {
77
- onClose();
78
- return true;
79
- };
80
-
81
- const backHandler = BackHandler.addEventListener(
82
- "hardwareBackPress",
83
- backAction
84
- );
85
-
86
- return () => backHandler.remove();
87
- }, [isOpen, onClose]);
88
-
89
- const panGesture = Gesture.Pan()
90
- .onUpdate((event) => {
91
- if (event.translationY > 0) {
92
-
93
- translateY.value = event.translationY;
94
- }
95
- })
96
- .onEnd((event) => {
97
- if (event.translationY > MODAL_HEIGHT * 0.3 || event.velocityY > 500) {
98
- runOnJS(onClose)();
99
- } else {
100
-
101
- translateY.value = withTiming(0, {
102
- duration: 300,
103
- easing: Easing.out(Easing.ease),
104
- });
105
- }
106
- });
107
-
108
- const modalAnimatedStyle = useAnimatedStyle(() => ({
109
- transform: [{ translateY: translateY.value }],
110
- }));
111
-
112
- const backdropAnimatedStyle = useAnimatedStyle(() => ({
113
- opacity: backdropOpacity.value,
114
- }));
115
-
116
- if (!isRendered && !isOpen) return null;
117
-
118
- return (
119
- <Portal>
120
- <KeyboardAvoidingView
121
- behavior={Platform.OS === "ios" ? "padding" : undefined}
122
- style={StyleSheet.absoluteFill}
123
- pointerEvents="box-none"
124
- >
125
- <View style={[StyleSheet.absoluteFill, { zIndex: 999 }]}>
126
- {/* Backdrop Overlay */}
127
- <TouchableWithoutFeedback onPress={onClose}>
128
- <Animated.View
129
- style={[
130
- StyleSheet.absoluteFill,
131
- { backgroundColor: "#000" },
132
- backdropAnimatedStyle,
133
- ]}
134
- />
135
- </TouchableWithoutFeedback>
136
-
137
- {/* Sliding Bottom Modal Panel */}
138
- <Animated.View
139
- style={[
140
- {
141
- position: "absolute",
142
- bottom: 0,
143
- left: 0,
144
- right: 0,
145
- height: MODAL_HEIGHT,
146
- backgroundColor: colors.base100,
147
- borderTopLeftRadius: 30,
148
- borderTopRightRadius: 30,
149
- elevation: 24,
150
- shadowColor: "#000",
151
- shadowOffset: { width: 0, height: -5 },
152
- shadowOpacity: 0.15,
153
- shadowRadius: 15,
154
- paddingBottom: insets.bottom > 0 ? insets.bottom : 20,
155
- },
156
- modalAnimatedStyle,
157
- ]}
158
- >
159
- {/* Drag Handle Indicator */}
160
- <GestureDetector gesture={panGesture}>
161
- <View
162
- className="items-center pt-4 pb-6 bg-base-100 rounded-t-[30px]"
163
- style={{ zIndex: 10 }}
164
- >
165
- <View className="w-16 h-1.5 rounded-full bg-base-300" />
166
- </View>
167
- </GestureDetector>
168
-
169
- {/* Modal Content */}
170
- <View className="flex-1 px-6">{children}</View>
171
- </Animated.View>
172
- </View>
173
- </KeyboardAvoidingView>
174
- </Portal>
175
- );
176
- }