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.
Files changed (51) hide show
  1. package/bin/index.js +78 -0
  2. package/package.json +25 -0
  3. package/templates/react/js/tailwind/axios/README.md +73 -0
  4. package/templates/react/js/tailwind/axios/eslint.config.js +23 -0
  5. package/templates/react/js/tailwind/axios/index.html +13 -0
  6. package/templates/react/js/tailwind/axios/package-lock.json +4469 -0
  7. package/templates/react/js/tailwind/axios/package.json +35 -0
  8. package/templates/react/js/tailwind/axios/postcss.config.js +6 -0
  9. package/templates/react/js/tailwind/axios/public/vite.svg +1 -0
  10. package/templates/react/js/tailwind/axios/react-ts-tailwind-axios/.env.example +1 -0
  11. package/templates/react/js/tailwind/axios/src/App.css +0 -0
  12. package/templates/react/js/tailwind/axios/src/App.jsx +14 -0
  13. package/templates/react/js/tailwind/axios/src/api/axios.js +201 -0
  14. package/templates/react/js/tailwind/axios/src/component/auth/VerifyOtpModal.jsx +65 -0
  15. package/templates/react/js/tailwind/axios/src/component/common/ui/Toast/Toast.js +59 -0
  16. package/templates/react/js/tailwind/axios/src/contants/constants.js +45 -0
  17. package/templates/react/js/tailwind/axios/src/index.css +4 -0
  18. package/templates/react/js/tailwind/axios/src/main.jsx +12 -0
  19. package/templates/react/js/tailwind/axios/src/pages/Dashboard.jsx +10 -0
  20. package/templates/react/js/tailwind/axios/src/pages/Login.jsx +70 -0
  21. package/templates/react/js/tailwind/axios/src/pages/Signup.jsx +75 -0
  22. package/templates/react/js/tailwind/axios/src/router/AppRoutes.jsx +19 -0
  23. package/templates/react/js/tailwind/axios/src/utils/utils.js +59 -0
  24. package/templates/react/js/tailwind/axios/tailwind.config.js +8 -0
  25. package/templates/react/js/tailwind/axios/vite.config.js +6 -0
  26. package/templates/react/ts/tailwind/axios/README.md +73 -0
  27. package/templates/react/ts/tailwind/axios/eslint.config.js +23 -0
  28. package/templates/react/ts/tailwind/axios/index.html +13 -0
  29. package/templates/react/ts/tailwind/axios/package-lock.json +4498 -0
  30. package/templates/react/ts/tailwind/axios/package.json +38 -0
  31. package/templates/react/ts/tailwind/axios/postcss.config.js +6 -0
  32. package/templates/react/ts/tailwind/axios/public/vite.svg +1 -0
  33. package/templates/react/ts/tailwind/axios/react-ts-tailwind-axios/.env.example +1 -0
  34. package/templates/react/ts/tailwind/axios/src/App.css +0 -0
  35. package/templates/react/ts/tailwind/axios/src/App.tsx +14 -0
  36. package/templates/react/ts/tailwind/axios/src/api/axios.ts +236 -0
  37. package/templates/react/ts/tailwind/axios/src/component/auth/VerifyOtpModal.tsx +70 -0
  38. package/templates/react/ts/tailwind/axios/src/component/common/ui/Toast/Toast.ts +58 -0
  39. package/templates/react/ts/tailwind/axios/src/contants/constants.ts +45 -0
  40. package/templates/react/ts/tailwind/axios/src/index.css +4 -0
  41. package/templates/react/ts/tailwind/axios/src/main.tsx +10 -0
  42. package/templates/react/ts/tailwind/axios/src/pages/Dashboard.tsx +10 -0
  43. package/templates/react/ts/tailwind/axios/src/pages/Login.tsx +70 -0
  44. package/templates/react/ts/tailwind/axios/src/pages/Signup.tsx +75 -0
  45. package/templates/react/ts/tailwind/axios/src/router/AppRoutes.tsx +19 -0
  46. package/templates/react/ts/tailwind/axios/src/utils/utils.ts +59 -0
  47. package/templates/react/ts/tailwind/axios/tailwind.config.js +8 -0
  48. package/templates/react/ts/tailwind/axios/tsconfig.app.json +28 -0
  49. package/templates/react/ts/tailwind/axios/tsconfig.json +12 -0
  50. package/templates/react/ts/tailwind/axios/tsconfig.node.json +26 -0
  51. 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,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -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,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 './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -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;
@@ -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;