create-fe-boilerplate 0.3.2 → 0.4.0

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 (109) hide show
  1. package/package.json +1 -1
  2. package/templates/react/js/scss/axios/README.md +73 -0
  3. package/templates/react/js/scss/axios/eslint.config.js +23 -0
  4. package/templates/react/js/scss/axios/index.html +13 -0
  5. package/templates/react/js/scss/axios/package-lock.json +4517 -0
  6. package/templates/react/js/scss/axios/package.json +35 -0
  7. package/templates/react/js/scss/axios/public/vite.svg +1 -0
  8. package/templates/react/js/scss/axios/react-ts-tailwind-axios/.env.example +1 -0
  9. package/templates/react/js/scss/axios/src/App.css +0 -0
  10. package/templates/react/js/scss/axios/src/App.jsx +14 -0
  11. package/templates/react/js/scss/axios/src/api/axios.js +201 -0
  12. package/templates/react/js/scss/axios/src/component/auth/VerifyOtpModal.jsx +60 -0
  13. package/templates/react/js/scss/axios/src/component/common/ui/Toast/Toast.js +58 -0
  14. package/templates/react/js/scss/axios/src/contants/constants.js +45 -0
  15. package/templates/react/js/scss/axios/src/index.css +4 -0
  16. package/templates/react/js/scss/axios/src/main.jsx +12 -0
  17. package/templates/react/js/scss/axios/src/pages/Auth.scss +203 -0
  18. package/templates/react/js/scss/axios/src/pages/Dashboard.jsx +10 -0
  19. package/templates/react/js/scss/axios/src/pages/Login.jsx +69 -0
  20. package/templates/react/js/scss/axios/src/pages/Signup.jsx +74 -0
  21. package/templates/react/js/scss/axios/src/router/AppRoutes.jsx +19 -0
  22. package/templates/react/js/scss/axios/src/styles/global.scss +5 -0
  23. package/templates/react/js/scss/axios/src/utils/utils.js +58 -0
  24. package/templates/react/js/scss/axios/tsconfig.app.json +28 -0
  25. package/templates/react/js/scss/axios/vite.config.js +7 -0
  26. package/templates/react/js/scss/rtk/README.md +73 -0
  27. package/templates/react/js/scss/rtk/eslint.config.js +23 -0
  28. package/templates/react/js/scss/rtk/index.html +13 -0
  29. package/templates/react/js/scss/rtk/package-lock.json +4208 -0
  30. package/templates/react/js/scss/rtk/package.json +35 -0
  31. package/templates/react/js/scss/rtk/public/vite.svg +1 -0
  32. package/templates/react/js/scss/rtk/react-ts-tailwind-axios/.env.example +1 -0
  33. package/templates/react/js/scss/rtk/src/App.css +0 -0
  34. package/templates/react/js/scss/rtk/src/App.jsx +14 -0
  35. package/templates/react/js/scss/rtk/src/api/axios.js +201 -0
  36. package/templates/react/js/scss/rtk/src/component/auth/VerifyOtpModal.jsx +61 -0
  37. package/templates/react/js/scss/rtk/src/component/auth/VerifyOtpModal.scss +62 -0
  38. package/templates/react/js/scss/rtk/src/component/common/ui/Toast/Toast.js +58 -0
  39. package/templates/react/js/scss/rtk/src/contants/constants.js +45 -0
  40. package/templates/react/js/scss/rtk/src/main.jsx +16 -0
  41. package/templates/react/js/scss/rtk/src/pages/Dashboard.jsx +12 -0
  42. package/templates/react/js/scss/rtk/src/pages/Dashboard.scss +13 -0
  43. package/templates/react/js/scss/rtk/src/pages/Login.jsx +75 -0
  44. package/templates/react/js/scss/rtk/src/pages/Login.scss +67 -0
  45. package/templates/react/js/scss/rtk/src/pages/Signup.jsx +73 -0
  46. package/templates/react/js/scss/rtk/src/pages/Signup.scss +66 -0
  47. package/templates/react/js/scss/rtk/src/router/AppRoutes.jsx +19 -0
  48. package/templates/react/js/scss/rtk/src/store/index.js +10 -0
  49. package/templates/react/js/scss/rtk/src/store/services/api.js +34 -0
  50. package/templates/react/js/scss/rtk/src/styles/global.scss +10 -0
  51. package/templates/react/js/scss/rtk/src/utils/utils.js +59 -0
  52. package/templates/react/js/scss/rtk/tsconfig.app.json +28 -0
  53. package/templates/react/js/scss/rtk/vite.config.js +7 -0
  54. package/templates/react/ts/scss/axios/README.md +73 -0
  55. package/templates/react/ts/scss/axios/eslint.config.js +23 -0
  56. package/templates/react/ts/scss/axios/index.html +13 -0
  57. package/templates/react/ts/scss/axios/package-lock.json +4868 -0
  58. package/templates/react/ts/scss/axios/package.json +39 -0
  59. package/templates/react/ts/scss/axios/public/vite.svg +1 -0
  60. package/templates/react/ts/scss/axios/react-ts-tailwind-axios/.env.example +1 -0
  61. package/templates/react/ts/scss/axios/src/App.css +0 -0
  62. package/templates/react/ts/scss/axios/src/App.tsx +14 -0
  63. package/templates/react/ts/scss/axios/src/api/axios.ts +236 -0
  64. package/templates/react/ts/scss/axios/src/component/auth/VerifyOtpModal.tsx +70 -0
  65. package/templates/react/ts/scss/axios/src/component/common/ui/Toast/Toast.ts +58 -0
  66. package/templates/react/ts/scss/axios/src/contants/constants.ts +45 -0
  67. package/templates/react/ts/scss/axios/src/index.css +4 -0
  68. package/templates/react/ts/scss/axios/src/main.tsx +10 -0
  69. package/templates/react/ts/scss/axios/src/pages/Auth.scss +203 -0
  70. package/templates/react/ts/scss/axios/src/pages/Dashboard.tsx +10 -0
  71. package/templates/react/ts/scss/axios/src/pages/Login.tsx +69 -0
  72. package/templates/react/ts/scss/axios/src/pages/Signup.tsx +74 -0
  73. package/templates/react/ts/scss/axios/src/router/AppRoutes.tsx +19 -0
  74. package/templates/react/ts/scss/axios/src/styles/global.scss +5 -0
  75. package/templates/react/ts/scss/axios/src/utils/utils.ts +59 -0
  76. package/templates/react/ts/scss/axios/tsconfig.app.json +28 -0
  77. package/templates/react/ts/scss/axios/tsconfig.json +12 -0
  78. package/templates/react/ts/scss/axios/tsconfig.node.json +26 -0
  79. package/templates/react/ts/scss/axios/vite.config.ts +7 -0
  80. package/templates/react/ts/scss/rtk/README.md +73 -0
  81. package/templates/react/ts/scss/rtk/eslint.config.js +23 -0
  82. package/templates/react/ts/scss/rtk/index.html +13 -0
  83. package/templates/react/ts/scss/rtk/package-lock.json +4218 -0
  84. package/templates/react/ts/scss/rtk/package.json +38 -0
  85. package/templates/react/ts/scss/rtk/public/vite.svg +1 -0
  86. package/templates/react/ts/scss/rtk/react-ts-tailwind-axios/.env.example +1 -0
  87. package/templates/react/ts/scss/rtk/src/App.css +0 -0
  88. package/templates/react/ts/scss/rtk/src/App.tsx +14 -0
  89. package/templates/react/ts/scss/rtk/src/api/axios.ts +236 -0
  90. package/templates/react/ts/scss/rtk/src/component/auth/VerifyOtpModal.scss +62 -0
  91. package/templates/react/ts/scss/rtk/src/component/auth/VerifyOtpModal.tsx +71 -0
  92. package/templates/react/ts/scss/rtk/src/component/common/ui/Toast/Toast.ts +58 -0
  93. package/templates/react/ts/scss/rtk/src/contants/constants.ts +45 -0
  94. package/templates/react/ts/scss/rtk/src/main.tsx +14 -0
  95. package/templates/react/ts/scss/rtk/src/pages/Dashboard.scss +13 -0
  96. package/templates/react/ts/scss/rtk/src/pages/Dashboard.tsx +12 -0
  97. package/templates/react/ts/scss/rtk/src/pages/Login.scss +67 -0
  98. package/templates/react/ts/scss/rtk/src/pages/Login.tsx +75 -0
  99. package/templates/react/ts/scss/rtk/src/pages/Signup.scss +66 -0
  100. package/templates/react/ts/scss/rtk/src/pages/Signup.tsx +73 -0
  101. package/templates/react/ts/scss/rtk/src/router/AppRoutes.tsx +19 -0
  102. package/templates/react/ts/scss/rtk/src/store/index.ts +13 -0
  103. package/templates/react/ts/scss/rtk/src/store/services/api.ts +37 -0
  104. package/templates/react/ts/scss/rtk/src/styles/global.scss +10 -0
  105. package/templates/react/ts/scss/rtk/src/utils/utils.ts +59 -0
  106. package/templates/react/ts/scss/rtk/tsconfig.app.json +28 -0
  107. package/templates/react/ts/scss/rtk/tsconfig.json +12 -0
  108. package/templates/react/ts/scss/rtk/tsconfig.node.json +26 -0
  109. package/templates/react/ts/scss/rtk/vite.config.ts +7 -0
