@imksh/create 1.0.25 → 1.0.26

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 (267) hide show
  1. package/package.json +1 -1
  2. package/src/commands/create.js +2 -1
  3. package/src/index.js +1 -1
  4. package/src/utils/askTemplate.js +1 -0
  5. package/templates/mern-react-native/mobile/_gitignore +43 -0
  6. package/templates/mern-react-native/mobile/package.json +2 -1
  7. package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +11 -15
  8. package/templates/mern-react-native/mobile/src/app/(tabs)/dashboard.tsx +2 -2
  9. package/templates/mern-react-native/mobile/src/app/(tabs)/search.tsx +2 -2
  10. package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +0 -60
  11. package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +6 -36
  12. package/templates/mern-react-native/mobile/src/services/notification.service.ts +22 -0
  13. package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +158 -33
  14. package/templates/next/next.config.mjs +1 -0
  15. package/templates/next/package.json +10 -9
  16. package/templates/next/postcss.config.js +5 -0
  17. package/templates/next/src/app/layout.js +1 -0
  18. package/templates/next/src/app/page.js +1 -1
  19. package/templates/next/src/components/layout/ProtectedRoute.jsx +3 -3
  20. package/templates/next/src/index.css +1 -0
  21. package/templates/next/src/store/useUiStore.js +13 -7
  22. package/templates/next-ts/.env.example +0 -0
  23. package/templates/next-ts/AGENTS.md +37 -0
  24. package/templates/next-ts/CLAUDE.md +1 -0
  25. package/templates/next-ts/_gitignore +0 -24
  26. package/templates/next-ts/next-env.d.ts +3 -1
  27. package/templates/next-ts/next.config.mjs +1 -0
  28. package/templates/next-ts/package.json +13 -12
  29. package/templates/next-ts/postcss.config.js +5 -0
  30. package/templates/next-ts/public/logo.png +0 -0
  31. package/templates/next-ts/src/app/error.tsx +41 -0
  32. package/templates/next-ts/src/app/layout.tsx +2 -1
  33. package/templates/next-ts/src/app/not-found.tsx +5 -0
  34. package/templates/next-ts/src/app/page.tsx +1 -1
  35. package/templates/next-ts/src/components/layout/ProtectedRoute.tsx +3 -3
  36. package/templates/next-ts/src/components/ui/Button.tsx +63 -0
  37. package/templates/next-ts/src/components/ui/Input.tsx +79 -0
  38. package/templates/next-ts/src/index.css +1 -0
  39. package/templates/next-ts/src/store/useUiStore.ts +13 -7
  40. package/templates/next-ts/tsconfig.json +19 -5
  41. package/templates/production-fullstack/client/index.html +14 -0
  42. package/templates/production-fullstack/client/package.json +47 -0
  43. package/templates/production-fullstack/client/public/_redirects +1 -0
  44. package/templates/production-fullstack/client/public/favicon.ico +0 -0
  45. package/templates/production-fullstack/client/public/favicon.svg +1 -0
  46. package/templates/production-fullstack/client/public/logo.png +0 -0
  47. package/templates/production-fullstack/client/public/manifest.json +24 -0
  48. package/templates/production-fullstack/client/public/sw.js +56 -0
  49. package/templates/production-fullstack/client/src/App.tsx +68 -0
  50. package/templates/production-fullstack/client/src/assets/animations/loading.json +1 -0
  51. package/templates/production-fullstack/client/src/assets/data/navigation.ts +29 -0
  52. package/templates/production-fullstack/client/src/assets/images/logo.png +0 -0
  53. package/templates/production-fullstack/client/src/components/layout/ProtectedRoute.tsx +23 -0
  54. package/templates/production-fullstack/client/src/components/layout/PublicLayout.tsx +13 -0
  55. package/templates/production-fullstack/client/src/components/layout/SidebarLayout.tsx +37 -0
  56. package/templates/production-fullstack/client/src/components/shared/Breadcrumbs.tsx +27 -0
  57. package/templates/production-fullstack/client/src/components/shared/LottieIcon.tsx +36 -0
  58. package/templates/production-fullstack/client/src/components/shared/NotFound.tsx +78 -0
  59. package/templates/production-fullstack/client/src/components/shared/PwaInstallPrompt.tsx +72 -0
  60. package/templates/production-fullstack/client/src/components/shared/SEO.tsx +67 -0
  61. package/templates/production-fullstack/client/src/components/shared/Scroll.tsx +25 -0
  62. package/templates/production-fullstack/client/src/components/shared/Unauthorized.tsx +76 -0
  63. package/templates/production-fullstack/client/src/components/shared/skeletons/common/CardSkeleton.tsx +23 -0
  64. package/templates/production-fullstack/client/src/components/shared/skeletons/common/DashboardSkeleton.tsx +19 -0
  65. package/templates/production-fullstack/client/src/components/shared/skeletons/common/ListSkeleton.tsx +24 -0
  66. package/templates/production-fullstack/client/src/components/shared/skeletons/common/PageSkeleton.tsx +26 -0
  67. package/templates/production-fullstack/client/src/components/shared/skeletons/common/ProfileSkeleton.tsx +23 -0
  68. package/templates/production-fullstack/client/src/components/shared/skeletons/common/Skeleton.tsx +18 -0
  69. package/templates/production-fullstack/client/src/components/shared/skeletons/common/TableSkeleton.tsx +33 -0
  70. package/templates/production-fullstack/client/src/components/ui/ConfirmModal.tsx +152 -0
  71. package/templates/production-fullstack/client/src/components/ui/Header.tsx +230 -0
  72. package/templates/production-fullstack/client/src/components/ui/Loading.tsx +19 -0
  73. package/templates/production-fullstack/client/src/components/ui/Modal.tsx +106 -0
  74. package/templates/production-fullstack/client/src/components/ui/SidebarHeader.tsx +34 -0
  75. package/templates/production-fullstack/client/src/components/ui/Table.tsx +118 -0
  76. package/templates/production-fullstack/client/src/components/ui/ToggleSwitch.tsx +38 -0
  77. package/templates/production-fullstack/client/src/components/ui/sidebar.tsx +152 -0
  78. package/templates/production-fullstack/client/src/config/index.ts +44 -0
  79. package/templates/production-fullstack/client/src/constants/navigation.ts +14 -0
  80. package/templates/production-fullstack/client/src/features/auth/api/authService.ts +19 -0
  81. package/templates/production-fullstack/client/src/features/auth/pages/Login.tsx +233 -0
  82. package/templates/production-fullstack/client/src/features/auth/store/useAuthStore.ts +143 -0
  83. package/templates/production-fullstack/client/src/features/core/pages/Home.tsx +150 -0
  84. package/templates/production-fullstack/client/src/features/core/pages/Profile.tsx +9 -0
  85. package/templates/production-fullstack/client/src/hooks/useLenis.ts +28 -0
  86. package/templates/production-fullstack/client/src/hooks/usePwaInstall.ts +45 -0
  87. package/templates/production-fullstack/client/src/hooks/useWindowSize.ts +25 -0
  88. package/templates/production-fullstack/client/src/index.css +158 -0
  89. package/templates/production-fullstack/client/src/lib/axios.ts +22 -0
  90. package/templates/production-fullstack/client/src/lib/dayjs.ts +12 -0
  91. package/templates/production-fullstack/client/src/lib/query-client.ts +11 -0
  92. package/templates/production-fullstack/client/src/lib/socket.ts +6 -0
  93. package/templates/production-fullstack/client/src/main.tsx +28 -0
  94. package/templates/production-fullstack/client/src/store/useUiStore.ts +74 -0
  95. package/templates/production-fullstack/client/src/utils/formatDate.ts +19 -0
  96. package/templates/production-fullstack/client/src/utils/getInitials.ts +10 -0
  97. package/templates/production-fullstack/client/src/vite-env.d.ts +1 -0
  98. package/templates/production-fullstack/client/tsconfig.json +25 -0
  99. package/templates/production-fullstack/client/tsconfig.node.json +10 -0
  100. package/templates/production-fullstack/client/vite.config.ts +8 -0
  101. package/templates/production-fullstack/mobile/LICENSE +21 -0
  102. package/templates/production-fullstack/mobile/app.json +84 -0
  103. package/templates/production-fullstack/mobile/assets/animations/celebrate.json +1 -0
  104. package/templates/production-fullstack/mobile/assets/animations/infinity.json +1 -0
  105. package/templates/production-fullstack/mobile/assets/animations/loading.json +1 -0
  106. package/templates/production-fullstack/mobile/assets/fonts/Inter_Bold.ttf +0 -0
  107. package/templates/production-fullstack/mobile/assets/fonts/Inter_ExtraBold.ttf +0 -0
  108. package/templates/production-fullstack/mobile/assets/fonts/Inter_Medium.ttf +0 -0
  109. package/templates/production-fullstack/mobile/assets/fonts/Inter_Regular.ttf +0 -0
  110. package/templates/production-fullstack/mobile/assets/fonts/Inter_SemiBold.ttf +0 -0
  111. package/templates/production-fullstack/mobile/assets/images/logo/logo.png +0 -0
  112. package/templates/production-fullstack/mobile/assets/images/logo/logo.svg +1 -0
  113. package/templates/production-fullstack/mobile/assets/images/logo/logoWithBg.png +0 -0
  114. package/templates/production-fullstack/mobile/babel.config.js +13 -0
  115. package/templates/production-fullstack/mobile/drizzle.config.ts +8 -0
  116. package/templates/production-fullstack/mobile/eas.json +24 -0
  117. package/templates/production-fullstack/mobile/eslint.config.js +10 -0
  118. package/templates/production-fullstack/mobile/metro.config.js +6 -0
  119. package/templates/production-fullstack/mobile/nativewind-env.d.ts +3 -0
  120. package/templates/production-fullstack/mobile/package.json +89 -0
  121. package/templates/production-fullstack/mobile/src/app/(auth)/_layout.tsx +12 -0
  122. package/templates/production-fullstack/mobile/src/app/(auth)/change-password.tsx +112 -0
  123. package/templates/production-fullstack/mobile/src/app/(auth)/edit-profile.tsx +160 -0
  124. package/templates/production-fullstack/mobile/src/app/(auth)/forgot-password.tsx +191 -0
  125. package/templates/production-fullstack/mobile/src/app/(auth)/login.tsx +253 -0
  126. package/templates/production-fullstack/mobile/src/app/_layout.tsx +102 -0
  127. package/templates/production-fullstack/mobile/src/app/global.css +82 -0
  128. package/templates/production-fullstack/mobile/src/components/common/AnimatedHeader.tsx +328 -0
  129. package/templates/production-fullstack/mobile/src/components/common/ConfirmationToast.tsx +67 -0
  130. package/templates/production-fullstack/mobile/src/components/common/Loading.tsx +26 -0
  131. package/templates/production-fullstack/mobile/src/components/common/LocationLoader.tsx +591 -0
  132. package/templates/production-fullstack/mobile/src/components/common/ScreenHeader.tsx +68 -0
  133. package/templates/production-fullstack/mobile/src/components/common/TabHeader.tsx +58 -0
  134. package/templates/production-fullstack/mobile/src/components/common/Typography.tsx +113 -0
  135. package/templates/production-fullstack/mobile/src/components/common/Welcome.tsx +55 -0
  136. package/templates/production-fullstack/mobile/src/components/ui/BottomModal.tsx +159 -0
  137. package/templates/production-fullstack/mobile/src/components/ui/ClassicTabBar.tsx +129 -0
  138. package/templates/production-fullstack/mobile/src/components/ui/CustomTabBar.tsx +238 -0
  139. package/templates/production-fullstack/mobile/src/components/ui/Sidebar.tsx +258 -0
  140. package/templates/production-fullstack/mobile/src/components/ui/buttons/Button.tsx +180 -0
  141. package/templates/production-fullstack/mobile/src/components/ui/buttons/ButtonGroup.tsx +32 -0
  142. package/templates/production-fullstack/mobile/src/components/ui/buttons/FloatingActionButton.tsx +72 -0
  143. package/templates/production-fullstack/mobile/src/components/ui/buttons/IconButton.tsx +84 -0
  144. package/templates/production-fullstack/mobile/src/components/ui/data-display/Accordion.tsx +82 -0
  145. package/templates/production-fullstack/mobile/src/components/ui/data-display/Avatar.tsx +47 -0
  146. package/templates/production-fullstack/mobile/src/components/ui/data-display/Badge.tsx +104 -0
  147. package/templates/production-fullstack/mobile/src/components/ui/data-display/Card.tsx +36 -0
  148. package/templates/production-fullstack/mobile/src/components/ui/data-display/Image.tsx +43 -0
  149. package/templates/production-fullstack/mobile/src/components/ui/data-display/ImageViewer.tsx +33 -0
  150. package/templates/production-fullstack/mobile/src/components/ui/data-display/SwipeableRow.tsx +94 -0
  151. package/templates/production-fullstack/mobile/src/components/ui/feedback/CircularProgress.tsx +38 -0
  152. package/templates/production-fullstack/mobile/src/components/ui/feedback/GestureWrapper.tsx +114 -0
  153. package/templates/production-fullstack/mobile/src/components/ui/feedback/ProgressBar.tsx +51 -0
  154. package/templates/production-fullstack/mobile/src/components/ui/feedback/Skeleton.tsx +84 -0
  155. package/templates/production-fullstack/mobile/src/components/ui/feedback/Spinner.tsx +50 -0
  156. package/templates/production-fullstack/mobile/src/components/ui/inputs/Checkbox.tsx +54 -0
  157. package/templates/production-fullstack/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
  158. package/templates/production-fullstack/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  159. package/templates/production-fullstack/mobile/src/components/ui/inputs/Input.tsx +158 -0
  160. package/templates/production-fullstack/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
  161. package/templates/production-fullstack/mobile/src/components/ui/inputs/OTPInput.tsx +83 -0
  162. package/templates/production-fullstack/mobile/src/components/ui/inputs/PasswordInput.tsx +16 -0
  163. package/templates/production-fullstack/mobile/src/components/ui/inputs/Radio.tsx +54 -0
  164. package/templates/production-fullstack/mobile/src/components/ui/inputs/SearchBar.tsx +53 -0
  165. package/templates/production-fullstack/mobile/src/components/ui/inputs/Slider.tsx +49 -0
  166. package/templates/production-fullstack/mobile/src/components/ui/inputs/Switch.tsx +75 -0
  167. package/templates/production-fullstack/mobile/src/components/ui/inputs/TextArea.tsx +15 -0
  168. package/templates/production-fullstack/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  169. package/templates/production-fullstack/mobile/src/components/ui/layout/Container.tsx +23 -0
  170. package/templates/production-fullstack/mobile/src/components/ui/layout/Divider.tsx +20 -0
  171. package/templates/production-fullstack/mobile/src/components/ui/layout/Grid.tsx +62 -0
  172. package/templates/production-fullstack/mobile/src/components/ui/layout/KeyboardAvoidingViewWrapper.tsx +16 -0
  173. package/templates/production-fullstack/mobile/src/components/ui/layout/ScrollContainer.tsx +21 -0
  174. package/templates/production-fullstack/mobile/src/components/ui/layout/Section.tsx +51 -0
  175. package/templates/production-fullstack/mobile/src/components/ui/layout/Spacer.tsx +6 -0
  176. package/templates/production-fullstack/mobile/src/components/ui/layout/Stack.tsx +34 -0
  177. package/templates/production-fullstack/mobile/src/components/ui/navigation/Pagination.tsx +61 -0
  178. package/templates/production-fullstack/mobile/src/components/ui/navigation/ProgressStepper.tsx +66 -0
  179. package/templates/production-fullstack/mobile/src/components/ui/navigation/Tabs.tsx +173 -0
  180. package/templates/production-fullstack/mobile/src/config/sidebar.config.ts +72 -0
  181. package/templates/production-fullstack/mobile/src/config/tabNavigation.ts +64 -0
  182. package/templates/production-fullstack/mobile/src/constants/Colors.ts +71 -0
  183. package/templates/production-fullstack/mobile/src/db/index.ts +9 -0
  184. package/templates/production-fullstack/mobile/src/db/schema.ts +14 -0
  185. package/templates/production-fullstack/mobile/src/features/auth/api/auth.service.ts +35 -0
  186. package/templates/production-fullstack/mobile/src/features/auth/store/useAuthStore.ts +273 -0
  187. package/templates/production-fullstack/mobile/src/features/auth/types/auth.type.ts +14 -0
  188. package/templates/production-fullstack/mobile/src/hooks/useDebounce.ts +17 -0
  189. package/templates/production-fullstack/mobile/src/lib/axios.ts +27 -0
  190. package/templates/production-fullstack/mobile/src/lib/query-client.ts +11 -0
  191. package/templates/production-fullstack/mobile/src/lib/socket.ts +36 -0
  192. package/templates/production-fullstack/mobile/src/lib/storage.ts +55 -0
  193. package/templates/production-fullstack/mobile/src/services/permission.service.ts +214 -0
  194. package/templates/production-fullstack/mobile/src/store/localStore.ts +7 -0
  195. package/templates/production-fullstack/mobile/src/store/useNotificationStore.ts +53 -0
  196. package/templates/production-fullstack/mobile/src/types/common.type.ts +6 -0
  197. package/templates/production-fullstack/mobile/src/types/images.d.ts +24 -0
  198. package/templates/production-fullstack/mobile/src/utils/toast.ts +26 -0
  199. package/templates/production-fullstack/mobile/tailwind.config.js +71 -0
  200. package/templates/production-fullstack/mobile/tsconfig.json +23 -0
  201. package/templates/production-fullstack/server/.env.example +63 -0
  202. package/templates/production-fullstack/server/README.md +75 -0
  203. package/templates/production-fullstack/server/eslint.config.js +13 -0
  204. package/templates/production-fullstack/server/package.json +58 -0
  205. package/templates/production-fullstack/server/prisma/schema/core/attachment.prisma +14 -0
  206. package/templates/production-fullstack/server/prisma/schema/core/notification.prisma +18 -0
  207. package/templates/production-fullstack/server/prisma/schema/core/permission.prisma +26 -0
  208. package/templates/production-fullstack/server/prisma/schema/core/role.prisma +25 -0
  209. package/templates/production-fullstack/server/prisma/schema/core/user.prisma +56 -0
  210. package/templates/production-fullstack/server/prisma/schema/enums/common.prisma +13 -0
  211. package/templates/production-fullstack/server/prisma/schema/enums/notification.prisma +6 -0
  212. package/templates/production-fullstack/server/prisma/schema/enums/user.prisma +11 -0
  213. package/templates/production-fullstack/server/prisma/schema/schema.prisma +7 -0
  214. package/templates/production-fullstack/server/prisma.config.ts +14 -0
  215. package/templates/production-fullstack/server/src/app.ts +36 -0
  216. package/templates/production-fullstack/server/src/common/middlewares/auth.middleware.ts +45 -0
  217. package/templates/production-fullstack/server/src/common/middlewares/errorHandler.ts +16 -0
  218. package/templates/production-fullstack/server/src/common/middlewares/multer.ts +20 -0
  219. package/templates/production-fullstack/server/src/common/middlewares/rateLimiter.ts +53 -0
  220. package/templates/production-fullstack/server/src/common/services/email.service.ts +28 -0
  221. package/templates/production-fullstack/server/src/common/services/push.service.ts +65 -0
  222. package/templates/production-fullstack/server/src/common/services/storage.service.ts +9 -0
  223. package/templates/production-fullstack/server/src/common/types/express.d.ts +10 -0
  224. package/templates/production-fullstack/server/src/common/utils/cloudinaryUpload.ts +44 -0
  225. package/templates/production-fullstack/server/src/common/utils/emailOtp.ts +30 -0
  226. package/templates/production-fullstack/server/src/common/utils/genOtpToken.ts +22 -0
  227. package/templates/production-fullstack/server/src/common/utils/generateVapidKeys.ts +12 -0
  228. package/templates/production-fullstack/server/src/common/utils/jwt.ts +27 -0
  229. package/templates/production-fullstack/server/src/common/utils/pushHelper.ts +22 -0
  230. package/templates/production-fullstack/server/src/config/env.ts +2 -0
  231. package/templates/production-fullstack/server/src/config/logger.ts +58 -0
  232. package/templates/production-fullstack/server/src/config/prisma.ts +26 -0
  233. package/templates/production-fullstack/server/src/features/attachments/upload.controller.ts +22 -0
  234. package/templates/production-fullstack/server/src/features/attachments/upload.routes.ts +11 -0
  235. package/templates/production-fullstack/server/src/features/auth/auth.controller.ts +124 -0
  236. package/templates/production-fullstack/server/src/features/auth/auth.routes.ts +22 -0
  237. package/templates/production-fullstack/server/src/features/auth/auth.service.ts +155 -0
  238. package/templates/production-fullstack/server/src/features/notifications/push.controller.ts +55 -0
  239. package/templates/production-fullstack/server/src/features/notifications/push.routes.ts +11 -0
  240. package/templates/production-fullstack/server/src/index.ts +17 -0
  241. package/templates/production-fullstack/server/src/routes/index.ts +12 -0
  242. package/templates/production-fullstack/server/src/sockets/auth.ts +0 -0
  243. package/templates/production-fullstack/server/src/sockets/connection.ts +31 -0
  244. package/templates/production-fullstack/server/src/sockets/emitters/assignment.emitter.ts +0 -0
  245. package/templates/production-fullstack/server/src/sockets/emitters/attendance.emitter.ts +0 -0
  246. package/templates/production-fullstack/server/src/sockets/emitters/exam.emitter.ts +0 -0
  247. package/templates/production-fullstack/server/src/sockets/emitters/notification.emitter.ts +17 -0
  248. package/templates/production-fullstack/server/src/sockets/events.ts +27 -0
  249. package/templates/production-fullstack/server/src/sockets/handlers/attendance.handler.ts +0 -0
  250. package/templates/production-fullstack/server/src/sockets/handlers/chat.handler.ts +0 -0
  251. package/templates/production-fullstack/server/src/sockets/handlers/index.ts +15 -0
  252. package/templates/production-fullstack/server/src/sockets/handlers/lms.handler.ts +0 -0
  253. package/templates/production-fullstack/server/src/sockets/handlers/notification.handler.ts +39 -0
  254. package/templates/production-fullstack/server/src/sockets/index.ts +39 -0
  255. package/templates/production-fullstack/server/src/sockets/middleware.ts +54 -0
  256. package/templates/production-fullstack/server/src/sockets/rooms.ts +11 -0
  257. package/templates/production-fullstack/server/src/sockets/types.ts +32 -0
  258. package/templates/production-fullstack/server/tsconfig.json +14 -0
  259. package/templates/react-native/package.json +2 -1
  260. package/templates/react-native/src/app/(auth)/edit-profile.tsx +11 -15
  261. package/templates/react-native/src/app/(tabs)/dashboard.tsx +2 -2
  262. package/templates/react-native/src/app/(tabs)/search.tsx +2 -2
  263. package/templates/react-native/src/config/sidebar.config.ts +0 -60
  264. package/templates/react-native/src/config/tabNavigation.ts +6 -36
  265. package/templates/react-native/src/services/notification.service.ts +22 -0
  266. package/templates/react-native/src/store/useNotificationStore.ts +158 -33
  267. /package/templates/{react-native → production-fullstack/mobile}/expo-env.d.ts +0 -0
