create-bus-project 1.0.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/PUBLISHING.md +35 -0
  3. package/README.md +51 -0
  4. package/bin/create-bus-project.js +113 -0
  5. package/package.json +31 -0
  6. package/template/README.md +35 -0
  7. package/template/backend/.env +8 -0
  8. package/template/backend/.env.example +8 -0
  9. package/template/backend/README.md +29 -0
  10. package/template/backend/config/db.js +21 -0
  11. package/template/backend/controllers/authController.js +109 -0
  12. package/template/backend/controllers/bookingController.js +99 -0
  13. package/template/backend/controllers/busController.js +34 -0
  14. package/template/backend/controllers/scheduleController.js +52 -0
  15. package/template/backend/controllers/userController.js +39 -0
  16. package/template/backend/index.js +39 -0
  17. package/template/backend/package.json +22 -0
  18. package/template/backend/routes/authRoutes.js +17 -0
  19. package/template/backend/routes/bookingRoutes.js +20 -0
  20. package/template/backend/routes/busRoutes.js +10 -0
  21. package/template/backend/routes/scheduleRoutes.js +10 -0
  22. package/template/backend/routes/userRoutes.js +10 -0
  23. package/template/backend/schema.sql +59 -0
  24. package/template/backend/seeders/seedUsers.js +35 -0
  25. package/template/frontend/.env +1 -0
  26. package/template/frontend/.env.example +1 -0
  27. package/template/frontend/README.md +14 -0
  28. package/template/frontend/index.html +12 -0
  29. package/template/frontend/package.json +21 -0
  30. package/template/frontend/src/App.jsx +55 -0
  31. package/template/frontend/src/api.js +1 -0
  32. package/template/frontend/src/main.jsx +10 -0
  33. package/template/frontend/src/pages/Bookings.jsx +129 -0
  34. package/template/frontend/src/pages/Buses.jsx +100 -0
  35. package/template/frontend/src/pages/Dashboard.jsx +10 -0
  36. package/template/frontend/src/pages/Login.jsx +141 -0
  37. package/template/frontend/src/pages/ManagerDashboard.jsx +39 -0
  38. package/template/frontend/src/pages/PassengerDashboard.jsx +248 -0
  39. package/template/frontend/src/pages/Report.jsx +158 -0
  40. package/template/frontend/src/pages/Schedules.jsx +142 -0
  41. package/template/frontend/src/pages/Users.jsx +105 -0
  42. package/template/frontend/src/style.css +236 -0
  43. package/template/frontend/vite.config.js +10 -0