@@ -0,0 +1,38 @@
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
+ "@types/react": "^19.2.5",
28
+ "@types/react-dom": "^19.2.3",
29
+ "@vitejs/plugin-react": "^5.1.1",
30
+ "eslint": "^9.39.1",
31
+ "eslint-plugin-react-hooks": "^7.0.1",
32
+ "eslint-plugin-react-refresh": "^0.4.24",
33
+ "globals": "^16.5.0",
34
+ "typescript": "~5.9.3",
35
+ "typescript-eslint": "^8.46.4",
36
+ "vite": "^7.2.4"
37
+ }
38
+ }
@@ -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,236 @@
1
+ import axios, {
2
+ AxiosError,
3
+ type AxiosInstance,
4
+ type AxiosRequestConfig,
5
+ type AxiosResponse,
6
+ } from "axios";
7
+ import { decryptData, encryptData } from "../utils/utils";
8
+ import { toast } from "react-toastify";
9
+ import { toasts } from "../component/common/ui/Toast/Toast";
10
+ import ENVIRONMENT, {
11
+ ENCRYPTION_EXCLUDED,
12
+ ROUTES,
13
+ } from "../contants/constants";
14
+
15
+ /* ---------------------------------- */
16
+ /* ENV */
17
+ /* ---------------------------------- */
18
+
19
+ const BASE_URL: string = (import.meta as any)?.env?.VITE_API_HOST || "";
20
+
21
+ /* ---------------------------------- */
22
+ /* AXIOS INSTANCE */
23
+ /* ---------------------------------- */
24
+
25
+ export const axiosApi: AxiosInstance = axios.create({
26
+ baseURL: BASE_URL,
27
+ });
28
+
29
+ /* ---------------------------------- */
30
+ /* REQUEST INTERCEPTOR */
31
+ /* ---------------------------------- */
32
+
33
+ axiosApi.interceptors.request.use(
34
+ (
35
+ config: import("axios").InternalAxiosRequestConfig
36
+ ): import("axios").InternalAxiosRequestConfig => {
37
+ const token =
38
+ localStorage.getItem("token") || localStorage.getItem("forgotPassToken");
39
+
40
+ if (token && config.headers) {
41
+ config.headers.Authorization = `Bearer ${token}`;
42
+ }
43
+
44
+ return config;
45
+ },
46
+ (error: AxiosError) => Promise.reject(error)
47
+ );
48
+
49
+ /* ---------------------------------- */
50
+ /* RESPONSE INTERCEPTOR */
51
+ /* ---------------------------------- */
52
+
53
+ axiosApi.interceptors.response.use(
54
+ (response: AxiosResponse): AxiosResponse => {
55
+ if (Number(ENVIRONMENT.ENABLE_ENCRYPTION)) {
56
+ const decryptedData = decryptData(response?.data?.resData);
57
+
58
+ response.data = {
59
+ ...response.data,
60
+ data: decryptedData,
61
+ };
62
+ }
63
+
64
+ return response;
65
+ },
66
+ (error: AxiosError<any>) => {
67
+ if (Number(ENVIRONMENT.ENABLE_ENCRYPTION)) {
68
+ const decryptedData = decryptData(error?.response?.data?.resData);
69
+ handleError(decryptedData);
70
+ throw decryptedData;
71
+ }
72
+
73
+ handleError(error);
74
+ throw error;
75
+ }
76
+ );
77
+
78
+ /* ---------------------------------- */
79
+ /* HELPERS */
80
+ /* ---------------------------------- */
81
+
82
+ const formatUrl = (
83
+ url: string,
84
+ params?: Record<string, string | number>
85
+ ): string => {
86
+ if (!params || Object.keys(params).length === 0) return url;
87
+ return `${url}?${new URLSearchParams(
88
+ params as Record<string, string>
89
+ ).toString()}`;
90
+ };
91
+
92
+ const clearWaitingQueue = (): void => {
93
+ toast.clearWaitingQueue();
94
+ };
95
+
96
+ const handleError = (error: any): void => {
97
+ const errorStatus: number | undefined =
98
+ error?.response?.status || error?.status;
99
+
100
+ const errorMessage: string | undefined =
101
+ error?.response?.data?.message || error?.data?.message || error?.message;
102
+
103
+ if (errorStatus === 401 || errorStatus === 403) {
104
+ toasts.error("Please re-login, last login session expired.");
105
+
106
+ localStorage.clear();
107
+ window.dispatchEvent(new Event("storage"));
108
+ clearWaitingQueue();
109
+
110
+ if (window.location.pathname !== ROUTES.ROOT) {
111
+ window.location.replace(ROUTES.ROOT);
112
+ }
113
+ } else {
114
+ errorMessage && toasts.error(errorMessage);
115
+ clearWaitingQueue();
116
+ }
117
+ };
118
+
119
+ const handleSuccess = (res: any): void => {
120
+ if (res?.status === 200 || res?.status === 201) {
121
+ res?.message && toasts.success(res.message);
122
+ res?.data?.message && toasts.success(res.data.message);
123
+ }
124
+
125
+ if (res?.status === 400 || res?.status === 403) {
126
+ res?.message && toasts.warning(res.message);
127
+ }
128
+ };
129
+
130
+ const getPayloadData = (data: any, url = ""): any => {
131
+ if (!data) return data;
132
+
133
+ if (
134
+ Number(ENVIRONMENT.ENABLE_ENCRYPTION) &&
135
+ !ENCRYPTION_EXCLUDED.includes(url)
136
+ ) {
137
+ return { reqData: encryptData(data) };
138
+ }
139
+
140
+ return data;
141
+ };
142
+
143
+ /* ---------------------------------- */
144
+ /* HTTP METHODS */
145
+ /* ---------------------------------- */
146
+
147
+ export const apiCallGet = async <T = any>(
148
+ url: string,
149
+ params: Record<string, string | number> = {},
150
+ toastOn?: boolean
151
+ ): Promise<T> => {
152
+ try {
153
+ const res = await axiosApi.get(formatUrl(url, params));
154
+ toastOn && handleSuccess(res.data);
155
+ return res.data;
156
+ } catch (error: any) {
157
+ return error?.response?.data;
158
+ }
159
+ };
160
+
161
+ export const apiCallPost = async <T = any>(
162
+ url: string,
163
+ data: any,
164
+ params: Record<string, string | number> = {},
165
+ toastOn?: boolean,
166
+ header?: AxiosRequestConfig
167
+ ): Promise<T> => {
168
+ try {
169
+ const res = await axiosApi.post(
170
+ formatUrl(url, params),
171
+ getPayloadData(data, url),
172
+ header || {}
173
+ );
174
+
175
+ toastOn && handleSuccess(res.data);
176
+ return res.data;
177
+ } catch (error: any) {
178
+ return error?.response?.data;
179
+ }
180
+ };
181
+
182
+ export const apiCallPatch = async <T = any>(
183
+ url: string,
184
+ data: any,
185
+ params: Record<string, string | number> = {},
186
+ toastOn?: boolean
187
+ ): Promise<T> => {
188
+ try {
189
+ const res = await axiosApi.patch(
190
+ formatUrl(url, params),
191
+ getPayloadData(data, url)
192
+ );
193
+
194
+ toastOn && handleSuccess(res.data);
195
+ return res.data;
196
+ } catch (error: any) {
197
+ return error?.response?.data;
198
+ }
199
+ };
200
+
201
+ export const apiCallPut = async <T = any>(
202
+ url: string,
203
+ data: any,
204
+ params: Record<string, string | number> = {},
205
+ toastOn?: boolean
206
+ ): Promise<T> => {
207
+ try {
208
+ const res = await axiosApi.put(
209
+ formatUrl(url, params),
210
+ getPayloadData(data, url)
211
+ );
212
+
213
+ toastOn && handleSuccess(res.data);
214
+ return res.data;
215
+ } catch (error: any) {
216
+ return error?.response?.data;
217
+ }
218
+ };
219
+
220
+ export const apiCallDelete = async <T = any>(
221
+ url: string,
222
+ data: any,
223
+ params: Record<string, string | number> = {},
224
+ toastOn?: boolean
225
+ ): Promise<T> => {
226
+ try {
227
+ const res = await axiosApi.delete(formatUrl(url, params), {
228
+ data: getPayloadData(data, url),
229
+ });
230
+
231
+ toastOn && handleSuccess(res.data);
232
+ return res.data;
233
+ } catch (error) {
234
+ throw error;
235
+ }
236
+ };
@@ -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,71 @@
1
+ import { useState } from "react";
2
+ import { apiCallPost } from "../../api/axios";
3
+ import { toasts } from "../../component/common/ui/Toast/Toast";
4
+ import "./VerifyOtpModal.scss";
5
+
6
+ interface VerifyOtpModalProps {
7
+ isOpen: boolean;
8
+ onClose: () => void;
9
+ onVerifySuccess: () => void;
10
+ }
11
+
12
+ const VerifyOtpModal = ({
13
+ isOpen,
14
+ onClose,
15
+ onVerifySuccess,
16
+ }: VerifyOtpModalProps) => {
17
+ const [otp, setOtp] = useState("");
18
+ const [loading, setLoading] = useState(false);
19
+
20
+ if (!isOpen) return null;
21
+
22
+ const handleVerify = async () => {
23
+ if (otp.length !== 6) {
24
+ toasts.error("Please enter a valid 6-digit OTP");
25
+ return;
26
+ }
27
+
28
+ setLoading(true);
29
+
30
+ // const res = await apiCallPost("/auth/verify-otp", { otp }, {}, true);
31
+
32
+ setLoading(false);
33
+
34
+ // if (res?.success) {
35
+ onVerifySuccess();
36
+ onClose();
37
+ // }
38
+ };
39
+
40
+ return (
41
+ <div className="modal-overlay">
42
+ <div className="modal-content">
43
+ <h2 className="modal-title">Verify OTP</h2>
44
+
45
+ <input
46
+ maxLength={6}
47
+ value={otp}
48
+ onChange={(e) => setOtp(e.target.value.replace(/\D/g, ""))}
49
+ className="otp-input"
50
+ placeholder="Enter OTP"
51
+ />
52
+
53
+ <div className="modal-actions">
54
+ <button onClick={onClose} className="btn-cancel">
55
+ Cancel
56
+ </button>
57
+
58
+ <button
59
+ onClick={handleVerify}
60
+ disabled={loading}
61
+ className="btn-verify"
62
+ >
63
+ {loading ? "Verifying..." : "Verify"}
64
+ </button>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ );
69
+ };
70
+
71
+ export default VerifyOtpModal;
@@ -0,0 +1,58 @@
1
+ import { toast, type ToastOptions } from "react-toastify";
2
+ import "react-toastify/dist/ReactToastify.css";
3
+
4
+ /* ---------------------------------- */
5
+ /* TOAST OPTIONS */
6
+ /* ---------------------------------- */
7
+
8
+ const toastOptions: 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: string): void {
25
+ toast.warn(message, {
26
+ ...toastOptions,
27
+ });
28
+ }
29
+ success(message: string): void {
30
+ toast.success(message, {
31
+ ...toastOptions,
32
+ });
33
+ }
34
+
35
+ error(message: string): void {
36
+ toast.error(message, {
37
+ ...toastOptions,
38
+ });
39
+ }
40
+
41
+ warn(message: string): void {
42
+ toast.warn(message, {
43
+ ...toastOptions,
44
+ });
45
+ }
46
+
47
+ info(message: string): void {
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,14 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import { Provider } from "react-redux";
4
+ import { store } from "./store";
5
+ import App from "./App";
6
+ import "./styles/global.scss";
7
+
8
+ ReactDOM.createRoot(document.getElementById("root")!).render(
9
+ <React.StrictMode>
10
+ <Provider store={store}>
11
+ <App />
12
+ </Provider>
13
+ </React.StrictMode>
14
+ );
@@ -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,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,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
+ }