create-fe-boilerplate 0.3.1 → 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 (131) hide show
  1. package/package.json +1 -1
  2. package/templates/next/js/tailwind/axios/README.md +36 -0
  3. package/templates/next/js/tailwind/axios/eslint.config.mjs +18 -0
  4. package/templates/next/js/tailwind/axios/next.config.js +6 -0
  5. package/templates/next/js/tailwind/axios/package-lock.json +6626 -0
  6. package/templates/next/js/tailwind/axios/package.json +27 -0
  7. package/templates/next/js/tailwind/axios/postcss.config.mjs +7 -0
  8. package/templates/next/js/tailwind/axios/public/file.svg +1 -0
  9. package/templates/next/js/tailwind/axios/public/globe.svg +1 -0
  10. package/templates/next/js/tailwind/axios/public/next.svg +1 -0
  11. package/templates/next/js/tailwind/axios/public/vercel.svg +1 -0
  12. package/templates/next/js/tailwind/axios/public/window.svg +1 -0
  13. package/templates/next/js/tailwind/axios/src/api/auth.js +10 -0
  14. package/templates/next/js/tailwind/axios/src/api/axios.js +15 -0
  15. package/templates/next/js/tailwind/axios/src/app/dashboard/page.jsx +32 -0
  16. package/templates/next/js/tailwind/axios/src/app/favicon.ico +0 -0
  17. package/templates/next/js/tailwind/axios/src/app/globals.css +26 -0
  18. package/templates/next/js/tailwind/axios/src/app/layout.jsx +10 -0
  19. package/templates/next/js/tailwind/axios/src/app/login/page.jsx +70 -0
  20. package/templates/next/js/tailwind/axios/src/app/page.jsx +5 -0
  21. package/templates/next/js/tailwind/axios/src/app/signup/page.jsx +56 -0
  22. package/templates/next/js/tailwind/axios/src/components/AuthGuard.jsx +28 -0
  23. package/templates/next/js/tailwind/axios/src/components/VerifyOtpModal.jsx +49 -0
  24. package/templates/react/js/scss/axios/README.md +73 -0
  25. package/templates/react/js/scss/axios/eslint.config.js +23 -0
  26. package/templates/react/js/scss/axios/index.html +13 -0
  27. package/templates/react/js/scss/axios/package-lock.json +4517 -0
  28. package/templates/react/js/scss/axios/package.json +35 -0
  29. package/templates/react/js/scss/axios/public/vite.svg +1 -0
  30. package/templates/react/js/scss/axios/react-ts-tailwind-axios/.env.example +1 -0
  31. package/templates/react/js/scss/axios/src/App.css +0 -0
  32. package/templates/react/js/scss/axios/src/App.jsx +14 -0
  33. package/templates/react/js/scss/axios/src/api/axios.js +201 -0
  34. package/templates/react/js/scss/axios/src/component/auth/VerifyOtpModal.jsx +60 -0
  35. package/templates/react/js/scss/axios/src/component/common/ui/Toast/Toast.js +58 -0
  36. package/templates/react/js/scss/axios/src/contants/constants.js +45 -0
  37. package/templates/react/js/scss/axios/src/index.css +4 -0
  38. package/templates/react/js/scss/axios/src/main.jsx +12 -0
  39. package/templates/react/js/scss/axios/src/pages/Auth.scss +203 -0
  40. package/templates/react/js/scss/axios/src/pages/Dashboard.jsx +10 -0
  41. package/templates/react/js/scss/axios/src/pages/Login.jsx +69 -0
  42. package/templates/react/js/scss/axios/src/pages/Signup.jsx +74 -0
  43. package/templates/react/js/scss/axios/src/router/AppRoutes.jsx +19 -0
  44. package/templates/react/js/scss/axios/src/styles/global.scss +5 -0
  45. package/templates/react/js/scss/axios/src/utils/utils.js +58 -0
  46. package/templates/react/js/scss/axios/tsconfig.app.json +28 -0
  47. package/templates/react/js/scss/axios/vite.config.js +7 -0
  48. package/templates/react/js/scss/rtk/README.md +73 -0
  49. package/templates/react/js/scss/rtk/eslint.config.js +23 -0
  50. package/templates/react/js/scss/rtk/index.html +13 -0
  51. package/templates/react/js/scss/rtk/package-lock.json +4208 -0
  52. package/templates/react/js/scss/rtk/package.json +35 -0
  53. package/templates/react/js/scss/rtk/public/vite.svg +1 -0
  54. package/templates/react/js/scss/rtk/react-ts-tailwind-axios/.env.example +1 -0
  55. package/templates/react/js/scss/rtk/src/App.css +0 -0
  56. package/templates/react/js/scss/rtk/src/App.jsx +14 -0
  57. package/templates/react/js/scss/rtk/src/api/axios.js +201 -0
  58. package/templates/react/js/scss/rtk/src/component/auth/VerifyOtpModal.jsx +61 -0
  59. package/templates/react/js/scss/rtk/src/component/auth/VerifyOtpModal.scss +62 -0
  60. package/templates/react/js/scss/rtk/src/component/common/ui/Toast/Toast.js +58 -0
  61. package/templates/react/js/scss/rtk/src/contants/constants.js +45 -0
  62. package/templates/react/js/scss/rtk/src/main.jsx +16 -0
  63. package/templates/react/js/scss/rtk/src/pages/Dashboard.jsx +12 -0
  64. package/templates/react/js/scss/rtk/src/pages/Dashboard.scss +13 -0
  65. package/templates/react/js/scss/rtk/src/pages/Login.jsx +75 -0
  66. package/templates/react/js/scss/rtk/src/pages/Login.scss +67 -0
  67. package/templates/react/js/scss/rtk/src/pages/Signup.jsx +73 -0
  68. package/templates/react/js/scss/rtk/src/pages/Signup.scss +66 -0
  69. package/templates/react/js/scss/rtk/src/router/AppRoutes.jsx +19 -0
  70. package/templates/react/js/scss/rtk/src/store/index.js +10 -0
  71. package/templates/react/js/scss/rtk/src/store/services/api.js +34 -0
  72. package/templates/react/js/scss/rtk/src/styles/global.scss +10 -0
  73. package/templates/react/js/scss/rtk/src/utils/utils.js +59 -0
  74. package/templates/react/js/scss/rtk/tsconfig.app.json +28 -0
  75. package/templates/react/js/scss/rtk/vite.config.js +7 -0
  76. package/templates/react/ts/scss/axios/README.md +73 -0
  77. package/templates/react/ts/scss/axios/eslint.config.js +23 -0
  78. package/templates/react/ts/scss/axios/index.html +13 -0
  79. package/templates/react/ts/scss/axios/package-lock.json +4868 -0
  80. package/templates/react/ts/scss/axios/package.json +39 -0
  81. package/templates/react/ts/scss/axios/public/vite.svg +1 -0
  82. package/templates/react/ts/scss/axios/react-ts-tailwind-axios/.env.example +1 -0
  83. package/templates/react/ts/scss/axios/src/App.css +0 -0
  84. package/templates/react/ts/scss/axios/src/App.tsx +14 -0
  85. package/templates/react/ts/scss/axios/src/api/axios.ts +236 -0
  86. package/templates/react/ts/scss/axios/src/component/auth/VerifyOtpModal.tsx +70 -0
  87. package/templates/react/ts/scss/axios/src/component/common/ui/Toast/Toast.ts +58 -0
  88. package/templates/react/ts/scss/axios/src/contants/constants.ts +45 -0
  89. package/templates/react/ts/scss/axios/src/index.css +4 -0
  90. package/templates/react/ts/scss/axios/src/main.tsx +10 -0
  91. package/templates/react/ts/scss/axios/src/pages/Auth.scss +203 -0
  92. package/templates/react/ts/scss/axios/src/pages/Dashboard.tsx +10 -0
  93. package/templates/react/ts/scss/axios/src/pages/Login.tsx +69 -0
  94. package/templates/react/ts/scss/axios/src/pages/Signup.tsx +74 -0
  95. package/templates/react/ts/scss/axios/src/router/AppRoutes.tsx +19 -0
  96. package/templates/react/ts/scss/axios/src/styles/global.scss +5 -0
  97. package/templates/react/ts/scss/axios/src/utils/utils.ts +59 -0
  98. package/templates/react/ts/scss/axios/tsconfig.app.json +28 -0
  99. package/templates/react/ts/scss/axios/tsconfig.json +12 -0
  100. package/templates/react/ts/scss/axios/tsconfig.node.json +26 -0
  101. package/templates/react/ts/scss/axios/vite.config.ts +7 -0
  102. package/templates/react/ts/scss/rtk/README.md +73 -0
  103. package/templates/react/ts/scss/rtk/eslint.config.js +23 -0
  104. package/templates/react/ts/scss/rtk/index.html +13 -0
  105. package/templates/react/ts/scss/rtk/package-lock.json +4218 -0
  106. package/templates/react/ts/scss/rtk/package.json +38 -0
  107. package/templates/react/ts/scss/rtk/public/vite.svg +1 -0
  108. package/templates/react/ts/scss/rtk/react-ts-tailwind-axios/.env.example +1 -0
  109. package/templates/react/ts/scss/rtk/src/App.css +0 -0
  110. package/templates/react/ts/scss/rtk/src/App.tsx +14 -0
  111. package/templates/react/ts/scss/rtk/src/api/axios.ts +236 -0
  112. package/templates/react/ts/scss/rtk/src/component/auth/VerifyOtpModal.scss +62 -0
  113. package/templates/react/ts/scss/rtk/src/component/auth/VerifyOtpModal.tsx +71 -0
  114. package/templates/react/ts/scss/rtk/src/component/common/ui/Toast/Toast.ts +58 -0
  115. package/templates/react/ts/scss/rtk/src/contants/constants.ts +45 -0
  116. package/templates/react/ts/scss/rtk/src/main.tsx +14 -0
  117. package/templates/react/ts/scss/rtk/src/pages/Dashboard.scss +13 -0
  118. package/templates/react/ts/scss/rtk/src/pages/Dashboard.tsx +12 -0
  119. package/templates/react/ts/scss/rtk/src/pages/Login.scss +67 -0
  120. package/templates/react/ts/scss/rtk/src/pages/Login.tsx +75 -0
  121. package/templates/react/ts/scss/rtk/src/pages/Signup.scss +66 -0
  122. package/templates/react/ts/scss/rtk/src/pages/Signup.tsx +73 -0
  123. package/templates/react/ts/scss/rtk/src/router/AppRoutes.tsx +19 -0
  124. package/templates/react/ts/scss/rtk/src/store/index.ts +13 -0
  125. package/templates/react/ts/scss/rtk/src/store/services/api.ts +37 -0
  126. package/templates/react/ts/scss/rtk/src/styles/global.scss +10 -0
  127. package/templates/react/ts/scss/rtk/src/utils/utils.ts +59 -0
  128. package/templates/react/ts/scss/rtk/tsconfig.app.json +28 -0
  129. package/templates/react/ts/scss/rtk/tsconfig.json +12 -0
  130. package/templates/react/ts/scss/rtk/tsconfig.node.json +26 -0
  131. 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
+ }