@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.
- 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/mobile/_gitignore +43 -0
- package/templates/mern-react-native/mobile/package.json +2 -1
- package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +11 -15
- package/templates/mern-react-native/mobile/src/app/(tabs)/dashboard.tsx +2 -2
- package/templates/mern-react-native/mobile/src/app/(tabs)/search.tsx +2 -2
- package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +0 -60
- package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +6 -36
- package/templates/mern-react-native/mobile/src/services/notification.service.ts +22 -0
- package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +158 -33
- 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/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/package.json +2 -1
- package/templates/react-native/src/app/(auth)/edit-profile.tsx +11 -15
- package/templates/react-native/src/app/(tabs)/dashboard.tsx +2 -2
- package/templates/react-native/src/app/(tabs)/search.tsx +2 -2
- package/templates/react-native/src/config/sidebar.config.ts +0 -60
- package/templates/react-native/src/config/tabNavigation.ts +6 -36
- package/templates/react-native/src/services/notification.service.ts +22 -0
- package/templates/react-native/src/store/useNotificationStore.ts +158 -33
- /package/templates/{react-native → production-fullstack/mobile}/expo-env.d.ts +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FiMenu } from "react-icons/fi";
|
|
2
|
+
import { useUiStore } from "../../store/useUiStore";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
const SidebarHeader = ({
|
|
6
|
+
title = "Dashboard",
|
|
7
|
+
actions = null,
|
|
8
|
+
}: {
|
|
9
|
+
title?: string;
|
|
10
|
+
actions?: ReactNode;
|
|
11
|
+
}) => {
|
|
12
|
+
const { toggleSidebar } = useUiStore();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<header className="sticky top-0 z-30 h-16 bg-base-200 backdrop-blur border-b border-base-300 flex items-center justify-between px-4 lg:px-6">
|
|
16
|
+
<div className="flex items-center gap-3">
|
|
17
|
+
<button
|
|
18
|
+
onClick={toggleSidebar}
|
|
19
|
+
className="btn btn-square border-none bg-base-200 shadow-none text-base-content lg:hidden"
|
|
20
|
+
>
|
|
21
|
+
<FiMenu size={22} />
|
|
22
|
+
</button>
|
|
23
|
+
|
|
24
|
+
<h1 className="text-xl font-bold">{title}</h1>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div className="flex items-center gap-2">
|
|
28
|
+
{actions}
|
|
29
|
+
</div>
|
|
30
|
+
</header>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default SidebarHeader;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import TableSkeleton from "../shared/skeletons/common/TableSkeleton";
|
|
3
|
+
|
|
4
|
+
export interface TableColumn<T = any> {
|
|
5
|
+
header: string;
|
|
6
|
+
accessor?: keyof T | string;
|
|
7
|
+
render?: (value: any, row: T, index: number) => React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface TableProps<T = any> {
|
|
11
|
+
columns: TableColumn<T>[];
|
|
12
|
+
data: T[];
|
|
13
|
+
loading?: boolean;
|
|
14
|
+
onRowClick?: ((row: T) => void) | null;
|
|
15
|
+
emptyMessage?: React.ReactNode;
|
|
16
|
+
currentPage?: number;
|
|
17
|
+
totalPages?: number;
|
|
18
|
+
onPageChange?: ((page: number) => void) | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const Table = <T extends Record<string, any>>({
|
|
22
|
+
columns = [],
|
|
23
|
+
data = [],
|
|
24
|
+
loading = false,
|
|
25
|
+
onRowClick = null,
|
|
26
|
+
emptyMessage = "No data available",
|
|
27
|
+
currentPage = 1,
|
|
28
|
+
totalPages = 1,
|
|
29
|
+
onPageChange = null,
|
|
30
|
+
}: TableProps<T>) => {
|
|
31
|
+
if (loading) {
|
|
32
|
+
return <TableSkeleton rows={5} columns={columns.length || 4} />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="flex flex-col w-full border border-base-300 rounded-xl overflow-hidden bg-base-100 shadow-sm">
|
|
37
|
+
<div className="overflow-x-auto w-full">
|
|
38
|
+
<table className="table table-hover w-full min-w-full">
|
|
39
|
+
<thead className="bg-base-200/60 border-b border-base-300">
|
|
40
|
+
<tr>
|
|
41
|
+
{columns.map((col, index) => (
|
|
42
|
+
<th
|
|
43
|
+
key={(col.accessor as string) || index}
|
|
44
|
+
className="px-6 py-4 text-left text-xs font-bold text-base-content/80 uppercase tracking-wider"
|
|
45
|
+
>
|
|
46
|
+
{col.header}
|
|
47
|
+
</th>
|
|
48
|
+
))}
|
|
49
|
+
</tr>
|
|
50
|
+
</thead>
|
|
51
|
+
<tbody className="divide-y divide-base-300 bg-base-100">
|
|
52
|
+
{data.length > 0 ? (
|
|
53
|
+
data.map((row, rowIndex) => (
|
|
54
|
+
<tr
|
|
55
|
+
key={row.id || rowIndex}
|
|
56
|
+
onClick={() => onRowClick && onRowClick(row)}
|
|
57
|
+
className={`transition-colors duration-150 ${
|
|
58
|
+
onRowClick ? "cursor-pointer hover:bg-base-200" : ""
|
|
59
|
+
}`}
|
|
60
|
+
>
|
|
61
|
+
{columns.map((col, colIndex) => {
|
|
62
|
+
const value = col.accessor ? row[col.accessor as string] : undefined;
|
|
63
|
+
return (
|
|
64
|
+
<td
|
|
65
|
+
key={(col.accessor as string) || colIndex}
|
|
66
|
+
className="px-6 py-4 text-sm text-base-content/90 whitespace-nowrap align-middle"
|
|
67
|
+
>
|
|
68
|
+
{col.render ? col.render(value, row, rowIndex) : value}
|
|
69
|
+
</td>
|
|
70
|
+
);
|
|
71
|
+
})}
|
|
72
|
+
</tr>
|
|
73
|
+
))
|
|
74
|
+
) : (
|
|
75
|
+
<tr>
|
|
76
|
+
<td
|
|
77
|
+
colSpan={columns.length}
|
|
78
|
+
className="px-6 py-12 text-center text-sm text-base-content/60"
|
|
79
|
+
>
|
|
80
|
+
<div className="flex flex-col items-center justify-center gap-2">
|
|
81
|
+
<span className="text-2xl">📁</span>
|
|
82
|
+
<span>{emptyMessage}</span>
|
|
83
|
+
</div>
|
|
84
|
+
</td>
|
|
85
|
+
</tr>
|
|
86
|
+
)}
|
|
87
|
+
</tbody>
|
|
88
|
+
</table>
|
|
89
|
+
</div>
|
|
90
|
+
{totalPages > 1 && onPageChange && (
|
|
91
|
+
<div className="border-t border-base-300 px-6 py-4 bg-base-200/30 flex items-center justify-between shrink-0">
|
|
92
|
+
<div className="text-xs text-base-content/75 font-medium">
|
|
93
|
+
Page <span className="font-bold">{currentPage}</span> of{" "}
|
|
94
|
+
<span className="font-bold">{totalPages}</span>
|
|
95
|
+
</div>
|
|
96
|
+
<div className="flex gap-2">
|
|
97
|
+
<button
|
|
98
|
+
onClick={() => onPageChange(currentPage - 1)}
|
|
99
|
+
disabled={currentPage <= 1}
|
|
100
|
+
className="btn btn-sm btn-outline rounded-lg text-xs"
|
|
101
|
+
>
|
|
102
|
+
Previous
|
|
103
|
+
</button>
|
|
104
|
+
<button
|
|
105
|
+
onClick={() => onPageChange(currentPage + 1)}
|
|
106
|
+
disabled={currentPage >= totalPages}
|
|
107
|
+
className="btn btn-sm btn-outline rounded-lg text-xs"
|
|
108
|
+
>
|
|
109
|
+
Next
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export default Table;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { motion } from "motion/react";
|
|
3
|
+
|
|
4
|
+
const ToggleSwitch = ({ checked, onChange, label }) => {
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
className="flex items-center gap-3 cursor-pointer select-none w-fit group"
|
|
8
|
+
onClick={() => onChange(!checked)}
|
|
9
|
+
>
|
|
10
|
+
<span className="text-sm font-bold">{label}</span>
|
|
11
|
+
<div
|
|
12
|
+
className={`w-12 h-6 flex items-center rounded-full p-1 transition-all duration-300 shadow-inner border ${
|
|
13
|
+
checked
|
|
14
|
+
? "bg-gradient-to-r from-emerald-400 to-cyan-500 border-transparent shadow-emerald-500/20"
|
|
15
|
+
: "bg-base-300 border-base-300/50"
|
|
16
|
+
}`}
|
|
17
|
+
>
|
|
18
|
+
<motion.div
|
|
19
|
+
className={`w-4 h-4 rounded-full shadow-sm ${
|
|
20
|
+
checked ? "bg-white" : "bg-base-content/40"
|
|
21
|
+
}`}
|
|
22
|
+
layout
|
|
23
|
+
initial={false}
|
|
24
|
+
animate={{
|
|
25
|
+
x: checked ? 24 : 0,
|
|
26
|
+
}}
|
|
27
|
+
transition={{
|
|
28
|
+
type: "spring",
|
|
29
|
+
stiffness: 500,
|
|
30
|
+
damping: 30,
|
|
31
|
+
}}
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default ToggleSwitch;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { NavLink } from "react-router-dom";
|
|
2
|
+
import { IoClose } from "react-icons/io5";
|
|
3
|
+
import { useUiStore } from "../../store/useUiStore";
|
|
4
|
+
import { navigation } from "../../constants/navigation";
|
|
5
|
+
import { ChevronLeft, ChevronRight, Moon, Sun } from "lucide-react";
|
|
6
|
+
import appConfig from "../../config/index";
|
|
7
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
8
|
+
import useWindowSize from "../../hooks/useWindowSize";
|
|
9
|
+
|
|
10
|
+
const Sidebar = () => {
|
|
11
|
+
const { open, close, collapsed, toggleCollapsed } = useUiStore();
|
|
12
|
+
const { theme, toggleTheme } = useUiStore();
|
|
13
|
+
const { width } = useWindowSize();
|
|
14
|
+
const isMobile = width < 1024; // lg breakpoint
|
|
15
|
+
|
|
16
|
+
const showSidebar = !isMobile || open;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
{/* Overlay with AnimatePresence */}
|
|
21
|
+
<AnimatePresence>
|
|
22
|
+
{isMobile && open && (
|
|
23
|
+
<motion.div
|
|
24
|
+
initial={{ opacity: 0 }}
|
|
25
|
+
animate={{ opacity: 1 }}
|
|
26
|
+
exit={{ opacity: 0 }}
|
|
27
|
+
transition={{ duration: 0.2 }}
|
|
28
|
+
className="fixed inset-0 bg-base-100/40 z-40 lg:hidden"
|
|
29
|
+
onClick={close}
|
|
30
|
+
/>
|
|
31
|
+
)}
|
|
32
|
+
</AnimatePresence>
|
|
33
|
+
|
|
34
|
+
{/* Sidebar with AnimatePresence */}
|
|
35
|
+
<AnimatePresence>
|
|
36
|
+
{showSidebar && (
|
|
37
|
+
<motion.aside
|
|
38
|
+
initial={isMobile ? { x: "-100%" } : false}
|
|
39
|
+
animate={{ x: 0 }}
|
|
40
|
+
exit={isMobile ? { x: "-100%" } : undefined}
|
|
41
|
+
transition={{ type: "tween", duration: 0.25, ease: "easeInOut" }}
|
|
42
|
+
className={`
|
|
43
|
+
fixed lg:static top-0 left-0 z-50
|
|
44
|
+
h-screen bg-base-200 border-r border-base-300
|
|
45
|
+
flex flex-col overflow-hidden
|
|
46
|
+
transition-[width] duration-300
|
|
47
|
+
${collapsed ? "w-20" : "w-[75%] md:w-[16%] md:max-w-60"}
|
|
48
|
+
`}
|
|
49
|
+
>
|
|
50
|
+
{/* Header */}
|
|
51
|
+
<div className="h-16 border-b border-base-300 flex items-center justify-between px-5">
|
|
52
|
+
<div className="flex items-center gap-3">
|
|
53
|
+
{appConfig.app.img ? (
|
|
54
|
+
<img
|
|
55
|
+
src={appConfig.app.img}
|
|
56
|
+
alt=""
|
|
57
|
+
className="w-8 h-8 rounded-lg"
|
|
58
|
+
/>
|
|
59
|
+
) : (
|
|
60
|
+
<div className="flex items-center">
|
|
61
|
+
<div className="bg-primary w-8 h-8 rounded-lg flex items-center justify-center text-primary-content font-bold">
|
|
62
|
+
{appConfig.app.name.charAt(0)}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
<h1
|
|
67
|
+
className={`text-xl font-black text-primary whitespace-nowrap overflow-hidden ${collapsed ? "hidden" : ""}`}
|
|
68
|
+
>
|
|
69
|
+
{appConfig.app.name}
|
|
70
|
+
</h1>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<button
|
|
74
|
+
className={`btn btn-sm btn-circle btn-ghost md:hidden ${collapsed ? "hidden" : ""}`}
|
|
75
|
+
onClick={close}
|
|
76
|
+
>
|
|
77
|
+
<IoClose size={20} />
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* Navigation */}
|
|
82
|
+
<nav className="flex-1 overflow-y-auto p-2.5 space-y-1" data-lenis-prevent>
|
|
83
|
+
{navigation.map((item) => {
|
|
84
|
+
const Icon = item.icon;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<NavLink
|
|
88
|
+
key={item.path}
|
|
89
|
+
to={item.path}
|
|
90
|
+
onClick={isMobile ? close : undefined}
|
|
91
|
+
className={({ isActive }) =>
|
|
92
|
+
`flex items-center gap-3 rounded-box px-4 py-3 transition-all h-12
|
|
93
|
+
${
|
|
94
|
+
isActive
|
|
95
|
+
? "bg-primary text-primary-content"
|
|
96
|
+
: "hover:bg-base-300"
|
|
97
|
+
} ${collapsed ? "justify-center px-0" : ""}`
|
|
98
|
+
}
|
|
99
|
+
title={collapsed ? item.title : ""}
|
|
100
|
+
>
|
|
101
|
+
<Icon
|
|
102
|
+
size={18}
|
|
103
|
+
className={`shrink-0 ${collapsed && "mr-auto"}`}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
{!collapsed && (
|
|
107
|
+
<span className="font-medium transition-all duration-300">
|
|
108
|
+
{item.title}
|
|
109
|
+
</span>
|
|
110
|
+
)}
|
|
111
|
+
</NavLink>
|
|
112
|
+
);
|
|
113
|
+
})}
|
|
114
|
+
</nav>
|
|
115
|
+
|
|
116
|
+
{/* Footer with Collapse Button */}
|
|
117
|
+
<button
|
|
118
|
+
onClick={toggleTheme}
|
|
119
|
+
className={`w-full flex items-center gap-3 px-3 py-2.5 md:px-4 text-sm font-semibold transition-all duration-150 text-base-content/60 hover:text-base-content hover:bg-base-300/60 justify-between rounded-none ${collapsed ? "justify-center" : ""}`}
|
|
120
|
+
title="Toggle Theme"
|
|
121
|
+
>
|
|
122
|
+
{!collapsed && <span>{theme === "dark" ? "Dark" : "Light"}</span>}
|
|
123
|
+
<span className="btn btn-xs btn-ghost btn-circle ">
|
|
124
|
+
{theme === "dark" ? (
|
|
125
|
+
<Sun size={16} className="shrink-0 " />
|
|
126
|
+
) : (
|
|
127
|
+
<Moon size={16} className="shrink-0" />
|
|
128
|
+
)}
|
|
129
|
+
</span>
|
|
130
|
+
</button>
|
|
131
|
+
<div className="p-3 border-t border-base-300/50 shrink-0 flex flex-col gap-2">
|
|
132
|
+
<button
|
|
133
|
+
onClick={toggleCollapsed}
|
|
134
|
+
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-semibold transition-all duration-150 text-base-content/60 hover:text-base-content hover:bg-base-300/60 ${collapsed ? "justify-center" : ""}`}
|
|
135
|
+
title={collapsed ? "Expand Sidebar" : "Collapse Sidebar"}
|
|
136
|
+
>
|
|
137
|
+
{collapsed ? (
|
|
138
|
+
<ChevronRight size={18} className="shrink-0 mr-auto" />
|
|
139
|
+
) : (
|
|
140
|
+
<ChevronLeft size={18} className="shrink-0" />
|
|
141
|
+
)}
|
|
142
|
+
{!collapsed && <span className="truncate">Collapse</span>}
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
</motion.aside>
|
|
146
|
+
)}
|
|
147
|
+
</AnimatePresence>
|
|
148
|
+
</>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export default Sidebar;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const appConfig = {
|
|
2
|
+
app: {
|
|
3
|
+
name: "React App",
|
|
4
|
+
logo: "./logo.png",
|
|
5
|
+
shortName: "React",
|
|
6
|
+
description: "A React application built with @imksh/create",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
author: "Karan Sharma",
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
api: {
|
|
12
|
+
baseURL: import.meta.env.VITE_API_URL,
|
|
13
|
+
timeout: 30000,
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
routes: {
|
|
17
|
+
home: "/",
|
|
18
|
+
login: "/login",
|
|
19
|
+
profile: "/profile",
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
pagination: {
|
|
23
|
+
pageSize: 10,
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
github: {
|
|
27
|
+
repository: "",
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
social: {
|
|
31
|
+
website: "",
|
|
32
|
+
linkedin: "",
|
|
33
|
+
github: "",
|
|
34
|
+
twitter: "",
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
features: {
|
|
38
|
+
darkMode: true,
|
|
39
|
+
notifications: true,
|
|
40
|
+
sidebar: true,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default appConfig;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { api } from "../../../lib/axios";
|
|
2
|
+
|
|
3
|
+
export const authService = {
|
|
4
|
+
sendOtp: (email: string) =>
|
|
5
|
+
api.post("/auth/send-otp", { email }),
|
|
6
|
+
|
|
7
|
+
signup: (name: string, email: string, password: string, otp: string, role = "user") =>
|
|
8
|
+
api.post("/auth/signup", { name, email, password, otp, role }),
|
|
9
|
+
|
|
10
|
+
login: (email: string, password: string) =>
|
|
11
|
+
api.post("/auth/login", { email, password }),
|
|
12
|
+
|
|
13
|
+
getMe: () => api.get("/auth/me"),
|
|
14
|
+
|
|
15
|
+
updateMe: (data: any) => api.put("/auth/me", data),
|
|
16
|
+
|
|
17
|
+
logout: () =>
|
|
18
|
+
api.post("/auth/logout"),
|
|
19
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Link, useNavigate } from "react-router-dom";
|
|
3
|
+
import {
|
|
4
|
+
Mail,
|
|
5
|
+
Lock,
|
|
6
|
+
User,
|
|
7
|
+
Eye,
|
|
8
|
+
EyeOff,
|
|
9
|
+
ArrowLeft,
|
|
10
|
+
GraduationCap,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import { useAuthStore } from "../store/useAuthStore";
|
|
13
|
+
import Loading from "../../../components/ui/Loading";
|
|
14
|
+
|
|
15
|
+
const Login = () => {
|
|
16
|
+
const navigate = useNavigate();
|
|
17
|
+
const { login, signup, sendOtp, loading, isCheckingAuth, error, user } = useAuthStore();
|
|
18
|
+
|
|
19
|
+
const [mode, setMode] = useState("login"); // "login" | "signup"
|
|
20
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
21
|
+
const [form, setForm] = useState({ name: "", email: "", password: "", otp: "" });
|
|
22
|
+
const [otpSent, setOtpSent] = useState(false);
|
|
23
|
+
|
|
24
|
+
const handleChange = (e) =>
|
|
25
|
+
setForm((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
|
26
|
+
|
|
27
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
let ok;
|
|
30
|
+
if (mode === "login") {
|
|
31
|
+
ok = await login(form.email, form.password);
|
|
32
|
+
if (ok) navigate("/");
|
|
33
|
+
} else {
|
|
34
|
+
if (!otpSent) {
|
|
35
|
+
ok = await sendOtp(form.email);
|
|
36
|
+
if (ok) setOtpSent(true);
|
|
37
|
+
} else {
|
|
38
|
+
ok = await signup(form.name, form.email, form.password, form.otp);
|
|
39
|
+
if (ok) navigate("/");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (isCheckingAuth || user) {
|
|
45
|
+
return <Loading />;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="min-h-dvh w-screen bg-base-100 text-base-content flex items-center justify-center p-4 font-sans">
|
|
50
|
+
<div className="relative w-full max-w-md">
|
|
51
|
+
{/* Card */}
|
|
52
|
+
<div className="bg-base-200/80 backdrop-blur-md border border-base-300 rounded-3xl p-8 shadow-2xl">
|
|
53
|
+
{/* Logo & title */}
|
|
54
|
+
<div className="flex flex-col items-center gap-2 mb-8">
|
|
55
|
+
<div className="w-12 h-12 rounded-2xl bg-primary/15 border border-primary/20 flex items-center justify-center text-2xl shadow-inner text-primary font-bold">
|
|
56
|
+
<GraduationCap size={24} />
|
|
57
|
+
</div>
|
|
58
|
+
<h1 className="text-2xl font-black tracking-tight">
|
|
59
|
+
{mode === "login" ? "Welcome back" : "Create account"}
|
|
60
|
+
</h1>
|
|
61
|
+
<p className="text-xs text-base-content/50 text-center">
|
|
62
|
+
{mode === "login"
|
|
63
|
+
? "Sign in to continue your learning journey"
|
|
64
|
+
: "Join LMS and start learning the MERN stack"}
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{/* Tab switcher */}
|
|
69
|
+
<div className="tabs tabs-box bg-base-300/50 p-1 rounded-2xl w-full flex mb-6 border border-base-300">
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
onClick={() => {
|
|
73
|
+
setMode("login");
|
|
74
|
+
setOtpSent(false);
|
|
75
|
+
}}
|
|
76
|
+
className={`tab flex-1 rounded-xl text-sm font-bold transition-all ${
|
|
77
|
+
mode === "login"
|
|
78
|
+
? "tab-active bg-primary text-primary-content!"
|
|
79
|
+
: ""
|
|
80
|
+
}`}
|
|
81
|
+
>
|
|
82
|
+
Sign In
|
|
83
|
+
</button>
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
onClick={() => {
|
|
87
|
+
setMode("signup");
|
|
88
|
+
setOtpSent(false);
|
|
89
|
+
}}
|
|
90
|
+
className={`tab flex-1 rounded-xl text-sm font-bold transition-all ${
|
|
91
|
+
mode === "signup"
|
|
92
|
+
? "tab-active bg-primary text-primary-content!"
|
|
93
|
+
: ""
|
|
94
|
+
}`}
|
|
95
|
+
>
|
|
96
|
+
Sign Up
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* Error */}
|
|
101
|
+
{error && (
|
|
102
|
+
<div className="alert alert-error alert-soft rounded-2xl text-xs mb-4 p-3 border border-error/20">
|
|
103
|
+
⚠️ {error}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{/* Form */}
|
|
108
|
+
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
109
|
+
{/* Name — signup only */}
|
|
110
|
+
{mode === "signup" && (
|
|
111
|
+
<label className="input input-bordered rounded-2xl flex items-center gap-2 bg-base-100 border-base-300 focus-within:border-primary transition-colors">
|
|
112
|
+
<User size={16} className="text-base-content/40 shrink-0" />
|
|
113
|
+
<input
|
|
114
|
+
name="name"
|
|
115
|
+
type="text"
|
|
116
|
+
placeholder="Full name"
|
|
117
|
+
value={form.name}
|
|
118
|
+
onChange={handleChange}
|
|
119
|
+
required
|
|
120
|
+
className="grow text-sm bg-transparent outline-none placeholder:text-base-content/30"
|
|
121
|
+
/>
|
|
122
|
+
</label>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
{/* Email */}
|
|
126
|
+
<label className="input input-bordered rounded-2xl flex items-center gap-2 bg-base-100 border-base-300 focus-within:border-primary transition-colors">
|
|
127
|
+
<Mail size={16} className="text-base-content/40 shrink-0" />
|
|
128
|
+
<input
|
|
129
|
+
name="email"
|
|
130
|
+
type="email"
|
|
131
|
+
placeholder="Email address"
|
|
132
|
+
value={form.email}
|
|
133
|
+
onChange={handleChange}
|
|
134
|
+
required
|
|
135
|
+
className="grow text-sm bg-transparent outline-none placeholder:text-base-content/30"
|
|
136
|
+
/>
|
|
137
|
+
</label>
|
|
138
|
+
|
|
139
|
+
{/* Password */}
|
|
140
|
+
<label className="input input-bordered rounded-2xl flex items-center gap-2 bg-base-100 border-base-300 focus-within:border-primary transition-colors">
|
|
141
|
+
<Lock size={16} className="text-base-content/40 shrink-0" />
|
|
142
|
+
<input
|
|
143
|
+
name="password"
|
|
144
|
+
type={showPassword ? "text" : "password"}
|
|
145
|
+
placeholder="Password"
|
|
146
|
+
value={form.password}
|
|
147
|
+
onChange={handleChange}
|
|
148
|
+
required
|
|
149
|
+
minLength={6}
|
|
150
|
+
className="grow text-sm bg-transparent outline-none placeholder:text-base-content/30"
|
|
151
|
+
/>
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
onClick={() => setShowPassword((p) => !p)}
|
|
155
|
+
className="text-base-content/40 hover:text-base-content transition-colors"
|
|
156
|
+
>
|
|
157
|
+
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
|
|
158
|
+
</button>
|
|
159
|
+
</label>
|
|
160
|
+
|
|
161
|
+
{/* OTP Input - only visible when OTP is sent during signup */}
|
|
162
|
+
{mode === "signup" && otpSent && (
|
|
163
|
+
<label className="input input-bordered rounded-2xl flex items-center gap-2 bg-base-100 border-base-300 focus-within:border-primary transition-colors">
|
|
164
|
+
<Lock size={16} className="text-base-content/40 shrink-0" />
|
|
165
|
+
<input
|
|
166
|
+
name="otp"
|
|
167
|
+
type="text"
|
|
168
|
+
placeholder="Enter OTP"
|
|
169
|
+
value={form.otp}
|
|
170
|
+
onChange={handleChange}
|
|
171
|
+
required
|
|
172
|
+
className="grow text-sm bg-transparent outline-none placeholder:text-base-content/30"
|
|
173
|
+
/>
|
|
174
|
+
</label>
|
|
175
|
+
)}
|
|
176
|
+
|
|
177
|
+
{/* Submit */}
|
|
178
|
+
<button
|
|
179
|
+
type="submit"
|
|
180
|
+
disabled={loading}
|
|
181
|
+
className="btn btn-primary rounded-2xl font-bold mt-2 w-full shadow-lg hover:scale-[1.01] active:scale-[0.99] transition-transform"
|
|
182
|
+
>
|
|
183
|
+
{loading ? (
|
|
184
|
+
<span className="loading loading-spinner loading-sm" />
|
|
185
|
+
) : mode === "login" ? (
|
|
186
|
+
"Sign In →"
|
|
187
|
+
) : otpSent ? (
|
|
188
|
+
"Verify & Create Account →"
|
|
189
|
+
) : (
|
|
190
|
+
"Send OTP →"
|
|
191
|
+
)}
|
|
192
|
+
</button>
|
|
193
|
+
</form>
|
|
194
|
+
|
|
195
|
+
{/* Footer note */}
|
|
196
|
+
<p className="text-center text-xs text-base-content/40 mt-6">
|
|
197
|
+
{mode === "login" ? (
|
|
198
|
+
<>
|
|
199
|
+
New here?{" "}
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
onClick={() => {
|
|
203
|
+
setMode("signup");
|
|
204
|
+
setOtpSent(false);
|
|
205
|
+
}}
|
|
206
|
+
className="text-primary font-bold hover:underline"
|
|
207
|
+
>
|
|
208
|
+
Create an account
|
|
209
|
+
</button>
|
|
210
|
+
</>
|
|
211
|
+
) : (
|
|
212
|
+
<>
|
|
213
|
+
Already have an account?{" "}
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
onClick={() => {
|
|
217
|
+
setMode("login");
|
|
218
|
+
setOtpSent(false);
|
|
219
|
+
}}
|
|
220
|
+
className="text-primary font-bold hover:underline"
|
|
221
|
+
>
|
|
222
|
+
Sign in
|
|
223
|
+
</button>
|
|
224
|
+
</>
|
|
225
|
+
)}
|
|
226
|
+
</p>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export default Login;
|