create-parachute 1.0.2
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/README.md +27 -0
- package/bin/index.js +474 -0
- package/package.json +29 -0
- package/templates/mysql/client/index.html +12 -0
- package/templates/mysql/client/package.json +18 -0
- package/templates/mysql/client/src/App.jsx +5 -0
- package/templates/mysql/client/src/api/client.js +6 -0
- package/templates/mysql/client/src/components/Navbar.jsx +57 -0
- package/templates/mysql/client/src/components/ProtectedRoute.jsx +10 -0
- package/templates/mysql/client/src/layouts/DashboardLayout.jsx +21 -0
- package/templates/mysql/client/src/main.jsx +9 -0
- package/templates/mysql/client/src/pages/Dashboard.jsx +38 -0
- package/templates/mysql/client/src/pages/Login.jsx +138 -0
- package/templates/mysql/client/src/pages/Page1.jsx +38 -0
- package/templates/mysql/client/src/pages/Page2.jsx +38 -0
- package/templates/mysql/client/src/pages/Page3.jsx +38 -0
- package/templates/mysql/client/src/router/index.jsx +43 -0
- package/templates/mysql/client/src/utils/auth.js +16 -0
- package/templates/mysql/client/vite.config.js +6 -0
- package/templates/mysql/server/config/db.js +13 -0
- package/templates/mysql/server/controllers/authController.js +66 -0
- package/templates/mysql/server/index.js +39 -0
- package/templates/mysql/server/middleware/authMiddleware.js +7 -0
- package/templates/mysql/server/package.json +18 -0
- package/templates/mysql/server/routes/authRoutes.js +14 -0
- package/templates/sequelize/client/index.html +12 -0
- package/templates/sequelize/client/package.json +18 -0
- package/templates/sequelize/client/src/App.jsx +5 -0
- package/templates/sequelize/client/src/api/client.js +6 -0
- package/templates/sequelize/client/src/components/Navbar.jsx +57 -0
- package/templates/sequelize/client/src/components/ProtectedRoute.jsx +10 -0
- package/templates/sequelize/client/src/layouts/DashboardLayout.jsx +21 -0
- package/templates/sequelize/client/src/main.jsx +9 -0
- package/templates/sequelize/client/src/pages/Dashboard.jsx +38 -0
- package/templates/sequelize/client/src/pages/Login.jsx +138 -0
- package/templates/sequelize/client/src/pages/Page1.jsx +38 -0
- package/templates/sequelize/client/src/pages/Page2.jsx +38 -0
- package/templates/sequelize/client/src/pages/Page3.jsx +38 -0
- package/templates/sequelize/client/src/router/index.jsx +43 -0
- package/templates/sequelize/client/src/utils/auth.js +16 -0
- package/templates/sequelize/client/vite.config.js +6 -0
- package/templates/sequelize/server/config/db.js +13 -0
- package/templates/sequelize/server/config/sequelize.js +17 -0
- package/templates/sequelize/server/controllers/authController.js +62 -0
- package/templates/sequelize/server/index.js +42 -0
- package/templates/sequelize/server/middleware/authMiddleware.js +7 -0
- package/templates/sequelize/server/models/User.js +24 -0
- package/templates/sequelize/server/package.json +19 -0
- package/templates/sequelize/server/routes/authRoutes.js +14 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useNavigate } from "react-router-dom";
|
|
3
|
+
import { api } from "../api/client";
|
|
4
|
+
import { saveUser } from "../utils/auth";
|
|
5
|
+
|
|
6
|
+
export default function Login() {
|
|
7
|
+
const navigate = useNavigate();
|
|
8
|
+
|
|
9
|
+
const [form, setForm] = useState({
|
|
10
|
+
Username: "",
|
|
11
|
+
Password: ""
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (localStorage.getItem("user")) {
|
|
16
|
+
navigate("/");
|
|
17
|
+
}
|
|
18
|
+
}, [navigate]);
|
|
19
|
+
|
|
20
|
+
const register = async () => {
|
|
21
|
+
try {
|
|
22
|
+
await api.post("/auth/register", form);
|
|
23
|
+
alert("Registered successfully. Now login.");
|
|
24
|
+
} catch (err) {
|
|
25
|
+
alert(err.response?.data?.message || "Registration failed");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const login = async () => {
|
|
30
|
+
try {
|
|
31
|
+
const res = await api.post("/auth/login", form);
|
|
32
|
+
saveUser(res.data);
|
|
33
|
+
navigate("/");
|
|
34
|
+
} catch (err) {
|
|
35
|
+
alert(err.response?.data?.message || "Login failed");
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div style={{
|
|
41
|
+
minHeight: "100vh",
|
|
42
|
+
display: "flex",
|
|
43
|
+
justifyContent: "center",
|
|
44
|
+
alignItems: "center",
|
|
45
|
+
background: "#f8fafc",
|
|
46
|
+
fontFamily: "Arial, sans-serif",
|
|
47
|
+
padding: "20px"
|
|
48
|
+
}}>
|
|
49
|
+
<div style={{
|
|
50
|
+
width: "380px",
|
|
51
|
+
background: "white",
|
|
52
|
+
padding: "32px",
|
|
53
|
+
borderRadius: "20px",
|
|
54
|
+
border: "1px solid #e5e7eb",
|
|
55
|
+
boxShadow: "0 1px 5px rgba(0,0,0,0.08)",
|
|
56
|
+
display: "flex",
|
|
57
|
+
flexDirection: "column",
|
|
58
|
+
gap: "14px"
|
|
59
|
+
}}>
|
|
60
|
+
<p style={{
|
|
61
|
+
color: "__THEME__",
|
|
62
|
+
fontWeight: "700",
|
|
63
|
+
margin: 0
|
|
64
|
+
}}>
|
|
65
|
+
Create Parachute
|
|
66
|
+
</p>
|
|
67
|
+
|
|
68
|
+
<h1 style={{
|
|
69
|
+
margin: 0,
|
|
70
|
+
fontSize: "28px"
|
|
71
|
+
}}>
|
|
72
|
+
Login or Register
|
|
73
|
+
</h1>
|
|
74
|
+
|
|
75
|
+
<p style={{
|
|
76
|
+
color: "#6b7280",
|
|
77
|
+
marginTop: 0,
|
|
78
|
+
lineHeight: "1.5"
|
|
79
|
+
}}>
|
|
80
|
+
Use a username and password. Passwords are hashed in the backend.
|
|
81
|
+
</p>
|
|
82
|
+
|
|
83
|
+
<input
|
|
84
|
+
placeholder="Username"
|
|
85
|
+
value={form.Username}
|
|
86
|
+
onChange={(e) => setForm({ ...form, Username: e.target.value })}
|
|
87
|
+
style={{
|
|
88
|
+
padding: "12px",
|
|
89
|
+
border: "1px solid #d1d5db",
|
|
90
|
+
borderRadius: "10px"
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
<input
|
|
95
|
+
type="password"
|
|
96
|
+
placeholder="Password"
|
|
97
|
+
value={form.Password}
|
|
98
|
+
onChange={(e) => setForm({ ...form, Password: e.target.value })}
|
|
99
|
+
style={{
|
|
100
|
+
padding: "12px",
|
|
101
|
+
border: "1px solid #d1d5db",
|
|
102
|
+
borderRadius: "10px"
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<button
|
|
107
|
+
onClick={register}
|
|
108
|
+
style={{
|
|
109
|
+
background: "__THEME__",
|
|
110
|
+
color: "white",
|
|
111
|
+
border: "none",
|
|
112
|
+
padding: "12px",
|
|
113
|
+
borderRadius: "10px",
|
|
114
|
+
cursor: "pointer",
|
|
115
|
+
fontWeight: "700"
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
Register
|
|
119
|
+
</button>
|
|
120
|
+
|
|
121
|
+
<button
|
|
122
|
+
onClick={login}
|
|
123
|
+
style={{
|
|
124
|
+
background: "black",
|
|
125
|
+
color: "white",
|
|
126
|
+
border: "none",
|
|
127
|
+
padding: "12px",
|
|
128
|
+
borderRadius: "10px",
|
|
129
|
+
cursor: "pointer",
|
|
130
|
+
fontWeight: "700"
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
Login
|
|
134
|
+
</button>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import DashboardLayout from "../layouts/DashboardLayout";
|
|
2
|
+
|
|
3
|
+
export default function Page1() {
|
|
4
|
+
return (
|
|
5
|
+
<DashboardLayout>
|
|
6
|
+
<section style={{
|
|
7
|
+
background: "white",
|
|
8
|
+
padding: "32px",
|
|
9
|
+
borderRadius: "18px",
|
|
10
|
+
border: "1px solid #e5e7eb",
|
|
11
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.06)"
|
|
12
|
+
}}>
|
|
13
|
+
<p style={{
|
|
14
|
+
color: "__THEME__",
|
|
15
|
+
fontWeight: "700",
|
|
16
|
+
marginBottom: "8px"
|
|
17
|
+
}}>
|
|
18
|
+
Ready to customize
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<h1 style={{
|
|
22
|
+
fontSize: "32px",
|
|
23
|
+
marginBottom: "12px"
|
|
24
|
+
}}>
|
|
25
|
+
Welcome to Page 1
|
|
26
|
+
</h1>
|
|
27
|
+
|
|
28
|
+
<p style={{
|
|
29
|
+
color: "#4b5563",
|
|
30
|
+
lineHeight: "1.7"
|
|
31
|
+
}}>
|
|
32
|
+
Start building your own features from this page. Add your forms,
|
|
33
|
+
tables, buttons, and API calls here.
|
|
34
|
+
</p>
|
|
35
|
+
</section>
|
|
36
|
+
</DashboardLayout>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import DashboardLayout from "../layouts/DashboardLayout";
|
|
2
|
+
|
|
3
|
+
export default function Page2() {
|
|
4
|
+
return (
|
|
5
|
+
<DashboardLayout>
|
|
6
|
+
<section style={{
|
|
7
|
+
background: "white",
|
|
8
|
+
padding: "32px",
|
|
9
|
+
borderRadius: "18px",
|
|
10
|
+
border: "1px solid #e5e7eb",
|
|
11
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.06)"
|
|
12
|
+
}}>
|
|
13
|
+
<p style={{
|
|
14
|
+
color: "__THEME__",
|
|
15
|
+
fontWeight: "700",
|
|
16
|
+
marginBottom: "8px"
|
|
17
|
+
}}>
|
|
18
|
+
Ready to customize
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<h1 style={{
|
|
22
|
+
fontSize: "32px",
|
|
23
|
+
marginBottom: "12px"
|
|
24
|
+
}}>
|
|
25
|
+
Welcome to Page 2
|
|
26
|
+
</h1>
|
|
27
|
+
|
|
28
|
+
<p style={{
|
|
29
|
+
color: "#4b5563",
|
|
30
|
+
lineHeight: "1.7"
|
|
31
|
+
}}>
|
|
32
|
+
Start building your own features from this page. Add your forms,
|
|
33
|
+
tables, buttons, and API calls here.
|
|
34
|
+
</p>
|
|
35
|
+
</section>
|
|
36
|
+
</DashboardLayout>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import DashboardLayout from "../layouts/DashboardLayout";
|
|
2
|
+
|
|
3
|
+
export default function Page3() {
|
|
4
|
+
return (
|
|
5
|
+
<DashboardLayout>
|
|
6
|
+
<section style={{
|
|
7
|
+
background: "white",
|
|
8
|
+
padding: "32px",
|
|
9
|
+
borderRadius: "18px",
|
|
10
|
+
border: "1px solid #e5e7eb",
|
|
11
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.06)"
|
|
12
|
+
}}>
|
|
13
|
+
<p style={{
|
|
14
|
+
color: "__THEME__",
|
|
15
|
+
fontWeight: "700",
|
|
16
|
+
marginBottom: "8px"
|
|
17
|
+
}}>
|
|
18
|
+
Ready to customize
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<h1 style={{
|
|
22
|
+
fontSize: "32px",
|
|
23
|
+
marginBottom: "12px"
|
|
24
|
+
}}>
|
|
25
|
+
Welcome to Page 3
|
|
26
|
+
</h1>
|
|
27
|
+
|
|
28
|
+
<p style={{
|
|
29
|
+
color: "#4b5563",
|
|
30
|
+
lineHeight: "1.7"
|
|
31
|
+
}}>
|
|
32
|
+
Start building your own features from this page. Add your forms,
|
|
33
|
+
tables, buttons, and API calls here.
|
|
34
|
+
</p>
|
|
35
|
+
</section>
|
|
36
|
+
</DashboardLayout>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
2
|
+
|
|
3
|
+
import ProtectedRoute from "../components/ProtectedRoute";
|
|
4
|
+
|
|
5
|
+
import Login from "../pages/Login";
|
|
6
|
+
import Dashboard from "../pages/Dashboard";
|
|
7
|
+
import Page1 from "../pages/Page1";
|
|
8
|
+
import Page2 from "../pages/Page2";
|
|
9
|
+
import Page3 from "../pages/Page3";
|
|
10
|
+
|
|
11
|
+
export default function Router() {
|
|
12
|
+
return (
|
|
13
|
+
<BrowserRouter>
|
|
14
|
+
<Routes>
|
|
15
|
+
<Route path="/login" element={<Login />} />
|
|
16
|
+
|
|
17
|
+
<Route path="/" element={
|
|
18
|
+
<ProtectedRoute>
|
|
19
|
+
<Dashboard />
|
|
20
|
+
</ProtectedRoute>
|
|
21
|
+
} />
|
|
22
|
+
|
|
23
|
+
<Route path="/page1" element={
|
|
24
|
+
<ProtectedRoute>
|
|
25
|
+
<Page1 />
|
|
26
|
+
</ProtectedRoute>
|
|
27
|
+
} />
|
|
28
|
+
|
|
29
|
+
<Route path="/page2" element={
|
|
30
|
+
<ProtectedRoute>
|
|
31
|
+
<Page2 />
|
|
32
|
+
</ProtectedRoute>
|
|
33
|
+
} />
|
|
34
|
+
|
|
35
|
+
<Route path="/page3" element={
|
|
36
|
+
<ProtectedRoute>
|
|
37
|
+
<Page3 />
|
|
38
|
+
</ProtectedRoute>
|
|
39
|
+
} />
|
|
40
|
+
</Routes>
|
|
41
|
+
</BrowserRouter>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const isAuthenticated = () => {
|
|
2
|
+
return !!localStorage.getItem("user");
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export const getUser = () => {
|
|
6
|
+
const user = localStorage.getItem("user");
|
|
7
|
+
return user ? JSON.parse(user) : null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const saveUser = (user) => {
|
|
11
|
+
localStorage.setItem("user", JSON.stringify(user));
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const clearUser = () => {
|
|
15
|
+
localStorage.removeItem("user");
|
|
16
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import mysql from "mysql2";
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
|
|
6
|
+
const db = mysql.createPool({
|
|
7
|
+
host: process.env.DB_HOST || "localhost",
|
|
8
|
+
user: process.env.DB_USER || "root",
|
|
9
|
+
password: process.env.DB_PASSWORD || "",
|
|
10
|
+
database: process.env.DB_NAME
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export default db;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import bcrypt from "bcrypt";
|
|
2
|
+
import db from "../config/db.js";
|
|
3
|
+
|
|
4
|
+
export const register = async (req, res) => {
|
|
5
|
+
const { Username, Password } = req.body;
|
|
6
|
+
|
|
7
|
+
if (!Username || !Password) {
|
|
8
|
+
return res.status(400).json({ message: "Username and Password are required" });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const hashedPassword = await bcrypt.hash(Password, 10);
|
|
12
|
+
|
|
13
|
+
db.query(
|
|
14
|
+
"INSERT INTO Users (Username, Password) VALUES (?, ?)",
|
|
15
|
+
[Username, hashedPassword],
|
|
16
|
+
(err) => {
|
|
17
|
+
if (err) {
|
|
18
|
+
if (err.code === "ER_DUP_ENTRY") {
|
|
19
|
+
return res.status(409).json({ message: "Username already exists" });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return res.status(500).json({ message: "Registration failed", error: err.message });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
res.status(201).json({ message: "Registered successfully" });
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const login = (req, res) => {
|
|
31
|
+
const { Username, Password } = req.body;
|
|
32
|
+
|
|
33
|
+
db.query(
|
|
34
|
+
"SELECT * FROM Users WHERE Username = ?",
|
|
35
|
+
[Username],
|
|
36
|
+
async (err, data) => {
|
|
37
|
+
if (err) {
|
|
38
|
+
return res.status(500).json({ message: "Login failed", error: err.message });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (data.length === 0) {
|
|
42
|
+
return res.status(404).json({ message: "User not found" });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const user = data[0];
|
|
46
|
+
const validPassword = await bcrypt.compare(Password, user.Password);
|
|
47
|
+
|
|
48
|
+
if (!validPassword) {
|
|
49
|
+
return res.status(401).json({ message: "Invalid credentials" });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
req.session.user = {
|
|
53
|
+
UserID: user.UserID,
|
|
54
|
+
Username: user.Username
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
res.json(req.session.user);
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const logout = (req, res) => {
|
|
63
|
+
req.session.destroy(() => {
|
|
64
|
+
res.json({ message: "Logged out" });
|
|
65
|
+
});
|
|
66
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import dotenv from "dotenv";
|
|
4
|
+
import session from "express-session";
|
|
5
|
+
import authRoutes from "./routes/authRoutes.js";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
dotenv.config();
|
|
9
|
+
|
|
10
|
+
const app = express();
|
|
11
|
+
|
|
12
|
+
app.use(cors({
|
|
13
|
+
origin: process.env.CLIENT_URL,
|
|
14
|
+
credentials: true
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
app.use(express.json());
|
|
18
|
+
|
|
19
|
+
app.use(session({
|
|
20
|
+
secret: process.env.SESSION_SECRET,
|
|
21
|
+
resave: false,
|
|
22
|
+
saveUninitialized: false,
|
|
23
|
+
cookie: {
|
|
24
|
+
httpOnly: true,
|
|
25
|
+
secure: false
|
|
26
|
+
}
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
app.use("/auth", authRoutes);
|
|
30
|
+
|
|
31
|
+
app.get("/", (req, res) => {
|
|
32
|
+
res.json({ message: "Parachute backend running" });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
app.listen(process.env.PORT, () => {
|
|
38
|
+
console.log(`Server running on port ${process.env.PORT}`);
|
|
39
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"scripts": {
|
|
4
|
+
"dev": "nodemon index.js",
|
|
5
|
+
"start": "node index.js"
|
|
6
|
+
},
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"bcrypt": "^5.1.1",
|
|
9
|
+
"cors": "^2.8.5",
|
|
10
|
+
"dotenv": "^16.4.5",
|
|
11
|
+
"express": "^4.19.2",
|
|
12
|
+
"express-session": "^1.18.0",
|
|
13
|
+
"mysql2": "^3.10.3"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"nodemon": "^3.1.4"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import {
|
|
3
|
+
register,
|
|
4
|
+
login,
|
|
5
|
+
logout
|
|
6
|
+
} from "../controllers/authController.js";
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
router.post("/register", register);
|
|
11
|
+
router.post("/login", login);
|
|
12
|
+
router.post("/logout", logout);
|
|
13
|
+
|
|
14
|
+
export default router;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Parachute</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"private": true,
|
|
3
|
+
"scripts": {
|
|
4
|
+
"dev": "vite",
|
|
5
|
+
"build": "vite build",
|
|
6
|
+
"preview": "vite preview"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"axios": "^1.7.2",
|
|
10
|
+
"react": "^18.3.1",
|
|
11
|
+
"react-dom": "^18.3.1",
|
|
12
|
+
"react-router-dom": "^6.30.1"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
16
|
+
"vite": "^5.3.4"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Link, useNavigate } from "react-router-dom";
|
|
2
|
+
import { api } from "../api/client";
|
|
3
|
+
import { clearUser, getUser } from "../utils/auth";
|
|
4
|
+
|
|
5
|
+
export default function Navbar() {
|
|
6
|
+
const navigate = useNavigate();
|
|
7
|
+
const user = getUser();
|
|
8
|
+
|
|
9
|
+
const logout = async () => {
|
|
10
|
+
try {
|
|
11
|
+
await api.post("/auth/logout");
|
|
12
|
+
} catch {}
|
|
13
|
+
|
|
14
|
+
clearUser();
|
|
15
|
+
navigate("/login");
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<nav style={{
|
|
20
|
+
background: "white",
|
|
21
|
+
borderBottom: "1px solid #e5e7eb",
|
|
22
|
+
padding: "18px 32px",
|
|
23
|
+
display: "flex",
|
|
24
|
+
gap: "24px",
|
|
25
|
+
alignItems: "center"
|
|
26
|
+
}}>
|
|
27
|
+
<strong style={{ marginRight: "12px" }}>Parachute</strong>
|
|
28
|
+
|
|
29
|
+
<Link to="/">Dashboard</Link>
|
|
30
|
+
<Link to="/page1">Page 1</Link>
|
|
31
|
+
<Link to="/page2">Page 2</Link>
|
|
32
|
+
<Link to="/page3">Page 3</Link>
|
|
33
|
+
|
|
34
|
+
<span style={{
|
|
35
|
+
marginLeft: "auto",
|
|
36
|
+
color: "#6b7280",
|
|
37
|
+
fontSize: "14px"
|
|
38
|
+
}}>
|
|
39
|
+
{user?.Username}
|
|
40
|
+
</span>
|
|
41
|
+
|
|
42
|
+
<button
|
|
43
|
+
onClick={logout}
|
|
44
|
+
style={{
|
|
45
|
+
background: "__THEME__",
|
|
46
|
+
color: "white",
|
|
47
|
+
border: "none",
|
|
48
|
+
padding: "10px 16px",
|
|
49
|
+
borderRadius: "10px",
|
|
50
|
+
cursor: "pointer"
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
Logout
|
|
54
|
+
</button>
|
|
55
|
+
</nav>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Navbar from "../components/Navbar";
|
|
2
|
+
|
|
3
|
+
export default function DashboardLayout({ children }) {
|
|
4
|
+
return (
|
|
5
|
+
<div style={{
|
|
6
|
+
minHeight: "100vh",
|
|
7
|
+
background: "#f8fafc",
|
|
8
|
+
fontFamily: "Arial, sans-serif"
|
|
9
|
+
}}>
|
|
10
|
+
<Navbar />
|
|
11
|
+
|
|
12
|
+
<main style={{
|
|
13
|
+
padding: "40px",
|
|
14
|
+
maxWidth: "1100px",
|
|
15
|
+
margin: "0 auto"
|
|
16
|
+
}}>
|
|
17
|
+
{children}
|
|
18
|
+
</main>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import DashboardLayout from "../layouts/DashboardLayout";
|
|
2
|
+
|
|
3
|
+
export default function Dashboard() {
|
|
4
|
+
return (
|
|
5
|
+
<DashboardLayout>
|
|
6
|
+
<section style={{
|
|
7
|
+
background: "white",
|
|
8
|
+
padding: "32px",
|
|
9
|
+
borderRadius: "18px",
|
|
10
|
+
border: "1px solid #e5e7eb",
|
|
11
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.06)"
|
|
12
|
+
}}>
|
|
13
|
+
<p style={{
|
|
14
|
+
color: "__THEME__",
|
|
15
|
+
fontWeight: "700",
|
|
16
|
+
marginBottom: "8px"
|
|
17
|
+
}}>
|
|
18
|
+
Ready to customize
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<h1 style={{
|
|
22
|
+
fontSize: "32px",
|
|
23
|
+
marginBottom: "12px"
|
|
24
|
+
}}>
|
|
25
|
+
Welcome Dashboard
|
|
26
|
+
</h1>
|
|
27
|
+
|
|
28
|
+
<p style={{
|
|
29
|
+
color: "#4b5563",
|
|
30
|
+
lineHeight: "1.7"
|
|
31
|
+
}}>
|
|
32
|
+
Start building your own features from this page. Add your forms,
|
|
33
|
+
tables, buttons, and API calls here.
|
|
34
|
+
</p>
|
|
35
|
+
</section>
|
|
36
|
+
</DashboardLayout>
|
|
37
|
+
);
|
|
38
|
+
}
|