@imksh/create 1.0.24 → 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.
- package/package.json +1 -1
- package/src/commands/create.js +2 -1
- package/src/index.js +1 -1
- package/src/utils/askTemplate.js +1 -0
- package/templates/mern-react-native/client/src/vite-env.d.ts +1 -0
- package/templates/mern-react-native/mobile/_gitignore +43 -0
- package/templates/mern-react-native/mobile/babel.config.js +1 -0
- package/templates/mern-react-native/mobile/drizzle.config.ts +8 -0
- package/templates/mern-react-native/mobile/expo-env.d.ts +3 -0
- package/templates/mern-react-native/mobile/package.json +8 -1
- package/templates/mern-react-native/mobile/src/app/(auth)/_layout.tsx +12 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/change-password.tsx +112 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +156 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/forgot-password.tsx +191 -0
- package/templates/mern-react-native/mobile/src/app/{login.tsx → (auth)/login.tsx} +69 -21
- package/templates/mern-react-native/mobile/src/app/(tabs)/dashboard.tsx +2 -2
- package/templates/mern-react-native/mobile/src/app/(tabs)/index.tsx +18 -17
- package/templates/mern-react-native/mobile/src/app/(tabs)/profile.tsx +23 -60
- package/templates/mern-react-native/mobile/src/app/(tabs)/search.tsx +2 -2
- package/templates/mern-react-native/mobile/src/app/_layout.tsx +22 -4
- package/templates/mern-react-native/mobile/src/components/common/AnimatedHeader.tsx +328 -0
- package/templates/mern-react-native/mobile/src/components/common/LocationLoader.tsx +591 -0
- package/templates/mern-react-native/mobile/src/components/common/ScreenHeader.tsx +41 -17
- package/templates/mern-react-native/mobile/src/components/common/TabHeader.tsx +58 -0
- package/templates/mern-react-native/mobile/src/components/common/Typography.tsx +49 -30
- package/templates/mern-react-native/mobile/src/components/common/Welcome.tsx +2 -2
- package/templates/mern-react-native/mobile/src/components/ui/BottomModal.tsx +31 -20
- package/templates/mern-react-native/mobile/src/components/ui/ClassicTabBar.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/CustomTabBar.tsx +108 -71
- package/templates/mern-react-native/mobile/src/components/ui/Sidebar.tsx +81 -56
- package/templates/mern-react-native/mobile/src/components/ui/buttons/Button.tsx +106 -37
- package/templates/mern-react-native/mobile/src/components/ui/buttons/IconButton.tsx +4 -2
- package/templates/mern-react-native/mobile/src/components/ui/data-display/Accordion.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/data-display/Avatar.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/data-display/Badge.tsx +45 -17
- package/templates/mern-react-native/mobile/src/components/ui/data-display/ImageViewer.tsx +19 -9
- package/templates/mern-react-native/mobile/src/components/ui/feedback/CircularProgress.tsx +5 -4
- package/templates/mern-react-native/mobile/src/components/ui/feedback/ProgressBar.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/feedback/Skeleton.tsx +3 -0
- package/templates/mern-react-native/mobile/src/components/ui/feedback/Spinner.tsx +11 -3
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Checkbox.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
- package/templates/mern-react-native/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Input.tsx +54 -14
- package/templates/mern-react-native/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
- package/templates/mern-react-native/mobile/src/components/ui/inputs/OTPInput.tsx +32 -9
- package/templates/mern-react-native/mobile/src/components/ui/inputs/PasswordInput.tsx +2 -1
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Radio.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/inputs/SearchBar.tsx +41 -4
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Slider.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Switch.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/inputs/TextArea.tsx +2 -1
- package/templates/mern-react-native/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
- package/templates/mern-react-native/mobile/src/components/ui/layout/Grid.tsx +52 -10
- package/templates/mern-react-native/mobile/src/components/ui/layout/Section.tsx +7 -8
- package/templates/mern-react-native/mobile/src/components/ui/navigation/Pagination.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/navigation/ProgressStepper.tsx +6 -5
- package/templates/mern-react-native/mobile/src/components/ui/navigation/Tabs.tsx +30 -4
- package/templates/mern-react-native/mobile/src/config/api.ts +12 -1
- package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +12 -0
- package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +1 -1
- package/templates/mern-react-native/mobile/src/db/index.ts +9 -0
- package/templates/mern-react-native/mobile/src/db/schema.ts +14 -0
- package/templates/mern-react-native/mobile/src/hooks/useDebounce.ts +17 -0
- package/templates/mern-react-native/mobile/src/services/notification.service.ts +22 -0
- package/templates/mern-react-native/mobile/src/services/socket.service.ts +36 -0
- package/templates/mern-react-native/mobile/src/store/useAuthStore.ts +122 -27
- package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +178 -0
- package/templates/mern-react-native/mobile/src/types/auth.type.ts +14 -0
- package/templates/mern-react-native/mobile/tsconfig.json +1 -0
- package/templates/mern-ts/client/src/vite-env.d.ts +1 -0
- package/templates/next/next.config.mjs +1 -0
- package/templates/next/package.json +10 -9
- package/templates/next/postcss.config.js +5 -0
- package/templates/next/src/app/layout.js +1 -0
- package/templates/next/src/app/page.js +1 -1
- package/templates/next/src/components/layout/ProtectedRoute.jsx +3 -3
- package/templates/next/src/index.css +1 -0
- package/templates/next/src/store/useUiStore.js +13 -7
- package/templates/next-ts/.env.example +0 -0
- package/templates/next-ts/AGENTS.md +37 -0
- package/templates/next-ts/CLAUDE.md +1 -0
- package/templates/next-ts/_gitignore +0 -24
- package/templates/next-ts/next-env.d.ts +3 -1
- package/templates/next-ts/next.config.mjs +1 -0
- package/templates/next-ts/package.json +13 -12
- package/templates/next-ts/postcss.config.js +5 -0
- package/templates/next-ts/public/logo.png +0 -0
- package/templates/next-ts/src/app/error.tsx +41 -0
- package/templates/next-ts/src/app/layout.tsx +2 -1
- package/templates/next-ts/src/app/not-found.tsx +5 -0
- package/templates/next-ts/src/app/page.tsx +1 -1
- package/templates/next-ts/src/components/layout/ProtectedRoute.tsx +3 -3
- package/templates/next-ts/src/components/ui/Button.tsx +63 -0
- package/templates/next-ts/src/components/ui/Input.tsx +79 -0
- package/templates/next-ts/src/index.css +1 -0
- package/templates/next-ts/src/store/useUiStore.ts +13 -7
- package/templates/next-ts/tsconfig.json +19 -5
- package/templates/production-fullstack/client/index.html +14 -0
- package/templates/production-fullstack/client/package.json +47 -0
- package/templates/production-fullstack/client/public/_redirects +1 -0
- package/templates/production-fullstack/client/public/favicon.ico +0 -0
- package/templates/production-fullstack/client/public/favicon.svg +1 -0
- package/templates/production-fullstack/client/public/logo.png +0 -0
- package/templates/production-fullstack/client/public/manifest.json +24 -0
- package/templates/production-fullstack/client/public/sw.js +56 -0
- package/templates/production-fullstack/client/src/App.tsx +68 -0
- package/templates/production-fullstack/client/src/assets/animations/loading.json +1 -0
- package/templates/production-fullstack/client/src/assets/data/navigation.ts +29 -0
- package/templates/production-fullstack/client/src/assets/images/logo.png +0 -0
- package/templates/production-fullstack/client/src/components/layout/ProtectedRoute.tsx +23 -0
- package/templates/production-fullstack/client/src/components/layout/PublicLayout.tsx +13 -0
- package/templates/production-fullstack/client/src/components/layout/SidebarLayout.tsx +37 -0
- package/templates/production-fullstack/client/src/components/shared/Breadcrumbs.tsx +27 -0
- package/templates/production-fullstack/client/src/components/shared/LottieIcon.tsx +36 -0
- package/templates/production-fullstack/client/src/components/shared/NotFound.tsx +78 -0
- package/templates/production-fullstack/client/src/components/shared/PwaInstallPrompt.tsx +72 -0
- package/templates/production-fullstack/client/src/components/shared/SEO.tsx +67 -0
- package/templates/production-fullstack/client/src/components/shared/Scroll.tsx +25 -0
- package/templates/production-fullstack/client/src/components/shared/Unauthorized.tsx +76 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/CardSkeleton.tsx +23 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/DashboardSkeleton.tsx +19 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/ListSkeleton.tsx +24 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/PageSkeleton.tsx +26 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/ProfileSkeleton.tsx +23 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/Skeleton.tsx +18 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/TableSkeleton.tsx +33 -0
- package/templates/production-fullstack/client/src/components/ui/ConfirmModal.tsx +152 -0
- package/templates/production-fullstack/client/src/components/ui/Header.tsx +230 -0
- package/templates/production-fullstack/client/src/components/ui/Loading.tsx +19 -0
- package/templates/production-fullstack/client/src/components/ui/Modal.tsx +106 -0
- package/templates/production-fullstack/client/src/components/ui/SidebarHeader.tsx +34 -0
- package/templates/production-fullstack/client/src/components/ui/Table.tsx +118 -0
- package/templates/production-fullstack/client/src/components/ui/ToggleSwitch.tsx +38 -0
- package/templates/production-fullstack/client/src/components/ui/sidebar.tsx +152 -0
- package/templates/production-fullstack/client/src/config/index.ts +44 -0
- package/templates/production-fullstack/client/src/constants/navigation.ts +14 -0
- package/templates/production-fullstack/client/src/features/auth/api/authService.ts +19 -0
- package/templates/production-fullstack/client/src/features/auth/pages/Login.tsx +233 -0
- package/templates/production-fullstack/client/src/features/auth/store/useAuthStore.ts +143 -0
- package/templates/production-fullstack/client/src/features/core/pages/Home.tsx +150 -0
- package/templates/production-fullstack/client/src/features/core/pages/Profile.tsx +9 -0
- package/templates/production-fullstack/client/src/hooks/useLenis.ts +28 -0
- package/templates/production-fullstack/client/src/hooks/usePwaInstall.ts +45 -0
- package/templates/production-fullstack/client/src/hooks/useWindowSize.ts +25 -0
- package/templates/production-fullstack/client/src/index.css +158 -0
- package/templates/production-fullstack/client/src/lib/axios.ts +22 -0
- package/templates/production-fullstack/client/src/lib/dayjs.ts +12 -0
- package/templates/production-fullstack/client/src/lib/query-client.ts +11 -0
- package/templates/production-fullstack/client/src/lib/socket.ts +6 -0
- package/templates/production-fullstack/client/src/main.tsx +28 -0
- package/templates/production-fullstack/client/src/store/useUiStore.ts +74 -0
- package/templates/production-fullstack/client/src/utils/formatDate.ts +19 -0
- package/templates/production-fullstack/client/src/utils/getInitials.ts +10 -0
- package/templates/production-fullstack/client/src/vite-env.d.ts +1 -0
- package/templates/production-fullstack/client/tsconfig.json +25 -0
- package/templates/production-fullstack/client/tsconfig.node.json +10 -0
- package/templates/production-fullstack/client/vite.config.ts +8 -0
- package/templates/production-fullstack/mobile/LICENSE +21 -0
- package/templates/production-fullstack/mobile/app.json +84 -0
- package/templates/production-fullstack/mobile/assets/animations/celebrate.json +1 -0
- package/templates/production-fullstack/mobile/assets/animations/infinity.json +1 -0
- package/templates/production-fullstack/mobile/assets/animations/loading.json +1 -0
- package/templates/production-fullstack/mobile/assets/fonts/Inter_Bold.ttf +0 -0
- package/templates/production-fullstack/mobile/assets/fonts/Inter_ExtraBold.ttf +0 -0
- package/templates/production-fullstack/mobile/assets/fonts/Inter_Medium.ttf +0 -0
- package/templates/production-fullstack/mobile/assets/fonts/Inter_Regular.ttf +0 -0
- package/templates/production-fullstack/mobile/assets/fonts/Inter_SemiBold.ttf +0 -0
- package/templates/production-fullstack/mobile/assets/images/logo/logo.png +0 -0
- package/templates/production-fullstack/mobile/assets/images/logo/logo.svg +1 -0
- package/templates/production-fullstack/mobile/assets/images/logo/logoWithBg.png +0 -0
- package/templates/production-fullstack/mobile/babel.config.js +13 -0
- package/templates/production-fullstack/mobile/drizzle.config.ts +8 -0
- package/templates/production-fullstack/mobile/eas.json +24 -0
- package/templates/production-fullstack/mobile/eslint.config.js +10 -0
- package/templates/production-fullstack/mobile/expo-env.d.ts +3 -0
- package/templates/production-fullstack/mobile/metro.config.js +6 -0
- package/templates/production-fullstack/mobile/nativewind-env.d.ts +3 -0
- package/templates/production-fullstack/mobile/package.json +89 -0
- package/templates/production-fullstack/mobile/src/app/(auth)/_layout.tsx +12 -0
- package/templates/production-fullstack/mobile/src/app/(auth)/change-password.tsx +112 -0
- package/templates/production-fullstack/mobile/src/app/(auth)/edit-profile.tsx +160 -0
- package/templates/production-fullstack/mobile/src/app/(auth)/forgot-password.tsx +191 -0
- package/templates/production-fullstack/mobile/src/app/(auth)/login.tsx +253 -0
- package/templates/production-fullstack/mobile/src/app/_layout.tsx +102 -0
- package/templates/production-fullstack/mobile/src/app/global.css +82 -0
- package/templates/production-fullstack/mobile/src/components/common/AnimatedHeader.tsx +328 -0
- package/templates/production-fullstack/mobile/src/components/common/ConfirmationToast.tsx +67 -0
- package/templates/production-fullstack/mobile/src/components/common/Loading.tsx +26 -0
- package/templates/production-fullstack/mobile/src/components/common/LocationLoader.tsx +591 -0
- package/templates/production-fullstack/mobile/src/components/common/ScreenHeader.tsx +68 -0
- package/templates/production-fullstack/mobile/src/components/common/TabHeader.tsx +58 -0
- package/templates/production-fullstack/mobile/src/components/common/Typography.tsx +113 -0
- package/templates/production-fullstack/mobile/src/components/common/Welcome.tsx +55 -0
- package/templates/production-fullstack/mobile/src/components/ui/BottomModal.tsx +159 -0
- package/templates/production-fullstack/mobile/src/components/ui/ClassicTabBar.tsx +129 -0
- package/templates/production-fullstack/mobile/src/components/ui/CustomTabBar.tsx +238 -0
- package/templates/production-fullstack/mobile/src/components/ui/Sidebar.tsx +258 -0
- package/templates/production-fullstack/mobile/src/components/ui/buttons/Button.tsx +180 -0
- package/templates/production-fullstack/mobile/src/components/ui/buttons/ButtonGroup.tsx +32 -0
- package/templates/production-fullstack/mobile/src/components/ui/buttons/FloatingActionButton.tsx +72 -0
- package/templates/production-fullstack/mobile/src/components/ui/buttons/IconButton.tsx +84 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/Accordion.tsx +82 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/Avatar.tsx +47 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/Badge.tsx +104 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/Card.tsx +36 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/Image.tsx +43 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/ImageViewer.tsx +33 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/SwipeableRow.tsx +94 -0
- package/templates/production-fullstack/mobile/src/components/ui/feedback/CircularProgress.tsx +38 -0
- package/templates/production-fullstack/mobile/src/components/ui/feedback/GestureWrapper.tsx +114 -0
- package/templates/production-fullstack/mobile/src/components/ui/feedback/ProgressBar.tsx +51 -0
- package/templates/production-fullstack/mobile/src/components/ui/feedback/Skeleton.tsx +84 -0
- package/templates/production-fullstack/mobile/src/components/ui/feedback/Spinner.tsx +50 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/Checkbox.tsx +54 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/Input.tsx +158 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/OTPInput.tsx +83 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/PasswordInput.tsx +16 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/Radio.tsx +54 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/SearchBar.tsx +53 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/Slider.tsx +49 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/Switch.tsx +75 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/TextArea.tsx +15 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Container.tsx +23 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Divider.tsx +20 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Grid.tsx +62 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/KeyboardAvoidingViewWrapper.tsx +16 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/ScrollContainer.tsx +21 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Section.tsx +51 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Spacer.tsx +6 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Stack.tsx +34 -0
- package/templates/production-fullstack/mobile/src/components/ui/navigation/Pagination.tsx +61 -0
- package/templates/production-fullstack/mobile/src/components/ui/navigation/ProgressStepper.tsx +66 -0
- package/templates/production-fullstack/mobile/src/components/ui/navigation/Tabs.tsx +173 -0
- package/templates/production-fullstack/mobile/src/config/sidebar.config.ts +72 -0
- package/templates/production-fullstack/mobile/src/config/tabNavigation.ts +64 -0
- package/templates/production-fullstack/mobile/src/constants/Colors.ts +71 -0
- package/templates/production-fullstack/mobile/src/db/index.ts +9 -0
- package/templates/production-fullstack/mobile/src/db/schema.ts +14 -0
- package/templates/production-fullstack/mobile/src/features/auth/api/auth.service.ts +35 -0
- package/templates/production-fullstack/mobile/src/features/auth/store/useAuthStore.ts +273 -0
- package/templates/production-fullstack/mobile/src/features/auth/types/auth.type.ts +14 -0
- package/templates/production-fullstack/mobile/src/hooks/useDebounce.ts +17 -0
- package/templates/production-fullstack/mobile/src/lib/axios.ts +27 -0
- package/templates/production-fullstack/mobile/src/lib/query-client.ts +11 -0
- package/templates/production-fullstack/mobile/src/lib/socket.ts +36 -0
- package/templates/production-fullstack/mobile/src/lib/storage.ts +55 -0
- package/templates/production-fullstack/mobile/src/services/permission.service.ts +214 -0
- package/templates/production-fullstack/mobile/src/store/localStore.ts +7 -0
- package/templates/production-fullstack/mobile/src/store/useNotificationStore.ts +53 -0
- package/templates/production-fullstack/mobile/src/types/common.type.ts +6 -0
- package/templates/production-fullstack/mobile/src/types/images.d.ts +24 -0
- package/templates/production-fullstack/mobile/src/utils/toast.ts +26 -0
- package/templates/production-fullstack/mobile/tailwind.config.js +71 -0
- package/templates/production-fullstack/mobile/tsconfig.json +23 -0
- package/templates/production-fullstack/server/.env.example +63 -0
- package/templates/production-fullstack/server/README.md +75 -0
- package/templates/production-fullstack/server/eslint.config.js +13 -0
- package/templates/production-fullstack/server/package.json +58 -0
- package/templates/production-fullstack/server/prisma/schema/core/attachment.prisma +14 -0
- package/templates/production-fullstack/server/prisma/schema/core/notification.prisma +18 -0
- package/templates/production-fullstack/server/prisma/schema/core/permission.prisma +26 -0
- package/templates/production-fullstack/server/prisma/schema/core/role.prisma +25 -0
- package/templates/production-fullstack/server/prisma/schema/core/user.prisma +56 -0
- package/templates/production-fullstack/server/prisma/schema/enums/common.prisma +13 -0
- package/templates/production-fullstack/server/prisma/schema/enums/notification.prisma +6 -0
- package/templates/production-fullstack/server/prisma/schema/enums/user.prisma +11 -0
- package/templates/production-fullstack/server/prisma/schema/schema.prisma +7 -0
- package/templates/production-fullstack/server/prisma.config.ts +14 -0
- package/templates/production-fullstack/server/src/app.ts +36 -0
- package/templates/production-fullstack/server/src/common/middlewares/auth.middleware.ts +45 -0
- package/templates/production-fullstack/server/src/common/middlewares/errorHandler.ts +16 -0
- package/templates/production-fullstack/server/src/common/middlewares/multer.ts +20 -0
- package/templates/production-fullstack/server/src/common/middlewares/rateLimiter.ts +53 -0
- package/templates/production-fullstack/server/src/common/services/email.service.ts +28 -0
- package/templates/production-fullstack/server/src/common/services/push.service.ts +65 -0
- package/templates/production-fullstack/server/src/common/services/storage.service.ts +9 -0
- package/templates/production-fullstack/server/src/common/types/express.d.ts +10 -0
- package/templates/production-fullstack/server/src/common/utils/cloudinaryUpload.ts +44 -0
- package/templates/production-fullstack/server/src/common/utils/emailOtp.ts +30 -0
- package/templates/production-fullstack/server/src/common/utils/genOtpToken.ts +22 -0
- package/templates/production-fullstack/server/src/common/utils/generateVapidKeys.ts +12 -0
- package/templates/production-fullstack/server/src/common/utils/jwt.ts +27 -0
- package/templates/production-fullstack/server/src/common/utils/pushHelper.ts +22 -0
- package/templates/production-fullstack/server/src/config/env.ts +2 -0
- package/templates/production-fullstack/server/src/config/logger.ts +58 -0
- package/templates/production-fullstack/server/src/config/prisma.ts +26 -0
- package/templates/production-fullstack/server/src/features/attachments/upload.controller.ts +22 -0
- package/templates/production-fullstack/server/src/features/attachments/upload.routes.ts +11 -0
- package/templates/production-fullstack/server/src/features/auth/auth.controller.ts +124 -0
- package/templates/production-fullstack/server/src/features/auth/auth.routes.ts +22 -0
- package/templates/production-fullstack/server/src/features/auth/auth.service.ts +155 -0
- package/templates/production-fullstack/server/src/features/notifications/push.controller.ts +55 -0
- package/templates/production-fullstack/server/src/features/notifications/push.routes.ts +11 -0
- package/templates/production-fullstack/server/src/index.ts +17 -0
- package/templates/production-fullstack/server/src/routes/index.ts +12 -0
- package/templates/production-fullstack/server/src/sockets/auth.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/connection.ts +31 -0
- package/templates/production-fullstack/server/src/sockets/emitters/assignment.emitter.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/emitters/attendance.emitter.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/emitters/exam.emitter.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/emitters/notification.emitter.ts +17 -0
- package/templates/production-fullstack/server/src/sockets/events.ts +27 -0
- package/templates/production-fullstack/server/src/sockets/handlers/attendance.handler.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/handlers/chat.handler.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/handlers/index.ts +15 -0
- package/templates/production-fullstack/server/src/sockets/handlers/lms.handler.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/handlers/notification.handler.ts +39 -0
- package/templates/production-fullstack/server/src/sockets/index.ts +39 -0
- package/templates/production-fullstack/server/src/sockets/middleware.ts +54 -0
- package/templates/production-fullstack/server/src/sockets/rooms.ts +11 -0
- package/templates/production-fullstack/server/src/sockets/types.ts +32 -0
- package/templates/production-fullstack/server/tsconfig.json +14 -0
- package/templates/react-native/babel.config.js +1 -0
- package/templates/react-native/drizzle.config.ts +8 -0
- package/templates/react-native/package.json +8 -1
- package/templates/react-native/src/app/(auth)/_layout.tsx +12 -0
- package/templates/react-native/src/app/(auth)/change-password.tsx +112 -0
- package/templates/react-native/src/app/(auth)/edit-profile.tsx +156 -0
- package/templates/react-native/src/app/(auth)/forgot-password.tsx +191 -0
- package/templates/react-native/src/app/{login.tsx → (auth)/login.tsx} +69 -21
- package/templates/react-native/src/app/(tabs)/dashboard.tsx +2 -2
- package/templates/react-native/src/app/(tabs)/index.tsx +18 -17
- package/templates/react-native/src/app/(tabs)/profile.tsx +23 -60
- package/templates/react-native/src/app/(tabs)/search.tsx +2 -2
- package/templates/react-native/src/app/_layout.tsx +22 -4
- package/templates/react-native/src/components/common/AnimatedHeader.tsx +328 -0
- package/templates/react-native/src/components/common/LocationLoader.tsx +591 -0
- package/templates/react-native/src/components/common/ScreenHeader.tsx +41 -17
- package/templates/react-native/src/components/common/TabHeader.tsx +58 -0
- package/templates/react-native/src/components/common/Typography.tsx +49 -30
- package/templates/react-native/src/components/common/Welcome.tsx +2 -2
- package/templates/react-native/src/components/ui/BottomModal.tsx +31 -20
- package/templates/react-native/src/components/ui/ClassicTabBar.tsx +4 -3
- package/templates/react-native/src/components/ui/CustomTabBar.tsx +108 -71
- package/templates/react-native/src/components/ui/Sidebar.tsx +81 -56
- package/templates/react-native/src/components/ui/buttons/Button.tsx +106 -37
- package/templates/react-native/src/components/ui/buttons/IconButton.tsx +4 -2
- package/templates/react-native/src/components/ui/data-display/Accordion.tsx +3 -2
- package/templates/react-native/src/components/ui/data-display/Avatar.tsx +4 -3
- package/templates/react-native/src/components/ui/data-display/Badge.tsx +45 -17
- package/templates/react-native/src/components/ui/data-display/ImageViewer.tsx +19 -9
- package/templates/react-native/src/components/ui/feedback/CircularProgress.tsx +5 -4
- package/templates/react-native/src/components/ui/feedback/ProgressBar.tsx +4 -3
- package/templates/react-native/src/components/ui/feedback/Skeleton.tsx +3 -0
- package/templates/react-native/src/components/ui/feedback/Spinner.tsx +11 -3
- package/templates/react-native/src/components/ui/inputs/Checkbox.tsx +3 -2
- package/templates/react-native/src/components/ui/inputs/DatePicker.tsx +186 -0
- package/templates/react-native/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
- package/templates/react-native/src/components/ui/inputs/Input.tsx +54 -14
- package/templates/react-native/src/components/ui/inputs/LocationPicker.tsx +211 -0
- package/templates/react-native/src/components/ui/inputs/OTPInput.tsx +32 -9
- package/templates/react-native/src/components/ui/inputs/PasswordInput.tsx +2 -1
- package/templates/react-native/src/components/ui/inputs/Radio.tsx +3 -2
- package/templates/react-native/src/components/ui/inputs/SearchBar.tsx +41 -4
- package/templates/react-native/src/components/ui/inputs/Slider.tsx +4 -3
- package/templates/react-native/src/components/ui/inputs/Switch.tsx +3 -2
- package/templates/react-native/src/components/ui/inputs/TextArea.tsx +2 -1
- package/templates/react-native/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
- package/templates/react-native/src/components/ui/layout/Grid.tsx +52 -10
- package/templates/react-native/src/components/ui/layout/Section.tsx +7 -8
- package/templates/react-native/src/components/ui/navigation/Pagination.tsx +4 -3
- package/templates/react-native/src/components/ui/navigation/ProgressStepper.tsx +6 -5
- package/templates/react-native/src/components/ui/navigation/Tabs.tsx +30 -4
- package/templates/react-native/src/config/api.ts +12 -1
- package/templates/react-native/src/config/sidebar.config.ts +12 -0
- package/templates/react-native/src/config/tabNavigation.ts +1 -1
- package/templates/react-native/src/db/index.ts +9 -0
- package/templates/react-native/src/db/schema.ts +14 -0
- package/templates/react-native/src/hooks/useDebounce.ts +17 -0
- package/templates/react-native/src/services/notification.service.ts +22 -0
- package/templates/react-native/src/services/socket.service.ts +36 -0
- package/templates/react-native/src/store/useAuthStore.ts +122 -27
- package/templates/react-native/src/store/useNotificationStore.ts +178 -0
- package/templates/react-native/src/types/auth.type.ts +14 -0
- package/templates/react-native/tsconfig.json +1 -0
- package/templates/react-ts/src/vite-env.d.ts +1 -0
- package/templates/mern-react-native/mobile/.env.example +0 -2
- package/templates/mern-react-native/mobile/.vscode/extensions.json +0 -1
- package/templates/mern-react-native/mobile/.vscode/settings.json +0 -7
- package/templates/mern-react-native/mobile/README.md +0 -159
- package/templates/mern-react-native/mobile/docs/README.md +0 -17
- package/templates/mern-react-native/mobile/docs/components/buttons.md +0 -40
- package/templates/mern-react-native/mobile/docs/components/data-display.md +0 -44
- package/templates/mern-react-native/mobile/docs/components/feedback.md +0 -36
- package/templates/mern-react-native/mobile/docs/components/gestures.md +0 -60
- package/templates/mern-react-native/mobile/docs/components/inputs.md +0 -41
- package/templates/mern-react-native/mobile/docs/components/layout.md +0 -34
- package/templates/mern-react-native/mobile/docs/components/navigation.md +0 -31
- package/templates/mern-react-native/mobile/docs/features.md +0 -85
- package/templates/mern-react-native/mobile/docs/getting-started.md +0 -47
- package/templates/mern-react-native/mobile/src/components/common/HomeHeader.tsx +0 -43
- package/templates/mern-react-native/mobile/src/components/settings/SettingToggle.tsx +0 -26
- package/templates/mern-react-native/mobile/src/components/settings/SettingsOptions.tsx +0 -47
- package/templates/react-native/.env.example +0 -2
- package/templates/react-native/.vscode/extensions.json +0 -1
- package/templates/react-native/.vscode/settings.json +0 -7
- package/templates/react-native/README.md +0 -159
- package/templates/react-native/docs/README.md +0 -17
- package/templates/react-native/docs/components/buttons.md +0 -40
- package/templates/react-native/docs/components/data-display.md +0 -44
- package/templates/react-native/docs/components/feedback.md +0 -36
- package/templates/react-native/docs/components/gestures.md +0 -60
- package/templates/react-native/docs/components/inputs.md +0 -41
- package/templates/react-native/docs/components/layout.md +0 -34
- package/templates/react-native/docs/components/navigation.md +0 -31
- package/templates/react-native/docs/features.md +0 -85
- package/templates/react-native/docs/getting-started.md +0 -47
- package/templates/react-native/src/components/common/HomeHeader.tsx +0 -43
- package/templates/react-native/src/components/settings/SettingToggle.tsx +0 -26
- package/templates/react-native/src/components/settings/SettingsOptions.tsx +0 -47
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import {
|
|
3
|
+
signup,
|
|
4
|
+
login,
|
|
5
|
+
getMe,
|
|
6
|
+
logout,
|
|
7
|
+
updateMe,
|
|
8
|
+
} from "./auth.controller.js";
|
|
9
|
+
import { protect } from "../../common/middlewares/auth.middleware.js";
|
|
10
|
+
import { apiLimiter } from "../../common/middlewares/rateLimiter.js";
|
|
11
|
+
|
|
12
|
+
const router = express.Router();
|
|
13
|
+
|
|
14
|
+
router.use(apiLimiter);
|
|
15
|
+
|
|
16
|
+
router.post("/signup", signup);
|
|
17
|
+
router.post("/login", login);
|
|
18
|
+
router.post("/logout", logout);
|
|
19
|
+
router.get("/me", protect, getMe);
|
|
20
|
+
router.put("/me", protect, updateMe);
|
|
21
|
+
|
|
22
|
+
export default router;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import bcrypt from "bcrypt";
|
|
2
|
+
import { prisma } from "../../config/prisma.js";
|
|
3
|
+
import { sendOtpEmail } from "../../common/utils/emailOtp.js";
|
|
4
|
+
import { uploadSingleToCloudinary, deleteFromCloudinary } from "../../common/utils/cloudinaryUpload.js";
|
|
5
|
+
import logger from "../../config/logger.js";
|
|
6
|
+
export const signup = async ({ name, email, phone, password }: any) => {
|
|
7
|
+
const existingUser = await prisma.user.findUnique({
|
|
8
|
+
where: { email },
|
|
9
|
+
});
|
|
10
|
+
if (existingUser) {
|
|
11
|
+
const err: any = new Error("User already exists");
|
|
12
|
+
err.status = 409;
|
|
13
|
+
throw err;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const salt = await bcrypt.genSalt(10);
|
|
17
|
+
const hashedPassword = await bcrypt.hash(password, salt);
|
|
18
|
+
const photoURL = `https://placehold.co/600x400?text=${name.charAt(0).toUpperCase()}`;
|
|
19
|
+
|
|
20
|
+
const newUser = await prisma.user.create({
|
|
21
|
+
data: {
|
|
22
|
+
name,
|
|
23
|
+
email: email.toLowerCase(),
|
|
24
|
+
phone,
|
|
25
|
+
password: hashedPassword,
|
|
26
|
+
avatarUrl: photoURL,
|
|
27
|
+
avatarPublicId: "",
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return newUser;
|
|
32
|
+
}
|
|
33
|
+
export const login = async ({ email, password }: any) => {
|
|
34
|
+
const user = await prisma.user.findUnique({
|
|
35
|
+
where: { email },
|
|
36
|
+
});
|
|
37
|
+
if (!user) {
|
|
38
|
+
const err: any = new Error("User not found");
|
|
39
|
+
err.status = 404;
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const isValid = await bcrypt.compare(password, user.password);
|
|
44
|
+
if (!isValid) {
|
|
45
|
+
const err: any = new Error("Invalid Credentials");
|
|
46
|
+
err.status = 401;
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return user;
|
|
51
|
+
}
|
|
52
|
+
export const generateOtp = async (email: string) => {
|
|
53
|
+
const user = await prisma.user.findUnique({
|
|
54
|
+
where: { email },
|
|
55
|
+
});
|
|
56
|
+
if (!user) {
|
|
57
|
+
const err: any = new Error("User not found");
|
|
58
|
+
err.status = 404;
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const existingOtp = await prisma.otp.findUnique({
|
|
63
|
+
where: { email },
|
|
64
|
+
});
|
|
65
|
+
if (existingOtp) {
|
|
66
|
+
await prisma.otp.delete({
|
|
67
|
+
where: { email },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const otp = Math.floor(100000 + Math.random() * 900000).toString();
|
|
72
|
+
const hashedOtp = await bcrypt.hash(otp, 10);
|
|
73
|
+
|
|
74
|
+
await prisma.otp.create({
|
|
75
|
+
data: {
|
|
76
|
+
email,
|
|
77
|
+
otp: hashedOtp,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await sendOtpEmail(email, otp);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
export const verifyOtp = async ({ email, otp }: any) => {
|
|
85
|
+
const user = await prisma.user.findUnique({
|
|
86
|
+
where: { email },
|
|
87
|
+
});
|
|
88
|
+
if (!user) {
|
|
89
|
+
const err: any = new Error("User not found");
|
|
90
|
+
err.status = 404;
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const existingOtp = await prisma.otp.findUnique({
|
|
95
|
+
where: { email },
|
|
96
|
+
});
|
|
97
|
+
if (!existingOtp) {
|
|
98
|
+
const err: any = new Error("OTP match failed");
|
|
99
|
+
err.status = 400;
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const isMatched = await bcrypt.compare(otp, existingOtp.otp);
|
|
104
|
+
if (!isMatched) {
|
|
105
|
+
const err: any = new Error("OTP match failed");
|
|
106
|
+
err.status = 400;
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return user;
|
|
111
|
+
}
|
|
112
|
+
export const resetPassword = async (user: any, newPassword: any) => {
|
|
113
|
+
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
|
114
|
+
await prisma.user.update({
|
|
115
|
+
where: { id: user.id },
|
|
116
|
+
data: { password: hashedPassword },
|
|
117
|
+
});
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
export const updateProfile = async (userId: any, { name, phone, file }: any) => {
|
|
121
|
+
const user = await prisma.user.findUnique({
|
|
122
|
+
where: { id: userId },
|
|
123
|
+
});
|
|
124
|
+
if (!user) {
|
|
125
|
+
const err: any = new Error("User not found");
|
|
126
|
+
err.status = 404;
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const updateData: any = {};
|
|
131
|
+
if (name) updateData.name = name;
|
|
132
|
+
if (phone) updateData.phone = phone;
|
|
133
|
+
|
|
134
|
+
if (file) {
|
|
135
|
+
const uploadResult = await uploadSingleToCloudinary(file, {
|
|
136
|
+
folder: "codelab/avatars",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (user.avatarPublicId) {
|
|
140
|
+
await deleteFromCloudinary(user.avatarPublicId).catch((err) =>
|
|
141
|
+
logger.error("Old avatar delete error", err)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
updateData.avatarUrl = uploadResult.url;
|
|
146
|
+
updateData.avatarPublicId = uploadResult.publicId;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const updatedUser = await prisma.user.update({
|
|
150
|
+
where: { id: userId },
|
|
151
|
+
data: updateData,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return updatedUser;
|
|
155
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { prisma } from "../../config/prisma.js";
|
|
3
|
+
import logger from "../../config/logger.js";
|
|
4
|
+
|
|
5
|
+
export const subscribe = async (req: Request, res: Response, next: NextFunction) => {
|
|
6
|
+
try {
|
|
7
|
+
const subscription = req.body;
|
|
8
|
+
const userId = req.user?._id || req.user?.id; // supports both mongoose and prisma IDs
|
|
9
|
+
|
|
10
|
+
if (!subscription || !subscription.endpoint) {
|
|
11
|
+
return next({ status: 400, message: "Invalid subscription payload" });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const existingSub = await prisma.pushSubscription.findUnique({ where: { endpoint: subscription.endpoint } });
|
|
15
|
+
|
|
16
|
+
if (existingSub) {
|
|
17
|
+
if (userId) {
|
|
18
|
+
await prisma.pushSubscription.update({ where: { endpoint: subscription.endpoint }, data: { userId } })
|
|
19
|
+
}
|
|
20
|
+
return res.status(200).json({ success: true, message: "Already subscribed." });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
await prisma.pushSubscription.create({
|
|
25
|
+
data: {
|
|
26
|
+
endpoint: subscription.endpoint,
|
|
27
|
+
p256dh: subscription.keys.p256dh,
|
|
28
|
+
auth: subscription.keys.auth,
|
|
29
|
+
userId: userId || null
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
res.status(201).json({ success: true, message: "Subscription created." });
|
|
35
|
+
} catch (error) {
|
|
36
|
+
logger.error("Error in push subscribe controller", error);
|
|
37
|
+
next(error);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const unsubscribe = async (req: Request, res: Response, next: NextFunction) => {
|
|
42
|
+
try {
|
|
43
|
+
const { endpoint } = req.body;
|
|
44
|
+
if (!endpoint) {
|
|
45
|
+
return next({ status: 400, message: "Endpoint required" });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await prisma.pushSubscription.delete({ where: { endpoint } }).catch(() => {})
|
|
49
|
+
|
|
50
|
+
res.status(200).json({ success: true, message: "Unsubscribed." });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
logger.error("Error in push unsubscribe controller", error);
|
|
53
|
+
next(error);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { subscribe, unsubscribe } from "./push.controller.js";
|
|
3
|
+
import { protect } from "../../common/middlewares/auth.middleware.js";
|
|
4
|
+
|
|
5
|
+
const router = express.Router();
|
|
6
|
+
|
|
7
|
+
// You can remove protect if you want anonymous users to be able to subscribe
|
|
8
|
+
router.post("/subscribe", protect, subscribe);
|
|
9
|
+
router.post("/unsubscribe", unsubscribe);
|
|
10
|
+
|
|
11
|
+
export default router;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import http from "http";
|
|
3
|
+
import app from "./app.js";
|
|
4
|
+
import connectDB from "./config/prisma.js";
|
|
5
|
+
import logger from "./config/logger.js";
|
|
6
|
+
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
const PORT = process.env.PORT || 5000;
|
|
10
|
+
|
|
11
|
+
connectDB();
|
|
12
|
+
|
|
13
|
+
const server = http.createServer(app);
|
|
14
|
+
|
|
15
|
+
server.listen(PORT, () => {
|
|
16
|
+
logger.success(`Server is running on port ${PORT}`);
|
|
17
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import authRoutes from "../features/auth/auth.routes.js";
|
|
3
|
+
import uploadRoutes from "../features/attachments/upload.routes.js";
|
|
4
|
+
import pushRoutes from "../features/notifications/push.routes.js";
|
|
5
|
+
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
|
|
8
|
+
router.use("/auth", authRoutes);
|
|
9
|
+
router.use("/upload", uploadRoutes);
|
|
10
|
+
router.use("/push", pushRoutes);
|
|
11
|
+
|
|
12
|
+
export default router;
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Socket, Server } from "socket.io";
|
|
2
|
+
import logger from "../config/logger.js";
|
|
3
|
+
import { registerHandlers } from "./handlers/index.js";
|
|
4
|
+
import { getUserRoom, getInstitutionRoom } from "./rooms.js";
|
|
5
|
+
import { SocketEvents } from "./events.js";
|
|
6
|
+
import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "./types.js";
|
|
7
|
+
|
|
8
|
+
type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
|
|
9
|
+
type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
|
|
10
|
+
|
|
11
|
+
export const onConnection = (io: AppServer, socket: AppSocket) => {
|
|
12
|
+
const user = socket.data.user;
|
|
13
|
+
|
|
14
|
+
logger.info(`Socket connected: ${socket.id} (User: ${user.id})`);
|
|
15
|
+
|
|
16
|
+
// Auto-join user-specific room
|
|
17
|
+
socket.join(getUserRoom(user.id));
|
|
18
|
+
|
|
19
|
+
// Auto-join institution room if applicable
|
|
20
|
+
if (user.institutionId) {
|
|
21
|
+
socket.join(getInstitutionRoom(user.institutionId));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Register all event handlers
|
|
25
|
+
registerHandlers(io, socket);
|
|
26
|
+
|
|
27
|
+
// Handle disconnect
|
|
28
|
+
socket.on(SocketEvents.DISCONNECT, (reason) => {
|
|
29
|
+
logger.info(`Socket disconnected: ${socket.id} (User: ${user.id}, Reason: ${reason})`);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getIO } from "../index.js";
|
|
2
|
+
import { getUserRoom } from "../rooms.js";
|
|
3
|
+
import { SocketEvents } from "../events.js";
|
|
4
|
+
import logger from "../../config/logger.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Emits a new notification to a specific user.
|
|
8
|
+
* This is meant to be called from business logic services.
|
|
9
|
+
*/
|
|
10
|
+
export const emitNewNotification = (userId: string, payload: unknown) => {
|
|
11
|
+
try {
|
|
12
|
+
const io = getIO();
|
|
13
|
+
io.to(getUserRoom(userId)).emit(SocketEvents.NOTIFICATION_NEW, payload);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
logger.error(`Failed to emit notification to user ${userId}`, error);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const SocketEvents = {
|
|
2
|
+
// Connection Events
|
|
3
|
+
DISCONNECT: "disconnect",
|
|
4
|
+
CONNECT_ERROR: "connect_error",
|
|
5
|
+
|
|
6
|
+
// Room Events
|
|
7
|
+
SUBSCRIBE: "room:subscribe",
|
|
8
|
+
UNSUBSCRIBE: "room:unsubscribe",
|
|
9
|
+
|
|
10
|
+
// Notification Events
|
|
11
|
+
NOTIFICATION_NEW: "notification:new",
|
|
12
|
+
NOTIFICATION_MARK_AS_READ: "notification:markAsRead",
|
|
13
|
+
|
|
14
|
+
// Chat Events
|
|
15
|
+
CHAT_NEW_MESSAGE: "chat:newMessage",
|
|
16
|
+
CHAT_TYPING: "chat:typing",
|
|
17
|
+
CHAT_STOP_TYPING: "chat:stopTyping",
|
|
18
|
+
|
|
19
|
+
// LMS Events
|
|
20
|
+
LMS_ASSIGNMENT_CREATED: "lms:assignmentCreated",
|
|
21
|
+
LMS_GRADE_PUBLISHED: "lms:gradePublished",
|
|
22
|
+
|
|
23
|
+
// Attendance Events
|
|
24
|
+
ATTENDANCE_MARKED: "attendance:marked",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
export type SocketEvent = (typeof SocketEvents)[keyof typeof SocketEvents];
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Socket, Server } from "socket.io";
|
|
2
|
+
import { registerNotificationHandlers } from "./notification.handler.js";
|
|
3
|
+
import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "../types.js";
|
|
4
|
+
|
|
5
|
+
type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
|
|
6
|
+
type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
|
|
7
|
+
|
|
8
|
+
export const registerHandlers = (io: AppServer, socket: AppSocket) => {
|
|
9
|
+
// Register domain specific handlers
|
|
10
|
+
registerNotificationHandlers(io, socket);
|
|
11
|
+
|
|
12
|
+
// Future handlers:
|
|
13
|
+
// registerChatHandlers(io, socket);
|
|
14
|
+
// registerLmsHandlers(io, socket);
|
|
15
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Socket, Server } from "socket.io";
|
|
2
|
+
import logger from "../../config/logger.js";
|
|
3
|
+
import { SocketEvents } from "../events.js";
|
|
4
|
+
import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "../types.js";
|
|
5
|
+
|
|
6
|
+
type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
|
|
7
|
+
type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
|
|
8
|
+
|
|
9
|
+
export const registerNotificationHandlers = (_io: AppServer, socket: AppSocket) => {
|
|
10
|
+
socket.on(SocketEvents.NOTIFICATION_MARK_AS_READ, (payload, callback) => {
|
|
11
|
+
try {
|
|
12
|
+
const { notificationId } = payload;
|
|
13
|
+
logger.info(`Notification marked as read: ${notificationId} by user ${socket.data.user.id}`);
|
|
14
|
+
|
|
15
|
+
if (typeof callback === "function") {
|
|
16
|
+
callback({ success: true, notificationId });
|
|
17
|
+
}
|
|
18
|
+
} catch (error) {
|
|
19
|
+
logger.error("Error in notification:markAsRead handler", error);
|
|
20
|
+
if (typeof callback === "function") {
|
|
21
|
+
callback({ success: false, error: "Failed to mark as read" });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
socket.on(SocketEvents.SUBSCRIBE, (payload) => {
|
|
27
|
+
if (payload?.topic) {
|
|
28
|
+
socket.join(`topic:${payload.topic}`);
|
|
29
|
+
logger.info(`User ${socket.data.user.id} subscribed to topic: ${payload.topic}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
socket.on(SocketEvents.UNSUBSCRIBE, (payload) => {
|
|
34
|
+
if (payload?.topic) {
|
|
35
|
+
socket.leave(`topic:${payload.topic}`);
|
|
36
|
+
logger.info(`User ${socket.data.user.id} unsubscribed from topic: ${payload.topic}`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Server } from "socket.io";
|
|
2
|
+
import { socketAuthMiddleware } from "./middleware.js";
|
|
3
|
+
import { onConnection } from "./connection.js";
|
|
4
|
+
import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "./types.js";
|
|
5
|
+
|
|
6
|
+
type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
|
|
7
|
+
|
|
8
|
+
let io: AppServer;
|
|
9
|
+
|
|
10
|
+
const initializeSocket = (server: import("http").Server) => {
|
|
11
|
+
io = new Server(server, {
|
|
12
|
+
cors: {
|
|
13
|
+
origin: ["http://localhost:5173"],
|
|
14
|
+
credentials: true,
|
|
15
|
+
},
|
|
16
|
+
// To support Redis in the future, you would add the adapter here:
|
|
17
|
+
// adapter: createAdapter(pubClient, subClient)
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Apply Authentication Middleware
|
|
21
|
+
io.use(socketAuthMiddleware);
|
|
22
|
+
|
|
23
|
+
// Handle Connections
|
|
24
|
+
io.on("connection", (socket) => {
|
|
25
|
+
onConnection(io, socket);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return io;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const getIO = (): AppServer => {
|
|
32
|
+
if (!io) {
|
|
33
|
+
throw new Error("Socket.IO has not been initialized yet");
|
|
34
|
+
}
|
|
35
|
+
return io;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export { io };
|
|
39
|
+
export default initializeSocket;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Socket } from "socket.io";
|
|
2
|
+
import { verifyToken } from "../common/utils/jwt.js";
|
|
3
|
+
import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "./types.js";
|
|
4
|
+
import { prisma } from "../config/prisma.js";
|
|
5
|
+
|
|
6
|
+
type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
|
|
7
|
+
|
|
8
|
+
const parseCookie = (cookieString: string, name: string) => {
|
|
9
|
+
const match = cookieString.match(new RegExp("(^| )" + name + "=([^;]+)"));
|
|
10
|
+
if (match) return match[2];
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const socketAuthMiddleware = async (socket: AppSocket, next: (err?: Error) => void) => {
|
|
15
|
+
try {
|
|
16
|
+
let token = socket.handshake.auth.token;
|
|
17
|
+
|
|
18
|
+
// Fallback to cookie if token is not explicitly passed in handshake
|
|
19
|
+
if (!token && socket.handshake.headers.cookie) {
|
|
20
|
+
token = parseCookie(socket.handshake.headers.cookie, "token");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!token) {
|
|
24
|
+
return next(new Error("Authentication error: No token provided"));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
const decoded: any = verifyToken(token);
|
|
29
|
+
|
|
30
|
+
if (!decoded || !decoded.id) {
|
|
31
|
+
return next(new Error("Authentication error: Invalid token"));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const user = await prisma.user.findUnique({
|
|
35
|
+
where: { id: decoded.id },
|
|
36
|
+
select: { id: true, roles: true, institutionId: true },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!user) {
|
|
40
|
+
return next(new Error("Authentication error: User not found"));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Attach user to socket data
|
|
44
|
+
socket.data.user = {
|
|
45
|
+
id: user.id,
|
|
46
|
+
roles: user.roles,
|
|
47
|
+
institutionId: user.institutionId,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
next();
|
|
51
|
+
} catch {
|
|
52
|
+
next(new Error("Authentication error: Token verification failed"));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Room generator functions.
|
|
3
|
+
* Using a consistent namespace pattern (e.g. user:<id>) ensures
|
|
4
|
+
* that rooms don't overlap and can be easily managed or targeted.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const getUserRoom = (userId: string) => `user:${userId}`;
|
|
8
|
+
export const getInstitutionRoom = (institutionId: string) => `institution:${institutionId}`;
|
|
9
|
+
export const getClassRoom = (classId: string) => `class:${classId}`;
|
|
10
|
+
export const getSectionRoom = (sectionId: string) => `section:${sectionId}`;
|
|
11
|
+
export const getTopicRoom = (topic: string) => `topic:${topic}`;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SocketEvents } from "./events.js";
|
|
2
|
+
|
|
3
|
+
// Basic user info injected into socket.data by the auth middleware
|
|
4
|
+
export interface SocketUser {
|
|
5
|
+
id: string;
|
|
6
|
+
roles?: { role: { name: string } }[] | unknown;
|
|
7
|
+
institutionId?: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SocketData {
|
|
11
|
+
user: SocketUser;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ServerToClientEvents {
|
|
15
|
+
[SocketEvents.NOTIFICATION_NEW]: (payload: unknown) => void;
|
|
16
|
+
[SocketEvents.CHAT_NEW_MESSAGE]: (payload: unknown) => void;
|
|
17
|
+
[SocketEvents.LMS_ASSIGNMENT_CREATED]: (payload: unknown) => void;
|
|
18
|
+
[SocketEvents.ATTENDANCE_MARKED]: (payload: unknown) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ClientToServerEvents {
|
|
22
|
+
[SocketEvents.SUBSCRIBE]: (payload: { topic: string }) => void;
|
|
23
|
+
[SocketEvents.UNSUBSCRIBE]: (payload: { topic: string }) => void;
|
|
24
|
+
[SocketEvents.NOTIFICATION_MARK_AS_READ]: (
|
|
25
|
+
payload: { notificationId: string },
|
|
26
|
+
callback: (response: { success: boolean; notificationId?: string; error?: string }) => void
|
|
27
|
+
) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface InterServerEvents {
|
|
31
|
+
ping: () => void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"forceConsistentCasingInFileNames": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src"
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "shivAi",
|
|
3
3
|
"main": "expo-router/entry",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"dependencies": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"@react-navigation/bottom-tabs": "^7.18.10",
|
|
12
12
|
"axios": "^1.18.1",
|
|
13
13
|
"babel-preset-expo": "^57.0.3",
|
|
14
|
+
"drizzle-orm": "^0.45.2",
|
|
14
15
|
"expo": "~57.0.7",
|
|
15
16
|
"expo-blur": "~57.0.2",
|
|
16
17
|
"expo-constants": "~57.0.6",
|
|
@@ -18,10 +19,13 @@
|
|
|
18
19
|
"expo-font": "~57.0.1",
|
|
19
20
|
"expo-glass-effect": "~57.0.1",
|
|
20
21
|
"expo-image": "~57.0.1",
|
|
22
|
+
"expo-image-picker": "^57.0.6",
|
|
23
|
+
"expo-linear-gradient": "^57.0.1",
|
|
21
24
|
"expo-linking": "~57.0.3",
|
|
22
25
|
"expo-notifications": "~57.0.6",
|
|
23
26
|
"expo-router": "~57.0.7",
|
|
24
27
|
"expo-splash-screen": "~57.0.4",
|
|
28
|
+
"expo-sqlite": "^57.0.1",
|
|
25
29
|
"expo-status-bar": "~57.0.1",
|
|
26
30
|
"expo-symbols": "~57.0.1",
|
|
27
31
|
"expo-system-ui": "~57.0.1",
|
|
@@ -45,11 +49,14 @@
|
|
|
45
49
|
"react-native-toast-message": "^2.4.0",
|
|
46
50
|
"react-native-web": "~0.21.0",
|
|
47
51
|
"react-native-worklets": "0.10.0",
|
|
52
|
+
"socket.io-client": "^4.8.3",
|
|
48
53
|
"tailwindcss": "^3.4.19",
|
|
49
54
|
"zustand": "^5.0.14"
|
|
50
55
|
},
|
|
51
56
|
"devDependencies": {
|
|
52
57
|
"@types/react": "~19.2.2",
|
|
58
|
+
"babel-plugin-inline-import": "^3.0.0",
|
|
59
|
+
"drizzle-kit": "^0.31.10",
|
|
53
60
|
"eslint": "^9.0.0",
|
|
54
61
|
"eslint-config-expo": "~57.0.0",
|
|
55
62
|
"typescript": "~6.0.3"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Stack } from 'expo-router';
|
|
2
|
+
|
|
3
|
+
export default function AuthLayout() {
|
|
4
|
+
return (
|
|
5
|
+
<Stack screenOptions={{ headerShown: false }}>
|
|
6
|
+
<Stack.Screen name="login" />
|
|
7
|
+
<Stack.Screen name="forgot-password" />
|
|
8
|
+
<Stack.Screen name="edit-profile" options={{ presentation: 'modal' }} />
|
|
9
|
+
<Stack.Screen name="change-password" options={{ presentation: 'modal' }} />
|
|
10
|
+
</Stack>
|
|
11
|
+
);
|
|
12
|
+
}
|