@@ -0,0 +1,39 @@
1
+ import express from "express";
2
+ import cors from "cors";
3
+ import session from "express-session";
4
+ import dotenv from "dotenv";
5
+
6
+ import authRoutes from "./routes/authRoutes.js";
7
+ import userRoutes from "./routes/userRoutes.js";
8
+ import busRoutes from "./routes/busRoutes.js";
9
+ import scheduleRoutes from "./routes/scheduleRoutes.js";
10
+ import bookingRoutes from "./routes/bookingRoutes.js";
11
+
12
+ dotenv.config();
13
+
14
+ const app = express();
15
+
16
+ app.use(cors({
17
+ origin: process.env.FRONTEND_URL || "http://localhost:__FRONTEND_PORT__",
18
+ credentials: true
19
+ }));
20
+
21
+ app.use(express.json());
22
+
23
+ app.use(session({
24
+ secret: process.env.SESSION_SECRET || "y_bus_secret",
25
+ resave: false,
26
+ saveUninitialized: false
27
+ }));
28
+
29
+ app.use("/api/auth", authRoutes);
30
+ app.use("/api/users", userRoutes);
31
+ app.use("/api/buses", busRoutes);
32
+ app.use("/api/schedules", scheduleRoutes);
33
+ app.use("/api/bookings", bookingRoutes);
34
+
35
+ const PORT = process.env.PORT || __BACKEND_PORT__;
36
+
37
+ app.listen(PORT, () => {
38
+ console.log(`Server running on port ${PORT}`);
39
+ });
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "__PROJECT_NAME__-backend",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "node index.js",
8
+ "dev": "nodemon index.js",
9
+ "seed": "node seeders/seedUsers.js"
10
+ },
11
+ "dependencies": {
12
+ "bcryptjs": "^2.4.3",
13
+ "cors": "^2.8.5",
14
+ "dotenv": "^16.4.5",
15
+ "express": "^4.19.2",
16
+ "express-session": "^1.18.0",
17
+ "mysql2": "^3.11.0"
18
+ },
19
+ "devDependencies": {
20
+ "nodemon": "^3.1.14"
21
+ }
22
+ }
@@ -0,0 +1,17 @@
1
+ import express from "express";
2
+
3
+ import {
4
+ register,
5
+ login,
6
+ logout,
7
+ me
8
+ } from "../controllers/authController.js";
9
+
10
+ const router = express.Router();
11
+
12
+ router.post("/register", register);
13
+ router.post("/login", login);
14
+ router.post("/logout", logout);
15
+ router.get("/me", me);
16
+
17
+ export default router;
@@ -0,0 +1,20 @@
1
+ import express from "express";
2
+ import {
3
+ getBookings,
4
+ getMyBookings,
5
+ getBookedSeats,
6
+ createBooking,
7
+ deleteBooking,
8
+ bookingReport
9
+ } from "../controllers/bookingController.js";
10
+
11
+ const router = express.Router();
12
+
13
+ router.get("/", getBookings);
14
+ router.get("/my/:userid", getMyBookings);
15
+ router.get("/seats/:scheduleid", getBookedSeats);
16
+ router.post("/", createBooking);
17
+ router.delete("/:id", deleteBooking);
18
+ router.get("/report", bookingReport);
19
+
20
+ export default router;
@@ -0,0 +1,10 @@
1
+ import express from "express";
2
+ import { getBuses, createBus, deleteBus } from "../controllers/busController.js";
3
+
4
+ const router = express.Router();
5
+
6
+ router.get("/", getBuses);
7
+ router.post("/", createBus);
8
+ router.delete("/:id", deleteBus);
9
+
10
+ export default router;
@@ -0,0 +1,10 @@
1
+ import express from "express";
2
+ import { getSchedules, createSchedule, deleteSchedule } from "../controllers/scheduleController.js";
3
+
4
+ const router = express.Router();
5
+
6
+ router.get("/", getSchedules);
7
+ router.post("/", createSchedule);
8
+ router.delete("/:id", deleteSchedule);
9
+
10
+ export default router;
@@ -0,0 +1,10 @@
1
+ import express from "express";
2
+ import { getUsers, createUser, deleteUser } from "../controllers/userController.js";
3
+
4
+ const router = express.Router();
5
+
6
+ router.get("/", getUsers);
7
+ router.post("/", createUser);
8
+ router.delete("/:id", deleteUser);
9
+
10
+ export default router;
@@ -0,0 +1,59 @@
1
+ CREATE DATABASE IF NOT EXISTS __DB_NAME__;
2
+ USE __DB_NAME__;
3
+
4
+ DROP TABLE IF EXISTS yk_bookings;
5
+ DROP TABLE IF EXISTS yk_schedules;
6
+ DROP TABLE IF EXISTS yk_buses;
7
+ DROP TABLE IF EXISTS yk_users;
8
+
9
+ CREATE TABLE yk_users (
10
+ userid INT AUTO_INCREMENT PRIMARY KEY,
11
+ username VARCHAR(100) NOT NULL UNIQUE,
12
+ password VARCHAR(255) NOT NULL,
13
+ userRole ENUM('passenger','manager','driver') NOT NULL DEFAULT 'passenger'
14
+ );
15
+
16
+ CREATE TABLE yk_buses (
17
+ busid INT AUTO_INCREMENT PRIMARY KEY,
18
+ platenumber VARCHAR(50) NOT NULL UNIQUE,
19
+ totalseats INT NOT NULL,
20
+ bustype VARCHAR(100) NOT NULL
21
+ );
22
+
23
+ CREATE TABLE yk_schedules (
24
+ scheduleid INT AUTO_INCREMENT PRIMARY KEY,
25
+ busid INT NOT NULL,
26
+ routename VARCHAR(100) NOT NULL,
27
+ departurepoint VARCHAR(100) NOT NULL,
28
+ destination VARCHAR(100) NOT NULL,
29
+ departuretime DATETIME NOT NULL,
30
+ estimatedarrivaltime DATETIME NOT NULL,
31
+ ticketprice DECIMAL(10,2) NOT NULL,
32
+ scheduleStatus ENUM('active','cancelled','completed') DEFAULT 'active',
33
+ FOREIGN KEY (busid) REFERENCES yk_buses(busid) ON DELETE CASCADE
34
+ );
35
+
36
+ CREATE TABLE yk_bookings (
37
+ bookingid INT AUTO_INCREMENT PRIMARY KEY,
38
+ scheduleid INT NOT NULL,
39
+ userid INT NOT NULL,
40
+ passengername VARCHAR(100) NOT NULL,
41
+ passengergender VARCHAR(20) NOT NULL,
42
+ passengerphone VARCHAR(30) NOT NULL,
43
+ seatnumber VARCHAR(20) NOT NULL,
44
+ paymentstatus ENUM('pending','paid') DEFAULT 'pending',
45
+ bookingdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
46
+ FOREIGN KEY (scheduleid) REFERENCES yk_schedules(scheduleid) ON DELETE CASCADE,
47
+ FOREIGN KEY (userid) REFERENCES yk_users(userid) ON DELETE CASCADE,
48
+ UNIQUE(scheduleid, seatnumber)
49
+ );
50
+
51
+ INSERT INTO yk_buses (platenumber, totalseats, bustype) VALUES
52
+ ('RAB 123A', 30, 'Coaster'),
53
+ ('RAC 456B', 45, 'Express Bus');
54
+
55
+ INSERT INTO yk_schedules
56
+ (busid, routename, departurepoint, destination, departuretime, estimatedarrivaltime, ticketprice, scheduleStatus)
57
+ VALUES
58
+ (1, 'Kigali - Huye', 'Kigali', 'Huye', '2026-06-01 08:00:00', '2026-06-01 11:00:00', 5000, 'active'),
59
+ (2, 'Kigali - Musanze', 'Kigali', 'Musanze', '2026-06-01 09:00:00', '2026-06-01 12:00:00', 4500, 'active');
@@ -0,0 +1,35 @@
1
+ import bcrypt from "bcryptjs";
2
+ import db from "../config/db.js";
3
+
4
+ const users = [
5
+ { username: "manager1", password: "123456", userRole: "manager" },
6
+ { username: "driver1", password: "123456", userRole: "driver" },
7
+ { username: "passenger1", password: "123456", userRole: "passenger" }
8
+ ];
9
+
10
+ const seedUsers = async () => {
11
+ for (const user of users) {
12
+ const hashedPassword = await bcrypt.hash(user.password, 10);
13
+
14
+ const sql = `
15
+ INSERT INTO yk_users(username, password, userRole)
16
+ VALUES(?,?,?)
17
+ ON DUPLICATE KEY UPDATE username=username
18
+ `;
19
+
20
+ db.query(sql, [user.username, hashedPassword, user.userRole], (err) => {
21
+ if (err) {
22
+ console.log(err.message);
23
+ } else {
24
+ console.log(`${user.userRole} seeded`);
25
+ }
26
+ });
27
+ }
28
+
29
+ setTimeout(() => {
30
+ console.log("Seed completed");
31
+ process.exit();
32
+ }, 1500);
33
+ };
34
+
35
+ seedUsers();
@@ -0,0 +1 @@
1
+ VITE_API_URL=http://localhost:__BACKEND_PORT__
@@ -0,0 +1 @@
1
+ VITE_API_URL=http://localhost:__BACKEND_PORT__
@@ -0,0 +1,14 @@
1
+ # Front-end
2
+
3
+ ## Steps
4
+
5
+ ```bash
6
+ npm install
7
+ npm run dev
8
+ ```
9
+
10
+ Open:
11
+
12
+ ```txt
13
+ http://localhost:__FRONTEND_PORT__
14
+ ```
@@ -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>Y Bus Booking</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
+ "name": "__PROJECT_NAME__-frontend",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "start": "vite",
9
+ "build": "vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@vitejs/plugin-react": "^4.3.1",
14
+ "jspdf": "^4.2.1",
15
+ "jspdf-autotable": "^5.0.8",
16
+ "lucide-react": "^0.468.0",
17
+ "react": "^18.3.1",
18
+ "react-dom": "^18.3.1",
19
+ "vite": "^5.4.2"
20
+ }
21
+ }
@@ -0,0 +1,55 @@
1
+ import React from "react";
2
+ import { useEffect, useState } from "react";
3
+ import Login from "./pages/Login.jsx";
4
+ import ManagerDashboard from "./pages/ManagerDashboard.jsx";
5
+ import PassengerDashboard from "./pages/PassengerDashboard.jsx";
6
+ import { API_BASE_URL } from "./api.js";
7
+
8
+ export default function App() {
9
+ const [user, setUser] = useState(null);
10
+ const [loading, setLoading] = useState(true);
11
+
12
+ const checkLoggedUser = async () => {
13
+ try {
14
+ const res = await fetch(`${API_BASE_URL}/api/auth/me`, {
15
+ credentials: "include"
16
+ });
17
+
18
+ if (res.ok) {
19
+ const data = await res.json();
20
+ setUser(data);
21
+ }
22
+ } catch (error) {
23
+ console.log(error);
24
+ } finally {
25
+ setLoading(false);
26
+ }
27
+ };
28
+
29
+ useEffect(() => {
30
+ checkLoggedUser();
31
+ }, []);
32
+
33
+ const logout = async () => {
34
+ await fetch(`${API_BASE_URL}/api/auth/logout`, {
35
+ method: "POST",
36
+ credentials: "include"
37
+ });
38
+
39
+ setUser(null);
40
+ };
41
+
42
+ if (loading) {
43
+ return <div className="center-page">Loading...</div>;
44
+ }
45
+
46
+ if (!user) {
47
+ return <Login setUser={setUser} />;
48
+ }
49
+
50
+ if (user.userRole === "manager") {
51
+ return <ManagerDashboard user={user} logout={logout} />;
52
+ }
53
+
54
+ return <PassengerDashboard user={user} logout={logout} />;
55
+ }
@@ -0,0 +1 @@
1
+ export const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:__BACKEND_PORT__";
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import App from "./App.jsx";
4
+ import "./style.css";
5
+
6
+ ReactDOM.createRoot(document.getElementById("root")).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
@@ -0,0 +1,129 @@
1
+ import React from "react";
2
+ import { useEffect, useState } from "react";
3
+ import { API_BASE_URL } from "../api.js";
4
+
5
+ export default function Bookings() {
6
+ const [bookings, setBookings] = useState([]);
7
+ const [schedules, setSchedules] = useState([]);
8
+ const [users, setUsers] = useState([]);
9
+
10
+ const [form, setForm] = useState({
11
+ scheduleid: "",
12
+ userid: "",
13
+ passengername: "",
14
+ passengergender: "Female",
15
+ passengerphone: "",
16
+ seatnumber: "",
17
+ paymentstatus: "pending"
18
+ });
19
+
20
+ const handleChange = (e) => {
21
+ setForm({
22
+ ...form,
23
+ [e.target.name]: e.target.value
24
+ });
25
+ };
26
+
27
+ const fetchBookings = async () => {
28
+ const res = await fetch(`${API_BASE_URL}/api/bookings`);
29
+ const data = await res.json();
30
+ setBookings(data);
31
+ };
32
+
33
+ const fetchSchedules = async () => {
34
+ const res = await fetch(`${API_BASE_URL}/api/schedules`);
35
+ const data = await res.json();
36
+ setSchedules(data);
37
+ };
38
+
39
+ const fetchUsers = async () => {
40
+ const res = await fetch(`${API_BASE_URL}/api/users`);
41
+ const data = await res.json();
42
+ setUsers(data);
43
+ };
44
+
45
+ useEffect(() => {
46
+ fetchBookings();
47
+ fetchSchedules();
48
+ fetchUsers();
49
+ }, []);
50
+
51
+ const handleSubmit = async (e) => {
52
+ e.preventDefault();
53
+
54
+ const res = await fetch(`${API_BASE_URL}/api/bookings`, {
55
+ method: "POST",
56
+ headers: {
57
+ "Content-Type": "application/json"
58
+ },
59
+ body: JSON.stringify(form)
60
+ });
61
+
62
+ const data = await res.json();
63
+
64
+ if (!res.ok) {
65
+ alert(data.sqlMessage || data.message || "Booking failed`);
66
+ return;
67
+ }
68
+
69
+ setForm({
70
+ scheduleid: "",
71
+ userid: "",
72
+ passengername: "",
73
+ passengergender: "Female",
74
+ passengerphone: "",
75
+ seatnumber: "",
76
+ paymentstatus: "pending"
77
+ });
78
+
79
+ fetchBookings();
80
+ };
81
+
82
+ const handleDelete = async (id) => {
83
+ await fetch(`${API_BASE_URL}/api/bookings/${id}`, {
84
+ method: "DELETE"
85
+ });
86
+
87
+ fetchBookings();
88
+ };
89
+
90
+ return (
91
+ <div>
92
+ <div className="card">
93
+ <h1>Bookings</h1>
94
+
95
+
96
+ </div>
97
+
98
+ <table>
99
+ <thead>
100
+ <tr>
101
+ <th>Route</th>
102
+ <th>Passenger</th>
103
+ <th>Phone</th>
104
+ <th>Seat</th>
105
+ <th>Payment</th>
106
+ <th>Action</th>
107
+ </tr>
108
+ </thead>
109
+
110
+ <tbody>
111
+ {bookings.map((booking) => (
112
+ <tr key={booking.bookingid}>
113
+ <td>{booking.routename}</td>
114
+ <td>{booking.passengername}</td>
115
+ <td>{booking.passengerphone}</td>
116
+ <td>{booking.seatnumber}</td>
117
+ <td>{booking.paymentstatus}</td>
118
+ <td>
119
+ <button className="delete" onClick={() => handleDelete(booking.bookingid)}>
120
+ Delete
121
+ </button>
122
+ </td>
123
+ </tr>
124
+ ))}
125
+ </tbody>
126
+ </table>
127
+ </div>
128
+ );
129
+ }
@@ -0,0 +1,100 @@
1
+ import React from "react";
2
+ import { useEffect, useState } from "react";
3
+ import { API_BASE_URL } from "../api.js";
4
+
5
+ export default function Buses() {
6
+ const [buses, setBuses] = useState([]);
7
+
8
+ const [form, setForm] = useState({
9
+ platenumber: "",
10
+ totalseats: "",
11
+ bustype: ""
12
+ });
13
+
14
+ const handleChange = (e) => {
15
+ setForm({
16
+ ...form,
17
+ [e.target.name]: e.target.value
18
+ });
19
+ };
20
+
21
+ const fetchBuses = async () => {
22
+ const res = await fetch(`${API_BASE_URL}/api/buses`);
23
+ const data = await res.json();
24
+ setBuses(data);
25
+ };
26
+
27
+ useEffect(() => {
28
+ fetchBuses();
29
+ }, []);
30
+
31
+ const handleSubmit = async (e) => {
32
+ e.preventDefault();
33
+
34
+ await fetch(`${API_BASE_URL}/api/buses`, {
35
+ method: "POST",
36
+ headers: {
37
+ "Content-Type": "application/json"
38
+ },
39
+ body: JSON.stringify(form)
40
+ });
41
+
42
+ setForm({
43
+ platenumber: "",
44
+ totalseats: "",
45
+ bustype: ""
46
+ });
47
+
48
+ fetchBuses();
49
+ };
50
+
51
+ const handleDelete = async (id) => {
52
+ await fetch(`${API_BASE_URL}/api/buses/${id}`, {
53
+ method: "DELETE"
54
+ });
55
+
56
+ fetchBuses();
57
+ };
58
+
59
+ return (
60
+ <div>
61
+ <div className="card">
62
+ <h1>Buses</h1>
63
+
64
+ <form className="form-grid" onSubmit={handleSubmit}>
65
+ <input name="platenumber" placeholder="Plate Number" value={form.platenumber} onChange={handleChange} />
66
+ <input name="totalseats" placeholder="Total Seats" value={form.totalseats} onChange={handleChange} />
67
+ <input name="bustype" placeholder="Bus Type" value={form.bustype} onChange={handleChange} />
68
+ <button type="submit">Add Bus</button>
69
+ </form>
70
+ </div>
71
+
72
+ <table>
73
+ <thead>
74
+ <tr>
75
+
76
+ <th>Plate Number</th>
77
+ <th>Total Seats</th>
78
+ <th>Bus Type</th>
79
+ <th>Action</th>
80
+ </tr>
81
+ </thead>
82
+
83
+ <tbody>
84
+ {buses.map((bus) => (
85
+ <tr key={bus.busid}>
86
+ <td>{bus.platenumber}</td>
87
+ <td>{bus.totalseats}</td>
88
+ <td>{bus.bustype}</td>
89
+ <td>
90
+ <button className="delete" onClick={() => handleDelete(bus.busid)}>
91
+ Delete
92
+ </button>
93
+ </td>
94
+ </tr>
95
+ ))}
96
+ </tbody>
97
+ </table>
98
+ </div>
99
+ );
100
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ export default function Dashboard({ user }) {
3
+ return (
4
+ <div className="card">
5
+ <h1>Ticket Booking and Fleet Management System</h1>
6
+ <p>This system manages users, buses, schedules, bookings, and passenger reports.</p>
7
+ {user && <p>Logged in as: <b>{user.username}</b> ({user.userRole})</p>}
8
+ </div>
9
+ );
10
+ }