create-fe-boilerplate 0.1.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.
- package/bin/index.js +78 -0
- package/package.json +25 -0
- package/templates/react/js/tailwind/axios/README.md +73 -0
- package/templates/react/js/tailwind/axios/eslint.config.js +23 -0
- package/templates/react/js/tailwind/axios/index.html +13 -0
- package/templates/react/js/tailwind/axios/package-lock.json +4469 -0
- package/templates/react/js/tailwind/axios/package.json +35 -0
- package/templates/react/js/tailwind/axios/postcss.config.js +6 -0
- package/templates/react/js/tailwind/axios/public/vite.svg +1 -0
- package/templates/react/js/tailwind/axios/react-ts-tailwind-axios/.env.example +1 -0
- package/templates/react/js/tailwind/axios/src/App.css +0 -0
- package/templates/react/js/tailwind/axios/src/App.jsx +14 -0
- package/templates/react/js/tailwind/axios/src/api/axios.js +201 -0
- package/templates/react/js/tailwind/axios/src/component/auth/VerifyOtpModal.jsx +65 -0
- package/templates/react/js/tailwind/axios/src/component/common/ui/Toast/Toast.js +59 -0
- package/templates/react/js/tailwind/axios/src/contants/constants.js +45 -0
- package/templates/react/js/tailwind/axios/src/index.css +4 -0
- package/templates/react/js/tailwind/axios/src/main.jsx +12 -0
- package/templates/react/js/tailwind/axios/src/pages/Dashboard.jsx +10 -0
- package/templates/react/js/tailwind/axios/src/pages/Login.jsx +70 -0
- package/templates/react/js/tailwind/axios/src/pages/Signup.jsx +75 -0
- package/templates/react/js/tailwind/axios/src/router/AppRoutes.jsx +19 -0
- package/templates/react/js/tailwind/axios/src/utils/utils.js +59 -0
- package/templates/react/js/tailwind/axios/tailwind.config.js +8 -0
- package/templates/react/js/tailwind/axios/vite.config.js +6 -0
- package/templates/react/ts/tailwind/axios/README.md +73 -0
- package/templates/react/ts/tailwind/axios/eslint.config.js +23 -0
- package/templates/react/ts/tailwind/axios/index.html +13 -0
- package/templates/react/ts/tailwind/axios/package-lock.json +4498 -0
- package/templates/react/ts/tailwind/axios/package.json +38 -0
- package/templates/react/ts/tailwind/axios/postcss.config.js +6 -0
- package/templates/react/ts/tailwind/axios/public/vite.svg +1 -0
- package/templates/react/ts/tailwind/axios/react-ts-tailwind-axios/.env.example +1 -0
- package/templates/react/ts/tailwind/axios/src/App.css +0 -0
- package/templates/react/ts/tailwind/axios/src/App.tsx +14 -0
- package/templates/react/ts/tailwind/axios/src/api/axios.ts +236 -0
- package/templates/react/ts/tailwind/axios/src/component/auth/VerifyOtpModal.tsx +70 -0
- package/templates/react/ts/tailwind/axios/src/component/common/ui/Toast/Toast.ts +58 -0
- package/templates/react/ts/tailwind/axios/src/contants/constants.ts +45 -0
- package/templates/react/ts/tailwind/axios/src/index.css +4 -0
- package/templates/react/ts/tailwind/axios/src/main.tsx +10 -0
- package/templates/react/ts/tailwind/axios/src/pages/Dashboard.tsx +10 -0
- package/templates/react/ts/tailwind/axios/src/pages/Login.tsx +70 -0
- package/templates/react/ts/tailwind/axios/src/pages/Signup.tsx +75 -0
- package/templates/react/ts/tailwind/axios/src/router/AppRoutes.tsx +19 -0
- package/templates/react/ts/tailwind/axios/src/utils/utils.ts +59 -0
- package/templates/react/ts/tailwind/axios/tailwind.config.js +8 -0
- package/templates/react/ts/tailwind/axios/tsconfig.app.json +28 -0
- package/templates/react/ts/tailwind/axios/tsconfig.json +12 -0
- package/templates/react/ts/tailwind/axios/tsconfig.node.json +26 -0
- package/templates/react/ts/tailwind/axios/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
|
+
"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
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@eslint/js": "^9.39.1",
|
|
22
|
+
"@types/crypto-js": "^4.2.2",
|
|
23
|
+
"@types/node": "^24.10.1",
|
|
24
|
+
"@types/react": "^19.2.5",
|
|
25
|
+
"@types/react-dom": "^19.2.3",
|
|
26
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
27
|
+
"autoprefixer": "^10.4.23",
|
|
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
|
+
"postcss": "^8.5.6",
|
|
33
|
+
"tailwindcss": "^3.4.17",
|
|
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,70 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { apiCallPost } from "../../api/axios";
|
|
3
|
+
import { toasts } from "../../component/common/ui/Toast/Toast";
|
|
4
|
+
|
|
5
|
+
interface VerifyOtpModalProps {
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
onVerifySuccess: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const VerifyOtpModal = ({
|
|
12
|
+
isOpen,
|
|
13
|
+
onClose,
|
|
14
|
+
onVerifySuccess,
|
|
15
|
+
}: VerifyOtpModalProps) => {
|
|
16
|
+
const [otp, setOtp] = useState("");
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
|
+
|
|
19
|
+
if (!isOpen) return null;
|
|
20
|
+
|
|
21
|
+
const handleVerify = async () => {
|
|
22
|
+
if (otp.length !== 6) {
|
|
23
|
+
toasts.error("Please enter a valid 6-digit OTP");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setLoading(true);
|
|
28
|
+
|
|
29
|
+
// const res = await apiCallPost("/auth/verify-otp", { otp }, {}, true);
|
|
30
|
+
|
|
31
|
+
setLoading(false);
|
|
32
|
+
|
|
33
|
+
// if (res?.success) {
|
|
34
|
+
onVerifySuccess();
|
|
35
|
+
onClose();
|
|
36
|
+
// }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50">
|
|
41
|
+
<div className="bg-white w-96 p-6 rounded shadow-lg">
|
|
42
|
+
<h2 className="text-xl font-bold mb-4">Verify OTP</h2>
|
|
43
|
+
|
|
44
|
+
<input
|
|
45
|
+
maxLength={6}
|
|
46
|
+
value={otp}
|
|
47
|
+
onChange={(e) => setOtp(e.target.value.replace(/\D/g, ""))}
|
|
48
|
+
className="border w-full p-2 mb-4 text-center tracking-widest text-lg rounded"
|
|
49
|
+
placeholder="Enter OTP"
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
<div className="flex justify-between">
|
|
53
|
+
<button onClick={onClose} className="px-4 py-2 border rounded">
|
|
54
|
+
Cancel
|
|
55
|
+
</button>
|
|
56
|
+
|
|
57
|
+
<button
|
|
58
|
+
onClick={handleVerify}
|
|
59
|
+
disabled={loading}
|
|
60
|
+
className="px-4 py-2 bg-blue-600 text-white rounded"
|
|
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,70 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Link, useNavigate } from "react-router-dom";
|
|
3
|
+
import VerifyOtpModal from "../component/auth/VerifyOtpModal";
|
|
4
|
+
|
|
5
|
+
const Login = () => {
|
|
6
|
+
const navigate = useNavigate();
|
|
7
|
+
const [showOtp, setShowOtp] = useState(false);
|
|
8
|
+
|
|
9
|
+
const [form, setForm] = useState({
|
|
10
|
+
email: "",
|
|
11
|
+
password: "",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const handleLogin = async () => {
|
|
15
|
+
// const res = await apiCallPost("/auth/login", form, {}, true);
|
|
16
|
+
|
|
17
|
+
// if (res?.success) {
|
|
18
|
+
setShowOtp(true);
|
|
19
|
+
// }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const handleOtpSuccess = () => {
|
|
23
|
+
localStorage.setItem("token", "dummy-token");
|
|
24
|
+
navigate("/dashboard");
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
|
29
|
+
<div className="w-96 p-6 bg-white rounded shadow">
|
|
30
|
+
<h2 className="text-xl font-bold mb-4">Login</h2>
|
|
31
|
+
|
|
32
|
+
<input
|
|
33
|
+
className="border w-full p-2 mb-2 rounded"
|
|
34
|
+
placeholder="Email"
|
|
35
|
+
value={form.email}
|
|
36
|
+
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<input
|
|
40
|
+
type="password"
|
|
41
|
+
className="border w-full p-2 mb-4 rounded"
|
|
42
|
+
placeholder="Password"
|
|
43
|
+
value={form.password}
|
|
44
|
+
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
|
45
|
+
/>
|
|
46
|
+
|
|
47
|
+
<button
|
|
48
|
+
onClick={handleLogin}
|
|
49
|
+
className="w-full bg-blue-600 text-white py-2 rounded"
|
|
50
|
+
>
|
|
51
|
+
Login
|
|
52
|
+
</button>
|
|
53
|
+
<span className="text-sm text-gray-600 mt-2 block text-center">
|
|
54
|
+
Dont have an account?{" "}
|
|
55
|
+
<Link to="/signup" className="text-blue-600 cursor-pointer">
|
|
56
|
+
Signup
|
|
57
|
+
</Link>
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<VerifyOtpModal
|
|
62
|
+
isOpen={showOtp}
|
|
63
|
+
onClose={() => setShowOtp(false)}
|
|
64
|
+
onVerifySuccess={handleOtpSuccess}
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default Login;
|
|
@@ -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
|
+
|
|
5
|
+
const Signup = () => {
|
|
6
|
+
const navigate = useNavigate();
|
|
7
|
+
const [showOtp, setShowOtp] = useState(false);
|
|
8
|
+
|
|
9
|
+
const [form, setForm] = useState({
|
|
10
|
+
email: "",
|
|
11
|
+
password: "",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const handleSignup = async () => {
|
|
15
|
+
// const res = await apiCallPost(
|
|
16
|
+
// "/auth/signup",
|
|
17
|
+
// form,
|
|
18
|
+
// {},
|
|
19
|
+
// true
|
|
20
|
+
// );
|
|
21
|
+
|
|
22
|
+
// if (res?.success) {
|
|
23
|
+
// setRefId(res?.data?.refId || "");
|
|
24
|
+
setShowOtp(true);
|
|
25
|
+
// }
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const handleOtpSuccess = () => {
|
|
29
|
+
navigate("/login");
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
|
34
|
+
<div className="w-96 p-6 bg-white rounded shadow">
|
|
35
|
+
<h2 className="text-xl font-bold mb-4">Signup</h2>
|
|
36
|
+
|
|
37
|
+
<input
|
|
38
|
+
className="border w-full p-2 mb-2 rounded"
|
|
39
|
+
placeholder="Email"
|
|
40
|
+
value={form.email}
|
|
41
|
+
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
<input
|
|
45
|
+
type="password"
|
|
46
|
+
className="border w-full p-2 mb-4 rounded"
|
|
47
|
+
placeholder="Password"
|
|
48
|
+
value={form.password}
|
|
49
|
+
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
<button
|
|
53
|
+
onClick={handleSignup}
|
|
54
|
+
className="w-full bg-green-600 text-white py-2 rounded"
|
|
55
|
+
>
|
|
56
|
+
Create Account
|
|
57
|
+
</button>
|
|
58
|
+
<span className="text-sm text-gray-600 mt-2 block text-center">
|
|
59
|
+
Already have an account?{" "}
|
|
60
|
+
<Link to="/login" className="text-blue-600 cursor-pointer">
|
|
61
|
+
Login
|
|
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 Signup;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
|
2
|
+
import Login from "../pages/Login";
|
|
3
|
+
import Signup from "../pages/Signup";
|
|
4
|
+
import Dashboard from "../pages/Dashboard";
|
|
5
|
+
|
|
6
|
+
const AppRouter = () => {
|
|
7
|
+
return (
|
|
8
|
+
<BrowserRouter>
|
|
9
|
+
<Routes>
|
|
10
|
+
<Route path="/" element={<Navigate to="/login" />} />
|
|
11
|
+
<Route path="/login" element={<Login />} />
|
|
12
|
+
<Route path="/signup" element={<Signup />} />
|
|
13
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
14
|
+
</Routes>
|
|
15
|
+
</BrowserRouter>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default AppRouter;
|