@@ -0,0 +1,238 @@
1
+ import React from "react";
2
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
3
+ import { Ionicons } from "@expo/vector-icons";
4
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
5
+ import { useColorScheme } from "nativewind";
6
+ import { Colors } from "../../constants/Colors";
7
+ import { getTabConfig } from "../../config/tabNavigation";
8
+ import { LinearGradient } from "expo-linear-gradient";
9
+
10
+ const TabItem = ({
11
+ isFocused,
12
+ onPress,
13
+ onLongPress,
14
+ iconName,
15
+ label,
16
+ colors,
17
+ }: any) => {
18
+ return (
19
+ <TouchableOpacity
20
+ accessibilityRole="button"
21
+ accessibilityState={isFocused ? { selected: true } : {}}
22
+ onPress={onPress}
23
+ onLongPress={onLongPress}
24
+ style={styles.tabButton}
25
+ activeOpacity={0.8}
26
+ >
27
+ <View style={styles.tabItem}>
28
+ <View
29
+ style={[
30
+ styles.iconWrapper,
31
+ isFocused && {
32
+ backgroundColor: colors.primary,
33
+ width: 54,
34
+ height: 54,
35
+ borderRadius: 100,
36
+ alignItems: "center",
37
+ justifyContent: "center",
38
+ },
39
+ ]}
40
+ >
41
+ <Ionicons
42
+ name={iconName as any}
43
+ size={isFocused ? 20 : 24}
44
+ color={isFocused ? colors.primaryContent : colors.secondary}
45
+ />
46
+ </View>
47
+ </View>
48
+ </TouchableOpacity>
49
+ );
50
+ };
51
+
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
+ };
71
+
72
+ export default function CustomTabBar({
73
+ state,
74
+ descriptors,
75
+ navigation,
76
+ showCenterButton = true,
77
+ onCenterButtonPress,
78
+ }: any) {
79
+ const insets = useSafeAreaInsets();
80
+ const { colorScheme } = useColorScheme();
81
+ const colors = Colors[colorScheme ?? "light"];
82
+
83
+ const currentRoute = state.routes[state.index];
84
+ const currentOptions = descriptors[currentRoute.key].options;
85
+ if (currentOptions.tabBarStyle?.display === "none") {
86
+ return null;
87
+ }
88
+
89
+ const bottomOffset = insets.bottom > 0 ? insets.bottom : 20;
90
+ const totalTabBarHeight = bottomOffset + 70; // 70 is the height of the tab bar container
91
+
92
+ return (
93
+ <View pointerEvents="box-none" style={StyleSheet.absoluteFill}>
94
+ <LinearGradient
95
+ colors={[`${colors.base100}00`, colors.base100, colors.base100]}
96
+ locations={[0, 0.5, 1]}
97
+ style={{
98
+ position: "absolute",
99
+ bottom: 0,
100
+ left: 0,
101
+ right: 0,
102
+ height: totalTabBarHeight,
103
+ }}
104
+ pointerEvents="none"
105
+ />
106
+ <View
107
+ style={[
108
+ styles.container,
109
+ {
110
+ bottom: bottomOffset,
111
+ backgroundColor: colors.base100,
112
+ borderColor: colors.base300,
113
+ },
114
+ ]}
115
+ >
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);
143
+ }
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
+ )}
162
+ <TabItem
163
+ isFocused={isFocused}
164
+ onPress={onPress}
165
+ onLongPress={onLongPress}
166
+ iconName={iconName}
167
+ label={label as string}
168
+ colors={colors}
169
+ />
170
+ </React.Fragment>
171
+ );
172
+ })}
173
+ </View>
174
+ </View>
175
+ );
176
+ }
177
+
178
+ const styles = StyleSheet.create({
179
+ container: {
180
+ position: "absolute",
181
+ flexDirection: "row",
182
+ justifyContent: "space-between",
183
+ alignItems: "center",
184
+ marginHorizontal: 20,
185
+ paddingVertical: 10,
186
+ paddingHorizontal: 15,
187
+ borderRadius: 40,
188
+ borderWidth: 1,
189
+ // shadowColor: "#000",
190
+ // shadowOffset: { width: 0, height: 5 },
191
+ // shadowOpacity: 0.10,
192
+ // shadowRadius: 10,
193
+ elevation: 1,
194
+ left: 0,
195
+ right: 0,
196
+ height: 70,
197
+ },
198
+ tabButton: {
199
+ flex: 1,
200
+ alignItems: "center",
201
+ justifyContent: "center",
202
+ height: "100%",
203
+ },
204
+ tabItem: {
205
+ alignItems: "center",
206
+ justifyContent: "center",
207
+ height: 50,
208
+ },
209
+ iconWrapper: {
210
+ alignItems: "center",
211
+ justifyContent: "center",
212
+ },
213
+ activeIndicator: {
214
+ width: 20,
215
+ height: 3,
216
+ borderRadius: 2,
217
+ marginTop: 6,
218
+ position: "absolute",
219
+ bottom: -6,
220
+ },
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
+ });
@@ -0,0 +1,258 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import {
3
+ View,
4
+ TouchableWithoutFeedback,
5
+ Dimensions,
6
+ StyleSheet,
7
+ TouchableOpacity,
8
+ } from "react-native";
9
+ import Animated, {
10
+ useSharedValue,
11
+ useAnimatedStyle,
12
+ withTiming,
13
+ Easing,
14
+ runOnJS,
15
+ } from "react-native-reanimated";
16
+ import { Portal } from "react-native-paper";
17
+ import { useColorScheme } from "nativewind";
18
+ import { Colors } from "../../constants/Colors";
19
+ import { Txt } from "../common/Typography";
20
+ import { Ionicons } from "@expo/vector-icons";
21
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
22
+ import { useAuthStore } from "../../features/auth/store/useAuthStore";
23
+ import { sidebarConfig } from "../../config/sidebar.config";
24
+ import { router } from "expo-router";
25
+ import Image from "./data-display/Image";
26
+
27
+ const { width } = Dimensions.get("window");
28
+ const SIDEBAR_WIDTH = width * 0.75;
29
+
30
+ interface SidebarProps {
31
+ isOpen: boolean;
32
+ onClose: () => void;
33
+ side?: "left" | "right";
34
+ children?: React.ReactNode;
35
+ }
36
+
37
+ const MenuItem = ({
38
+ icon,
39
+ label,
40
+ onPress,
41
+ colors,
42
+ }: {
43
+ icon: any;
44
+ label: string;
45
+ onPress?: () => void;
46
+ colors: any;
47
+ }) => (
48
+ <TouchableOpacity
49
+ activeOpacity={0.7}
50
+ onPress={onPress}
51
+ className="flex-row items-center py-4 px-6 rounded-2xl mx-2"
52
+ >
53
+ <View className="w-10 h-10 rounded-full items-center justify-center bg-primary/10 mr-4">
54
+ <Ionicons name={icon} size={20} color={colors.primary} />
55
+ </View>
56
+ <Txt variant="md" className="text-base-content font-semibold">
57
+ {label}
58
+ </Txt>
59
+ </TouchableOpacity>
60
+ );
61
+
62
+ export default function Sidebar({
63
+ isOpen,
64
+ onClose,
65
+ side = "left",
66
+ children,
67
+ }: SidebarProps) {
68
+ const { colorScheme } = useColorScheme();
69
+ const colors = Colors[colorScheme ?? "light"];
70
+ const insets = useSafeAreaInsets();
71
+ const { user, logout } = useAuthStore();
72
+
73
+ const startValue = side === "left" ? -SIDEBAR_WIDTH : SIDEBAR_WIDTH;
74
+ const translateX = useSharedValue(isOpen ? 0 : startValue);
75
+ const backdropOpacity = useSharedValue(0);
76
+ const [isRendered, setIsRendered] = useState(false);
77
+
78
+ if (isOpen && !isRendered) {
79
+ setIsRendered(true);
80
+ }
81
+
82
+ useEffect(() => {
83
+ const targetValue = isOpen
84
+ ? 0
85
+ : side === "left"
86
+ ? -SIDEBAR_WIDTH
87
+ : SIDEBAR_WIDTH;
88
+ translateX.value = withTiming(targetValue, {
89
+ duration: 300,
90
+ easing: Easing.out(Easing.ease),
91
+ });
92
+
93
+ if (isOpen) {
94
+ backdropOpacity.value = withTiming(0.4, { duration: 300 });
95
+ } else {
96
+ backdropOpacity.value = withTiming(0, { duration: 300 }, (finished) => {
97
+ if (finished) {
98
+ runOnJS(setIsRendered)(false);
99
+ }
100
+ });
101
+ }
102
+ }, [isOpen, side, backdropOpacity, translateX, startValue]);
103
+
104
+ const sidebarAnimatedStyle = useAnimatedStyle(() => ({
105
+ transform: [{ translateX: translateX.value }],
106
+ }));
107
+
108
+ const backdropAnimatedStyle = useAnimatedStyle(() => ({
109
+ opacity: backdropOpacity.value,
110
+ }));
111
+
112
+ if (!isRendered && !isOpen) return null;
113
+
114
+ const role = user?.role?.toLowerCase() || "default";
115
+ const menuItems = sidebarConfig[role] || sidebarConfig.default;
116
+
117
+ return (
118
+ <Portal>
119
+ <View style={[StyleSheet.absoluteFill, { zIndex: 999 }]}>
120
+ {/* Backdrop Overlay */}
121
+ <TouchableWithoutFeedback onPress={onClose}>
122
+ <Animated.View
123
+ style={[
124
+ StyleSheet.absoluteFill,
125
+ { backgroundColor: "#000" },
126
+ backdropAnimatedStyle,
127
+ ]}
128
+ />
129
+ </TouchableWithoutFeedback>
130
+
131
+ {/* Sliding Sidebar Panel */}
132
+ <Animated.View
133
+ style={[
134
+ {
135
+ position: "absolute",
136
+ top: 0,
137
+ bottom: 0,
138
+ left: side === "left" ? 0 : undefined,
139
+ right: side === "right" ? 0 : undefined,
140
+ width: SIDEBAR_WIDTH,
141
+ backgroundColor: colors.base100,
142
+ elevation: 24,
143
+ shadowColor: "#000",
144
+ shadowOffset: { width: side === "left" ? 5 : -5, height: 0 },
145
+ shadowOpacity: 0.2,
146
+ shadowRadius: 15,
147
+ overflow: "hidden",
148
+ },
149
+ sidebarAnimatedStyle,
150
+ ]}
151
+ >
152
+ {children || (
153
+ <View className="flex-1">
154
+ {/* Sidebar Header */}
155
+ <View
156
+ className="px-6 bg-base-200 border-b border-base-300 items-center flex flex-row gap-4"
157
+ style={{
158
+ paddingTop: insets.top > 0 ? insets.top + 12 : 30,
159
+ paddingBottom: 16,
160
+ }}
161
+ >
162
+ <TouchableOpacity
163
+ onPress={() => {
164
+ onClose();
165
+ router.push("/(tenant)/profile");
166
+ }}
167
+ className="w-12 h-12 rounded-full bg-primary items-center justify-center"
168
+ style={{
169
+ shadowColor: colors.primary,
170
+ shadowOffset: { width: 0, height: 4 },
171
+ shadowOpacity: 0.4,
172
+ shadowRadius: 10,
173
+ elevation: 8,
174
+ }}
175
+ >
176
+ {user?.avatar?.url ? (
177
+ <View className="w-11 h-11 rounded-full border border-base-300 overflow-hidden">
178
+ <Image
179
+ src={user.avatar.url}
180
+ style={{ width: 44, height: 44 }}
181
+ />
182
+ </View>
183
+ ) : (
184
+ <View className="w-11 h-11 rounded-full bg-primary/15 items-center justify-center border border-primary/10">
185
+ <Ionicons
186
+ name="person"
187
+ size={22}
188
+ color={colors.primary}
189
+ />
190
+ </View>
191
+ )}
192
+ </TouchableOpacity>
193
+ <View className="flex-1">
194
+ <Txt
195
+ variant="md"
196
+ className="font-bold text-base-content"
197
+ numberOfLines={1}
198
+ >
199
+ {user?.name || "User Name"}
200
+ </Txt>
201
+ <Txt
202
+ variant="xs"
203
+ className="text-base-content/50"
204
+ numberOfLines={1}
205
+ >
206
+ {user?.email || "user@example.com"}
207
+ </Txt>
208
+ </View>
209
+ </View>
210
+
211
+ {/* Menu Items */}
212
+ <View className="flex-1 pt-4">
213
+ {menuItems.map((item, index) => (
214
+ <MenuItem
215
+ key={index}
216
+ icon={item.icon}
217
+ label={item.label}
218
+ onPress={() => {
219
+ onClose();
220
+ router.push(item.route as any);
221
+ }}
222
+ colors={colors}
223
+ />
224
+ ))}
225
+ </View>
226
+
227
+ {/* Footer */}
228
+ <View
229
+ className="p-6 border-t border-base-300"
230
+ style={{
231
+ paddingBottom: insets.bottom > 0 ? insets.bottom + 10 : 20,
232
+ }}
233
+ >
234
+ <TouchableOpacity
235
+ activeOpacity={0.8}
236
+ className="flex-row items-center justify-center p-3 rounded-xl bg-error/10 border border-error/20"
237
+ onPress={() => {
238
+ onClose();
239
+ logout();
240
+ }}
241
+ >
242
+ <Ionicons
243
+ name="log-out-outline"
244
+ size={20}
245
+ color={colors.error}
246
+ />
247
+ <Txt variant="md" className="ml-2 font-bold text-error">
248
+ Log Out
249
+ </Txt>
250
+ </TouchableOpacity>
251
+ </View>
252
+ </View>
253
+ )}
254
+ </Animated.View>
255
+ </View>
256
+ </Portal>
257
+ );
258
+ }
@@ -0,0 +1,180 @@
1
+ import React from "react";
2
+ import {
3
+ TouchableOpacity,
4
+ ActivityIndicator,
5
+ TouchableOpacityProps,
6
+ View,
7
+ } from "react-native";
8
+ import { useColorScheme } from "nativewind";
9
+ import { Colors } from "../../../constants/Colors";
10
+ import { Txt } from "../../common/Typography";
11
+ import { Ionicons } from "@expo/vector-icons";
12
+
13
+ export type ButtonVariant =
14
+ | "primary"
15
+ | "secondary"
16
+ | "accent"
17
+ | "info"
18
+ | "success"
19
+ | "warning"
20
+ | "error"
21
+ | "ghost"
22
+ | "outline";
23
+ export type ButtonSize = "xs" | "sm" | "base" | "md" | "lg";
24
+
25
+ export interface ButtonProps extends TouchableOpacityProps {
26
+ label: string;
27
+ variant?: ButtonVariant;
28
+ size?: ButtonSize;
29
+ isLoading?: boolean;
30
+ leftIcon?: keyof typeof Ionicons.glyphMap;
31
+ rightIcon?: keyof typeof Ionicons.glyphMap;
32
+ isFullWidth?: boolean;
33
+ soft?: boolean;
34
+ outline?: boolean;
35
+ }
36
+
37
+ export default function Button({
38
+ label,
39
+ variant = "primary",
40
+ size = "base",
41
+ isLoading = false,
42
+ leftIcon,
43
+ rightIcon,
44
+ isFullWidth = false,
45
+ soft = false,
46
+ outline = false,
47
+ style,
48
+ disabled,
49
+ ...props
50
+ }: ButtonProps) {
51
+ const { colorScheme } = useColorScheme();
52
+ const theme = Colors[colorScheme ?? "light"];
53
+
54
+ const getVariantStyles = (): {
55
+ bg: string;
56
+ text: string;
57
+ border?: string;
58
+ } => {
59
+ switch (variant) {
60
+ case "primary":
61
+ return { bg: theme.primary, text: theme.primaryContent };
62
+ case "secondary":
63
+ return { bg: theme.secondary, text: theme.secondaryContent };
64
+ case "accent":
65
+ return { bg: theme.accent, text: theme.accentContent };
66
+ case "info":
67
+ return { bg: theme.info, text: "#fff" };
68
+ case "success":
69
+ return { bg: theme.success, text: "#fff" };
70
+ case "warning":
71
+ return { bg: theme.warning, text: "#fff" };
72
+ case "error":
73
+ return { bg: theme.error, text: "#fff" };
74
+ case "ghost":
75
+ return { bg: "transparent", text: theme.baseContent };
76
+ case "outline":
77
+ return {
78
+ bg: "transparent",
79
+ text: theme.baseContent,
80
+ border: theme.base300,
81
+ };
82
+ default:
83
+ return { bg: theme.primary, text: theme.primaryContent };
84
+ }
85
+ };
86
+
87
+ const getSizeStyles = (): {
88
+ height: number;
89
+ px: number;
90
+ fontSize: number;
91
+ iconSize: number;
92
+ } => {
93
+ switch (size) {
94
+ case "xs":
95
+ return { height: 30, px: 10, fontSize: 12, iconSize: 14 };
96
+ case "sm":
97
+ return { height: 36, px: 12, fontSize: 13, iconSize: 16 };
98
+ case "base":
99
+ return { height: 40, px: 12, fontSize: 14, iconSize: 16 };
100
+ case "md":
101
+ return { height: 48, px: 16, fontSize: 15, iconSize: 20 };
102
+ case "lg":
103
+ return { height: 56, px: 24, fontSize: 17, iconSize: 24 };
104
+ default:
105
+ return { height: 40, px: 12, fontSize: 14, iconSize: 16 };
106
+ }
107
+ };
108
+
109
+ const vStyles = getVariantStyles();
110
+ if (outline) {
111
+ vStyles.border = vStyles.bg === "transparent" ? theme.base300 : vStyles.bg;
112
+ vStyles.text = vStyles.bg === "transparent" ? theme.baseContent : vStyles.bg;
113
+ vStyles.bg = "transparent";
114
+ } else if (soft && variant !== "ghost" && variant !== "outline") {
115
+ vStyles.text = vStyles.bg;
116
+ vStyles.bg = vStyles.bg + "26"; // ~15% opacity
117
+ }
118
+
119
+ const sStyles = getSizeStyles();
120
+
121
+ const isDisabled = disabled || isLoading;
122
+
123
+ return (
124
+ <TouchableOpacity
125
+ activeOpacity={0.7}
126
+ disabled={isDisabled}
127
+ style={[
128
+ {
129
+ backgroundColor: vStyles.bg as any,
130
+ height: sStyles.height,
131
+ paddingHorizontal: sStyles.px,
132
+ borderRadius: 12,
133
+ flexDirection: "row",
134
+ alignItems: "center",
135
+ justifyContent: "center",
136
+ opacity: isDisabled ? 0.6 : 1,
137
+ borderWidth: (variant === "outline" || outline) ? 1 : 0,
138
+ borderColor: vStyles.border,
139
+ width: isFullWidth ? "100%" : undefined,
140
+ },
141
+ style,
142
+ ]}
143
+ {...props}
144
+ >
145
+ {/* Loading overlay */}
146
+ {isLoading && (
147
+ <View style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0, justifyContent: "center", alignItems: "center" }}>
148
+ <ActivityIndicator color={vStyles.text} size="small" />
149
+ </View>
150
+ )}
151
+
152
+ {/* Button content (hidden while loading to maintain dimensions) */}
153
+ <View style={{ flexDirection: "row", alignItems: "center", opacity: isLoading ? 0 : 1 }}>
154
+ {leftIcon && (
155
+ <Ionicons
156
+ name={leftIcon}
157
+ size={sStyles.iconSize}
158
+ color={vStyles.text}
159
+ style={{ marginRight: 8 }}
160
+ />
161
+ )}
162
+ <Txt
163
+ color={vStyles.text}
164
+ weight="semibold"
165
+ style={{ fontSize: sStyles.fontSize }}
166
+ >
167
+ {label}
168
+ </Txt>
169
+ {rightIcon && (
170
+ <Ionicons
171
+ name={rightIcon}
172
+ size={sStyles.iconSize}
173
+ color={vStyles.text}
174
+ style={{ marginLeft: 8 }}
175
+ />
176
+ )}
177
+ </View>
178
+ </TouchableOpacity>
179
+ );
180
+ }
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import { View, StyleProp, ViewStyle } from "react-native";
3
+
4
+ export interface ButtonGroupProps {
5
+ children: React.ReactNode;
6
+ orientation?: "horizontal" | "vertical";
7
+ spacing?: number;
8
+ style?: StyleProp<ViewStyle>;
9
+ }
10
+
11
+ export default function ButtonGroup({
12
+ children,
13
+ orientation = "horizontal",
14
+ spacing = 8,
15
+ style,
16
+ }: ButtonGroupProps) {
17
+ const isHorizontal = orientation === "horizontal";
18
+
19
+ return (
20
+ <View
21
+ style={[
22
+ {
23
+ flexDirection: isHorizontal ? "row" : "column",
24
+ gap: spacing,
25
+ },
26
+ style,
27
+ ]}
28
+ >
29
+ {children}
30
+ </View>
31
+ );
32
+ }