create-batman 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.
Files changed (55) hide show
  1. package/README.md +27 -0
  2. package/bin/index.js +485 -0
  3. package/package.json +30 -0
  4. package/templates/mysql/client/index.html +12 -0
  5. package/templates/mysql/client/package.json +21 -0
  6. package/templates/mysql/client/postcss.config.js +6 -0
  7. package/templates/mysql/client/src/App.jsx +5 -0
  8. package/templates/mysql/client/src/api/client.js +6 -0
  9. package/templates/mysql/client/src/components/Navbar.jsx +75 -0
  10. package/templates/mysql/client/src/components/ProtectedRoute.jsx +10 -0
  11. package/templates/mysql/client/src/index.css +24 -0
  12. package/templates/mysql/client/src/layouts/DashboardLayout.jsx +12 -0
  13. package/templates/mysql/client/src/main.jsx +10 -0
  14. package/templates/mysql/client/src/pages/Dashboard.jsx +44 -0
  15. package/templates/mysql/client/src/pages/Login.jsx +121 -0
  16. package/templates/mysql/client/src/pages/Page1.jsx +101 -0
  17. package/templates/mysql/client/src/pages/Page2.jsx +82 -0
  18. package/templates/mysql/client/src/pages/Page3.jsx +49 -0
  19. package/templates/mysql/client/src/router/index.jsx +43 -0
  20. package/templates/mysql/client/src/utils/auth.js +16 -0
  21. package/templates/mysql/client/tailwind.config.js +15 -0
  22. package/templates/mysql/client/vite.config.js +6 -0
  23. package/templates/mysql/server/config/db.js +13 -0
  24. package/templates/mysql/server/controllers/authController.js +66 -0
  25. package/templates/mysql/server/index.js +39 -0
  26. package/templates/mysql/server/middleware/authMiddleware.js +7 -0
  27. package/templates/mysql/server/package.json +18 -0
  28. package/templates/mysql/server/routes/authRoutes.js +14 -0
  29. package/templates/sequelize/client/index.html +12 -0
  30. package/templates/sequelize/client/package.json +21 -0
  31. package/templates/sequelize/client/postcss.config.js +6 -0
  32. package/templates/sequelize/client/src/App.jsx +5 -0
  33. package/templates/sequelize/client/src/api/client.js +6 -0
  34. package/templates/sequelize/client/src/components/Navbar.jsx +75 -0
  35. package/templates/sequelize/client/src/components/ProtectedRoute.jsx +10 -0
  36. package/templates/sequelize/client/src/index.css +24 -0
  37. package/templates/sequelize/client/src/layouts/DashboardLayout.jsx +12 -0
  38. package/templates/sequelize/client/src/main.jsx +10 -0
  39. package/templates/sequelize/client/src/pages/Dashboard.jsx +44 -0
  40. package/templates/sequelize/client/src/pages/Login.jsx +121 -0
  41. package/templates/sequelize/client/src/pages/Page1.jsx +101 -0
  42. package/templates/sequelize/client/src/pages/Page2.jsx +82 -0
  43. package/templates/sequelize/client/src/pages/Page3.jsx +49 -0
  44. package/templates/sequelize/client/src/router/index.jsx +43 -0
  45. package/templates/sequelize/client/src/utils/auth.js +16 -0
  46. package/templates/sequelize/client/tailwind.config.js +15 -0
  47. package/templates/sequelize/client/vite.config.js +6 -0
  48. package/templates/sequelize/server/config/db.js +13 -0
  49. package/templates/sequelize/server/config/sequelize.js +17 -0
  50. package/templates/sequelize/server/controllers/authController.js +62 -0
  51. package/templates/sequelize/server/index.js +42 -0
  52. package/templates/sequelize/server/middleware/authMiddleware.js +7 -0
  53. package/templates/sequelize/server/models/User.js +24 -0
  54. package/templates/sequelize/server/package.json +19 -0
  55. package/templates/sequelize/server/routes/authRoutes.js +14 -0
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+
2
+ # Create Parachute
3
+
4
+ A clean fullstack starter generator for students.
5
+
6
+ ## Usage
7
+
8
+ ```bash
9
+ npx create-parachute my-project
10
+ ```
11
+
12
+ The generator creates:
13
+
14
+ - Express backend
15
+ - React + Vite frontend
16
+ - Raw MySQL or Sequelize option
17
+ - Session authentication
18
+ - Username login
19
+ - Hashed passwords
20
+ - React Router navigation
21
+ - Protected routes
22
+ - Dashboard + 3 pages
23
+ - `schema.sql`
24
+ - `instructions.md`
25
+ - `tailwind.md`
26
+ - `sql.md`
27
+ - Optional dependency installation
package/bin/index.js ADDED
@@ -0,0 +1,485 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import inquirer from "inquirer";
6
+ import chalk from "chalk";
7
+ import { spawnSync } from "child_process";
8
+ import { fileURLToPath } from "url";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ let projectName = process.argv[2];
14
+
15
+ if (!projectName) {
16
+ const projectAnswer = await inquirer.prompt([
17
+ {
18
+ type: "input",
19
+ name: "projectName",
20
+ message: "Project name:",
21
+ validate: (input) => {
22
+ if (!input.trim()) {
23
+ return "Project name is required.";
24
+ }
25
+
26
+ if (!/^[a-zA-Z0-9-_]+$/.test(input)) {
27
+ return "Only letters, numbers, hyphens, and underscores are allowed.";
28
+ }
29
+
30
+ return true;
31
+ },
32
+ filter: (input) => input.trim()
33
+ }
34
+ ]);
35
+
36
+ projectName = projectAnswer.projectName;
37
+ }
38
+
39
+ const answers = await inquirer.prompt([
40
+ {
41
+ type: "input",
42
+ name: "dbName",
43
+ message: "Database name:",
44
+ default: `${projectName.replace(/-/g, "_")}_db`
45
+ },
46
+ {
47
+ type: "input",
48
+ name: "serverPort",
49
+ message: "Backend port:",
50
+ default: "5000"
51
+ },
52
+ {
53
+ type: "input",
54
+ name: "clientPort",
55
+ message: "Frontend port:",
56
+ default: "5173"
57
+ },
58
+ {
59
+ type: "list",
60
+ name: "database",
61
+ message: "Choose database style:",
62
+ choices: [
63
+ { name: "Raw MySQL", value: "mysql" },
64
+ { name: "Sequelize", value: "sequelize" }
65
+ ]
66
+ },
67
+ {
68
+ type: "list",
69
+ name: "theme",
70
+ message: "Choose theme:",
71
+ choices: [
72
+ { name: chalk.blue("Blue"), value: "#2563EB" },
73
+ { name: chalk.green("Green"), value: "#16A34A" },
74
+ { name: chalk.red("Red"), value: "#DC2626" },
75
+ { name: chalk.magenta("Purple"), value: "#9333EA" },
76
+ { name: chalk.yellow("Yellow"), value: "#CA8A04" },
77
+ { name: chalk.cyan("Cyan"), value: "#0891B2" },
78
+ { name: chalk.hex("#800000")("Maroon"), value: "#800000" }
79
+ ]
80
+ },
81
+ {
82
+ type: "confirm",
83
+ name: "installDeps",
84
+ message: "Install backend and frontend dependencies now?",
85
+ default: true
86
+ }
87
+ ]);
88
+
89
+ const template = path.join(__dirname, "..", "templates", answers.database);
90
+ const target = path.join(process.cwd(), projectName);
91
+
92
+ if (fs.existsSync(target)) {
93
+ console.log(chalk.red("Folder already exists. Choose another project name."));
94
+ process.exit(1);
95
+ }
96
+
97
+ await fs.copy(template, target);
98
+
99
+ const env = `PORT=${answers.serverPort}
100
+ DB_HOST=localhost
101
+ DB_USER=root
102
+ DB_PASSWORD=
103
+ DB_NAME=${answers.dbName}
104
+ CLIENT_URL=http://localhost:${answers.clientPort}
105
+ SESSION_SECRET=batman_secret`;
106
+
107
+ await fs.writeFile(path.join(target, "server/.env"), env);
108
+
109
+ const schema = `CREATE DATABASE IF NOT EXISTS ${answers.dbName};
110
+ USE ${answers.dbName};
111
+
112
+ CREATE TABLE Users(
113
+ UserID INT AUTO_INCREMENT PRIMARY KEY,
114
+ Username VARCHAR(255) UNIQUE NOT NULL,
115
+ Password VARCHAR(255) NOT NULL
116
+ );`;
117
+
118
+ await fs.writeFile(path.join(target, "schema.sql"), schema);
119
+
120
+ const instructions = `# ${projectName}
121
+
122
+ ## 1. Run the database file
123
+
124
+ Open MySQL and run schema.sql.
125
+
126
+ Example:
127
+
128
+ SOURCE /full/path/to/${projectName}/schema.sql;
129
+
130
+ ## 2. Start backend
131
+
132
+ cd server
133
+ npm run dev
134
+
135
+ ## 3. Start frontend
136
+
137
+ cd client
138
+ npm run dev
139
+
140
+ ## Notes
141
+
142
+ - Backend runs on port ${answers.serverPort}
143
+ - Frontend runs on port ${answers.clientPort}
144
+ - Auth uses Username and hashed Password
145
+ - Add your own backend features inside controllers and routes
146
+ - Add your own frontend screens inside client/src/pages
147
+ `;
148
+
149
+ await fs.writeFile(path.join(target, "instructions.md"), instructions);
150
+
151
+ const tailwindCheat = `# Tailwind Cheat Sheet
152
+
153
+ ## Layout
154
+
155
+ flex
156
+ grid
157
+ block
158
+ hidden
159
+ relative
160
+ absolute
161
+ fixed
162
+ sticky
163
+
164
+ ## Flexbox
165
+
166
+ flex
167
+ flex-col
168
+ flex-row
169
+ items-center
170
+ items-start
171
+ items-end
172
+ justify-center
173
+ justify-between
174
+ justify-end
175
+ gap-2
176
+ gap-4
177
+ gap-6
178
+
179
+ ## Grid
180
+
181
+ grid
182
+ grid-cols-1
183
+ grid-cols-2
184
+ grid-cols-3
185
+ md:grid-cols-2
186
+ lg:grid-cols-3
187
+
188
+ ## Spacing
189
+
190
+ p-2
191
+ p-4
192
+ p-6
193
+ p-8
194
+ px-4
195
+ px-6
196
+ py-2
197
+ py-3
198
+ py-4
199
+ m-2
200
+ m-4
201
+ mt-4
202
+ mb-4
203
+ mx-auto
204
+
205
+ ## Width and Height
206
+
207
+ w-full
208
+ w-screen
209
+ max-w-md
210
+ max-w-xl
211
+ max-w-6xl
212
+ min-h-screen
213
+ h-screen
214
+
215
+ ## Typography
216
+
217
+ text-sm
218
+ text-base
219
+ text-lg
220
+ text-xl
221
+ text-2xl
222
+ font-medium
223
+ font-semibold
224
+ font-bold
225
+ text-center
226
+
227
+ ## Colors
228
+
229
+ bg-white
230
+ bg-black
231
+ bg-gray-50
232
+ bg-gray-100
233
+ text-black
234
+ text-white
235
+ text-gray-500
236
+ text-gray-700
237
+
238
+ ## Borders and Radius
239
+
240
+ border
241
+ border-gray-200
242
+ rounded
243
+ rounded-lg
244
+ rounded-xl
245
+ rounded-2xl
246
+
247
+ ## Shadows
248
+
249
+ shadow
250
+ shadow-md
251
+ shadow-lg
252
+
253
+ ## Buttons
254
+
255
+ bg-black text-white px-4 py-2 rounded-lg
256
+ bg-blue-600 text-white px-4 py-2 rounded-lg
257
+
258
+ ## Forms
259
+
260
+ border rounded-lg px-4 py-2 w-full
261
+ focus:outline-none focus:ring-2 focus:ring-blue-500
262
+
263
+ ## Responsive Examples
264
+
265
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
266
+ </div>
267
+
268
+ <div className="p-4 md:p-8">
269
+ </div>
270
+ `;
271
+
272
+ await fs.writeFile(path.join(target, "tailwind.md"), tailwindCheat);
273
+
274
+ const sqlCheat = `# SQL Cheat Sheet
275
+
276
+ ## Create Database
277
+
278
+ CREATE DATABASE school_db;
279
+ USE school_db;
280
+
281
+ ## Create Table
282
+
283
+ CREATE TABLE Students(
284
+ StudentID INT AUTO_INCREMENT PRIMARY KEY,
285
+ StudentName VARCHAR(100) NOT NULL,
286
+ Age INT
287
+ );
288
+
289
+ ## Insert
290
+
291
+ INSERT INTO Students (StudentName, Age)
292
+ VALUES ('Aime', 20);
293
+
294
+ ## Select All
295
+
296
+ SELECT * FROM Students;
297
+
298
+ ## Select Specific Columns
299
+
300
+ SELECT StudentName, Age FROM Students;
301
+
302
+ ## Filter with WHERE
303
+
304
+ SELECT * FROM Students
305
+ WHERE Age > 18;
306
+
307
+ ## Search with LIKE
308
+
309
+ SELECT * FROM Students
310
+ WHERE StudentName LIKE '%ai%';
311
+
312
+ ## Sort
313
+
314
+ SELECT * FROM Students
315
+ ORDER BY StudentName ASC;
316
+
317
+ SELECT * FROM Students
318
+ ORDER BY Age DESC;
319
+
320
+ ## Limit
321
+
322
+ SELECT * FROM Students
323
+ LIMIT 5;
324
+
325
+ ## Update
326
+
327
+ UPDATE Students
328
+ SET Age = 21
329
+ WHERE StudentID = 1;
330
+
331
+ ## Delete
332
+
333
+ DELETE FROM Students
334
+ WHERE StudentID = 1;
335
+
336
+ ## Count
337
+
338
+ SELECT COUNT(*) AS TotalStudents
339
+ FROM Students;
340
+
341
+ ## Sum
342
+
343
+ SELECT SUM(Amount) AS TotalAmount
344
+ FROM Payments;
345
+
346
+ ## Average
347
+
348
+ SELECT AVG(Marks) AS AverageMarks
349
+ FROM Results;
350
+
351
+ ## Group By
352
+
353
+ SELECT ClassID, COUNT(*) AS TotalStudents
354
+ FROM Students
355
+ GROUP BY ClassID;
356
+
357
+ ## Having
358
+
359
+ SELECT ClassID, COUNT(*) AS TotalStudents
360
+ FROM Students
361
+ GROUP BY ClassID
362
+ HAVING COUNT(*) > 5;
363
+
364
+ ## Inner Join
365
+
366
+ SELECT Students.StudentName, Classes.ClassName
367
+ FROM Students
368
+ INNER JOIN Classes
369
+ ON Students.ClassID = Classes.ClassID;
370
+
371
+ ## Left Join
372
+
373
+ SELECT Students.StudentName, Payments.Amount
374
+ FROM Students
375
+ LEFT JOIN Payments
376
+ ON Students.StudentID = Payments.StudentID;
377
+
378
+ ## Multiple Joins
379
+
380
+ SELECT Students.StudentName, Classes.ClassName, Payments.Amount
381
+ FROM Students
382
+ JOIN Classes ON Students.ClassID = Classes.ClassID
383
+ JOIN Payments ON Students.StudentID = Payments.StudentID;
384
+
385
+ ## Subquery
386
+
387
+ SELECT * FROM Students
388
+ WHERE ClassID IN (
389
+ SELECT ClassID FROM Classes WHERE ClassName = 'S6'
390
+ );
391
+
392
+ ## Between
393
+
394
+ SELECT * FROM Payments
395
+ WHERE Amount BETWEEN 1000 AND 5000;
396
+
397
+ ## Dates
398
+
399
+ SELECT * FROM Orders
400
+ WHERE DATE(CreatedAt) = CURDATE();
401
+
402
+ ## Beginner Exam CRUD Pattern
403
+
404
+ GET all:
405
+ SELECT * FROM TableName;
406
+
407
+ GET one:
408
+ SELECT * FROM TableName WHERE ID = ?;
409
+
410
+ CREATE:
411
+ INSERT INTO TableName (Column1, Column2) VALUES (?, ?);
412
+
413
+ UPDATE:
414
+ UPDATE TableName SET Column1 = ?, Column2 = ? WHERE ID = ?;
415
+
416
+ DELETE:
417
+ DELETE FROM TableName WHERE ID = ?;
418
+ `;
419
+
420
+ await fs.writeFile(path.join(target, "sql.md"), sqlCheat);
421
+
422
+ async function inject(dir) {
423
+ const files = await fs.readdir(dir);
424
+
425
+ for (const file of files) {
426
+ const full = path.join(dir, file);
427
+ const stat = await fs.stat(full);
428
+
429
+ if (stat.isDirectory()) {
430
+ await inject(full);
431
+ } else {
432
+ try {
433
+ let content = await fs.readFile(full, "utf8");
434
+
435
+ content = content.replaceAll("__THEME__", answers.theme);
436
+ content = content.replaceAll("__PORT__", answers.serverPort);
437
+
438
+ await fs.writeFile(full, content);
439
+ } catch {}
440
+ }
441
+ }
442
+ }
443
+
444
+ await inject(target);
445
+
446
+ console.log(chalk.green("\nProject created successfully.\n"));
447
+
448
+ if (answers.installDeps) {
449
+ console.log(chalk.white("Installing backend dependencies..."));
450
+
451
+ const serverInstall = spawnSync("npm", ["install"], {
452
+ cwd: path.join(target, "server"),
453
+ stdio: "inherit",
454
+ shell: true
455
+ });
456
+
457
+ if (serverInstall.status !== 0) {
458
+ console.log(
459
+ chalk.red(
460
+ "Backend dependency installation failed. Run npm install manually inside server."
461
+ )
462
+ );
463
+ }
464
+
465
+ console.log(chalk.white("Installing frontend dependencies..."));
466
+
467
+ const clientInstall = spawnSync("npm", ["install"], {
468
+ cwd: path.join(target, "client"),
469
+ stdio: "inherit",
470
+ shell: true
471
+ });
472
+
473
+ if (clientInstall.status !== 0) {
474
+ console.log(
475
+ chalk.red(
476
+ "Frontend dependency installation failed. Run npm install manually inside client."
477
+ )
478
+ );
479
+ }
480
+ }
481
+
482
+ console.log(chalk.cyan("\nNext steps:\n"));
483
+ console.log(chalk.gray(`1. Run schema.sql in MySQL`));
484
+ console.log(chalk.gray(`2. cd ${projectName}/server && npm run dev`));
485
+ console.log(chalk.gray(`3. cd ${projectName}/client && npm run dev\n`));
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "create-batman",
3
+ "version": "1.0.2",
4
+ "description": "Student-friendly fullstack exam starter generator",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-batman": "bin/index.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/index.js"
11
+ },
12
+ "keywords": [
13
+ "cli",
14
+ "student",
15
+ "exam",
16
+ "react",
17
+ "vite",
18
+ "express",
19
+ "mysql",
20
+ "sequelize",
21
+ "batman"
22
+ ],
23
+ "author": "Aime",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "chalk": "^5.3.0",
27
+ "fs-extra": "^11.2.0",
28
+ "inquirer": "^9.2.23"
29
+ }
30
+ }
@@ -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,21 @@
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
+ "autoprefixer": "^10.4.20",
17
+ "postcss": "^8.4.41",
18
+ "tailwindcss": "^3.4.10",
19
+ "vite": "^5.3.4"
20
+ }
21
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {}
5
+ }
6
+ };
@@ -0,0 +1,5 @@
1
+ import Router from "./router";
2
+
3
+ export default function App() {
4
+ return <Router />;
5
+ }
@@ -0,0 +1,6 @@
1
+ import axios from "axios";
2
+
3
+ export const api = axios.create({
4
+ baseURL: "http://localhost:__PORT__",
5
+ withCredentials: true
6
+ });
@@ -0,0 +1,75 @@
1
+ import { NavLink, useNavigate } from "react-router-dom";
2
+ import { api } from "../api/client";
3
+ import { clearUser, getUser } from "../utils/auth";
4
+
5
+ const links = [
6
+ { to: "/", label: "Dashboard" },
7
+ { to: "/page1", label: "Form" },
8
+ { to: "/page2", label: "Table" },
9
+ { to: "/page3", label: "Cards" }
10
+ ];
11
+
12
+ export default function Navbar() {
13
+ const navigate = useNavigate();
14
+ const user = getUser();
15
+
16
+ const logout = async () => {
17
+ try {
18
+ await api.post("/auth/logout");
19
+ } catch {}
20
+
21
+ clearUser();
22
+ navigate("/login");
23
+ };
24
+
25
+ return (
26
+ <header className="sticky top-0 z-50 border-b border-slate-200 bg-white/95 backdrop-blur">
27
+ <div className="mx-auto flex max-w-6xl flex-col gap-4 px-4 py-4 sm:px-6 lg:flex-row lg:items-center lg:justify-between lg:px-8">
28
+ <div className="flex items-center justify-between gap-4">
29
+ <div>
30
+ <p className="text-lg font-black tracking-tight text-slate-950">Parachute</p>
31
+ <p className="text-xs text-slate-500">Full-stack starter</p>
32
+ </div>
33
+
34
+ <button
35
+ onClick={logout}
36
+ className="rounded-xl bg-slate-950 px-4 py-2 text-sm font-bold text-white lg:hidden"
37
+ >
38
+ Logout
39
+ </button>
40
+ </div>
41
+
42
+ <nav className="flex gap-2 overflow-x-auto rounded-2xl border border-slate-200 bg-slate-50 p-1">
43
+ {links.map((link) => (
44
+ <NavLink
45
+ key={link.to}
46
+ to={link.to}
47
+ end={link.to === "/"}
48
+ className={({ isActive }) =>
49
+ `whitespace-nowrap rounded-xl px-4 py-2 text-sm font-bold transition ${
50
+ isActive
51
+ ? "bg-white text-slate-950 shadow-sm"
52
+ : "text-slate-500 hover:bg-white hover:text-slate-900"
53
+ }`
54
+ }
55
+ >
56
+ {link.label}
57
+ </NavLink>
58
+ ))}
59
+ </nav>
60
+
61
+ <div className="hidden items-center gap-3 lg:flex">
62
+ <span className="max-w-[180px] truncate text-sm font-semibold text-slate-500">
63
+ {user?.Username || "User"}
64
+ </span>
65
+ <button
66
+ onClick={logout}
67
+ className="rounded-xl bg-slate-950 px-4 py-2 text-sm font-bold text-white transition hover:bg-slate-800"
68
+ >
69
+ Logout
70
+ </button>
71
+ </div>
72
+ </div>
73
+ </header>
74
+ );
75
+ }
@@ -0,0 +1,10 @@
1
+ import { Navigate } from "react-router-dom";
2
+ import { isAuthenticated } from "../utils/auth";
3
+
4
+ export default function ProtectedRoute({ children }) {
5
+ if (!isAuthenticated()) {
6
+ return <Navigate to="/login" />;
7
+ }
8
+
9
+ return children;
10
+ }
@@ -0,0 +1,24 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ * {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ body {
10
+ margin: 0;
11
+ min-width: 320px;
12
+ background: #f8fafc;
13
+ color: #111827;
14
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
15
+ }
16
+
17
+ a {
18
+ color: inherit;
19
+ text-decoration: none;
20
+ }
21
+
22
+ input, select, textarea, button {
23
+ font: inherit;
24
+ }