create-fe-boilerplate 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/bin/index.js +1 -1
  2. package/package.json +1 -1
  3. package/templates/react/js/scss/axios/README.md +73 -0
  4. package/templates/react/js/scss/axios/eslint.config.js +23 -0
  5. package/templates/react/js/scss/axios/index.html +13 -0
  6. package/templates/react/js/scss/axios/package-lock.json +4517 -0
  7. package/templates/react/js/scss/axios/package.json +35 -0
  8. package/templates/react/js/scss/axios/public/vite.svg +1 -0
  9. package/templates/react/js/scss/axios/react-ts-tailwind-axios/.env.example +1 -0
  10. package/templates/react/js/scss/axios/src/App.css +0 -0
  11. package/templates/react/js/scss/axios/src/App.jsx +14 -0
  12. package/templates/react/js/scss/axios/src/api/axios.js +201 -0
  13. package/templates/react/js/scss/axios/src/component/auth/VerifyOtpModal.jsx +60 -0
  14. package/templates/react/js/scss/axios/src/component/common/ui/Toast/Toast.js +58 -0
  15. package/templates/react/js/scss/axios/src/contants/constants.js +45 -0
  16. package/templates/react/js/scss/axios/src/index.css +4 -0
  17. package/templates/react/js/scss/axios/src/main.jsx +12 -0
  18. package/templates/react/js/scss/axios/src/pages/Auth.scss +203 -0
  19. package/templates/react/js/scss/axios/src/pages/Dashboard.jsx +10 -0
  20. package/templates/react/js/scss/axios/src/pages/Login.jsx +69 -0
  21. package/templates/react/js/scss/axios/src/pages/Signup.jsx +74 -0
  22. package/templates/react/js/scss/axios/src/router/AppRoutes.jsx +19 -0
  23. package/templates/react/js/scss/axios/src/styles/global.scss +5 -0
  24. package/templates/react/js/scss/axios/src/utils/utils.js +58 -0
  25. package/templates/react/js/scss/axios/tsconfig.app.json +28 -0
  26. package/templates/react/js/scss/axios/vite.config.js +7 -0
  27. package/templates/react/js/scss/rtk/README.md +73 -0
  28. package/templates/react/js/scss/rtk/eslint.config.js +23 -0
  29. package/templates/react/js/scss/rtk/index.html +13 -0
  30. package/templates/react/js/scss/rtk/package-lock.json +4208 -0
  31. package/templates/react/js/scss/rtk/package.json +35 -0
  32. package/templates/react/js/scss/rtk/public/vite.svg +1 -0
  33. package/templates/react/js/scss/rtk/react-ts-tailwind-axios/.env.example +1 -0
  34. package/templates/react/js/scss/rtk/src/App.css +0 -0
  35. package/templates/react/js/scss/rtk/src/App.jsx +14 -0
  36. package/templates/react/js/scss/rtk/src/api/axios.js +201 -0
  37. package/templates/react/js/scss/rtk/src/component/auth/VerifyOtpModal.jsx +61 -0
  38. package/templates/react/js/scss/rtk/src/component/auth/VerifyOtpModal.scss +62 -0
  39. package/templates/react/js/scss/rtk/src/component/common/ui/Toast/Toast.js +58 -0
  40. package/templates/react/js/scss/rtk/src/contants/constants.js +45 -0
  41. package/templates/react/js/scss/rtk/src/main.jsx +16 -0
  42. package/templates/react/js/scss/rtk/src/pages/Dashboard.jsx +12 -0
  43. package/templates/react/js/scss/rtk/src/pages/Dashboard.scss +13 -0
  44. package/templates/react/js/scss/rtk/src/pages/Login.jsx +75 -0
  45. package/templates/react/js/scss/rtk/src/pages/Login.scss +67 -0
  46. package/templates/react/js/scss/rtk/src/pages/Signup.jsx +73 -0
  47. package/templates/react/js/scss/rtk/src/pages/Signup.scss +66 -0
  48. package/templates/react/js/scss/rtk/src/router/AppRoutes.jsx +19 -0
  49. package/templates/react/js/scss/rtk/src/store/index.js +10 -0
  50. package/templates/react/js/scss/rtk/src/store/services/api.js +34 -0
  51. package/templates/react/js/scss/rtk/src/styles/global.scss +10 -0
  52. package/templates/react/js/scss/rtk/src/utils/utils.js +59 -0
  53. package/templates/react/js/scss/rtk/tsconfig.app.json +28 -0
  54. package/templates/react/js/scss/rtk/vite.config.js +7 -0
  55. package/templates/react/ts/scss/axios/README.md +73 -0
  56. package/templates/react/ts/scss/axios/eslint.config.js +23 -0
  57. package/templates/react/ts/scss/axios/index.html +13 -0
  58. package/templates/react/ts/scss/axios/package-lock.json +4868 -0
  59. package/templates/react/ts/scss/axios/package.json +39 -0
  60. package/templates/react/ts/scss/axios/public/vite.svg +1 -0
  61. package/templates/react/ts/scss/axios/react-ts-tailwind-axios/.env.example +1 -0
  62. package/templates/react/ts/scss/axios/src/App.css +0 -0
  63. package/templates/react/ts/scss/axios/src/App.tsx +14 -0
  64. package/templates/react/ts/scss/axios/src/api/axios.ts +236 -0
  65. package/templates/react/ts/scss/axios/src/component/auth/VerifyOtpModal.tsx +70 -0
  66. package/templates/react/ts/scss/axios/src/component/common/ui/Toast/Toast.ts +58 -0
  67. package/templates/react/ts/scss/axios/src/contants/constants.ts +45 -0
  68. package/templates/react/ts/scss/axios/src/index.css +4 -0
  69. package/templates/react/ts/scss/axios/src/main.tsx +10 -0
  70. package/templates/react/ts/scss/axios/src/pages/Auth.scss +203 -0
  71. package/templates/react/ts/scss/axios/src/pages/Dashboard.tsx +10 -0
  72. package/templates/react/ts/scss/axios/src/pages/Login.tsx +69 -0
  73. package/templates/react/ts/scss/axios/src/pages/Signup.tsx +74 -0
  74. package/templates/react/ts/scss/axios/src/router/AppRoutes.tsx +19 -0
  75. package/templates/react/ts/scss/axios/src/styles/global.scss +5 -0
  76. package/templates/react/ts/scss/axios/src/utils/utils.ts +59 -0
  77. package/templates/react/ts/scss/axios/tsconfig.app.json +28 -0
  78. package/templates/react/ts/scss/axios/tsconfig.json +12 -0
  79. package/templates/react/ts/scss/axios/tsconfig.node.json +26 -0
  80. package/templates/react/ts/scss/axios/vite.config.ts +7 -0
  81. package/templates/react/ts/scss/rtk/README.md +73 -0
  82. package/templates/react/ts/scss/rtk/eslint.config.js +23 -0
  83. package/templates/react/ts/scss/rtk/index.html +13 -0
  84. package/templates/react/ts/scss/rtk/package-lock.json +4218 -0
  85. package/templates/react/ts/scss/rtk/package.json +38 -0
  86. package/templates/react/ts/scss/rtk/public/vite.svg +1 -0
  87. package/templates/react/ts/scss/rtk/react-ts-tailwind-axios/.env.example +1 -0
  88. package/templates/react/ts/scss/rtk/src/App.css +0 -0
  89. package/templates/react/ts/scss/rtk/src/App.tsx +14 -0
  90. package/templates/react/ts/scss/rtk/src/api/axios.ts +236 -0
  91. package/templates/react/ts/scss/rtk/src/component/auth/VerifyOtpModal.scss +62 -0
  92. package/templates/react/ts/scss/rtk/src/component/auth/VerifyOtpModal.tsx +71 -0
  93. package/templates/react/ts/scss/rtk/src/component/common/ui/Toast/Toast.ts +58 -0
  94. package/templates/react/ts/scss/rtk/src/contants/constants.ts +45 -0
  95. package/templates/react/ts/scss/rtk/src/main.tsx +14 -0
  96. package/templates/react/ts/scss/rtk/src/pages/Dashboard.scss +13 -0
  97. package/templates/react/ts/scss/rtk/src/pages/Dashboard.tsx +12 -0
  98. package/templates/react/ts/scss/rtk/src/pages/Login.scss +67 -0
  99. package/templates/react/ts/scss/rtk/src/pages/Login.tsx +75 -0
  100. package/templates/react/ts/scss/rtk/src/pages/Signup.scss +66 -0
  101. package/templates/react/ts/scss/rtk/src/pages/Signup.tsx +73 -0
  102. package/templates/react/ts/scss/rtk/src/router/AppRoutes.tsx +19 -0
  103. package/templates/react/ts/scss/rtk/src/store/index.ts +13 -0
  104. package/templates/react/ts/scss/rtk/src/store/services/api.ts +37 -0
  105. package/templates/react/ts/scss/rtk/src/styles/global.scss +10 -0
  106. package/templates/react/ts/scss/rtk/src/utils/utils.ts +59 -0
  107. package/templates/react/ts/scss/rtk/tsconfig.app.json +28 -0
  108. package/templates/react/ts/scss/rtk/tsconfig.json +12 -0
  109. package/templates/react/ts/scss/rtk/tsconfig.node.json +26 -0
  110. package/templates/react/ts/scss/rtk/vite.config.ts +7 -0
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "react-ts-tailwind-axios",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@reduxjs/toolkit": "^2.11.2",
14
+ "axios": "^1.13.2",
15
+ "crypto-js": "^4.2.0",
16
+ "react": "^19.2.0",
17
+ "react-dom": "^19.2.0",
18
+ "react-redux": "^9.2.0",
19
+ "react-router-dom": "^7.12.0",
20
+ "react-toastify": "^11.0.5",
21
+ "sass": "^1.97.2"
22
+ },
23
+ "devDependencies": {
24
+ "@eslint/js": "^9.39.1",
25
+ "@types/crypto-js": "^4.2.2",
26
+ "@types/node": "^24.10.1",
27
+ "@vitejs/plugin-react": "^5.1.1",
28
+ "eslint": "^9.39.1",
29
+ "eslint-plugin-react-hooks": "^7.0.1",
30
+ "eslint-plugin-react-refresh": "^0.4.24",
31
+ "globals": "^16.5.0",
32
+ "typescript-eslint": "^8.46.4",
33
+ "vite": "^7.2.4"
34
+ }
35
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
@@ -0,0 +1 @@
1
+ VITE_API_HOST=https://api.example.com
File without changes
@@ -0,0 +1,14 @@
1
+ import { ToastContainer } from "react-toastify";
2
+ import "react-toastify/dist/ReactToastify.css";
3
+ import AppRouter from "./router/AppRoutes";
4
+
5
+ const App = () => {
6
+ return (
7
+ <>
8
+ <AppRouter />
9
+ <ToastContainer aria-label="Notification Container" />
10
+ </>
11
+ );
12
+ };
13
+
14
+ export default App;
@@ -0,0 +1,201 @@
1
+ import axios from "axios";
2
+ import { decryptData, encryptData } from "../utils/utils";
3
+ import { toast } from "react-toastify";
4
+ import { toasts } from "../component/common/ui/Toast/Toast";
5
+ import ENVIRONMENT, {
6
+ ENCRYPTION_EXCLUDED,
7
+ ROUTES,
8
+ } from "../contants/constants";
9
+
10
+ /* ---------------------------------- */
11
+ /* ENV */
12
+ /* ---------------------------------- */
13
+
14
+ const BASE_URL = import.meta?.env?.VITE_API_HOST || "";
15
+
16
+ /* ---------------------------------- */
17
+ /* AXIOS INSTANCE */
18
+ /* ---------------------------------- */
19
+
20
+ export const axiosApi = axios.create({
21
+ baseURL: BASE_URL,
22
+ });
23
+
24
+ /* ---------------------------------- */
25
+ /* REQUEST INTERCEPTOR */
26
+ /* ---------------------------------- */
27
+
28
+ axiosApi.interceptors.request.use(
29
+ (config) => {
30
+ const token =
31
+ localStorage.getItem("token") || localStorage.getItem("forgotPassToken");
32
+
33
+ if (token && config.headers) {
34
+ config.headers.Authorization = `Bearer ${token}`;
35
+ }
36
+
37
+ return config;
38
+ },
39
+ (error) => Promise.reject(error)
40
+ );
41
+
42
+ /* ---------------------------------- */
43
+ /* RESPONSE INTERCEPTOR */
44
+ /* ---------------------------------- */
45
+
46
+ axiosApi.interceptors.response.use(
47
+ (response) => {
48
+ if (Number(ENVIRONMENT.ENABLE_ENCRYPTION)) {
49
+ const decryptedData = decryptData(response?.data?.resData);
50
+
51
+ response.data = {
52
+ ...response.data,
53
+ data: decryptedData,
54
+ };
55
+ }
56
+
57
+ return response;
58
+ },
59
+ (error) => {
60
+ if (Number(ENVIRONMENT.ENABLE_ENCRYPTION)) {
61
+ const decryptedData = decryptData(error?.response?.data?.resData);
62
+ handleError(decryptedData);
63
+ throw decryptedData;
64
+ }
65
+
66
+ handleError(error);
67
+ throw error;
68
+ }
69
+ );
70
+
71
+ /* ---------------------------------- */
72
+ /* HELPERS */
73
+ /* ---------------------------------- */
74
+
75
+ const formatUrl = (url, params = {}) => {
76
+ if (!params || Object.keys(params).length === 0) return url;
77
+
78
+ return `${url}?${new URLSearchParams(params).toString()}`;
79
+ };
80
+
81
+ const clearWaitingQueue = () => {
82
+ toast.clearWaitingQueue();
83
+ };
84
+
85
+ const handleError = (error) => {
86
+ const errorStatus = error?.response?.status || error?.status;
87
+
88
+ const errorMessage =
89
+ error?.response?.data?.message || error?.data?.message || error?.message;
90
+
91
+ if (errorStatus === 401 || errorStatus === 403) {
92
+ toasts.error("Please re-login, last login session expired.");
93
+
94
+ localStorage.clear();
95
+ window.dispatchEvent(new Event("storage"));
96
+ clearWaitingQueue();
97
+
98
+ if (window.location.pathname !== ROUTES.ROOT) {
99
+ window.location.replace(ROUTES.ROOT);
100
+ }
101
+ } else {
102
+ if (errorMessage) {
103
+ toasts.error(errorMessage);
104
+ }
105
+ clearWaitingQueue();
106
+ }
107
+ };
108
+
109
+ const handleSuccess = (res) => {
110
+ if (res?.status === 200 || res?.status === 201) {
111
+ res?.message && toasts.success(res.message);
112
+ res?.data?.message && toasts.success(res.data.message);
113
+ }
114
+
115
+ if (res?.status === 400 || res?.status === 403) {
116
+ res?.message && toasts.warning(res.message);
117
+ }
118
+ };
119
+
120
+ const getPayloadData = (data, url = "") => {
121
+ if (!data) return data;
122
+
123
+ if (
124
+ Number(ENVIRONMENT.ENABLE_ENCRYPTION) &&
125
+ !ENCRYPTION_EXCLUDED.includes(url)
126
+ ) {
127
+ return { reqData: encryptData(data) };
128
+ }
129
+
130
+ return data;
131
+ };
132
+
133
+ /* ---------------------------------- */
134
+ /* HTTP METHODS */
135
+ /* ---------------------------------- */
136
+
137
+ export const apiCallGet = async (url, params = {}, toastOn) => {
138
+ try {
139
+ const res = await axiosApi.get(formatUrl(url, params));
140
+ toastOn && handleSuccess(res.data);
141
+ return res.data;
142
+ } catch (error) {
143
+ return error?.response?.data;
144
+ }
145
+ };
146
+
147
+ export const apiCallPost = async (url, data, params = {}, toastOn, header) => {
148
+ try {
149
+ const res = await axiosApi.post(
150
+ formatUrl(url, params),
151
+ getPayloadData(data, url),
152
+ header || {}
153
+ );
154
+
155
+ toastOn && handleSuccess(res.data);
156
+ return res.data;
157
+ } catch (error) {
158
+ return error?.response?.data;
159
+ }
160
+ };
161
+
162
+ export const apiCallPatch = async (url, data, params = {}, toastOn) => {
163
+ try {
164
+ const res = await axiosApi.patch(
165
+ formatUrl(url, params),
166
+ getPayloadData(data, url)
167
+ );
168
+
169
+ toastOn && handleSuccess(res.data);
170
+ return res.data;
171
+ } catch (error) {
172
+ return error?.response?.data;
173
+ }
174
+ };
175
+
176
+ export const apiCallPut = async (url, data, params = {}, toastOn) => {
177
+ try {
178
+ const res = await axiosApi.put(
179
+ formatUrl(url, params),
180
+ getPayloadData(data, url)
181
+ );
182
+
183
+ toastOn && handleSuccess(res.data);
184
+ return res.data;
185
+ } catch (error) {
186
+ return error?.response?.data;
187
+ }
188
+ };
189
+
190
+ export const apiCallDelete = async (url, data, params = {}, toastOn) => {
191
+ try {
192
+ const res = await axiosApi.delete(formatUrl(url, params), {
193
+ data: getPayloadData(data, url),
194
+ });
195
+
196
+ toastOn && handleSuccess(res.data);
197
+ return res.data;
198
+ } catch (error) {
199
+ throw error;
200
+ }
201
+ };
@@ -0,0 +1,61 @@
1
+ import { useState } from "react";
2
+ import { apiCallPost } from "../../api/axios";
3
+ import { toasts } from "../common/ui/Toast/Toast";
4
+ import "./VerifyOtpModal.scss";
5
+
6
+ const VerifyOtpModal = ({ isOpen, onClose, onVerifySuccess }) => {
7
+ const [otp, setOtp] = useState("");
8
+ const [loading, setLoading] = useState(false);
9
+
10
+ if (!isOpen) return null;
11
+
12
+ const handleVerify = async () => {
13
+ if (otp.length !== 6) {
14
+ toasts.error("Please enter a valid 6-digit OTP");
15
+ return;
16
+ }
17
+
18
+ setLoading(true);
19
+
20
+ // const res = await apiCallPost("/auth/verify-otp", { otp }, {}, true);
21
+
22
+ setLoading(false);
23
+
24
+ // if (res?.success) {
25
+ onVerifySuccess();
26
+ onClose();
27
+ // }
28
+ };
29
+
30
+ return (
31
+ <div className="modal-overlay">
32
+ <div className="modal-content">
33
+ <h2 className="modal-title">Verify OTP</h2>
34
+
35
+ <input
36
+ maxLength={6}
37
+ value={otp}
38
+ onChange={(e) => setOtp(e.target.value.replace(/\D/g, ""))}
39
+ className="otp-input"
40
+ placeholder="Enter OTP"
41
+ />
42
+
43
+ <div className="modal-actions">
44
+ <button onClick={onClose} className="btn-cancel">
45
+ Cancel
46
+ </button>
47
+
48
+ <button
49
+ onClick={handleVerify}
50
+ disabled={loading}
51
+ className="btn-verify"
52
+ >
53
+ {loading ? "Verifying..." : "Verify"}
54
+ </button>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ );
59
+ };
60
+
61
+ export default VerifyOtpModal;
@@ -0,0 +1,62 @@
1
+ .modal-overlay {
2
+ position: fixed;
3
+ inset: 0;
4
+ background-color: rgba(0, 0, 0, 0.4); // bg-black bg-opacity-40
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ z-index: 50;
9
+
10
+
11
+ .modal-content {
12
+ background-color: #ffffff;
13
+ width: 24rem; // w-96
14
+ padding: 1.5rem; // p-6
15
+ border-radius: 0.5rem; // rounded
16
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); // shadow-lg
17
+
18
+ .modal-title {
19
+ font-size: 1.25rem; // text-xl
20
+ font-weight: 700; // font-bold
21
+ margin-bottom: 1rem; // mb-4
22
+ }
23
+
24
+ .otp-input {
25
+ border: 1px solid #d1d5db;
26
+ width: 100%;
27
+ padding: 0.5rem; // p-2
28
+ margin-bottom: 1rem; // mb-4
29
+ text-align: center;
30
+ letter-spacing: 0.25em; // tracking-widest
31
+ font-size: 1.125rem; // text-lg
32
+ border-radius: 0.25rem;
33
+ }
34
+
35
+ .modal-actions {
36
+ display: flex;
37
+ justify-content: space-between;
38
+
39
+ .btn-cancel {
40
+ padding: 0.5rem 1rem; // px-4 py-2
41
+ border: 1px solid #d1d5db;
42
+ border-radius: 0.25rem;
43
+ background: transparent;
44
+ cursor: pointer;
45
+ }
46
+
47
+ .btn-verify {
48
+ padding: 0.5rem 1rem; // px-4 py-2
49
+ background-color: #2563eb; // bg-blue-600
50
+ color: #ffffff;
51
+ border-radius: 0.25rem;
52
+ border: none;
53
+ cursor: pointer;
54
+
55
+ &:disabled {
56
+ opacity: 0.7;
57
+ cursor: not-allowed;
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,58 @@
1
+ import { toast } from "react-toastify";
2
+ import "react-toastify/dist/ReactToastify.css";
3
+
4
+ /* ---------------------------------- */
5
+ /* TOAST OPTIONS */
6
+ /* ---------------------------------- */
7
+
8
+ const toastOptions = {
9
+ position: "top-right",
10
+ autoClose: 1000,
11
+ closeOnClick: true,
12
+ pauseOnHover: true,
13
+ draggable: true,
14
+ progress: 0,
15
+ hideProgressBar: false,
16
+ theme: "light",
17
+ };
18
+
19
+ /* ---------------------------------- */
20
+ /* TOASTER CLASS */
21
+ /* ---------------------------------- */
22
+
23
+ class Toaster {
24
+ warning(message) {
25
+ toast.warn(message, {
26
+ ...toastOptions,
27
+ });
28
+ }
29
+ success(message) {
30
+ toast.success(message, {
31
+ ...toastOptions,
32
+ });
33
+ }
34
+
35
+ error(message) {
36
+ toast.error(message, {
37
+ ...toastOptions,
38
+ });
39
+ }
40
+
41
+ warn(message) {
42
+ toast.warn(message, {
43
+ ...toastOptions,
44
+ });
45
+ }
46
+
47
+ info(message) {
48
+ toast.info(message, {
49
+ ...toastOptions,
50
+ });
51
+ }
52
+ }
53
+
54
+ /* ---------------------------------- */
55
+ /* EXPORT SINGLETON */
56
+ /* ---------------------------------- */
57
+
58
+ export const toasts = new Toaster();
@@ -0,0 +1,45 @@
1
+ export const API_URLS = {
2
+ LOGIN: "api/v1/auth/login",
3
+ SIGNUP: "api/v1/auth/signup",
4
+ LOGOUT: "api/v1/auth/logout",
5
+ REFRESH_TOKEN: "api/v1/auth/refresh-token",
6
+ IMAGE_UPLOAD: "api/v1/auth/image-upload",
7
+ };
8
+
9
+ export const ROUTES = {
10
+ ROOT: "/",
11
+ LOGIN: "/login",
12
+ SIGNUP: "/signup",
13
+ DASHBOARD: "/dashboard",
14
+ PROFILE: "/profile",
15
+ SETTINGS: "/settings",
16
+ };
17
+
18
+ const ENVIRONMENT = {
19
+ ENABLE_ENCRYPTION: import.meta.env.VITE_ENABLE_ENCRYPTION,
20
+ API_HOST: import.meta.env.VITE_API_HOST,
21
+ S3_BUCKET_URL: import.meta.env.VITE_S3_BUCKET,
22
+ STRING: import.meta.env.VITE_STRING,
23
+
24
+ // CHAIN
25
+ RPC_URL: import.meta.env.VITE_RPC_URL,
26
+ CHAIN_ID: import.meta.env.VITE_CHAIN_ID,
27
+ CHAIN_SYMBOL: import.meta.env.VITE_CHAIN_SYMBOL,
28
+ CHAIN_NAME: import.meta.env.VITE_CHAIN_NAME,
29
+
30
+ // CONTRACT
31
+ EXAM_CONTRACT_ADD: import.meta.env.VITE_EXAM_CONTRACT_ADD,
32
+ STUDENT_CONTRACT_ADD: import.meta.env.VITE_STUDENT_CONTRACT_ADD,
33
+ RSA_PRIVATE_KEY: import.meta.env.VITE_RSA_PRIVATE_KEY,
34
+
35
+ // AWS S3 Configuration
36
+ AWS_S3_BUCKET_NAME: import.meta.env.VITE_AWS_S3_BUCKET_NAME,
37
+ AWS_ACCESS_KEY_ID: import.meta.env.VITE_AWS_ACCESS_KEY_ID,
38
+ AWS_SECRET_ACCESS_KEY: import.meta.env.VITE_AWS_SECRET_ACCESS_KEY,
39
+ AWS_REGION: import.meta.env.VITE_AWS_REGION,
40
+ S3_BUCKET_CDN_LINK: import.meta.env.VITE_S3_BUCKET_CDN_LINK,
41
+ IMAGE_KIT_URL: import.meta.env.VITE_IMAGE_KIT_URL,
42
+ };
43
+ export const ENCRYPTION_EXCLUDED = [API_URLS?.IMAGE_UPLOAD];
44
+
45
+ export default ENVIRONMENT;
@@ -0,0 +1,16 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import "./styles/global.scss";
4
+ import App from "./App.jsx";
5
+ import { Provider } from "react-redux";
6
+ import { store } from "./store/index.js";
7
+
8
+ const rootElement = document.getElementById("root");
9
+
10
+ createRoot(rootElement).render(
11
+ <StrictMode>
12
+ <Provider store={store}>
13
+ <App />
14
+ </Provider>
15
+ </StrictMode>
16
+ );
@@ -0,0 +1,12 @@
1
+ import "./Dashboard.scss";
2
+
3
+ const Dashboard = () => {
4
+ return (
5
+ <div className="dashboard-container">
6
+ <h1 className="dashboard-title">Dashboard</h1>
7
+ <p className="dashboard-text">This is a static sample dashboard.</p>
8
+ </div>
9
+ );
10
+ };
11
+
12
+ export default Dashboard;
@@ -0,0 +1,13 @@
1
+ .dashboard-container {
2
+ padding: 1.5rem; // p-6
3
+
4
+ .dashboard-title {
5
+ font-size: 1.5rem; // text-2xl
6
+ font-weight: 700; // font-bold
7
+ }
8
+
9
+ .dashboard-text {
10
+ margin-top: 0.5rem; // mt-2
11
+ color: #4b5563; // text-gray-600
12
+ }
13
+ }
@@ -0,0 +1,75 @@
1
+ import { useState } from "react";
2
+ import { Link, useNavigate } from "react-router-dom";
3
+ import VerifyOtpModal from "../component/auth/VerifyOtpModal";
4
+ import "./Login.scss";
5
+ import { useLoginMutation } from "../store/services/api";
6
+
7
+ const Login = () => {
8
+ const navigate = useNavigate();
9
+ const [login, { isLoading }] = useLoginMutation();
10
+
11
+ const [showOtp, setShowOtp] = useState(false);
12
+
13
+ const [form, setForm] = useState({
14
+ email: "",
15
+ password: "",
16
+ });
17
+
18
+ const handleLogin = async () => {
19
+ // await login({
20
+ // email: "test@test.com",
21
+ // password: "123456",
22
+ // });
23
+ setShowOtp(true);
24
+ };
25
+
26
+ const handleOtpSuccess = () => {
27
+ localStorage.setItem("token", "dummy-token");
28
+ navigate("/dashboard");
29
+ };
30
+
31
+ return (
32
+ <div className="login-container">
33
+ <div className="auth-card">
34
+ <h2 className="auth-title">Login</h2>
35
+
36
+ <input
37
+ className="auth-input"
38
+ placeholder="Email"
39
+ value={form.email}
40
+ onChange={(e) => setForm({ ...form, email: e.target.value })}
41
+ />
42
+
43
+ <input
44
+ type="password"
45
+ className="auth-input password-input"
46
+ placeholder="Password"
47
+ value={form.password}
48
+ onChange={(e) => setForm({ ...form, password: e.target.value })}
49
+ />
50
+
51
+ <button
52
+ className="auth-button"
53
+ onClick={handleLogin}
54
+ disabled={isLoading}
55
+ >
56
+ {isLoading ? "Loading..." : "Login"}
57
+ </button>
58
+ <span className="auth-footer">
59
+ Dont have an account?{" "}
60
+ <Link to="/signup" className="auth-link">
61
+ Signup
62
+ </Link>
63
+ </span>
64
+ </div>
65
+
66
+ <VerifyOtpModal
67
+ isOpen={showOtp}
68
+ onClose={() => setShowOtp(false)}
69
+ onVerifySuccess={handleOtpSuccess}
70
+ />
71
+ </div>
72
+ );
73
+ };
74
+
75
+ export default Login;
@@ -0,0 +1,67 @@
1
+ .login-container {
2
+ min-height: 100vh;
3
+ display: flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ background-color: #f3f4f6;
7
+
8
+ .auth-card {
9
+ width: 24rem; // w-96
10
+ padding: 1.5rem; // p-6
11
+ background-color: #ffffff;
12
+ border-radius: 0.375rem; // rounded
13
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); // shadow
14
+
15
+ .auth-title {
16
+ font-size: 1.25rem; // text-xl
17
+ font-weight: 700; // font-bold
18
+ margin-bottom: 1rem; // mb-4
19
+ }
20
+
21
+ .auth-input {
22
+ width: 100%;
23
+ padding: 0.5rem; // p-2
24
+ margin-bottom: 0.5rem; // mb-2
25
+ border: 1px solid #d1d5db; // border
26
+ border-radius: 0.25rem; // rounded
27
+
28
+ &.password-input {
29
+ margin-bottom: 1rem; // mb-4
30
+ }
31
+ }
32
+
33
+ .auth-button {
34
+ width: 100%;
35
+ background-color: #2563eb; // blue-600
36
+ color: #ffffff;
37
+ padding: 0.5rem 0;
38
+ border-radius: 0.25rem;
39
+ border: none;
40
+ cursor: pointer;
41
+ font-weight: 600;
42
+
43
+ &:disabled {
44
+ opacity: 0.7;
45
+ cursor: not-allowed;
46
+ }
47
+ }
48
+
49
+ .auth-footer {
50
+ display: block;
51
+ text-align: center;
52
+ margin-top: 0.5rem; // mt-2
53
+ font-size: 0.875rem; // text-sm
54
+ color: #4b5563; // text-gray-600
55
+
56
+ .auth-link {
57
+ color: #2563eb; // text-blue-600
58
+ text-decoration: none;
59
+ cursor: pointer;
60
+
61
+ &:hover {
62
+ text-decoration: underline;
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }