create-fe-boilerplate 0.2.0 → 0.3.1
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 +58 -14
- package/package.json +11 -3
- package/templates/next/ts/tailwind/axios/README.md +36 -0
- package/templates/next/ts/tailwind/axios/eslint.config.mjs +18 -0
- package/templates/next/ts/tailwind/axios/next.config.ts +7 -0
- package/templates/next/ts/tailwind/axios/package-lock.json +6626 -0
- package/templates/next/ts/tailwind/axios/package.json +27 -0
- package/templates/next/ts/tailwind/axios/postcss.config.mjs +7 -0
- package/templates/next/ts/tailwind/axios/public/file.svg +1 -0
- package/templates/next/ts/tailwind/axios/public/globe.svg +1 -0
- package/templates/next/ts/tailwind/axios/public/next.svg +1 -0
- package/templates/next/ts/tailwind/axios/public/vercel.svg +1 -0
- package/templates/next/ts/tailwind/axios/public/window.svg +1 -0
- package/templates/next/ts/tailwind/axios/src/api/auth.ts +10 -0
- package/templates/next/ts/tailwind/axios/src/api/axios.ts +31 -0
- package/templates/next/ts/tailwind/axios/src/app/dashboard/page.tsx +32 -0
- package/templates/next/ts/tailwind/axios/src/app/favicon.ico +0 -0
- package/templates/next/ts/tailwind/axios/src/app/globals.css +26 -0
- package/templates/next/ts/tailwind/axios/src/app/layout.tsx +34 -0
- package/templates/next/ts/tailwind/axios/src/app/login/page.tsx +71 -0
- package/templates/next/ts/tailwind/axios/src/app/page.tsx +5 -0
- package/templates/next/ts/tailwind/axios/src/app/signup/page.tsx +56 -0
- package/templates/next/ts/tailwind/axios/src/components/AuthGuard.tsx +35 -0
- package/templates/next/ts/tailwind/axios/src/components/VerifyOtpModal.tsx +62 -0
- package/templates/next/ts/tailwind/axios/tsconfig.json +36 -0
- package/templates/react/js/tailwind/axios/vite.config.js +1 -1
- package/templates/react/js/tailwind/rtk/README.md +73 -0
- package/templates/react/js/tailwind/rtk/eslint.config.js +23 -0
- package/templates/react/js/tailwind/rtk/index.html +13 -0
- package/templates/react/js/tailwind/rtk/package-lock.json +4597 -0
- package/templates/react/js/tailwind/rtk/package.json +37 -0
- package/templates/react/js/tailwind/rtk/postcss.config.js +6 -0
- package/templates/react/js/tailwind/rtk/public/vite.svg +1 -0
- package/templates/react/js/tailwind/rtk/react-ts-tailwind-axios/.env.example +1 -0
- package/templates/react/js/tailwind/rtk/src/App.css +0 -0
- package/templates/react/js/tailwind/rtk/src/App.jsx +14 -0
- package/templates/react/js/tailwind/rtk/src/api/axios.ts +236 -0
- package/templates/react/js/tailwind/rtk/src/component/auth/VerifyOtpModal.jsx +60 -0
- package/templates/react/js/tailwind/rtk/src/component/common/ui/Toast/Toast.js +59 -0
- package/templates/react/js/tailwind/rtk/src/contants/constants.js +45 -0
- package/templates/react/js/tailwind/rtk/src/index.css +4 -0
- package/templates/react/js/tailwind/rtk/src/main.jsx +14 -0
- package/templates/react/js/tailwind/rtk/src/pages/Dashboard.jsx +10 -0
- package/templates/react/js/tailwind/rtk/src/pages/Login.jsx +69 -0
- package/templates/react/js/tailwind/rtk/src/pages/Signup.jsx +75 -0
- package/templates/react/js/tailwind/rtk/src/router/AppRoutes.jsx +19 -0
- package/templates/react/js/tailwind/rtk/src/store/index.js +10 -0
- package/templates/react/js/tailwind/rtk/src/store/services/api.js +34 -0
- package/templates/react/js/tailwind/rtk/src/utils/utils.ts +59 -0
- package/templates/react/js/tailwind/rtk/tailwind.config.js +8 -0
- package/templates/react/js/tailwind/rtk/vite.config.js +7 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "axios",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "eslint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"axios": "^1.13.2",
|
|
13
|
+
"next": "16.1.1",
|
|
14
|
+
"react": "19.2.3",
|
|
15
|
+
"react-dom": "19.2.3"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@tailwindcss/postcss": "^4",
|
|
19
|
+
"@types/node": "^20",
|
|
20
|
+
"@types/react": "^19",
|
|
21
|
+
"@types/react-dom": "^19",
|
|
22
|
+
"eslint": "^9",
|
|
23
|
+
"eslint-config-next": "16.1.1",
|
|
24
|
+
"tailwindcss": "^4",
|
|
25
|
+
"typescript": "^5"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { axiosApi } from "./axios";
|
|
2
|
+
|
|
3
|
+
export const loginApi = (data: { email: string; password: string }) =>
|
|
4
|
+
axiosApi.post("/auth/login", data);
|
|
5
|
+
|
|
6
|
+
export const signupApi = (data: { email: string; password: string }) =>
|
|
7
|
+
axiosApi.post("/auth/signup", data);
|
|
8
|
+
|
|
9
|
+
export const verifyOtpApi = (data: { refId: string; otp: string }) =>
|
|
10
|
+
axiosApi.post("/auth/verify-otp", data);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
|
|
3
|
+
export const axiosApi = axios.create({
|
|
4
|
+
baseURL: process.env.NEXT_PUBLIC_API_HOST,
|
|
5
|
+
headers: {
|
|
6
|
+
"Content-Type": "application/json",
|
|
7
|
+
},
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
axiosApi.interceptors.request.use((config) => {
|
|
11
|
+
if (typeof window !== "undefined") {
|
|
12
|
+
const token = localStorage.getItem("token");
|
|
13
|
+
if (token) {
|
|
14
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return config;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
axiosApi.interceptors.response.use(
|
|
21
|
+
(response) => response,
|
|
22
|
+
(error) => {
|
|
23
|
+
if (error?.response?.status === 401) {
|
|
24
|
+
if (typeof window !== "undefined") {
|
|
25
|
+
localStorage.removeItem("token");
|
|
26
|
+
window.location.href = "/login";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return Promise.reject(error);
|
|
30
|
+
}
|
|
31
|
+
);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import AuthGuard from "@/components/AuthGuard";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
|
|
6
|
+
export default function DashboardPage() {
|
|
7
|
+
const router = useRouter();
|
|
8
|
+
|
|
9
|
+
const handleLogout = () => {
|
|
10
|
+
localStorage.removeItem("token");
|
|
11
|
+
router.replace("/login");
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<AuthGuard>
|
|
16
|
+
<div className="p-6">
|
|
17
|
+
<div className="flex justify-between items-center">
|
|
18
|
+
<h1 className="text-2xl font-bold">Dashboard</h1>
|
|
19
|
+
|
|
20
|
+
<button
|
|
21
|
+
onClick={handleLogout}
|
|
22
|
+
className="px-4 py-2 bg-red-600 text-white rounded"
|
|
23
|
+
>
|
|
24
|
+
Logout
|
|
25
|
+
</button>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<p className="mt-4 text-gray-600">Protected dashboard content</p>
|
|
29
|
+
</div>
|
|
30
|
+
</AuthGuard>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--background: #ffffff;
|
|
5
|
+
--foreground: #171717;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@theme inline {
|
|
9
|
+
--color-background: var(--background);
|
|
10
|
+
--color-foreground: var(--foreground);
|
|
11
|
+
--font-sans: var(--font-geist-sans);
|
|
12
|
+
--font-mono: var(--font-geist-mono);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
:root {
|
|
17
|
+
--background: #0a0a0a;
|
|
18
|
+
--foreground: #ededed;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
body {
|
|
23
|
+
background: var(--background);
|
|
24
|
+
color: var(--foreground);
|
|
25
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
26
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Geist, Geist_Mono } from "next/font/google";
|
|
3
|
+
import "./globals.css";
|
|
4
|
+
|
|
5
|
+
const geistSans = Geist({
|
|
6
|
+
variable: "--font-geist-sans",
|
|
7
|
+
subsets: ["latin"],
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const geistMono = Geist_Mono({
|
|
11
|
+
variable: "--font-geist-mono",
|
|
12
|
+
subsets: ["latin"],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const metadata: Metadata = {
|
|
16
|
+
title: "Create Next App",
|
|
17
|
+
description: "Generated by create next app",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function RootLayout({
|
|
21
|
+
children,
|
|
22
|
+
}: Readonly<{
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
}>) {
|
|
25
|
+
return (
|
|
26
|
+
<html lang="en" suppressHydrationWarning>
|
|
27
|
+
<body
|
|
28
|
+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/navigation";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import VerifyOtpModal from "@/components/VerifyOtpModal";
|
|
6
|
+
import { loginApi } from "@/api/auth";
|
|
7
|
+
|
|
8
|
+
export default function LoginPage() {
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
const [showOtp, setShowOtp] = useState(false);
|
|
11
|
+
const [refId, setRefId] = useState("");
|
|
12
|
+
|
|
13
|
+
const handleLogin = async () => {
|
|
14
|
+
// 🔴 simulate API response
|
|
15
|
+
// await loginApi({
|
|
16
|
+
// email: "",
|
|
17
|
+
// password: "",
|
|
18
|
+
// });
|
|
19
|
+
|
|
20
|
+
const requiresOtp = true;
|
|
21
|
+
|
|
22
|
+
if (requiresOtp) {
|
|
23
|
+
setRefId("ref-123");
|
|
24
|
+
setShowOtp(true);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
router.push("/dashboard");
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
|
34
|
+
<div className="w-96 p-6 bg-white shadow rounded">
|
|
35
|
+
<h2 className="text-xl font-bold mb-4 text-black">Login</h2>
|
|
36
|
+
|
|
37
|
+
<input className="border w-full p-2 mb-2" placeholder="Email" />
|
|
38
|
+
<input
|
|
39
|
+
className="border w-full p-2 mb-4"
|
|
40
|
+
placeholder="Password"
|
|
41
|
+
type="password"
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
<button
|
|
45
|
+
onClick={handleLogin}
|
|
46
|
+
className="w-full bg-blue-600 text-white py-2 rounded"
|
|
47
|
+
>
|
|
48
|
+
Login
|
|
49
|
+
</button>
|
|
50
|
+
|
|
51
|
+
<p
|
|
52
|
+
className="text-sm text-blue-600 mt-4 cursor-pointer"
|
|
53
|
+
onClick={() => router.push("/signup")}
|
|
54
|
+
>
|
|
55
|
+
Don’t have an account? Signup
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<VerifyOtpModal
|
|
61
|
+
isOpen={showOtp}
|
|
62
|
+
refId={refId}
|
|
63
|
+
onClose={() => setShowOtp(false)}
|
|
64
|
+
onSuccess={(token) => {
|
|
65
|
+
localStorage.setItem("token", token);
|
|
66
|
+
router.push("/dashboard");
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/navigation";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import VerifyOtpModal from "@/components/VerifyOtpModal";
|
|
6
|
+
|
|
7
|
+
export default function SignupPage() {
|
|
8
|
+
const router = useRouter();
|
|
9
|
+
const [showOtp, setShowOtp] = useState(false);
|
|
10
|
+
const [refId, setRefId] = useState("");
|
|
11
|
+
|
|
12
|
+
const handleSignup = async () => {
|
|
13
|
+
setRefId("ref-456");
|
|
14
|
+
setShowOtp(true);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
|
20
|
+
<div className="w-96 p-6 bg-white shadow rounded">
|
|
21
|
+
<h2 className="text-xl font-bold mb-4 text-black">Signup</h2>
|
|
22
|
+
|
|
23
|
+
<input className="border w-full p-2 mb-2" placeholder="Email" />
|
|
24
|
+
<input
|
|
25
|
+
className="border w-full p-2 mb-4"
|
|
26
|
+
placeholder="Password"
|
|
27
|
+
type="password"
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
<button
|
|
31
|
+
onClick={handleSignup}
|
|
32
|
+
className="w-full bg-blue-600 text-white py-2 rounded"
|
|
33
|
+
>
|
|
34
|
+
Signup
|
|
35
|
+
</button>
|
|
36
|
+
<p
|
|
37
|
+
className="text-sm text-blue-600 mt-4 cursor-pointer"
|
|
38
|
+
onClick={() => router.push("/login")}
|
|
39
|
+
>
|
|
40
|
+
Already have an Account? Login
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<VerifyOtpModal
|
|
46
|
+
isOpen={showOtp}
|
|
47
|
+
refId={refId}
|
|
48
|
+
onClose={() => setShowOtp(false)}
|
|
49
|
+
onSuccess={(token) => {
|
|
50
|
+
localStorage.setItem("token", token);
|
|
51
|
+
router.push("/dashboard");
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
|
|
6
|
+
interface AuthGuardProps {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function AuthGuard({ children }: AuthGuardProps) {
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const [checking, setChecking] = useState(true);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const token = localStorage.getItem("token");
|
|
16
|
+
|
|
17
|
+
if (!token) {
|
|
18
|
+
router.replace("/login");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setChecking(false);
|
|
23
|
+
}, [router]);
|
|
24
|
+
|
|
25
|
+
// Prevent flicker
|
|
26
|
+
if (checking) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
29
|
+
<p className="text-gray-600">Checking authentication...</p>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return <>{children}</>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { axiosApi } from "@/api/axios";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
interface VerifyOtpModalProps {
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
refId: string;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
onSuccess: (token: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function VerifyOtpModal({
|
|
14
|
+
isOpen,
|
|
15
|
+
refId,
|
|
16
|
+
onClose,
|
|
17
|
+
onSuccess,
|
|
18
|
+
}: VerifyOtpModalProps) {
|
|
19
|
+
const [otp, setOtp] = useState("");
|
|
20
|
+
|
|
21
|
+
if (!isOpen) return null;
|
|
22
|
+
|
|
23
|
+
const handleVerify = async () => {
|
|
24
|
+
// const res = await axiosApi.post("/auth/verify-otp", {
|
|
25
|
+
// refId,
|
|
26
|
+
// otp,
|
|
27
|
+
// });
|
|
28
|
+
|
|
29
|
+
onSuccess("dummy Token");
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
|
34
|
+
<div className="bg-white w-96 p-6 rounded shadow">
|
|
35
|
+
<h2 className="text-xl font-bold mb-4 text-black">Verify OTP</h2>
|
|
36
|
+
|
|
37
|
+
<input
|
|
38
|
+
className="border-1 border-gray-300 w-full p-2 mb-4 text-black"
|
|
39
|
+
placeholder="Enter OTP"
|
|
40
|
+
value={otp}
|
|
41
|
+
onChange={(e) => setOtp(e.target.value)}
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
<div className="flex justify-end gap-2">
|
|
45
|
+
<button
|
|
46
|
+
onClick={onClose}
|
|
47
|
+
className="px-4 py-2 border rounded text-black"
|
|
48
|
+
>
|
|
49
|
+
Cancel
|
|
50
|
+
</button>
|
|
51
|
+
|
|
52
|
+
<button
|
|
53
|
+
onClick={handleVerify}
|
|
54
|
+
className="px-4 py-2 bg-green-600 text-white rounded"
|
|
55
|
+
>
|
|
56
|
+
Verify
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"]
|
|
23
|
+
},
|
|
24
|
+
"types": ["next", "react", "react-dom", "node"],
|
|
25
|
+
"typeRoots": ["./node_modules/@types"]
|
|
26
|
+
},
|
|
27
|
+
"include": [
|
|
28
|
+
"next-env.d.ts",
|
|
29
|
+
"**/*.ts",
|
|
30
|
+
"**/*.tsx",
|
|
31
|
+
".next/types/**/*.ts",
|
|
32
|
+
".next/dev/types/**/*.ts",
|
|
33
|
+
"**/*.mts"
|
|
34
|
+
],
|
|
35
|
+
"exclude": ["node_modules"]
|
|
36
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x'
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores(['dist']),
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs.flat.recommended,
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>react-ts-tailwind-axios</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|