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,39 @@
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
+ "axios": "^1.13.2",
14
+ "crypto-js": "^4.2.0",
15
+ "react": "^19.2.0",
16
+ "react-dom": "^19.2.0",
17
+ "react-router-dom": "^7.12.0",
18
+ "react-toastify": "^11.0.5",
19
+ "sass": "^1.97.2"
20
+ },
21
+ "devDependencies": {
22
+ "@eslint/js": "^9.39.1",
23
+ "@types/crypto-js": "^4.2.2",
24
+ "@types/node": "^24.10.1",
25
+ "@types/react": "^19.2.5",
26
+ "@types/react-dom": "^19.2.3",
27
+ "@vitejs/plugin-react": "^5.1.1",
28
+ "autoprefixer": "^10.4.23",
29
+ "eslint": "^9.39.1",
30
+ "eslint-plugin-react-hooks": "^7.0.1",
31
+ "eslint-plugin-react-refresh": "^0.4.24",
32
+ "globals": "^16.5.0",
33
+ "postcss": "^8.5.6",
34
+ "tailwindcss": "^3.4.17",
35
+ "typescript": "~5.9.3",
36
+ "typescript-eslint": "^8.46.4",
37
+ "vite": "^7.2.4"
38
+ }
39
+ }
@@ -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,70 @@
1
+ import { useState } from "react";
2
+ import "../../pages/Auth.scss";
3
+ import { toasts } from "../../component/common/ui/Toast/Toast";
4
+ import { apiCallPost } from "../../api/axios";
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
+ // Simulate API call or use apiCallPost if ready
30
+ // const res = await apiCallPost("/auth/verify-otp", { otp }, {}, true);
31
+
32
+ setTimeout(() => {
33
+ setLoading(false);
34
+ onVerifySuccess();
35
+ onClose();
36
+ }, 1000);
37
+ };
38
+
39
+ return (
40
+ <div className="modal-overlay">
41
+ <div className="modal-content">
42
+ <h2 className="modal-title">Verify OTP</h2>
43
+
44
+ <input
45
+ maxLength={6}
46
+ value={otp}
47
+ onChange={(e) => setOtp(e.target.value.replace(/\D/g, ""))}
48
+ className="modal-input"
49
+ placeholder="000000"
50
+ />
51
+
52
+ <div className="modal-actions">
53
+ <button onClick={onClose} className="btn-cancel">
54
+ Cancel
55
+ </button>
56
+
57
+ <button
58
+ onClick={handleVerify}
59
+ disabled={loading}
60
+ className="btn-verify"
61
+ >
62
+ {loading ? "Verifying..." : "Verify"}
63
+ </button>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ );
68
+ };
69
+
70
+ 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,4 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import "./styles/global.scss";
4
+ import App from "./App.tsx";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>
10
+ );
@@ -0,0 +1,203 @@
1
+ $primary-color: #4f46e5;
2
+ $primary-hover: #4338ca;
3
+ $bg-gradient: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
4
+ $card-bg: #ffffff;
5
+ $text-main: #1f2937;
6
+ $text-muted: #6b7280;
7
+ $border-color: #e5e7eb;
8
+ $shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
9
+
10
+ .login-wrapper {
11
+ min-height: 100vh;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ background: $bg-gradient;
16
+ padding: 1.5rem;
17
+
18
+ .login-card {
19
+ background: $card-bg;
20
+ width: 100%;
21
+ max-width: 400px;
22
+ padding: 2.5rem;
23
+ border-radius: 1rem;
24
+ box-shadow: $shadow;
25
+ transition: transform 0.3s ease;
26
+
27
+ &:hover {
28
+ transform: translateY(-4px);
29
+ }
30
+
31
+ .login-title {
32
+ font-size: 1.875rem;
33
+ font-weight: 700;
34
+ color: $text-main;
35
+ margin-bottom: 2rem;
36
+ text-align: center;
37
+ }
38
+
39
+ .login-input {
40
+ width: 100%;
41
+ padding: 0.75rem 1rem;
42
+ margin-bottom: 1rem;
43
+ border: 1px solid $border-color;
44
+ border-radius: 0.5rem;
45
+ font-size: 1rem;
46
+ transition: all 0.2s ease;
47
+ box-sizing: border-box;
48
+
49
+ &:focus {
50
+ outline: none;
51
+ border-color: $primary-color;
52
+ box-shadow: 0 0 0 3px rgba($primary-color, 0.1);
53
+ }
54
+
55
+ &::placeholder {
56
+ color: #9ca3af;
57
+ }
58
+ }
59
+
60
+ .login-button {
61
+ width: 100%;
62
+ padding: 0.75rem;
63
+ background-color: $primary-color;
64
+ color: white;
65
+ border: none;
66
+ border-radius: 0.5rem;
67
+ font-size: 1rem;
68
+ font-weight: 600;
69
+ cursor: pointer;
70
+ transition: background-color 0.2s ease;
71
+ margin-top: 0.5rem;
72
+
73
+ &:hover {
74
+ background-color: $primary-hover;
75
+ }
76
+
77
+ &:active {
78
+ transform: scale(0.98);
79
+ }
80
+ }
81
+
82
+ .login-footer {
83
+ margin-top: 1.5rem;
84
+ text-align: center;
85
+ font-size: 0.875rem;
86
+ color: $text-muted;
87
+
88
+ .signup-link {
89
+ color: $primary-color;
90
+ text-decoration: none;
91
+ font-weight: 600;
92
+ margin-left: 0.25rem;
93
+
94
+ &:hover {
95
+ text-decoration: underline;
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ .modal-overlay {
103
+ position: fixed;
104
+ inset: 0;
105
+ background-color: rgba(0, 0, 0, 0.5);
106
+ backdrop-filter: blur(4px);
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ z-index: 1000;
111
+ padding: 1rem;
112
+
113
+ .modal-content {
114
+ background: $card-bg;
115
+ width: 100%;
116
+ max-width: 400px;
117
+ padding: 2rem;
118
+ border-radius: 1rem;
119
+ box-shadow: $shadow;
120
+ animation: modal-fade-in 0.3s ease-out;
121
+
122
+ .modal-title {
123
+ font-size: 1.5rem;
124
+ font-weight: 700;
125
+ color: $text-main;
126
+ margin-bottom: 1.5rem;
127
+ text-align: center;
128
+ }
129
+
130
+ .modal-input {
131
+ width: 100%;
132
+ padding: 0.75rem 1rem;
133
+ margin-bottom: 1.5rem;
134
+ border: 1px solid $border-color;
135
+ border-radius: 0.5rem;
136
+ font-size: 1.25rem;
137
+ text-align: center;
138
+ letter-spacing: 0.5rem;
139
+ transition: all 0.2s ease;
140
+ box-sizing: border-box;
141
+
142
+ &:focus {
143
+ outline: none;
144
+ border-color: $primary-color;
145
+ box-shadow: 0 0 0 3px rgba($primary-color, 0.1);
146
+ }
147
+ }
148
+
149
+ .modal-actions {
150
+ display: flex;
151
+ gap: 1rem;
152
+
153
+ .btn-cancel {
154
+ flex: 1;
155
+ padding: 0.75rem;
156
+ background: transparent;
157
+ border: 1px solid $border-color;
158
+ border-radius: 0.5rem;
159
+ color: $text-muted;
160
+ font-weight: 600;
161
+ cursor: pointer;
162
+ transition: all 0.2s ease;
163
+
164
+ &:hover {
165
+ background: #f9fafb;
166
+ color: $text-main;
167
+ }
168
+ }
169
+
170
+ .btn-verify {
171
+ flex: 2;
172
+ padding: 0.75rem;
173
+ background: $primary-color;
174
+ border: none;
175
+ border-radius: 0.5rem;
176
+ color: white;
177
+ font-weight: 600;
178
+ cursor: pointer;
179
+ transition: background-color 0.2s ease;
180
+
181
+ &:hover {
182
+ background: $primary-hover;
183
+ }
184
+
185
+ &:disabled {
186
+ opacity: 0.7;
187
+ cursor: not-allowed;
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ @keyframes modal-fade-in {
195
+ from {
196
+ opacity: 0;
197
+ transform: scale(0.95);
198
+ }
199
+ to {
200
+ opacity: 1;
201
+ transform: scale(1);
202
+ }
203
+ }
@@ -0,0 +1,10 @@
1
+ const Dashboard = () => {
2
+ return (
3
+ <div className="p-6">
4
+ <h1 className="text-2xl font-bold">Dashboard</h1>
5
+ <p className="mt-2 text-gray-600">This is a static sample dashboard.</p>
6
+ </div>
7
+ );
8
+ };
9
+
10
+ export default Dashboard;