├── .env
├── .eslintrc.cjs
├── .gitignore
├── index.html
├── package-lock.json
├── postcss.config.js
├── public
└── vite.svg
├── src
├── App.css
├── App.jsx
├── assets
│ ├── images
│ │ ├── background
│ │ │ ├── backgroundwhite.png
│ │ │ ├── header-background.jpg
│ │ │ ├── index.js
│ │ │ └── loginBg.png
│ │ ├── index.js
│ │ ├── logos
│ │ │ ├── fileLogos
│ │ │ │ ├── csv.png
│ │ │ │ ├── excel.png
│ │ │ │ ├── pdf.png
│ │ │ │ ├── ppt.png
│ │ │ │ ├── text.png
│ │ │ │ └── word.png
│ │ │ ├── ilovetaguig.jpg
│ │ │ ├── index.js
│ │ │ ├── logo.png
│ │ │ ├── think-big-taguig.png
│ │ │ ├── topapp.jpg
│ │ │ └── tower-system.jpg
│ │ └── other
│ │ │ └── passport
│ │ │ └── header.JPG
│ └── react.svg
├── helpers
│ ├── arrObjectFormats.js
│ ├── currencyFormat.js
│ ├── dateFilters.js
│ ├── dateFormats.js
│ ├── filters.js
│ ├── formatData.js
│ ├── getStatus.js
│ ├── getUserAgent.js
│ ├── itemFormat.jsx
│ ├── regExp.js
│ └── tobase64.js
├── hooks
│ ├── regExp.js
│ ├── useTableSearch.jsx
│ ├── useTableSearchServerside.jsx
│ └── useWindowSize.js
├── index.css
├── main.jsx
├── pages
│ └── admin
│ │ ├── Dashboard
│ │ └── index.jsx
│ │ ├── Login.jsx
│ │ └── index.jsx
├── routes
│ ├── index.jsx
│ ├── pageRoutes
│ │ └── AdminRoutes.jsx
│ └── validateAuth.jsx
├── services
│ ├── api
│ │ └── admin
│ │ │ └── auth.js
│ ├── axios.js
│ └── requests
│ │ └── admin
│ │ └── useLoginAuth.js
└── store
│ ├── admin
│ └── useAuth.js
│ └── user
│ └── useAuth.js
├── tailwind.config.js
└── vite.config.js
/.env:
--------------------------------------------------------------------------------
1 | #DEV - API BASE_URL
2 | VITE_BASE_URL=YOUR_API_URL_HERE
3 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | "eslint:recommended",
6 | "plugin:react/recommended",
7 | "plugin:react/jsx-runtime",
8 | "plugin:react-hooks/recommended",
9 | ],
10 | ignorePatterns: ["dist", ".eslintrc.cjs"],
11 | parserOptions: { ecmaVersion: "latest", sourceType: "module" },
12 | settings: { react: { version: "18.2" } },
13 | plugins: ["react-refresh"],
14 | rules: {
15 | "react/jsx-no-target-blank": "off",
16 | "react-refresh/only-export-components": [
17 | "warn",
18 | { allowConstantExport: true },
19 | ],
20 | "react/prop-types": 0,
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ATIMS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
2 |
3 | :root {
4 | --scrollbar-color: #F5F5F5;
5 | --scrollbar-thumb-color: #CC0011;
6 | }
7 |
8 | #root {
9 | margin: 0 auto;
10 | }
11 |
12 | body {
13 | font-family: "Montserrat", sans-serif;
14 | }
15 |
16 | ::-webkit-scrollbar {
17 | width: 6px;
18 | height: 6px;
19 | }
20 |
21 | ::-webkit-scrollbar-track {
22 | box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
23 | border-radius: 7px;
24 | background-color: var(--scrollbar-color);
25 | }
26 |
27 | ::-webkit-scrollbar-thumb {
28 | border-radius: 7px;
29 | box-shadow: inset 0 0 6px rgba(0,0,0,.3);
30 | background-color: var(--scrollbar-thumb-color);
31 | cursor: pointer;
32 | }
33 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import RootRoutes from "./routes";
3 |
4 | function App() {
5 | return (
6 | <>
7 |
8 | >
9 | );
10 | }
11 |
12 | export default App;
13 |
--------------------------------------------------------------------------------
/src/assets/images/background/backgroundwhite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/background/backgroundwhite.png
--------------------------------------------------------------------------------
/src/assets/images/background/header-background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/background/header-background.jpg
--------------------------------------------------------------------------------
/src/assets/images/background/index.js:
--------------------------------------------------------------------------------
1 | import bgBackground from "./header-background.jpg";
2 | import bgBackgroundWhite from "./backgroundwhite.png";
3 | import loginBg from "./loginBg.png";
4 |
5 | export { bgBackground, bgBackgroundWhite, loginBg };
6 |
--------------------------------------------------------------------------------
/src/assets/images/background/loginBg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/background/loginBg.png
--------------------------------------------------------------------------------
/src/assets/images/index.js:
--------------------------------------------------------------------------------
1 | import loginBg from "./background/loginBg.png";
2 | import logo from "./logos/logo.png";
3 |
4 | export { loginBg, logo };
5 |
--------------------------------------------------------------------------------
/src/assets/images/logos/fileLogos/csv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/fileLogos/csv.png
--------------------------------------------------------------------------------
/src/assets/images/logos/fileLogos/excel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/fileLogos/excel.png
--------------------------------------------------------------------------------
/src/assets/images/logos/fileLogos/pdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/fileLogos/pdf.png
--------------------------------------------------------------------------------
/src/assets/images/logos/fileLogos/ppt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/fileLogos/ppt.png
--------------------------------------------------------------------------------
/src/assets/images/logos/fileLogos/text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/fileLogos/text.png
--------------------------------------------------------------------------------
/src/assets/images/logos/fileLogos/word.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/fileLogos/word.png
--------------------------------------------------------------------------------
/src/assets/images/logos/ilovetaguig.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/ilovetaguig.jpg
--------------------------------------------------------------------------------
/src/assets/images/logos/index.js:
--------------------------------------------------------------------------------
1 | import logo from "./taguig.png";
2 |
3 | export { logo };
4 |
--------------------------------------------------------------------------------
/src/assets/images/logos/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/logo.png
--------------------------------------------------------------------------------
/src/assets/images/logos/think-big-taguig.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/think-big-taguig.png
--------------------------------------------------------------------------------
/src/assets/images/logos/topapp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/topapp.jpg
--------------------------------------------------------------------------------
/src/assets/images/logos/tower-system.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/logos/tower-system.jpg
--------------------------------------------------------------------------------
/src/assets/images/other/passport/header.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/reactjs-vite-reactquery-zustand-template/d2e32d2742c3495828007013bf6253865f904e45/src/assets/images/other/passport/header.JPG
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/helpers/arrObjectFormats.js:
--------------------------------------------------------------------------------
1 | export const objectToQueryParams = (object) => {
2 | return Object.keys(object)
3 | .map((key) => `${key}=${encodeURIComponent(object[key])}`)
4 | .join("&");
5 | };
6 |
--------------------------------------------------------------------------------
/src/helpers/currencyFormat.js:
--------------------------------------------------------------------------------
1 | export const format_addCommas = (num) => {
2 | var str = num.toString().split(".");
3 | if (str[0].length >= 4) {
4 | str[0] = str[0].replace(/(\d)(?=(\d{3})+$)/g, "$1,");
5 | }
6 | return str.join(".");
7 | };
8 |
9 | const formatCurency = new Intl.NumberFormat("en-US", {
10 | style: "currency",
11 | currency: "PHP",
12 | minimumFractionDigits: 2,
13 | });
14 |
15 | const formatCurency3 = new Intl.NumberFormat("en-US", {
16 | style: "currency",
17 | currency: "PHP",
18 | minimumFractionDigits: 3,
19 | });
20 |
21 | export const format_PHCurrency = (num) => formatCurency.format(num);
22 | export const checkFree = (num) =>
23 | num === 0 ? "FREE" : formatCurency.format(num);
24 | export const format_PHCurrency3 = (num) => formatCurency3.format(num);
25 | export const unFormat_PHCurrency = (num = "") =>
26 | Number(num.replace(/[^0-9\\.]+/g, ""));
27 |
28 | export const getPercentage = (partialValue, totalValue) => {
29 | return ((100 * partialValue) / totalValue).toFixed(2);
30 | };
31 |
--------------------------------------------------------------------------------
/src/helpers/dateFilters.js:
--------------------------------------------------------------------------------
1 | import dayjs from "dayjs";
2 | import customParseFormat from "dayjs/plugin/customParseFormat";
3 | dayjs.extend(customParseFormat);
4 |
5 | const range = (start, end) => {
6 | const result = [];
7 | for (let i = start; i < end; i++) {
8 | result.push(i);
9 | }
10 | return result;
11 | };
12 |
13 | // eslint-disable-next-line arrow-body-style
14 | export const disabledDate = (current) => {
15 | // Can not select days before today and today
16 | return current && current < dayjs().endOf("day");
17 | };
18 |
19 | export const disabledDateTime = () => ({
20 | disabledHours: () => range(0, 24).splice(4, 20),
21 | disabledMinutes: () => range(30, 60),
22 | disabledSeconds: () => [55, 56],
23 | });
24 |
25 | export const customDisabledDate = (current, startDate, endDate) => {
26 | if (!startDate || !endDate) {
27 | // Allow all dates if either startDate or endDate is not selected
28 | return false;
29 | }
30 |
31 | // Disable dates before startDate and after endDate
32 | return current < startDate || current > endDate;
33 | };
34 |
35 | export const disabledRangeTime = (_, type) => {
36 | if (type === "start") {
37 | return {
38 | disabledHours: () => range(0, 60).splice(4, 20),
39 | disabledMinutes: () => range(30, 60),
40 | disabledSeconds: () => [55, 56],
41 | };
42 | }
43 | return {
44 | disabledHours: () => range(0, 60).splice(20, 4),
45 | disabledMinutes: () => range(0, 31),
46 | disabledSeconds: () => [55, 56],
47 | };
48 | };
49 |
50 | //disable dates onwards starts on given date
51 | export const isDateDisabledOnwards = (current, givenDate) => {
52 | return givenDate && current && current >= dayjs(givenDate);
53 | };
54 |
55 | export const disabledFutureDates = (current) => {
56 | // Disable future dates
57 | return current && current > dayjs().endOf("day");
58 | };
59 |
60 | export const enabledRangeDates = (start, end) => {
61 | // Disable future dates
62 | return start && start < dayjs().endOf("day") && end > dayjs().endOf("day");
63 | };
64 |
--------------------------------------------------------------------------------
/src/helpers/dateFormats.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 |
3 | export const formatDateRange = (start, end) => {
4 | const dateFrom = moment(start);
5 | const dateTo = moment(end);
6 |
7 | const formattedDateFrom = dateFrom.format("MMMM D");
8 | const formattedDateTo = dateTo.format(dateFrom.month() === dateTo.month() ? "D" : "MMMM D");
9 | const year = dateFrom.year();
10 |
11 | return `${formattedDateFrom} - ${formattedDateTo}, ${year}`;
12 | };
13 |
14 | export const enumerateDates = (startDate, endDate) => {
15 | var now = moment(startDate),
16 | dates = [];
17 |
18 | while (now.isSameOrBefore(endDate)) {
19 | dates.push(now.format("YYYY-MM-DD"));
20 | now.add(1, "days");
21 | }
22 | return dates; // August 10 - 11, 2023 || August 30 - September 03, 2023
23 | };
24 |
25 | export const formatTimeRange = (dateFrom, dateTo) => {
26 | const timeFrom = dateFrom.format("h:mmA");
27 | const timeTo = dateTo.format("h:mmA");
28 |
29 | return `${timeFrom} - ${timeTo}`;
30 | };
31 |
--------------------------------------------------------------------------------
/src/helpers/filters.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 | import { CARDDATESTATUS, CARDDATESTATUSENUMS } from "../constants/enums";
3 |
4 | export const filterOption = (input, option) =>
5 | (option?.label ?? "").toLowerCase().includes(input.toLowerCase());
6 |
7 | export const removeDuplicate = (array, key) => {
8 | return array.reduce((acc, item) => {
9 | if (!acc.some((obj) => obj[key] === item[key])) {
10 | acc.push(item);
11 | }
12 | return acc;
13 | }, []);
14 | };
15 |
16 | export const groupArrayByKey = (arr, key) => {
17 | return Object.groupBy(arr, (obj) => obj[key]);
18 | };
19 |
20 | // Removes item when existing array 2
21 | export const filterArrayFromArray = (array, array2, arr1Key, arr2Key) => {
22 | return array.filter(
23 | (obj) => !array2.find((obj2) => obj[arr1Key] == obj2[arr2Key])
24 | );
25 | };
26 |
27 | export const getCurrentDataByDate = (arr = [], key = "") => {
28 | return arr.filter((obj) =>
29 | moment().isSameOrBefore(moment(obj[key], "YYYY-MM-DD"))
30 | );
31 | };
32 |
33 | export const getExpiredDataByDate = (arr = [], key = "") => {
34 | const result = arr.filter((obj) =>
35 | moment(obj[key], "YYYY-MM-DD").isAfter(moment())
36 | );
37 | return result;
38 | };
39 |
40 | export const getCardStatusByDate = (date) => {
41 | const currentDate = moment().format("YYYY-MM-DD");
42 | return moment(currentDate).isBefore(moment(date))
43 | ? CARDDATESTATUS.ONGOING
44 | : CARDDATESTATUS.EXPIRED;
45 | };
46 | // export const getCardStatusByDate_1 = (date) => {
47 | // const currentDate = moment().format("YYYY-MM-DD");
48 | // return moment(moment(currentDate).add(1, "d")).isSame(moment(date))
49 | // ? CARDDATESTATUS.LASTDAY
50 | // : moment(currentDate).isBefore(moment(date))
51 | // ? CARDDATESTATUS.ONGOING
52 | // : CARDDATESTATUS.EXPIRED;
53 | // };
54 |
55 | export const getCountByStatusDateAndKey = (arr = [], key) => {
56 | const currentDate = moment().format("YYYY-MM-DD");
57 | const result = arr.reduce(
58 | (accumulator, currentValue) => {
59 | if (moment(currentDate).isBefore(moment(currentValue[key]))) {
60 | accumulator[CARDDATESTATUSENUMS.ONGOING].push(currentValue);
61 | } else {
62 | moment(currentDate).isAfter(moment(currentValue[key]));
63 | accumulator[CARDDATESTATUSENUMS.EXPIRED].push(currentValue);
64 | }
65 | return accumulator;
66 | },
67 | {
68 | [CARDDATESTATUSENUMS.ONGOING]: [],
69 | [CARDDATESTATUSENUMS.EXPIRED]: [],
70 | }
71 | );
72 |
73 | return result;
74 | };
75 |
76 | // export const getCountByStatusDateAndKey_V1 = (arr = [], key) => {
77 | // const currentDate = moment().format("YYYY-MM-DD");
78 | // const result = arr.reduce(
79 | // (accumulator, currentValue) => {
80 | // if (
81 | // moment(moment(currentDate).add(1, "d")).isSame(
82 | // moment(currentValue[key])
83 | // )
84 | // ) {
85 | // accumulator[CARDDATESTATUSENUMS.LASTDAY].push(currentValue);
86 | // } else if (moment(currentDate).isBefore(moment(currentValue[key]))) {
87 | // accumulator[CARDDATESTATUSENUMS.ONGOING].push(currentValue);
88 | // } else {
89 | // moment(currentDate).isAfter(moment(currentValue[key]));
90 | // accumulator[CARDDATESTATUSENUMS.EXPIRED].push(currentValue);
91 | // }
92 | // return accumulator;
93 | // },
94 | // {
95 | // [CARDDATESTATUSENUMS.LASTDAY]: [],
96 | // [CARDDATESTATUSENUMS.ONGOING]: [],
97 | // [CARDDATESTATUSENUMS.EXPIRED]: [],
98 | // }
99 | // );
100 |
101 | // return result;
102 | // };
103 |
--------------------------------------------------------------------------------
/src/helpers/formatData.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 | import { MINDAYSRETORECEIVE } from "../constants/static";
3 | import { viewHandlerCopyable } from "./itemFormat";
4 |
5 | export const formatNavigations = (arr) => {
6 | return Object.entries(arr)
7 | .reduce((acc, [key, data]) => {
8 | const newData = data.map((obj) => ({ ...obj, module: key }));
9 | acc.push(newData);
10 | return acc;
11 | }, [])
12 | .flat();
13 | };
14 |
15 | export const arrayToSelectDropdown = (arr, keyValue, keyLabel) => {
16 | return arr.map((obj) => ({ label: obj[keyLabel], value: obj[keyValue] }));
17 | };
18 |
19 | export const getBirthdate = (month, day, year) => {
20 | if (month && day && year) {
21 | const date = moment(`${month} ${day}, ${year}`, "MMMM D, YYYY");
22 |
23 | return date.isValid() ? date.format("YYYY-MM-DD") : "";
24 | } else {
25 | return "";
26 | }
27 | };
28 |
29 | export const decodeHtmlEntities = (html, isCopyable = false) => {
30 | var txt = document.createElement("textarea");
31 | txt.innerHTML = html;
32 | return isCopyable ? viewHandlerCopyable(txt.value) : txt.value;
33 | };
34 |
35 | export const sortArrayObjects = (arr, key, type = "desc") => {
36 | return arr.sort((a, b) => {
37 | if (type == "desc") {
38 | if (a[key] > b[key]) return 1;
39 | if (a[key] < b[key]) return -1;
40 | } else {
41 | if (a[key] < b[key]) return 1;
42 | if (a[key] > b[key]) return -1;
43 | }
44 | return 0;
45 | });
46 | };
47 |
48 | export const scannerAddStatusOnCitizenMedicines = (data) => {
49 | return {
50 | ...data,
51 | medicines: (data?.medicines || []).map((obj) => {
52 | const monthlyQty = parseInt(obj.quantity || 0);
53 | const receivedQty = parseInt(obj.receivedQty || 0);
54 | let isInvalid = true;
55 | let errorMessage = "";
56 | let formValue = null;
57 | let maxQuantity = null;
58 | let remainingDays = 0;
59 |
60 | if (obj.status == 0) {
61 | isInvalid = false;
62 | formValue = monthlyQty;
63 | } else if (obj.status == 1) {
64 | const days = moment().diff(moment(obj.dateDistributed), "days");
65 | if (receivedQty < monthlyQty && days < MINDAYSRETORECEIVE) {
66 | isInvalid = false;
67 | formValue = monthlyQty - receivedQty;
68 | maxQuantity = monthlyQty - receivedQty;
69 | errorMessage = `Have received total of ${receivedQty} last ${moment(
70 | obj.dateDistributed
71 | ).format("ll")}`;
72 | } else if (days < MINDAYSRETORECEIVE) {
73 | remainingDays = MINDAYSRETORECEIVE - days;
74 | errorMessage = `${remainingDays} days left to get another medicine.(${moment()
75 | .add(remainingDays, "day")
76 | .format("ll")})`;
77 | } else {
78 | isInvalid = false;
79 | formValue = monthlyQty;
80 | maxQuantity = monthlyQty;
81 | }
82 | } else {
83 | const days = moment().diff(moment(obj.dateDistributed), "days");
84 | remainingDays = MINDAYSRETORECEIVE - days;
85 | if (days < MINDAYSRETORECEIVE) {
86 | errorMessage = `${remainingDays} days left to get another medicine.(${moment()
87 | .add(remainingDays, "day")
88 | .format("ll")})`;
89 | } else {
90 | isInvalid = false;
91 | formValue = monthlyQty;
92 | }
93 | }
94 |
95 | return {
96 | ...obj,
97 | isInvalid,
98 | maxQuantity,
99 | errorMessage,
100 | formValue,
101 | remainingDays,
102 | };
103 | }),
104 | };
105 | };
106 |
107 | export const getAllMedicines = (data = []) => {
108 | const result = data
109 | .reduce((acc, item) => {
110 | acc.push(...(item?.medicines || []));
111 | return acc;
112 | }, [])
113 | .map((obj) => ({ ...obj, quantity: obj.monthlyQuantity }))
114 | .reduce((acc, item) => {
115 | const isExist = acc.find((obj) => obj.productId === item.productId);
116 | if (isExist) {
117 | acc = acc.map((obj) => {
118 | if (obj.productId === item.productId) {
119 | return { ...obj, quantity: obj.quantity + item.quantity };
120 | } else {
121 | return obj;
122 | }
123 | });
124 | } else {
125 | acc.push(item);
126 | }
127 |
128 | return acc;
129 | }, []);
130 |
131 | return result;
132 | };
133 |
134 | export const getAge = (birthdate) => {
135 | return moment().diff(birthdate, "years");
136 | };
137 |
--------------------------------------------------------------------------------
/src/helpers/getStatus.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 | const MINDAYSRETORECEIVE = 25;
3 |
4 | export const getScannerMedicineIsInvalid = (date, total, current) => {
5 | if (total <= 0) {
6 | return true;
7 | } else if (date) {
8 | const days = moment(date).diff(moment(), "days");
9 | if (current < total && days >= MINDAYSRETORECEIVE) {
10 | return false;
11 | } else {
12 | return true;
13 | }
14 | } else {
15 | return false;
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/src/helpers/getUserAgent.js:
--------------------------------------------------------------------------------
1 | import { USERAGENT } from "../constants/enums";
2 |
3 | export const getUSERAGENT = () => {
4 | const ua = navigator.USERAGENT;
5 | if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {
6 | return USERAGENT.TABLET;
7 | } else if (
8 | /Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(
9 | ua
10 | )
11 | ) {
12 | return USERAGENT.MOBILE;
13 | } else {
14 | return USERAGENT.WEB;
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/src/helpers/itemFormat.jsx:
--------------------------------------------------------------------------------
1 | import { Typography } from "antd";
2 | import { motion } from "framer-motion";
3 | import { Link } from "react-router-dom";
4 |
5 | export const getItem = (label, key, icon, children, ...props) => {
6 | return {
7 | key,
8 | icon,
9 | children,
10 | label,
11 | ...props,
12 | };
13 | };
14 |
15 | export const viewHandler = (value) => (value ? value : "N/A");
16 |
17 | export const viewHandlerCopyable = (value) =>
18 | value ? {value} : "N/A";
19 |
20 | export const generateItems = (arr = []) => {
21 | return arr.map((obj) =>
22 | getItem(
23 |
37 | {obj.label}
38 | ,
39 | obj.link,
40 |
41 | {obj.icon}
42 | )
43 | );
44 | };
45 |
46 | export const getSex = (data) => {
47 | if (data == 0) {
48 | return "Male";
49 | } else if (data == 1) {
50 | return "Female";
51 | } else {
52 | return "N/A";
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/src/helpers/regExp.js:
--------------------------------------------------------------------------------
1 | // Password
2 | export const passwordSmallLetter = new RegExp(/[a-z]/g);
3 | export const passwordCapitalLetter = new RegExp(/[A-Z]/g);
4 | export const passwordNumber = new RegExp(/[0-9]/g);
5 | export const passwordSpecialChar = new RegExp(/[!@#$%^&)(+=.-]/g);
6 | export const passwordLength = new RegExp(/[a-zA-Z0-9!@#$%^&)(+=.-]{6}/g);
7 | //
8 |
9 | export const regExp_Names = new RegExp(/^[a-zA-Z\s]+$/);
10 | export const regExp_StudentId = new RegExp(/^\d{4}-\d{2}-\d{6}$/);
11 | export const regExp_Username = new RegExp(/^[a-zA-Z0-9\s]+$/);
12 | export const regExp_MobileNumber = new RegExp(/^[9][0-9]{9}$/);
13 | export const regExp_Email = new RegExp(
14 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/g
15 | );
16 |
--------------------------------------------------------------------------------
/src/helpers/tobase64.js:
--------------------------------------------------------------------------------
1 | import * as xlsx from "xlsx";
2 |
3 | // File to base64
4 | export const toBase64 = (file) =>
5 | new Promise((resolve, reject) => {
6 | const reader = new FileReader();
7 | reader.readAsDataURL(file);
8 | reader.onload = () => resolve(reader.result);
9 | reader.onerror = (error) => reject(error);
10 | });
11 |
12 | export const convertToBase64 = async (file) => {
13 | try {
14 | const result = await toBase64(file);
15 | return result;
16 | } catch (error) {
17 | alert("Image convertion failed. Please try again or contact support.");
18 | return;
19 | }
20 | };
21 |
22 | // URL to base64
23 | export const imageToBase64 = (imageUrl) => {
24 | return new Promise((resolve, reject) => {
25 | const img = new Image();
26 | img.crossOrigin = "Anonymous";
27 | img.onload = () => {
28 | const canvas = document.createElement("canvas");
29 | canvas.width = img.width;
30 | canvas.height = img.height;
31 | const ctx = canvas.getContext("2d");
32 | ctx.drawImage(img, 0, 0, img.width, img.height);
33 | const base64 = canvas.toDataURL("image/png");
34 | resolve(base64);
35 | };
36 | img.onerror = (error) => {
37 | reject(error);
38 | };
39 | img.src = imageUrl;
40 | });
41 | };
42 |
43 | export const fileCSVToJSON = (file) => {
44 | return new Promise((resolve, reject) => {
45 | if (file) {
46 | const reader = new FileReader();
47 | reader.onload = (e) => {
48 | const data = e.target.result;
49 | const workbook = xlsx.read(data, { type: "array" });
50 | const sheetName = workbook.SheetNames[0];
51 | const worksheet = workbook.Sheets[sheetName];
52 | const json = xlsx.utils.sheet_to_json(worksheet, { defval: "" });
53 | resolve(json); // Resolve the promise with the JSON data
54 | };
55 | reader.readAsArrayBuffer(file);
56 | } else {
57 | reject("File is not provided."); // Reject the promise if no file is provided
58 | }
59 | });
60 | };
61 |
62 | export const validateSheetJsonKeys = (filePath, keys, data = []) => {
63 | // STATUS
64 | // 0 : invalid file
65 | // -1 : Invalid Data columns
66 | // 1 : valid data
67 |
68 | var allowedExtensions = /(\.xlsx|\.csv|\.xls)$/i;
69 | if (!allowedExtensions.exec(filePath)) {
70 | return 0;
71 | } else {
72 | let errArr = [];
73 | let errKeys = [];
74 |
75 | keys.map((key) => {
76 | // eslint-disable-next-line no-prototype-builtins
77 | if (data?.[0]?.hasOwnProperty(key)) {
78 | errArr.push(true);
79 | errKeys.push(key);
80 | } else {
81 | errArr.push(false);
82 | }
83 | });
84 |
85 | if (errArr.includes(false)) {
86 | return -1;
87 | } else {
88 | return 1;
89 | }
90 | }
91 | };
92 |
--------------------------------------------------------------------------------
/src/hooks/regExp.js:
--------------------------------------------------------------------------------
1 | // Password
2 | export const passwordSmallLetter = new RegExp(/[a-z]/g);
3 | export const passwordCapitalLetter = new RegExp(/[A-Z]/g);
4 | export const passwordNumber = new RegExp(/[0-9]/g);
5 | export const passwordSpecialChar = new RegExp(/[!@#$%^&)(+=.-]/g);
6 | export const passwordLength = new RegExp(/[a-zA-Z0-9!@#$%^&)(+=.-]{6}/g);
7 | //
8 |
9 | export const regExp_Names = new RegExp(/^[a-zA-Z\s]+$/);
10 | export const regExp_StudentId = new RegExp(/^\d{4}-\d{2}-\d{6}$/);
11 | export const regExp_Username = new RegExp(/^[a-zA-Z0-9\s]+$/);
12 | export const regExp_MobileNumber = new RegExp(/^[9][0-9]{9}$/);
13 | export const regExp_Email = new RegExp(
14 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/g
15 | );
16 |
17 | export const regExp_TrimName = (string) => {
18 | return string
19 | ? string
20 | .replace(/[^A-Za-z ñÑ]+/g, "")
21 | .replace(/^\s+|\s+$/g, "")
22 | .replace(/\s+/g, " ")
23 | : "";
24 | };
25 |
26 | export const regExp_Trim = (string) => {
27 | return string ? string.replace(/^\s+|\s+$/g, "").replace(/\s+/g, " ") : "";
28 | };
29 |
--------------------------------------------------------------------------------
/src/hooks/useTableSearch.jsx:
--------------------------------------------------------------------------------
1 | import { FileSearchOutlined, SearchOutlined } from "@ant-design/icons";
2 | import { Button, Input, Space, Typography } from "antd";
3 | import { useRef, useState } from "react";
4 | import Highlighter from "react-highlight-words";
5 | import { decodeHtmlEntities } from "../helpers/formatData";
6 |
7 | const useTableSearch = () => {
8 | const [searchText, setSearchText] = useState("");
9 | const [searchedColumn, setSearchedColumn] = useState("");
10 | const searchInput = useRef(null);
11 |
12 | const handleSearch = (selectedKeys, confirm, dataIndex) => {
13 | confirm();
14 | setSearchText(selectedKeys[0]);
15 | setSearchedColumn(dataIndex);
16 | };
17 |
18 | const handleReset = (clearFilters, selectedKeys, confirm, dataIndex) => {
19 | clearFilters();
20 | setSearchText("");
21 | confirm();
22 | setSearchedColumn(dataIndex);
23 | };
24 |
25 | const getColumnSearchProps = (dataIndex) => ({
26 | filterDropdown: ({
27 | setSelectedKeys,
28 | selectedKeys,
29 | confirm,
30 | clearFilters,
31 | close,
32 | }) => (
33 | e.stopPropagation()}
38 | >
39 |
44 | setSelectedKeys(e.target.value ? [e.target.value] : [])
45 | }
46 | onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
47 | style={{
48 | marginBottom: 8,
49 | display: "block",
50 | }}
51 | />
52 |
53 |
64 |
76 |
89 |
98 |
99 |
100 | ),
101 | filterIcon: (filtered) =>
102 | filtered ? (
103 |
104 | ) : (
105 |
111 | ),
112 | onFilter: (value, record) => {
113 | return record[dataIndex]
114 | ?.toString()
115 | ?.toUpperCase()
116 | ?.includes(value.toUpperCase());
117 | },
118 | onFilterDropdownOpenChange: (visible) => {
119 | if (visible) {
120 | setTimeout(() => searchInput.current?.select(), 100);
121 | }
122 | },
123 | });
124 | const renderInputSearch = (dataIndex, isCopyable = false) => ({
125 | render: (text) => {
126 | return searchedColumn === dataIndex ? (
127 | isCopyable ? (
128 |
129 |
138 |
143 |
144 | ) : (
145 |
154 | )
155 | ) : (
156 | decodeHtmlEntities(text, isCopyable)
157 | );
158 | },
159 | });
160 | return { getColumnSearchProps, renderInputSearch };
161 | };
162 |
163 | export default useTableSearch;
164 |
--------------------------------------------------------------------------------
/src/hooks/useTableSearchServerside.jsx:
--------------------------------------------------------------------------------
1 | import { FileSearchOutlined, SearchOutlined } from "@ant-design/icons";
2 | import { Button, Input, Space } from "antd";
3 | import { useRef, useState } from "react";
4 | import Highlighter from "react-highlight-words";
5 | const useTableSearch = () => {
6 | const [searchText, setSearchText] = useState("");
7 | const [searchedColumn, setSearchedColumn] = useState("");
8 | const searchInput = useRef(null);
9 |
10 | const handleSearch = (selectedKeys, confirm, dataIndex) => {
11 | confirm();
12 | setSearchText(selectedKeys[0]);
13 | setSearchedColumn(dataIndex);
14 | };
15 |
16 | const handleReset = (clearFilters, selectedKeys, confirm, dataIndex) => {
17 | clearFilters();
18 | setSearchText("");
19 | confirm();
20 | setSearchedColumn(dataIndex);
21 | };
22 |
23 | const getColumnSearchProps = (dataIndex) => ({
24 | filterDropdown: ({
25 | setSelectedKeys,
26 | selectedKeys,
27 | confirm,
28 | clearFilters,
29 | close,
30 | }) => {
31 | return (
32 | e.stopPropagation()}
37 | >
38 |
43 | setSelectedKeys(e.target.value ? [e.target.value] : [])
44 | }
45 | onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
46 | style={{
47 | marginBottom: 8,
48 | display: "block",
49 | }}
50 | />
51 |
52 |
63 |
75 |
88 |
97 |
98 |
99 | );
100 | },
101 | filterIcon: (filtered) =>
102 | filtered ? (
103 |
104 | ) : (
105 |
111 | ),
112 | onFilter: (value, record) => {
113 | return record[dataIndex]
114 | .toString()
115 | .toUpperCase()
116 | .includes(value.toUpperCase());
117 | },
118 | onFilterDropdownOpenChange: (visible) => {
119 | if (visible) {
120 | setTimeout(() => searchInput.current?.select(), 100);
121 | }
122 | },
123 | });
124 |
125 | const renderInputSearch = (dataIndex) => ({
126 | render: (text) => {
127 | return searchedColumn === dataIndex ? (
128 |
137 | ) : (
138 | text
139 | );
140 | },
141 | });
142 |
143 | return { getColumnSearchProps, renderInputSearch };
144 | };
145 |
146 | export default useTableSearch;
147 |
--------------------------------------------------------------------------------
/src/hooks/useWindowSize.js:
--------------------------------------------------------------------------------
1 | import { useLayoutEffect, useState } from "react";
2 |
3 | export const useWindowSize = () => {
4 | const [size, setSize] = useState([0, 0]);
5 | useLayoutEffect(() => {
6 | function updateSize() {
7 | setSize([window.innerWidth, window.innerHeight]);
8 | }
9 | window.addEventListener("resize", updateSize);
10 | updateSize();
11 | return () => window.removeEventListener("resize", updateSize);
12 | }, []);
13 | return size;
14 | };
15 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | font-family: 'Inter';
7 | font-synthesis: none;
8 | text-rendering: optimizeLegibility;
9 | }
10 |
11 | body{
12 | margin: 0;
13 | }
14 |
15 | .customExportModal .ant-modal-body div{
16 | display: grid;
17 | }
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 | import "./index.css";
5 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
6 | import { ConfigProvider } from "antd";
7 |
8 | const queryClient = new QueryClient();
9 | ReactDOM.createRoot(document.getElementById("root")).render(
10 |
11 |
12 |
26 |
27 |
28 |
29 |
30 | );
31 |
--------------------------------------------------------------------------------
/src/pages/admin/Dashboard/index.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Modal } from "antd";
2 | import { useAdminAuthStore } from "../../../store/admin/useAuth";
3 | import { ExclamationCircleFilled } from "@ant-design/icons";
4 |
5 | const { confirm } = Modal;
6 | const Dashboard = () => {
7 | const { reset } = useAdminAuthStore();
8 |
9 | const handleLogout = () => {
10 | confirm({
11 | title: "LOGOUT",
12 | icon: ,
13 | content: "CONFIRMATION message",
14 | okText: "CONFIRM",
15 | okButtonProps: {
16 | danger: true,
17 | },
18 | onOk() {
19 | reset();
20 | },
21 | });
22 | };
23 |
24 | return (
25 | <>
26 |
27 |
Dashboard
28 |
31 |
32 | >
33 | );
34 | };
35 |
36 | export default Dashboard;
37 |
--------------------------------------------------------------------------------
/src/pages/admin/Login.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Form, Input } from "antd";
2 | import { loginBg, logo } from "../../assets/images";
3 | import { useLoginApi } from "../../services/requests/admin/useLoginAuth";
4 |
5 | const Login = () => {
6 | const [form] = Form.useForm();
7 | const { isLoading, mutate } = useLoginApi();
8 |
9 | const onFinish = (values) => {
10 | mutate(values);
11 | };
12 |
13 | form.setFieldsValue({
14 | username: "admin",
15 | password: "Tphr@2024!",
16 | });
17 |
18 | return (
19 |
26 |
27 |
28 |
29 |
30 |
31 | ADMIN
32 |
33 |
34 |
35 |
51 |
52 |
53 |
54 |
55 |
64 |
65 |
66 |
67 |
68 |
69 |
78 |
79 |
80 |
81 |
82 |
83 | Don’t have an account?{" "}
84 |
85 |
86 | Sign Up!
87 |
88 |
89 |
90 |
91 |
92 | );
93 | };
94 |
95 | export default Login;
96 |
--------------------------------------------------------------------------------
/src/pages/admin/index.jsx:
--------------------------------------------------------------------------------
1 | import Login from "./Login";
2 | import Dashboard from "./Dashboard";
3 |
4 | export { Login, Dashboard };
5 |
--------------------------------------------------------------------------------
/src/routes/index.jsx:
--------------------------------------------------------------------------------
1 | import { RouterProvider, createBrowserRouter } from "react-router-dom";
2 | import AdminRoutes from "./pageRoutes/AdminRoutes";
3 |
4 | const RootRoutes = () => {
5 | const router = createBrowserRouter([
6 | { path: "/admin/*", Component: AdminRoutes },
7 | { path: "/*", Component: AdminRoutes },
8 | ]);
9 |
10 | return ;
11 | };
12 |
13 | export default RootRoutes;
14 |
--------------------------------------------------------------------------------
/src/routes/pageRoutes/AdminRoutes.jsx:
--------------------------------------------------------------------------------
1 | import { Navigate, Route, Routes } from "react-router-dom";
2 | import { Auth, UnAuth } from "../validateAuth";
3 | import { useAdminAuthStore } from "../../store/admin/useAuth";
4 | import { Dashboard, Login } from "../../pages/admin";
5 |
6 | const AdminRoutes = () => {
7 | return (
8 |
9 | } />
10 |
11 | }
13 | >
14 | } />
15 |
16 |
17 | }>
18 | } />
19 |
20 |
21 | );
22 | };
23 |
24 | export default AdminRoutes;
25 |
--------------------------------------------------------------------------------
/src/routes/validateAuth.jsx:
--------------------------------------------------------------------------------
1 | import { Navigate, Outlet, useLocation } from "react-router-dom";
2 |
3 | export const Auth = ({ store, redirect }) => {
4 | const { token, userData } = store();
5 | const location = useLocation();
6 |
7 | return token && userData ? (
8 |
9 | ) : (
10 |
11 | );
12 | };
13 |
14 | export const UnAuth = ({ store, redirect }) => {
15 | const { token, userData } = store();
16 | const location = useLocation();
17 |
18 | return token && userData ? (
19 |
20 | ) : (
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/src/services/api/admin/auth.js:
--------------------------------------------------------------------------------
1 | import { axiosAuth } from "../../axios";
2 |
3 | export const loginApi = async (payload) => {
4 | return await axiosAuth.post("/api/v1/cms/auth/login", payload);
5 | };
6 |
--------------------------------------------------------------------------------
/src/services/axios.js:
--------------------------------------------------------------------------------
1 | import { message } from "antd";
2 | import axios from "axios";
3 | import { useAdminAuthStore } from "../store/admin/useAuth";
4 | import { useUserAuthStore } from "../store/user/useAuth";
5 |
6 | export const axiosAuth = axios.create({
7 | baseURL: import.meta.env.VITE_BASE_URL,
8 | headers: {
9 | "Content-Type": "application/json",
10 | "Access-Control-Allow-Origin": "*",
11 | },
12 | });
13 |
14 | export const getUsersValues = {
15 | admin: "ADMIN",
16 | user: "USER",
17 | };
18 |
19 | export const getUserToken = (user = getUsersValues.user) => {
20 | if (user === getUsersValues.admin) {
21 | return useAdminAuthStore.getState();
22 | } else {
23 | return useUserAuthStore.getState();
24 | }
25 | };
26 |
27 | export const createAxiosInstanceWithInterceptor = (type = "data", user) => {
28 | const headers = {
29 | "Access-Control-Allow-Origin": "*",
30 | };
31 |
32 | if (type === "data") {
33 | headers["Content-Type"] = "application/json";
34 | } else {
35 | headers["content-type"] = "multipart/form-data";
36 | }
37 |
38 | const instance = axios.create({
39 | baseURL: import.meta.env.VITE_BASE_URL,
40 | headers,
41 | });
42 |
43 | instance.interceptors.request.use(async (config) => {
44 | try {
45 | const { token } = getUserToken(user);
46 | if (token) {
47 | config.headers.Authorization = `Bearer ${token}`;
48 | } else {
49 | throw new Error("Authorization token not found.");
50 | }
51 | } catch (error) {
52 | console.error({ error });
53 | }
54 | return config;
55 | });
56 |
57 | instance.interceptors.response.use(
58 | (response) => {
59 | // Any status code that lie within the range of 2xx cause this function to trigger
60 | // Do something with response data
61 | return response;
62 | },
63 | function (error) {
64 | const { reset } = getUserToken(user);
65 | const errMessage = error.response?.data;
66 | if (errMessage?.message === "Invalid token." || errMessage?.code == 300) {
67 | message.warning(
68 | "Unable to process transaction. You have to login again."
69 | );
70 | reset();
71 | }
72 | // Any status codes that falls outside the range of 2xx cause this function to trigger
73 | // Do something with response error
74 | return Promise.reject(error);
75 | }
76 | );
77 |
78 | return instance;
79 | };
80 |
--------------------------------------------------------------------------------
/src/services/requests/admin/useLoginAuth.js:
--------------------------------------------------------------------------------
1 | import { message } from "antd";
2 | import { loginApi } from "../../api/admin/auth";
3 | import { useMutation } from "@tanstack/react-query";
4 | import { useAdminAuthStore } from "../../../store/admin/useAuth";
5 |
6 | export const useLoginApi = () => {
7 | const { setuserData, setToken } = useAdminAuthStore();
8 |
9 | return useMutation({
10 | mutationFn: loginApi,
11 | onSuccess: ({ data }) => {
12 | setuserData(data?.data);
13 | setToken(data.token);
14 | },
15 | onError: (error) => {
16 | message.warning(
17 | error?.response?.data?.message ||
18 | "Login failed. Please check your connection or contact support."
19 | );
20 | },
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/src/store/admin/useAuth.js:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 | import { createJSONStorage, persist } from "zustand/middleware";
3 |
4 | export const useAdminAuthStore = create(
5 | persist(
6 | (set) => ({
7 | userData: null,
8 | token: null,
9 | setuserData: (userData) => set({ userData }),
10 | setToken: (token) => set({ token }),
11 | reset: () => set({ userData: null, token: null }),
12 | }),
13 | {
14 | name: "adminAuth",
15 | storage: createJSONStorage(() => sessionStorage),
16 | }
17 | )
18 | );
19 |
--------------------------------------------------------------------------------
/src/store/user/useAuth.js:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 | import { createJSONStorage, persist } from "zustand/middleware";
3 |
4 | export const useUserAuthStore = create(
5 | persist(
6 | (set) => ({
7 | userData: null,
8 | token: null,
9 | setuserData: (userData) => set({ userData }),
10 | setToken: (token) => set({ token }),
11 | reset: () => set({ userData: null, token: null }),
12 | }),
13 | {
14 | name: "userAuth",
15 | storage: createJSONStorage(() => sessionStorage),
16 | }
17 | )
18 | );
19 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | corePlugins: {
4 | preflight: false,
5 | },
6 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
7 | theme: {
8 | extend: {
9 | colors: {
10 | primaryColor: "#CC0011",
11 | },
12 | },
13 | },
14 | plugins: [],
15 | };
16 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | server: {
8 | host: true,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------