@imksh/create 1.0.11 → 1.0.16
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 +7 -1
- package/templates/mern/client/package.json +0 -1
- package/templates/mern/client/src/App.jsx +10 -10
- package/templates/mern/client/src/components/layout/ProtectedRoute.jsx +2 -2
- package/templates/mern/client/src/store/useAuthStore.js +4 -4
- package/templates/mern/client/src/utils/formatDate.js +15 -0
- package/templates/mern/server/.env.example +1 -1
- package/templates/mern/server/src/index.js +1 -1
- package/templates/mern-ts/client/package.json +0 -4
- package/templates/mern-ts/client/src/App.tsx +10 -10
- package/templates/mern-ts/client/src/components/layout/ProtectedRoute.tsx +2 -2
- package/templates/mern-ts/client/src/components/modals/ConfirmModal.tsx +21 -4
- package/templates/mern-ts/client/src/components/modals/Modal.tsx +14 -2
- package/templates/mern-ts/client/src/services/authService.ts +3 -3
- package/templates/mern-ts/client/src/store/useAuthStore.ts +38 -12
- package/templates/mern-ts/client/src/store/useUiStore.ts +23 -3
- package/templates/mern-ts/client/src/utils/formatDate.ts +19 -0
- package/templates/mern-ts/server/.env.example +1 -1
- package/templates/mern-ts/server/src/index.ts +1 -1
- package/templates/mern-ts/server/src/models/otp.model.ts +9 -3
- package/templates/mern-ts/server/src/models/pushSubscription.model.ts +16 -3
- package/templates/mern-ts/server/src/models/user.model.ts +17 -3
- package/templates/node/.env.example +1 -1
- package/templates/node-ts/.env.example +1 -1
- package/templates/node-ts/src/models/otp.model.ts +9 -3
- package/templates/node-ts/src/models/pushSubscription.model.ts +16 -3
- package/templates/node-ts/src/models/user.model.ts +17 -3
- package/templates/react/package.json +0 -1
- package/templates/react/src/App.jsx +10 -10
- package/templates/react/src/components/layout/ProtectedRoute.jsx +2 -2
- package/templates/react/src/store/useAuthStore.js +4 -4
- package/templates/react/src/utils/formatDate.js +15 -0
- package/templates/react-ts/package.json +0 -4
- package/templates/react-ts/src/components/layout/ProtectedRoute.tsx +2 -2
- package/templates/react-ts/src/components/modals/ConfirmModal.tsx +21 -4
- package/templates/react-ts/src/components/modals/Modal.tsx +14 -2
- package/templates/react-ts/src/services/authService.ts +3 -3
- package/templates/react-ts/src/store/useAuthStore.ts +38 -12
- package/templates/react-ts/src/store/useUiStore.ts +23 -3
- package/templates/react-ts/src/utils/formatDate.ts +19 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imksh/create",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "A production-ready create for scaffolding modern applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,5 +39,11 @@
|
|
|
39
39
|
"fs-extra": "^11.2.0",
|
|
40
40
|
"inquirer": "^9.2.16",
|
|
41
41
|
"ora": "^8.0.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/react": "^18.2.37",
|
|
45
|
+
"@types/react-dom": "^18.2.15",
|
|
46
|
+
"react": "^18.2.0",
|
|
47
|
+
"react-dom": "^18.2.0"
|
|
42
48
|
}
|
|
43
49
|
}
|
|
@@ -20,17 +20,17 @@ const App = () => {
|
|
|
20
20
|
// Initialize Lenis smooth scroll
|
|
21
21
|
// useLenis();
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
getMe();
|
|
25
|
+
}, []);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
if (loading) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex h-screen items-center justify-center bg-base-100">
|
|
30
|
+
<Loading />
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
36
|
<div className="min-h-screen bg-base-100 text-base-content">
|
|
@@ -3,9 +3,9 @@ import { useAuthStore } from "../../store/useAuthStore";
|
|
|
3
3
|
import Loading from "../common/Loading";
|
|
4
4
|
|
|
5
5
|
const ProtectedRoute = ({ children, role }) => {
|
|
6
|
-
const { user,
|
|
6
|
+
const { user, loading } = useAuthStore();
|
|
7
7
|
|
|
8
|
-
if (
|
|
8
|
+
if (loading) return <Loading />;
|
|
9
9
|
|
|
10
10
|
if (!user) return <Navigate to="/login" />;
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@ export const useAuthStore = create((set, get) => ({
|
|
|
14
14
|
localStorage.setItem("token", data.token);
|
|
15
15
|
}
|
|
16
16
|
set({
|
|
17
|
-
user: data,
|
|
17
|
+
user: data.data,
|
|
18
18
|
loading: false,
|
|
19
19
|
});
|
|
20
20
|
return true;
|
|
@@ -37,7 +37,7 @@ export const useAuthStore = create((set, get) => ({
|
|
|
37
37
|
localStorage.setItem("token", data.token);
|
|
38
38
|
}
|
|
39
39
|
set({
|
|
40
|
-
user: data,
|
|
40
|
+
user: data.data,
|
|
41
41
|
loading: false,
|
|
42
42
|
});
|
|
43
43
|
return true;
|
|
@@ -56,7 +56,7 @@ export const useAuthStore = create((set, get) => ({
|
|
|
56
56
|
set({ loading: true });
|
|
57
57
|
try {
|
|
58
58
|
const { data } = await authService.getMe();
|
|
59
|
-
set({ user: data, loading: false });
|
|
59
|
+
set({ user: data.data, loading: false });
|
|
60
60
|
} catch (err) {
|
|
61
61
|
set({ loading: false });
|
|
62
62
|
} finally {
|
|
@@ -68,7 +68,7 @@ export const useAuthStore = create((set, get) => ({
|
|
|
68
68
|
set({ loading: true, error: null });
|
|
69
69
|
try {
|
|
70
70
|
const { data } = await authService.updateMe(updates);
|
|
71
|
-
set({ user: data, loading: false });
|
|
71
|
+
set({ user: data.data, loading: false });
|
|
72
72
|
return true;
|
|
73
73
|
} catch (err) {
|
|
74
74
|
set({
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const formatDate = (
|
|
2
|
+
date,
|
|
3
|
+
options = { day: "2-digit", month: "short", year: "numeric" },
|
|
4
|
+
locale = "en-IN",
|
|
5
|
+
) => {
|
|
6
|
+
if (!date) return "-";
|
|
7
|
+
|
|
8
|
+
const parsedDate = new Date(date);
|
|
9
|
+
|
|
10
|
+
if (isNaN(parsedDate.getTime())) {
|
|
11
|
+
return "-";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return new Intl.DateTimeFormat(locale, options).format(parsedDate);
|
|
15
|
+
};
|
|
@@ -31,13 +31,9 @@
|
|
|
31
31
|
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
|
32
32
|
"@typescript-eslint/parser": "^6.10.0",
|
|
33
33
|
"@vitejs/plugin-react": "^4.2.0",
|
|
34
|
-
"autoprefixer": "^10.4.19",
|
|
35
34
|
"eslint": "^8.53.0",
|
|
36
35
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
37
36
|
"eslint-plugin-react-refresh": "^0.4.4",
|
|
38
|
-
"flyonui": "^1.0.0",
|
|
39
|
-
"postcss": "^8.4.38",
|
|
40
|
-
"tailwindcss": "^3.4.3",
|
|
41
37
|
"typescript": "^5.2.2",
|
|
42
38
|
"vite": "^5.0.0",
|
|
43
39
|
"eslint-plugin-react": "^7.33.2"
|
|
@@ -20,17 +20,17 @@ const App = () => {
|
|
|
20
20
|
// Initialize Lenis smooth scroll
|
|
21
21
|
// useLenis();
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
getMe();
|
|
25
|
+
}, []);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
if (loading) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex h-screen items-center justify-center bg-base-100">
|
|
30
|
+
<Loading />
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
36
|
<div className="min-h-screen bg-base-100 text-base-content">
|
|
@@ -9,9 +9,9 @@ interface ProtectedRouteProps {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children, role }) => {
|
|
12
|
-
const { user,
|
|
12
|
+
const { user, loading } = useAuthStore();
|
|
13
13
|
|
|
14
|
-
if (
|
|
14
|
+
if (loading) return <Loading />;
|
|
15
15
|
|
|
16
16
|
if (!user) return <Navigate to="/login" />;
|
|
17
17
|
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
FaCircleXmark,
|
|
8
8
|
} from "react-icons/fa6";
|
|
9
9
|
|
|
10
|
-
const variants
|
|
10
|
+
const variants: Record<
|
|
11
|
+
"warning" | "error" | "success" | "info",
|
|
12
|
+
{ icon: any; iconBg: string; iconColor: string; button: string }
|
|
13
|
+
> = {
|
|
11
14
|
warning: {
|
|
12
15
|
icon: FaTriangleExclamation,
|
|
13
16
|
iconBg: "bg-warning/15",
|
|
@@ -34,6 +37,20 @@ const variants = {
|
|
|
34
37
|
},
|
|
35
38
|
};
|
|
36
39
|
|
|
40
|
+
export interface ConfirmModalProps {
|
|
41
|
+
open: boolean;
|
|
42
|
+
variant?: "warning" | "error" | "success" | "info";
|
|
43
|
+
title?: string;
|
|
44
|
+
message?: string;
|
|
45
|
+
confirmText?: string;
|
|
46
|
+
cancelText?: string;
|
|
47
|
+
loading?: boolean;
|
|
48
|
+
closeOnBackdrop?: boolean;
|
|
49
|
+
closeOnEsc?: boolean;
|
|
50
|
+
onConfirm: () => void | Promise<void>;
|
|
51
|
+
onCancel: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
37
54
|
const ConfirmModal = ({
|
|
38
55
|
open,
|
|
39
56
|
variant = "warning",
|
|
@@ -46,11 +63,11 @@ const ConfirmModal = ({
|
|
|
46
63
|
closeOnEsc = true,
|
|
47
64
|
onConfirm,
|
|
48
65
|
onCancel,
|
|
49
|
-
}) => {
|
|
66
|
+
}: ConfirmModalProps) => {
|
|
50
67
|
useEffect(() => {
|
|
51
68
|
if (!open || !closeOnEsc) return;
|
|
52
69
|
|
|
53
|
-
const handleKeyDown = (e) => {
|
|
70
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
54
71
|
if (e.key === "Escape") {
|
|
55
72
|
onCancel?.();
|
|
56
73
|
}
|
|
@@ -63,7 +80,7 @@ const ConfirmModal = ({
|
|
|
63
80
|
|
|
64
81
|
if (!open) return null;
|
|
65
82
|
|
|
66
|
-
const style = variants[variant
|
|
83
|
+
const style = variants[variant || "warning"];
|
|
67
84
|
const Icon = style.icon;
|
|
68
85
|
|
|
69
86
|
return createPortal(
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
1
|
+
import { useEffect, ReactNode } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import { IoClose } from "react-icons/io5";
|
|
4
4
|
|
|
5
|
+
export interface ModalProps {
|
|
6
|
+
open: boolean;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
title: string;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
size?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";
|
|
11
|
+
closeOnBackdrop?: boolean;
|
|
12
|
+
closeOnEsc?: boolean;
|
|
13
|
+
showCloseButton?: boolean;
|
|
14
|
+
footer?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
const sizes = {
|
|
6
18
|
sm: "max-w-sm",
|
|
7
19
|
md: "max-w-md",
|
|
@@ -21,7 +33,7 @@ const Modal = ({
|
|
|
21
33
|
closeOnEsc = true,
|
|
22
34
|
showCloseButton = true,
|
|
23
35
|
footer,
|
|
24
|
-
}) => {
|
|
36
|
+
}: ModalProps) => {
|
|
25
37
|
useEffect(() => {
|
|
26
38
|
if (!open || !closeOnEsc) return;
|
|
27
39
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { api } from "../config/api";
|
|
2
2
|
|
|
3
3
|
export const authService = {
|
|
4
|
-
signup: (name, email, password, role = "user") =>
|
|
4
|
+
signup: (name: string, email: string, password: string, role = "user") =>
|
|
5
5
|
api.post("/auth/signup", { name, email, password, role }),
|
|
6
6
|
|
|
7
|
-
login: (email, password) =>
|
|
7
|
+
login: (email: string, password: string) =>
|
|
8
8
|
api.post("/auth/login", { email, password }),
|
|
9
9
|
|
|
10
10
|
getMe: () => api.get("/auth/me"),
|
|
11
11
|
|
|
12
|
-
updateMe: (data) => api.put("/auth/me", data),
|
|
12
|
+
updateMe: (data: any) => api.put("/auth/me", data),
|
|
13
13
|
|
|
14
14
|
logout: () =>
|
|
15
15
|
api.post("/auth/logout"),
|
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
2
|
import { authService } from "../services/authService";
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export interface User {
|
|
5
|
+
_id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
email: string;
|
|
8
|
+
phone?: string;
|
|
9
|
+
role: string;
|
|
10
|
+
avatar?: {
|
|
11
|
+
url?: string;
|
|
12
|
+
publicId?: string;
|
|
13
|
+
};
|
|
14
|
+
createdAt?: string;
|
|
15
|
+
updatedAt?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AuthState {
|
|
19
|
+
user: User | null;
|
|
20
|
+
loading: boolean;
|
|
21
|
+
error: string | null;
|
|
22
|
+
signup: (name: string, email: string, password: string, role?: string) => Promise<boolean>;
|
|
23
|
+
login: (email: string, password: string) => Promise<boolean>;
|
|
24
|
+
getMe: () => Promise<void>;
|
|
25
|
+
updateProfile: (updates: Partial<User> | FormData) => Promise<boolean>;
|
|
26
|
+
logout: () => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const useAuthStore = create<AuthState>((set) => ({
|
|
5
30
|
user: null,
|
|
6
31
|
loading: true,
|
|
7
32
|
error: null,
|
|
8
33
|
|
|
9
|
-
signup: async (name, email, password, role = "user") => {
|
|
34
|
+
signup: async (name: string, email: string, password: string, role = "user") => {
|
|
10
35
|
set({ loading: true, error: null });
|
|
11
36
|
try {
|
|
12
37
|
const { data } = await authService.signup(name, email, password, role);
|
|
@@ -14,11 +39,11 @@ export const useAuthStore = create((set, get) => ({
|
|
|
14
39
|
localStorage.setItem("token", data.token);
|
|
15
40
|
}
|
|
16
41
|
set({
|
|
17
|
-
user: data,
|
|
42
|
+
user: data.data,
|
|
18
43
|
loading: false,
|
|
19
44
|
});
|
|
20
45
|
return true;
|
|
21
|
-
} catch (err) {
|
|
46
|
+
} catch (err: any) {
|
|
22
47
|
set({
|
|
23
48
|
error: err.response?.data?.message || err.message,
|
|
24
49
|
loading: false,
|
|
@@ -29,7 +54,7 @@ export const useAuthStore = create((set, get) => ({
|
|
|
29
54
|
}
|
|
30
55
|
},
|
|
31
56
|
|
|
32
|
-
login: async (email, password) => {
|
|
57
|
+
login: async (email: string, password: string) => {
|
|
33
58
|
set({ loading: true, error: null });
|
|
34
59
|
try {
|
|
35
60
|
const { data } = await authService.login(email, password);
|
|
@@ -37,11 +62,11 @@ export const useAuthStore = create((set, get) => ({
|
|
|
37
62
|
localStorage.setItem("token", data.token);
|
|
38
63
|
}
|
|
39
64
|
set({
|
|
40
|
-
user: data,
|
|
65
|
+
user: data.data,
|
|
41
66
|
loading: false,
|
|
42
67
|
});
|
|
43
68
|
return true;
|
|
44
|
-
} catch (err) {
|
|
69
|
+
} catch (err: any) {
|
|
45
70
|
set({
|
|
46
71
|
error: err.response?.data?.message || err.message,
|
|
47
72
|
loading: false,
|
|
@@ -56,21 +81,21 @@ export const useAuthStore = create((set, get) => ({
|
|
|
56
81
|
set({ loading: true });
|
|
57
82
|
try {
|
|
58
83
|
const { data } = await authService.getMe();
|
|
59
|
-
set({ user: data, loading: false });
|
|
60
|
-
} catch (err) {
|
|
84
|
+
set({ user: data.data, loading: false });
|
|
85
|
+
} catch (err: any) {
|
|
61
86
|
set({ loading: false });
|
|
62
87
|
} finally {
|
|
63
88
|
set({ loading: false });
|
|
64
89
|
}
|
|
65
90
|
},
|
|
66
91
|
|
|
67
|
-
updateProfile: async (updates) => {
|
|
92
|
+
updateProfile: async (updates: Partial<User> | FormData) => {
|
|
68
93
|
set({ loading: true, error: null });
|
|
69
94
|
try {
|
|
70
95
|
const { data } = await authService.updateMe(updates);
|
|
71
|
-
set({ user: data, loading: false });
|
|
96
|
+
set({ user: data.data, loading: false });
|
|
72
97
|
return true;
|
|
73
|
-
} catch (err) {
|
|
98
|
+
} catch (err: any) {
|
|
74
99
|
set({
|
|
75
100
|
error: err.response?.data?.message || err.message,
|
|
76
101
|
loading: false,
|
|
@@ -96,3 +121,4 @@ export const useAuthStore = create((set, get) => ({
|
|
|
96
121
|
});
|
|
97
122
|
},
|
|
98
123
|
}));
|
|
124
|
+
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
|
+
import { ReactNode } from "react";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
+
export interface UiState {
|
|
5
|
+
open: boolean;
|
|
6
|
+
collapsed: boolean;
|
|
7
|
+
isMobileMenuOpened: boolean;
|
|
8
|
+
headerTitle: string;
|
|
9
|
+
headerActions: ReactNode;
|
|
10
|
+
setHeaderTitle: (title: string) => void;
|
|
11
|
+
setHeaderActions: (actions: ReactNode) => void;
|
|
12
|
+
toggleCollapsed: () => void;
|
|
13
|
+
setMobileMenuOpened: (open: boolean) => void;
|
|
14
|
+
toggleMobileMenuOpened: () => void;
|
|
15
|
+
theme: string;
|
|
16
|
+
setTheme: (theme: string) => void;
|
|
17
|
+
toggleTheme: () => void;
|
|
18
|
+
toggleSidebar: () => void;
|
|
19
|
+
openSidebar: () => void;
|
|
20
|
+
close: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const useUiStore = create<UiState>((set, get) => ({
|
|
4
24
|
open: false,
|
|
5
25
|
collapsed: false,
|
|
6
26
|
isMobileMenuOpened: false,
|
|
7
27
|
headerTitle: "Dashboard",
|
|
8
28
|
headerActions: null,
|
|
9
|
-
setHeaderTitle: (title) => set({ headerTitle: title }),
|
|
10
|
-
setHeaderActions: (actions) => set({ headerActions: actions }),
|
|
29
|
+
setHeaderTitle: (title: string) => set({ headerTitle: title }),
|
|
30
|
+
setHeaderActions: (actions: ReactNode) => set({ headerActions: actions }),
|
|
11
31
|
toggleCollapsed: () => set((state) => ({ collapsed: !state.collapsed })),
|
|
12
32
|
|
|
13
33
|
setMobileMenuOpened: (open) => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const formatDate = (
|
|
2
|
+
date: string | Date | number,
|
|
3
|
+
options: Intl.DateTimeFormatOptions = {
|
|
4
|
+
day: "2-digit",
|
|
5
|
+
month: "short",
|
|
6
|
+
year: "numeric",
|
|
7
|
+
},
|
|
8
|
+
locale = "en-IN"
|
|
9
|
+
): string => {
|
|
10
|
+
if (!date) return "-";
|
|
11
|
+
|
|
12
|
+
const parsedDate = new Date(date);
|
|
13
|
+
|
|
14
|
+
if (isNaN(parsedDate.getTime())) {
|
|
15
|
+
return "-";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return new Intl.DateTimeFormat(locale, options).format(parsedDate);
|
|
19
|
+
};
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import mongoose from "mongoose";
|
|
1
|
+
import mongoose, { Document } from "mongoose";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export interface IOtp extends Document {
|
|
4
|
+
email: string;
|
|
5
|
+
otp: string;
|
|
6
|
+
createdAt: Date;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const otpSchema = new mongoose.Schema<IOtp>({
|
|
4
10
|
email: {
|
|
5
11
|
type: String,
|
|
6
12
|
required: true,
|
|
@@ -16,6 +22,6 @@ const otpSchema = new mongoose.Schema({
|
|
|
16
22
|
},
|
|
17
23
|
});
|
|
18
24
|
|
|
19
|
-
const Otp = mongoose.model("Otp", otpSchema);
|
|
25
|
+
const Otp = mongoose.model<IOtp>("Otp", otpSchema);
|
|
20
26
|
|
|
21
27
|
export default Otp;
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
-
import mongoose from "mongoose";
|
|
1
|
+
import mongoose, { Document } from "mongoose";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export interface IPushSubscription extends Document {
|
|
4
|
+
endpoint: string;
|
|
5
|
+
keys: {
|
|
6
|
+
p256dh: string;
|
|
7
|
+
auth: string;
|
|
8
|
+
};
|
|
9
|
+
user?: mongoose.Types.ObjectId;
|
|
10
|
+
role: "admin" | "user";
|
|
11
|
+
expiresAt: Date;
|
|
12
|
+
createdAt: Date;
|
|
13
|
+
updatedAt: Date;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const pushSubscriptionSchema = new mongoose.Schema<IPushSubscription>(
|
|
4
17
|
{
|
|
5
18
|
endpoint: {
|
|
6
19
|
type: String,
|
|
@@ -29,4 +42,4 @@ const pushSubscriptionSchema = new mongoose.Schema(
|
|
|
29
42
|
{ timestamps: true },
|
|
30
43
|
);
|
|
31
44
|
|
|
32
|
-
export default mongoose.model("PushSubscription", pushSubscriptionSchema);
|
|
45
|
+
export default mongoose.model<IPushSubscription>("PushSubscription", pushSubscriptionSchema);
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
import mongoose from "mongoose";
|
|
1
|
+
import mongoose, { Document } from "mongoose";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export interface IUser extends Document {
|
|
4
|
+
name: string;
|
|
5
|
+
email: string;
|
|
6
|
+
phone?: string;
|
|
7
|
+
avatar?: {
|
|
8
|
+
publicId?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
};
|
|
11
|
+
password?: string;
|
|
12
|
+
role: "user" | "admin" | "other";
|
|
13
|
+
createdAt: Date;
|
|
14
|
+
updatedAt: Date;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const userSchema = new mongoose.Schema<IUser>(
|
|
4
18
|
{
|
|
5
19
|
name: {
|
|
6
20
|
type: String,
|
|
@@ -39,5 +53,5 @@ const userSchema = new mongoose.Schema(
|
|
|
39
53
|
{ timestamps: true },
|
|
40
54
|
);
|
|
41
55
|
|
|
42
|
-
const User = mongoose.model("User", userSchema);
|
|
56
|
+
const User = mongoose.model<IUser>("User", userSchema);
|
|
43
57
|
export default User;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import mongoose from "mongoose";
|
|
1
|
+
import mongoose, { Document } from "mongoose";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export interface IOtp extends Document {
|
|
4
|
+
email: string;
|
|
5
|
+
otp: string;
|
|
6
|
+
createdAt: Date;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const otpSchema = new mongoose.Schema<IOtp>({
|
|
4
10
|
email: {
|
|
5
11
|
type: String,
|
|
6
12
|
required: true,
|
|
@@ -16,6 +22,6 @@ const otpSchema = new mongoose.Schema({
|
|
|
16
22
|
},
|
|
17
23
|
});
|
|
18
24
|
|
|
19
|
-
const Otp = mongoose.model("Otp", otpSchema);
|
|
25
|
+
const Otp = mongoose.model<IOtp>("Otp", otpSchema);
|
|
20
26
|
|
|
21
27
|
export default Otp;
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
-
import mongoose from "mongoose";
|
|
1
|
+
import mongoose, { Document } from "mongoose";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export interface IPushSubscription extends Document {
|
|
4
|
+
endpoint: string;
|
|
5
|
+
keys: {
|
|
6
|
+
p256dh: string;
|
|
7
|
+
auth: string;
|
|
8
|
+
};
|
|
9
|
+
user?: mongoose.Types.ObjectId;
|
|
10
|
+
role: "admin" | "user";
|
|
11
|
+
expiresAt: Date;
|
|
12
|
+
createdAt: Date;
|
|
13
|
+
updatedAt: Date;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const pushSubscriptionSchema = new mongoose.Schema<IPushSubscription>(
|
|
4
17
|
{
|
|
5
18
|
endpoint: {
|
|
6
19
|
type: String,
|
|
@@ -29,4 +42,4 @@ const pushSubscriptionSchema = new mongoose.Schema(
|
|
|
29
42
|
{ timestamps: true },
|
|
30
43
|
);
|
|
31
44
|
|
|
32
|
-
export default mongoose.model("PushSubscription", pushSubscriptionSchema);
|
|
45
|
+
export default mongoose.model<IPushSubscription>("PushSubscription", pushSubscriptionSchema);
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
import mongoose from "mongoose";
|
|
1
|
+
import mongoose, { Document } from "mongoose";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export interface IUser extends Document {
|
|
4
|
+
name: string;
|
|
5
|
+
email: string;
|
|
6
|
+
phone?: string;
|
|
7
|
+
avatar?: {
|
|
8
|
+
publicId?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
};
|
|
11
|
+
password?: string;
|
|
12
|
+
role: "user" | "admin" | "other";
|
|
13
|
+
createdAt: Date;
|
|
14
|
+
updatedAt: Date;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const userSchema = new mongoose.Schema<IUser>(
|
|
4
18
|
{
|
|
5
19
|
name: {
|
|
6
20
|
type: String,
|
|
@@ -39,5 +53,5 @@ const userSchema = new mongoose.Schema(
|
|
|
39
53
|
{ timestamps: true },
|
|
40
54
|
);
|
|
41
55
|
|
|
42
|
-
const User = mongoose.model("User", userSchema);
|
|
56
|
+
const User = mongoose.model<IUser>("User", userSchema);
|
|
43
57
|
export default User;
|
|
@@ -20,17 +20,17 @@ const App = () => {
|
|
|
20
20
|
// Initialize Lenis smooth scroll
|
|
21
21
|
// useLenis();
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
getMe();
|
|
25
|
+
}, []);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
if (loading) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex h-screen items-center justify-center bg-base-100">
|
|
30
|
+
<Loading />
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
36
|
<div className="min-h-screen bg-base-100 text-base-content">
|
|
@@ -3,9 +3,9 @@ import { useAuthStore } from "../../store/useAuthStore";
|
|
|
3
3
|
import Loading from "../common/Loading";
|
|
4
4
|
|
|
5
5
|
const ProtectedRoute = ({ children, role }) => {
|
|
6
|
-
const { user,
|
|
6
|
+
const { user, loading } = useAuthStore();
|
|
7
7
|
|
|
8
|
-
if (
|
|
8
|
+
if (loading) return <Loading />;
|
|
9
9
|
|
|
10
10
|
if (!user) return <Navigate to="/login" />;
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@ export const useAuthStore = create((set, get) => ({
|
|
|
14
14
|
localStorage.setItem("token", data.token);
|
|
15
15
|
}
|
|
16
16
|
set({
|
|
17
|
-
user: data,
|
|
17
|
+
user: data.data,
|
|
18
18
|
loading: false,
|
|
19
19
|
});
|
|
20
20
|
return true;
|
|
@@ -37,7 +37,7 @@ export const useAuthStore = create((set, get) => ({
|
|
|
37
37
|
localStorage.setItem("token", data.token);
|
|
38
38
|
}
|
|
39
39
|
set({
|
|
40
|
-
user: data,
|
|
40
|
+
user: data.data,
|
|
41
41
|
loading: false,
|
|
42
42
|
});
|
|
43
43
|
return true;
|
|
@@ -56,7 +56,7 @@ export const useAuthStore = create((set, get) => ({
|
|
|
56
56
|
set({ loading: true });
|
|
57
57
|
try {
|
|
58
58
|
const { data } = await authService.getMe();
|
|
59
|
-
set({ user: data, loading: false });
|
|
59
|
+
set({ user: data.data, loading: false });
|
|
60
60
|
} catch (err) {
|
|
61
61
|
set({ loading: false });
|
|
62
62
|
} finally {
|
|
@@ -68,7 +68,7 @@ export const useAuthStore = create((set, get) => ({
|
|
|
68
68
|
set({ loading: true, error: null });
|
|
69
69
|
try {
|
|
70
70
|
const { data } = await authService.updateMe(updates);
|
|
71
|
-
set({ user: data, loading: false });
|
|
71
|
+
set({ user: data.data, loading: false });
|
|
72
72
|
return true;
|
|
73
73
|
} catch (err) {
|
|
74
74
|
set({
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const formatDate = (
|
|
2
|
+
date,
|
|
3
|
+
options = { day: "2-digit", month: "short", year: "numeric" },
|
|
4
|
+
locale = "en-IN",
|
|
5
|
+
) => {
|
|
6
|
+
if (!date) return "-";
|
|
7
|
+
|
|
8
|
+
const parsedDate = new Date(date);
|
|
9
|
+
|
|
10
|
+
if (isNaN(parsedDate.getTime())) {
|
|
11
|
+
return "-";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return new Intl.DateTimeFormat(locale, options).format(parsedDate);
|
|
15
|
+
};
|
|
@@ -31,13 +31,9 @@
|
|
|
31
31
|
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
|
32
32
|
"@typescript-eslint/parser": "^6.10.0",
|
|
33
33
|
"@vitejs/plugin-react": "^4.2.0",
|
|
34
|
-
"autoprefixer": "^10.4.19",
|
|
35
34
|
"eslint": "^8.53.0",
|
|
36
35
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
37
36
|
"eslint-plugin-react-refresh": "^0.4.4",
|
|
38
|
-
"flyonui": "^1.0.0",
|
|
39
|
-
"postcss": "^8.4.38",
|
|
40
|
-
"tailwindcss": "^3.4.3",
|
|
41
37
|
"typescript": "^5.2.2",
|
|
42
38
|
"vite": "^5.0.0",
|
|
43
39
|
"eslint-plugin-react": "^7.33.2"
|
|
@@ -9,9 +9,9 @@ interface ProtectedRouteProps {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children, role }) => {
|
|
12
|
-
const { user,
|
|
12
|
+
const { user, loading } = useAuthStore();
|
|
13
13
|
|
|
14
|
-
if (
|
|
14
|
+
if (loading) return <Loading />;
|
|
15
15
|
|
|
16
16
|
if (!user) return <Navigate to="/login" />;
|
|
17
17
|
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
FaCircleXmark,
|
|
8
8
|
} from "react-icons/fa6";
|
|
9
9
|
|
|
10
|
-
const variants
|
|
10
|
+
const variants: Record<
|
|
11
|
+
"warning" | "error" | "success" | "info",
|
|
12
|
+
{ icon: any; iconBg: string; iconColor: string; button: string }
|
|
13
|
+
> = {
|
|
11
14
|
warning: {
|
|
12
15
|
icon: FaTriangleExclamation,
|
|
13
16
|
iconBg: "bg-warning/15",
|
|
@@ -34,6 +37,20 @@ const variants = {
|
|
|
34
37
|
},
|
|
35
38
|
};
|
|
36
39
|
|
|
40
|
+
export interface ConfirmModalProps {
|
|
41
|
+
open: boolean;
|
|
42
|
+
variant?: "warning" | "error" | "success" | "info";
|
|
43
|
+
title?: string;
|
|
44
|
+
message?: string;
|
|
45
|
+
confirmText?: string;
|
|
46
|
+
cancelText?: string;
|
|
47
|
+
loading?: boolean;
|
|
48
|
+
closeOnBackdrop?: boolean;
|
|
49
|
+
closeOnEsc?: boolean;
|
|
50
|
+
onConfirm: () => void | Promise<void>;
|
|
51
|
+
onCancel: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
37
54
|
const ConfirmModal = ({
|
|
38
55
|
open,
|
|
39
56
|
variant = "warning",
|
|
@@ -46,11 +63,11 @@ const ConfirmModal = ({
|
|
|
46
63
|
closeOnEsc = true,
|
|
47
64
|
onConfirm,
|
|
48
65
|
onCancel,
|
|
49
|
-
}) => {
|
|
66
|
+
}: ConfirmModalProps) => {
|
|
50
67
|
useEffect(() => {
|
|
51
68
|
if (!open || !closeOnEsc) return;
|
|
52
69
|
|
|
53
|
-
const handleKeyDown = (e) => {
|
|
70
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
54
71
|
if (e.key === "Escape") {
|
|
55
72
|
onCancel?.();
|
|
56
73
|
}
|
|
@@ -63,7 +80,7 @@ const ConfirmModal = ({
|
|
|
63
80
|
|
|
64
81
|
if (!open) return null;
|
|
65
82
|
|
|
66
|
-
const style = variants[variant
|
|
83
|
+
const style = variants[variant || "warning"];
|
|
67
84
|
const Icon = style.icon;
|
|
68
85
|
|
|
69
86
|
return createPortal(
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
1
|
+
import { useEffect, ReactNode } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import { IoClose } from "react-icons/io5";
|
|
4
4
|
|
|
5
|
+
export interface ModalProps {
|
|
6
|
+
open: boolean;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
title: string;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
size?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";
|
|
11
|
+
closeOnBackdrop?: boolean;
|
|
12
|
+
closeOnEsc?: boolean;
|
|
13
|
+
showCloseButton?: boolean;
|
|
14
|
+
footer?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
const sizes = {
|
|
6
18
|
sm: "max-w-sm",
|
|
7
19
|
md: "max-w-md",
|
|
@@ -21,7 +33,7 @@ const Modal = ({
|
|
|
21
33
|
closeOnEsc = true,
|
|
22
34
|
showCloseButton = true,
|
|
23
35
|
footer,
|
|
24
|
-
}) => {
|
|
36
|
+
}: ModalProps) => {
|
|
25
37
|
useEffect(() => {
|
|
26
38
|
if (!open || !closeOnEsc) return;
|
|
27
39
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { api } from "../config/api";
|
|
2
2
|
|
|
3
3
|
export const authService = {
|
|
4
|
-
signup: (name, email, password, role = "user") =>
|
|
4
|
+
signup: (name: string, email: string, password: string, role = "user") =>
|
|
5
5
|
api.post("/auth/signup", { name, email, password, role }),
|
|
6
6
|
|
|
7
|
-
login: (email, password) =>
|
|
7
|
+
login: (email: string, password: string) =>
|
|
8
8
|
api.post("/auth/login", { email, password }),
|
|
9
9
|
|
|
10
10
|
getMe: () => api.get("/auth/me"),
|
|
11
11
|
|
|
12
|
-
updateMe: (data) => api.put("/auth/me", data),
|
|
12
|
+
updateMe: (data: any) => api.put("/auth/me", data),
|
|
13
13
|
|
|
14
14
|
logout: () =>
|
|
15
15
|
api.post("/auth/logout"),
|
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
2
|
import { authService } from "../services/authService";
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export interface User {
|
|
5
|
+
_id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
email: string;
|
|
8
|
+
phone?: string;
|
|
9
|
+
role: string;
|
|
10
|
+
avatar?: {
|
|
11
|
+
url?: string;
|
|
12
|
+
publicId?: string;
|
|
13
|
+
};
|
|
14
|
+
createdAt?: string;
|
|
15
|
+
updatedAt?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AuthState {
|
|
19
|
+
user: User | null;
|
|
20
|
+
loading: boolean;
|
|
21
|
+
error: string | null;
|
|
22
|
+
signup: (name: string, email: string, password: string, role?: string) => Promise<boolean>;
|
|
23
|
+
login: (email: string, password: string) => Promise<boolean>;
|
|
24
|
+
getMe: () => Promise<void>;
|
|
25
|
+
updateProfile: (updates: Partial<User> | FormData) => Promise<boolean>;
|
|
26
|
+
logout: () => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const useAuthStore = create<AuthState>((set) => ({
|
|
5
30
|
user: null,
|
|
6
31
|
loading: true,
|
|
7
32
|
error: null,
|
|
8
33
|
|
|
9
|
-
signup: async (name, email, password, role = "user") => {
|
|
34
|
+
signup: async (name: string, email: string, password: string, role = "user") => {
|
|
10
35
|
set({ loading: true, error: null });
|
|
11
36
|
try {
|
|
12
37
|
const { data } = await authService.signup(name, email, password, role);
|
|
@@ -14,11 +39,11 @@ export const useAuthStore = create((set, get) => ({
|
|
|
14
39
|
localStorage.setItem("token", data.token);
|
|
15
40
|
}
|
|
16
41
|
set({
|
|
17
|
-
user: data,
|
|
42
|
+
user: data.data,
|
|
18
43
|
loading: false,
|
|
19
44
|
});
|
|
20
45
|
return true;
|
|
21
|
-
} catch (err) {
|
|
46
|
+
} catch (err: any) {
|
|
22
47
|
set({
|
|
23
48
|
error: err.response?.data?.message || err.message,
|
|
24
49
|
loading: false,
|
|
@@ -29,7 +54,7 @@ export const useAuthStore = create((set, get) => ({
|
|
|
29
54
|
}
|
|
30
55
|
},
|
|
31
56
|
|
|
32
|
-
login: async (email, password) => {
|
|
57
|
+
login: async (email: string, password: string) => {
|
|
33
58
|
set({ loading: true, error: null });
|
|
34
59
|
try {
|
|
35
60
|
const { data } = await authService.login(email, password);
|
|
@@ -37,11 +62,11 @@ export const useAuthStore = create((set, get) => ({
|
|
|
37
62
|
localStorage.setItem("token", data.token);
|
|
38
63
|
}
|
|
39
64
|
set({
|
|
40
|
-
user: data,
|
|
65
|
+
user: data.data,
|
|
41
66
|
loading: false,
|
|
42
67
|
});
|
|
43
68
|
return true;
|
|
44
|
-
} catch (err) {
|
|
69
|
+
} catch (err: any) {
|
|
45
70
|
set({
|
|
46
71
|
error: err.response?.data?.message || err.message,
|
|
47
72
|
loading: false,
|
|
@@ -56,21 +81,21 @@ export const useAuthStore = create((set, get) => ({
|
|
|
56
81
|
set({ loading: true });
|
|
57
82
|
try {
|
|
58
83
|
const { data } = await authService.getMe();
|
|
59
|
-
set({ user: data, loading: false });
|
|
60
|
-
} catch (err) {
|
|
84
|
+
set({ user: data.data, loading: false });
|
|
85
|
+
} catch (err: any) {
|
|
61
86
|
set({ loading: false });
|
|
62
87
|
} finally {
|
|
63
88
|
set({ loading: false });
|
|
64
89
|
}
|
|
65
90
|
},
|
|
66
91
|
|
|
67
|
-
updateProfile: async (updates) => {
|
|
92
|
+
updateProfile: async (updates: Partial<User> | FormData) => {
|
|
68
93
|
set({ loading: true, error: null });
|
|
69
94
|
try {
|
|
70
95
|
const { data } = await authService.updateMe(updates);
|
|
71
|
-
set({ user: data, loading: false });
|
|
96
|
+
set({ user: data.data, loading: false });
|
|
72
97
|
return true;
|
|
73
|
-
} catch (err) {
|
|
98
|
+
} catch (err: any) {
|
|
74
99
|
set({
|
|
75
100
|
error: err.response?.data?.message || err.message,
|
|
76
101
|
loading: false,
|
|
@@ -96,3 +121,4 @@ export const useAuthStore = create((set, get) => ({
|
|
|
96
121
|
});
|
|
97
122
|
},
|
|
98
123
|
}));
|
|
124
|
+
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
|
+
import { ReactNode } from "react";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
+
export interface UiState {
|
|
5
|
+
open: boolean;
|
|
6
|
+
collapsed: boolean;
|
|
7
|
+
isMobileMenuOpened: boolean;
|
|
8
|
+
headerTitle: string;
|
|
9
|
+
headerActions: ReactNode;
|
|
10
|
+
setHeaderTitle: (title: string) => void;
|
|
11
|
+
setHeaderActions: (actions: ReactNode) => void;
|
|
12
|
+
toggleCollapsed: () => void;
|
|
13
|
+
setMobileMenuOpened: (open: boolean) => void;
|
|
14
|
+
toggleMobileMenuOpened: () => void;
|
|
15
|
+
theme: string;
|
|
16
|
+
setTheme: (theme: string) => void;
|
|
17
|
+
toggleTheme: () => void;
|
|
18
|
+
toggleSidebar: () => void;
|
|
19
|
+
openSidebar: () => void;
|
|
20
|
+
close: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const useUiStore = create<UiState>((set, get) => ({
|
|
4
24
|
open: false,
|
|
5
25
|
collapsed: false,
|
|
6
26
|
isMobileMenuOpened: false,
|
|
7
27
|
headerTitle: "Dashboard",
|
|
8
28
|
headerActions: null,
|
|
9
|
-
setHeaderTitle: (title) => set({ headerTitle: title }),
|
|
10
|
-
setHeaderActions: (actions) => set({ headerActions: actions }),
|
|
29
|
+
setHeaderTitle: (title: string) => set({ headerTitle: title }),
|
|
30
|
+
setHeaderActions: (actions: ReactNode) => set({ headerActions: actions }),
|
|
11
31
|
toggleCollapsed: () => set((state) => ({ collapsed: !state.collapsed })),
|
|
12
32
|
|
|
13
33
|
setMobileMenuOpened: (open) => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const formatDate = (
|
|
2
|
+
date: string | Date | number,
|
|
3
|
+
options: Intl.DateTimeFormatOptions = {
|
|
4
|
+
day: "2-digit",
|
|
5
|
+
month: "short",
|
|
6
|
+
year: "numeric",
|
|
7
|
+
},
|
|
8
|
+
locale = "en-IN"
|
|
9
|
+
): string => {
|
|
10
|
+
if (!date) return "-";
|
|
11
|
+
|
|
12
|
+
const parsedDate = new Date(date);
|
|
13
|
+
|
|
14
|
+
if (isNaN(parsedDate.getTime())) {
|
|
15
|
+
return "-";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return new Intl.DateTimeFormat(locale, options).format(parsedDate);
|
|
19
|
+
};
|