create-jinmankn-app 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.
- package/bin/index.js +76 -0
- package/package.json +20 -0
- package/templates/blueprint/BLUEPRINT_REPRODUCTION_PROMPT.md +996 -0
- package/templates/blueprint/HOW_IT_WORKS.md +286 -0
- package/templates/blueprint/README.md +123 -0
- package/templates/blueprint/backend/config/db.js +12 -0
- package/templates/blueprint/backend/controllers/authController.js +90 -0
- package/templates/blueprint/backend/controllers/itemController.js +74 -0
- package/templates/blueprint/backend/middleware/auth.js +32 -0
- package/templates/blueprint/backend/middleware/errorHandler.js +23 -0
- package/templates/blueprint/backend/models/Item.js +26 -0
- package/templates/blueprint/backend/models/User.js +28 -0
- package/templates/blueprint/backend/package-lock.json +2190 -0
- package/templates/blueprint/backend/package.json +23 -0
- package/templates/blueprint/backend/routes/authRoutes.js +11 -0
- package/templates/blueprint/backend/routes/healthRoutes.js +9 -0
- package/templates/blueprint/backend/routes/itemRoutes.js +21 -0
- package/templates/blueprint/backend/server.js +29 -0
- package/templates/blueprint/frontend/.env.example +1 -0
- package/templates/blueprint/frontend/index.html +13 -0
- package/templates/blueprint/frontend/package-lock.json +2844 -0
- package/templates/blueprint/frontend/package.json +23 -0
- package/templates/blueprint/frontend/public/favicon.svg +4 -0
- package/templates/blueprint/frontend/src/App.jsx +78 -0
- package/templates/blueprint/frontend/src/assets/logo.svg +4 -0
- package/templates/blueprint/frontend/src/components/DashboardLayout.jsx +103 -0
- package/templates/blueprint/frontend/src/components/ProtectedRoute.jsx +18 -0
- package/templates/blueprint/frontend/src/index.css +1 -0
- package/templates/blueprint/frontend/src/main.jsx +13 -0
- package/templates/blueprint/frontend/src/pages/DashboardHome.jsx +74 -0
- package/templates/blueprint/frontend/src/pages/Items.jsx +243 -0
- package/templates/blueprint/frontend/src/pages/Login.jsx +101 -0
- package/templates/blueprint/frontend/src/pages/Profile.jsx +79 -0
- package/templates/blueprint/frontend/src/pages/Register.jsx +122 -0
- package/templates/blueprint/frontend/src/pages/Report.jsx +124 -0
- package/templates/blueprint/frontend/vite.config.js +10 -0
- package/templates/blueprint/package.json +13 -0
- package/templates/blueprint/scripts/pack-blueprint.ps1 +18 -0
- package/templates/chom/Backend/app.js +25 -0
- package/templates/chom/Backend/package-lock.json +1551 -0
- package/templates/chom/Backend/package.json +23 -0
- package/templates/chom/Backend/seedAdmin.js +21 -0
- package/templates/chom/Backend/src/controllers/payment.c.js +57 -0
- package/templates/chom/Backend/src/controllers/students.c.js +58 -0
- package/templates/chom/Backend/src/controllers/users.c.js +62 -0
- package/templates/chom/Backend/src/middleware/authentication.js +18 -0
- package/templates/chom/Backend/src/models/payment.m.js +13 -0
- package/templates/chom/Backend/src/models/students.m.js +10 -0
- package/templates/chom/Backend/src/models/users.m.js +11 -0
- package/templates/chom/Backend/src/routes/users.r.js +21 -0
- package/templates/chom/Frontend/README.md +16 -0
- package/templates/chom/Frontend/eslint.config.js +21 -0
- package/templates/chom/Frontend/index.html +13 -0
- package/templates/chom/Frontend/package-lock.json +3075 -0
- package/templates/chom/Frontend/package.json +31 -0
- package/templates/chom/Frontend/public/favicon.svg +1 -0
- package/templates/chom/Frontend/public/icons.svg +24 -0
- package/templates/chom/Frontend/src/App.css +189 -0
- package/templates/chom/Frontend/src/App.jsx +28 -0
- package/templates/chom/Frontend/src/api/api.jsx +27 -0
- package/templates/chom/Frontend/src/assets/hero.png +0 -0
- package/templates/chom/Frontend/src/assets/react.svg +1 -0
- package/templates/chom/Frontend/src/assets/vite.svg +1 -0
- package/templates/chom/Frontend/src/components/Navbar.jsx +21 -0
- package/templates/chom/Frontend/src/index.css +8 -0
- package/templates/chom/Frontend/src/main.jsx +10 -0
- package/templates/chom/Frontend/src/pages/Dashboard.jsx +21 -0
- package/templates/chom/Frontend/src/pages/Landing.jsx +39 -0
- package/templates/chom/Frontend/src/pages/Login.jsx +49 -0
- package/templates/chom/Frontend/src/pages/Overview.jsx +42 -0
- package/templates/chom/Frontend/src/pages/Register.jsx +76 -0
- package/templates/chom/Frontend/src/pages/Students.jsx +14 -0
- package/templates/chom/Frontend/vite.config.js +8 -0
- package/templates/chom/package.json +13 -0
- package/templates/hospital-faisal/backend/.env.example +9 -0
- package/templates/hospital-faisal/backend/config/db.js +96 -0
- package/templates/hospital-faisal/backend/controllers/appointmentController.js +164 -0
- package/templates/hospital-faisal/backend/controllers/authController.js +106 -0
- package/templates/hospital-faisal/backend/controllers/hospitalReportController.js +72 -0
- package/templates/hospital-faisal/backend/controllers/medicalReportController.js +105 -0
- package/templates/hospital-faisal/backend/controllers/patientController.js +98 -0
- package/templates/hospital-faisal/backend/database/schema.sql +47 -0
- package/templates/hospital-faisal/backend/middleware/auth.js +30 -0
- package/templates/hospital-faisal/backend/middleware/errorHandler.js +23 -0
- package/templates/hospital-faisal/backend/middleware/role.js +6 -0
- package/templates/hospital-faisal/backend/package-lock.json +2092 -0
- package/templates/hospital-faisal/backend/package.json +23 -0
- package/templates/hospital-faisal/backend/routes/appointmentRoutes.js +25 -0
- package/templates/hospital-faisal/backend/routes/authRoutes.js +12 -0
- package/templates/hospital-faisal/backend/routes/healthRoutes.js +9 -0
- package/templates/hospital-faisal/backend/routes/hospitalReportRoutes.js +10 -0
- package/templates/hospital-faisal/backend/routes/medicalReportRoutes.js +16 -0
- package/templates/hospital-faisal/backend/routes/patientRoutes.js +22 -0
- package/templates/hospital-faisal/backend/server.js +46 -0
- package/templates/hospital-faisal/frontend/.env.example +1 -0
- package/templates/hospital-faisal/frontend/index.html +10 -0
- package/templates/hospital-faisal/frontend/package-lock.json +2844 -0
- package/templates/hospital-faisal/frontend/package.json +23 -0
- package/templates/hospital-faisal/frontend/public/favicon.svg +4 -0
- package/templates/hospital-faisal/frontend/src/App.jsx +56 -0
- package/templates/hospital-faisal/frontend/src/api.js +20 -0
- package/templates/hospital-faisal/frontend/src/assets/logo.svg +4 -0
- package/templates/hospital-faisal/frontend/src/components/DashboardLayout.jsx +114 -0
- package/templates/hospital-faisal/frontend/src/components/ProtectedRoute.jsx +18 -0
- package/templates/hospital-faisal/frontend/src/components/RoleRoute.jsx +14 -0
- package/templates/hospital-faisal/frontend/src/index.css +1 -0
- package/templates/hospital-faisal/frontend/src/main.jsx +13 -0
- package/templates/hospital-faisal/frontend/src/pages/Appointments.jsx +305 -0
- package/templates/hospital-faisal/frontend/src/pages/DashboardHome.jsx +105 -0
- package/templates/hospital-faisal/frontend/src/pages/Login.jsx +98 -0
- package/templates/hospital-faisal/frontend/src/pages/MedicalReports.jsx +182 -0
- package/templates/hospital-faisal/frontend/src/pages/Patients.jsx +237 -0
- package/templates/hospital-faisal/frontend/src/pages/Profile.jsx +78 -0
- package/templates/hospital-faisal/frontend/src/pages/Register.jsx +133 -0
- package/templates/hospital-faisal/frontend/src/pages/Report.jsx +167 -0
- package/templates/hospital-faisal/frontend/vite.config.js +10 -0
- package/templates/hospital-faisal/package.json +13 -0
|
@@ -0,0 +1,996 @@
|
|
|
1
|
+
# Blueprint — 100% Identical Reproduction Prompt
|
|
2
|
+
|
|
3
|
+
Copy everything below the line into another AI. Tell it: **Follow exactly. Do not add, remove, rename, or refactor anything. Create every file with the exact paths and contents given. Use only the dependencies and versions listed.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## YOUR TASK
|
|
8
|
+
|
|
9
|
+
Recreate the **Blueprint** full-stack project from scratch in a folder named `blueprint/` with this **exact** structure and **exact** file contents. After creating all files, run `npm install` in `backend/` and `frontend/`.
|
|
10
|
+
|
|
11
|
+
**Hard rules:**
|
|
12
|
+
- Do **not** create `frontend/src/api/axios.js` or any shared API module.
|
|
13
|
+
- Use **axios directly inside pages** with **inline event handlers** (`onSubmit`, `onClick`, `useEffect` IIFEs).
|
|
14
|
+
- Every React component/page: `function Name() { ... }` then **`export default Name` at the end** (never `export default function Name`).
|
|
15
|
+
- Tailwind **v4** via **`@tailwindcss/vite`** plugin only (no `postcss.config.js`, no `tailwind.config.js`, no autoprefixer).
|
|
16
|
+
- Dashboard UI: **side navbar** (`DashboardLayout`) with nested routes under `/dashboard`.
|
|
17
|
+
- Backend entry: **`node src/server.js`** (there is no root `server.js`).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## FILE TREE (create exactly these; nothing else except `node_modules`, `dist`, lockfiles after install)
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
blueprint/
|
|
25
|
+
├── README.md
|
|
26
|
+
├── backend/
|
|
27
|
+
│ ├── .env.example
|
|
28
|
+
│ ├── package.json
|
|
29
|
+
│ └── src/
|
|
30
|
+
│ ├── server.js
|
|
31
|
+
│ ├── config/
|
|
32
|
+
│ │ └── db.js
|
|
33
|
+
│ ├── controllers/
|
|
34
|
+
│ │ ├── authController.js
|
|
35
|
+
│ │ └── itemController.js
|
|
36
|
+
│ ├── middleware/
|
|
37
|
+
│ │ ├── auth.js
|
|
38
|
+
│ │ └── errorHandler.js
|
|
39
|
+
│ ├── models/
|
|
40
|
+
│ │ ├── User.js
|
|
41
|
+
│ │ └── Item.js
|
|
42
|
+
│ └── routes/
|
|
43
|
+
│ ├── authRoutes.js
|
|
44
|
+
│ ├── healthRoutes.js
|
|
45
|
+
│ └── itemRoutes.js
|
|
46
|
+
└── frontend/
|
|
47
|
+
├── .env.example
|
|
48
|
+
├── index.html
|
|
49
|
+
├── package.json
|
|
50
|
+
├── vite.config.js
|
|
51
|
+
└── src/
|
|
52
|
+
├── main.jsx
|
|
53
|
+
├── App.jsx
|
|
54
|
+
├── index.css
|
|
55
|
+
├── components/
|
|
56
|
+
│ ├── DashboardLayout.jsx
|
|
57
|
+
│ └── ProtectedRoute.jsx
|
|
58
|
+
└── pages/
|
|
59
|
+
├── Login.jsx
|
|
60
|
+
├── Register.jsx
|
|
61
|
+
├── DashboardHome.jsx
|
|
62
|
+
├── Items.jsx
|
|
63
|
+
└── Profile.jsx
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## ROOT: `README.md`
|
|
69
|
+
|
|
70
|
+
Use the README content from the section **README CONTENT** at the end of this document.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## BACKEND
|
|
75
|
+
|
|
76
|
+
### `backend/package.json`
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"name": "blueprint-backend",
|
|
81
|
+
"version": "1.0.0",
|
|
82
|
+
"description": "Blueprint API — Node, Express, MongoDB",
|
|
83
|
+
"main": "server.js",
|
|
84
|
+
"scripts": {
|
|
85
|
+
"dev": "nodemon server.js",
|
|
86
|
+
"start": "node server.js"
|
|
87
|
+
},
|
|
88
|
+
"keywords": [],
|
|
89
|
+
"license": "MIT",
|
|
90
|
+
"dependencies": {
|
|
91
|
+
"bcrypt": "^5.1.1",
|
|
92
|
+
"cors": "^2.8.5",
|
|
93
|
+
"dotenv": "^16.4.7",
|
|
94
|
+
"express": "^4.21.2",
|
|
95
|
+
"jsonwebtoken": "^9.0.2",
|
|
96
|
+
"mongoose": "^8.9.3"
|
|
97
|
+
},
|
|
98
|
+
"devDependencies": {
|
|
99
|
+
"nodemon": "^3.1.9"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `backend/.env.example`
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
PORT=5000
|
|
108
|
+
MONGO_URI=mongodb://127.0.0.1:27017/blueprint_db
|
|
109
|
+
JWT_SECRET=change_this_to_a_long_random_secret
|
|
110
|
+
CLIENT_URL=http://localhost:5173
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `backend/src/config/db.js`
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
const mongoose = require('mongoose');
|
|
117
|
+
|
|
118
|
+
async function connectDB() {
|
|
119
|
+
const uri = process.env.MONGO_URI;
|
|
120
|
+
if (!uri) {
|
|
121
|
+
throw new Error('MONGO_URI is not defined');
|
|
122
|
+
}
|
|
123
|
+
await mongoose.connect(uri);
|
|
124
|
+
console.log('MongoDB connected');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = { connectDB };
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `backend/src/server.js`
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
require('dotenv').config();
|
|
134
|
+
|
|
135
|
+
const express = require('express');
|
|
136
|
+
const cors = require('cors');
|
|
137
|
+
const { connectDB } = require('./config/db');
|
|
138
|
+
const authRoutes = require('./routes/authRoutes');
|
|
139
|
+
const itemRoutes = require('./routes/itemRoutes');
|
|
140
|
+
const healthRoutes = require('./routes/healthRoutes');
|
|
141
|
+
const { notFound, errorHandler } = require('./middleware/errorHandler');
|
|
142
|
+
|
|
143
|
+
const app = express();
|
|
144
|
+
|
|
145
|
+
app.use(
|
|
146
|
+
cors({
|
|
147
|
+
origin: process.env.CLIENT_URL || 'http://localhost:5173',
|
|
148
|
+
credentials: true,
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
app.use(express.json());
|
|
152
|
+
|
|
153
|
+
app.use('/api/health', healthRoutes);
|
|
154
|
+
app.use('/api/auth', authRoutes);
|
|
155
|
+
app.use('/api/items', itemRoutes);
|
|
156
|
+
|
|
157
|
+
app.use(notFound);
|
|
158
|
+
app.use(errorHandler);
|
|
159
|
+
|
|
160
|
+
const PORT = process.env.PORT || 5000;
|
|
161
|
+
|
|
162
|
+
async function start() {
|
|
163
|
+
await connectDB();
|
|
164
|
+
app.listen(PORT, function onListen() {
|
|
165
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
start().catch(function onStartError(err) {
|
|
170
|
+
console.error('Failed to start server:', err.message);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `backend/src/middleware/auth.js`
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
const jwt = require('jsonwebtoken');
|
|
179
|
+
const User = require('../models/User');
|
|
180
|
+
|
|
181
|
+
async function protect(req, res, next) {
|
|
182
|
+
try {
|
|
183
|
+
const header = req.headers.authorization;
|
|
184
|
+
|
|
185
|
+
if (!header || !header.startsWith('Bearer ')) {
|
|
186
|
+
return res.status(401).json({ message: 'Not authorized' });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const token = header.split(' ')[1];
|
|
190
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
191
|
+
|
|
192
|
+
const user = await User.findById(decoded.userId).select('-password');
|
|
193
|
+
if (!user) {
|
|
194
|
+
return res.status(401).json({ message: 'Not authorized' });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
req.user = user;
|
|
198
|
+
next();
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return res.status(401).json({ message: 'Not authorized' });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = { protect };
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `backend/src/middleware/errorHandler.js`
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
function notFound(req, res, next) {
|
|
211
|
+
res.status(404).json({ message: 'Route not found' });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function errorHandler(err, req, res, next) {
|
|
215
|
+
console.error(err);
|
|
216
|
+
|
|
217
|
+
if (err.name === 'ValidationError') {
|
|
218
|
+
const messages = Object.values(err.errors).map((e) => e.message);
|
|
219
|
+
return res.status(400).json({ message: messages.join(', ') });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (err.name === 'CastError') {
|
|
223
|
+
return res.status(400).json({ message: 'Invalid ID' });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const status = err.statusCode || 500;
|
|
227
|
+
const message = err.message || 'Server error';
|
|
228
|
+
|
|
229
|
+
res.status(status).json({ message });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = { notFound, errorHandler };
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### `backend/src/models/User.js`
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
const mongoose = require('mongoose');
|
|
239
|
+
|
|
240
|
+
const userSchema = new mongoose.Schema(
|
|
241
|
+
{
|
|
242
|
+
name: {
|
|
243
|
+
type: String,
|
|
244
|
+
required: true,
|
|
245
|
+
trim: true,
|
|
246
|
+
},
|
|
247
|
+
email: {
|
|
248
|
+
type: String,
|
|
249
|
+
required: true,
|
|
250
|
+
unique: true,
|
|
251
|
+
lowercase: true,
|
|
252
|
+
trim: true,
|
|
253
|
+
},
|
|
254
|
+
password: {
|
|
255
|
+
type: String,
|
|
256
|
+
required: true,
|
|
257
|
+
minlength: 6,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{ timestamps: true }
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const User = mongoose.model('User', userSchema);
|
|
264
|
+
|
|
265
|
+
module.exports = User;
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### `backend/src/models/Item.js`
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
const mongoose = require('mongoose');
|
|
272
|
+
|
|
273
|
+
const itemSchema = new mongoose.Schema(
|
|
274
|
+
{
|
|
275
|
+
title: {
|
|
276
|
+
type: String,
|
|
277
|
+
required: true,
|
|
278
|
+
trim: true,
|
|
279
|
+
},
|
|
280
|
+
description: {
|
|
281
|
+
type: String,
|
|
282
|
+
default: '',
|
|
283
|
+
trim: true,
|
|
284
|
+
},
|
|
285
|
+
user: {
|
|
286
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
287
|
+
ref: 'User',
|
|
288
|
+
required: true,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
{ timestamps: true }
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const Item = mongoose.model('Item', itemSchema);
|
|
295
|
+
|
|
296
|
+
module.exports = Item;
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### `backend/src/controllers/authController.js`
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
const bcrypt = require('bcrypt');
|
|
303
|
+
const jwt = require('jsonwebtoken');
|
|
304
|
+
const User = require('../models/User');
|
|
305
|
+
|
|
306
|
+
function createToken(userId) {
|
|
307
|
+
return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '7d' });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function formatUser(user) {
|
|
311
|
+
return {
|
|
312
|
+
id: user._id,
|
|
313
|
+
name: user.name,
|
|
314
|
+
email: user.email,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function register(req, res, next) {
|
|
319
|
+
try {
|
|
320
|
+
const { name, email, password } = req.body;
|
|
321
|
+
|
|
322
|
+
if (!name || !email || !password) {
|
|
323
|
+
return res.status(400).json({ message: 'Name, email, and password are required' });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (password.length < 6) {
|
|
327
|
+
return res.status(400).json({ message: 'Password must be at least 6 characters' });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const existing = await User.findOne({ email: email.toLowerCase() });
|
|
331
|
+
if (existing) {
|
|
332
|
+
return res.status(409).json({ message: 'Email already registered' });
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const hashedPassword = await bcrypt.hash(password, 10);
|
|
336
|
+
const user = await User.create({
|
|
337
|
+
name,
|
|
338
|
+
email: email.toLowerCase(),
|
|
339
|
+
password: hashedPassword,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const token = createToken(user._id);
|
|
343
|
+
|
|
344
|
+
res.status(201).json({
|
|
345
|
+
token,
|
|
346
|
+
user: formatUser(user),
|
|
347
|
+
});
|
|
348
|
+
} catch (err) {
|
|
349
|
+
next(err);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function login(req, res, next) {
|
|
354
|
+
try {
|
|
355
|
+
const { email, password } = req.body;
|
|
356
|
+
|
|
357
|
+
if (!email || !password) {
|
|
358
|
+
return res.status(400).json({ message: 'Email and password are required' });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const user = await User.findOne({ email: email.toLowerCase() });
|
|
362
|
+
if (!user) {
|
|
363
|
+
return res.status(401).json({ message: 'Invalid email or password' });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const match = await bcrypt.compare(password, user.password);
|
|
367
|
+
if (!match) {
|
|
368
|
+
return res.status(401).json({ message: 'Invalid email or password' });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const token = createToken(user._id);
|
|
372
|
+
|
|
373
|
+
res.json({
|
|
374
|
+
token,
|
|
375
|
+
user: formatUser(user),
|
|
376
|
+
});
|
|
377
|
+
} catch (err) {
|
|
378
|
+
next(err);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function getMe(req, res, next) {
|
|
383
|
+
try {
|
|
384
|
+
res.json({ user: formatUser(req.user) });
|
|
385
|
+
} catch (err) {
|
|
386
|
+
next(err);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
module.exports = {
|
|
391
|
+
register,
|
|
392
|
+
login,
|
|
393
|
+
getMe,
|
|
394
|
+
};
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### `backend/src/controllers/itemController.js`
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
const Item = require('../models/Item');
|
|
401
|
+
|
|
402
|
+
async function getItems(req, res, next) {
|
|
403
|
+
try {
|
|
404
|
+
const items = await Item.find({ user: req.user._id }).sort({ createdAt: -1 });
|
|
405
|
+
res.json({ items });
|
|
406
|
+
} catch (err) {
|
|
407
|
+
next(err);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function getItem(req, res, next) {
|
|
412
|
+
try {
|
|
413
|
+
const item = await Item.findOne({ _id: req.params.id, user: req.user._id });
|
|
414
|
+
if (!item) {
|
|
415
|
+
return res.status(404).json({ message: 'Item not found' });
|
|
416
|
+
}
|
|
417
|
+
res.json({ item });
|
|
418
|
+
} catch (err) {
|
|
419
|
+
next(err);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function createItem(req, res, next) {
|
|
424
|
+
try {
|
|
425
|
+
const { title, description } = req.body;
|
|
426
|
+
|
|
427
|
+
if (!title) {
|
|
428
|
+
return res.status(400).json({ message: 'Title is required' });
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const item = await Item.create({
|
|
432
|
+
title,
|
|
433
|
+
description: description || '',
|
|
434
|
+
user: req.user._id,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
res.status(201).json({ item });
|
|
438
|
+
} catch (err) {
|
|
439
|
+
next(err);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function updateItem(req, res, next) {
|
|
444
|
+
try {
|
|
445
|
+
const { title, description } = req.body;
|
|
446
|
+
|
|
447
|
+
const item = await Item.findOne({ _id: req.params.id, user: req.user._id });
|
|
448
|
+
if (!item) {
|
|
449
|
+
return res.status(404).json({ message: 'Item not found' });
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (title !== undefined) item.title = title;
|
|
453
|
+
if (description !== undefined) item.description = description;
|
|
454
|
+
|
|
455
|
+
await item.save();
|
|
456
|
+
|
|
457
|
+
res.json({ item });
|
|
458
|
+
} catch (err) {
|
|
459
|
+
next(err);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function deleteItem(req, res, next) {
|
|
464
|
+
try {
|
|
465
|
+
const item = await Item.findOneAndDelete({ _id: req.params.id, user: req.user._id });
|
|
466
|
+
if (!item) {
|
|
467
|
+
return res.status(404).json({ message: 'Item not found' });
|
|
468
|
+
}
|
|
469
|
+
res.json({ message: 'Item deleted' });
|
|
470
|
+
} catch (err) {
|
|
471
|
+
next(err);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
module.exports = {
|
|
476
|
+
getItems,
|
|
477
|
+
getItem,
|
|
478
|
+
createItem,
|
|
479
|
+
updateItem,
|
|
480
|
+
deleteItem,
|
|
481
|
+
};
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### `backend/src/routes/healthRoutes.js`
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
const express = require('express');
|
|
488
|
+
|
|
489
|
+
const router = express.Router();
|
|
490
|
+
|
|
491
|
+
router.get('/', function healthCheck(req, res) {
|
|
492
|
+
res.json({ status: 'ok' });
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
module.exports = router;
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### `backend/src/routes/authRoutes.js`
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
const express = require('express');
|
|
502
|
+
const { register, login, getMe } = require('../controllers/authController');
|
|
503
|
+
const { protect } = require('../middleware/auth');
|
|
504
|
+
|
|
505
|
+
const router = express.Router();
|
|
506
|
+
|
|
507
|
+
router.post('/register', register);
|
|
508
|
+
router.post('/login', login);
|
|
509
|
+
router.get('/me', protect, getMe);
|
|
510
|
+
|
|
511
|
+
module.exports = router;
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### `backend/src/routes/itemRoutes.js`
|
|
515
|
+
|
|
516
|
+
```javascript
|
|
517
|
+
const express = require('express');
|
|
518
|
+
const {
|
|
519
|
+
getItems,
|
|
520
|
+
getItem,
|
|
521
|
+
createItem,
|
|
522
|
+
updateItem,
|
|
523
|
+
deleteItem,
|
|
524
|
+
} = require('../controllers/itemController');
|
|
525
|
+
const { protect } = require('../middleware/auth');
|
|
526
|
+
|
|
527
|
+
const router = express.Router();
|
|
528
|
+
|
|
529
|
+
router.use(protect);
|
|
530
|
+
|
|
531
|
+
router.get('/', getItems);
|
|
532
|
+
router.get('/:id', getItem);
|
|
533
|
+
router.post('/', createItem);
|
|
534
|
+
router.put('/:id', updateItem);
|
|
535
|
+
router.delete('/:id', deleteItem);
|
|
536
|
+
|
|
537
|
+
module.exports = router;
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## FRONTEND
|
|
543
|
+
|
|
544
|
+
### `frontend/package.json`
|
|
545
|
+
|
|
546
|
+
```json
|
|
547
|
+
{
|
|
548
|
+
"name": "blueprint-frontend",
|
|
549
|
+
"private": true,
|
|
550
|
+
"version": "1.0.0",
|
|
551
|
+
"type": "module",
|
|
552
|
+
"scripts": {
|
|
553
|
+
"dev": "vite",
|
|
554
|
+
"build": "vite build",
|
|
555
|
+
"preview": "vite preview"
|
|
556
|
+
},
|
|
557
|
+
"dependencies": {
|
|
558
|
+
"axios": "^1.7.9",
|
|
559
|
+
"react": "^18.3.1",
|
|
560
|
+
"react-dom": "^18.3.1",
|
|
561
|
+
"react-router-dom": "^6.28.1"
|
|
562
|
+
},
|
|
563
|
+
"devDependencies": {
|
|
564
|
+
"@tailwindcss/vite": "^4.1.8",
|
|
565
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
566
|
+
"tailwindcss": "^4.1.8",
|
|
567
|
+
"vite": "^6.0.6"
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### `frontend/.env.example`
|
|
573
|
+
|
|
574
|
+
```
|
|
575
|
+
VITE_API_URL=http://localhost:5000/api
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### `frontend/index.html`
|
|
579
|
+
|
|
580
|
+
```html
|
|
581
|
+
<!DOCTYPE html>
|
|
582
|
+
<html lang="en">
|
|
583
|
+
<head>
|
|
584
|
+
<meta charset="UTF-8" />
|
|
585
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
586
|
+
<title>Blueprint</title>
|
|
587
|
+
</head>
|
|
588
|
+
<body>
|
|
589
|
+
<div id="root"></div>
|
|
590
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
591
|
+
</body>
|
|
592
|
+
</html>
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
### `frontend/vite.config.js`
|
|
597
|
+
|
|
598
|
+
```javascript
|
|
599
|
+
import { defineConfig } from 'vite';
|
|
600
|
+
import react from '@vitejs/plugin-react';
|
|
601
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
602
|
+
|
|
603
|
+
export default defineConfig({
|
|
604
|
+
plugins: [react(), tailwindcss()],
|
|
605
|
+
server: {
|
|
606
|
+
port: 5173,
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### `frontend/src/index.css`
|
|
612
|
+
|
|
613
|
+
```css
|
|
614
|
+
@import 'tailwindcss';
|
|
615
|
+
|
|
616
|
+
body {
|
|
617
|
+
@apply bg-slate-50 text-slate-900 antialiased;
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### `frontend/src/main.jsx`
|
|
622
|
+
|
|
623
|
+
```javascript
|
|
624
|
+
import React from 'react';
|
|
625
|
+
import ReactDOM from 'react-dom/client';
|
|
626
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
627
|
+
import App from './App';
|
|
628
|
+
import './index.css';
|
|
629
|
+
|
|
630
|
+
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
631
|
+
<React.StrictMode>
|
|
632
|
+
<BrowserRouter>
|
|
633
|
+
<App />
|
|
634
|
+
</BrowserRouter>
|
|
635
|
+
</React.StrictMode>
|
|
636
|
+
);
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### `frontend/src/App.jsx`
|
|
640
|
+
|
|
641
|
+
```javascript
|
|
642
|
+
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
643
|
+
import ProtectedRoute from './components/ProtectedRoute';
|
|
644
|
+
import DashboardLayout from './components/DashboardLayout';
|
|
645
|
+
import Login from './pages/Login';
|
|
646
|
+
import Register from './pages/Register';
|
|
647
|
+
import DashboardHome from './pages/DashboardHome';
|
|
648
|
+
import Items from './pages/Items';
|
|
649
|
+
import Profile from './pages/Profile';
|
|
650
|
+
|
|
651
|
+
function App() {
|
|
652
|
+
return (
|
|
653
|
+
<Routes>
|
|
654
|
+
<Route path="/login" element={<Login />} />
|
|
655
|
+
<Route path="/register" element={<Register />} />
|
|
656
|
+
|
|
657
|
+
<Route
|
|
658
|
+
path="/dashboard"
|
|
659
|
+
element={
|
|
660
|
+
<ProtectedRoute>
|
|
661
|
+
<DashboardLayout />
|
|
662
|
+
</ProtectedRoute>
|
|
663
|
+
}
|
|
664
|
+
>
|
|
665
|
+
<Route index element={<DashboardHome />} />
|
|
666
|
+
<Route path="items" element={<Items />} />
|
|
667
|
+
<Route path="profile" element={<Profile />} />
|
|
668
|
+
</Route>
|
|
669
|
+
|
|
670
|
+
<Route path="/items" element={<Navigate to="/dashboard/items" replace />} />
|
|
671
|
+
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
|
672
|
+
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
|
673
|
+
</Routes>
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
export default App;
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### `frontend/src/components/ProtectedRoute.jsx`
|
|
681
|
+
|
|
682
|
+
```javascript
|
|
683
|
+
// this component is helping us to protect our routes so that only authenticated users can access the routes
|
|
684
|
+
import { Navigate } from 'react-router-dom';
|
|
685
|
+
|
|
686
|
+
// this function is firstly checking if the user is authenticated by checking the token in the localStorage
|
|
687
|
+
// if the user is not authenticated, it will redirect the user to the login page
|
|
688
|
+
// if the user is authenticated, it will return the children components
|
|
689
|
+
//and the children component is the component that we want to protect
|
|
690
|
+
function ProtectedRoute({ children }) {
|
|
691
|
+
const token = localStorage.getItem('token');
|
|
692
|
+
|
|
693
|
+
if (!token) {
|
|
694
|
+
return <Navigate to="/login" replace />;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return children;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export default ProtectedRoute;
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### `frontend/src/components/DashboardLayout.jsx`
|
|
704
|
+
|
|
705
|
+
```javascript
|
|
706
|
+
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
|
|
707
|
+
|
|
708
|
+
const navLinks = [
|
|
709
|
+
{ to: '/dashboard', label: 'Overview', end: true },
|
|
710
|
+
{ to: '/dashboard/items', label: 'Items', end: false },
|
|
711
|
+
{ to: '/dashboard/profile', label: 'Profile', end: false },
|
|
712
|
+
];
|
|
713
|
+
|
|
714
|
+
function DashboardLayout() {
|
|
715
|
+
const navigate = useNavigate();
|
|
716
|
+
const storedUser = localStorage.getItem('user');
|
|
717
|
+
const user = storedUser ? JSON.parse(storedUser) : null;
|
|
718
|
+
|
|
719
|
+
return (
|
|
720
|
+
<div className="min-h-screen flex bg-slate-100">
|
|
721
|
+
<aside className="w-64 shrink-0 bg-slate-900 text-slate-100 flex flex-col">
|
|
722
|
+
<div className="px-5 py-6 border-b border-slate-700">
|
|
723
|
+
<p className="text-xs font-semibold uppercase tracking-wider text-slate-400">Blueprint</p>
|
|
724
|
+
<h1 className="text-lg font-bold text-white mt-1">Dashboard</h1>
|
|
725
|
+
</div>
|
|
726
|
+
|
|
727
|
+
<nav className="flex-1 px-3 py-4 space-y-1">
|
|
728
|
+
{navLinks.map(function renderNavLink(link) {
|
|
729
|
+
return (
|
|
730
|
+
<NavLink
|
|
731
|
+
key={link.to}
|
|
732
|
+
to={link.to}
|
|
733
|
+
end={link.end}
|
|
734
|
+
className={function navClass({ isActive }) {
|
|
735
|
+
return [
|
|
736
|
+
'block rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
|
|
737
|
+
isActive
|
|
738
|
+
? 'bg-blue-600 text-white'
|
|
739
|
+
: 'text-slate-300 hover:bg-slate-800 hover:text-white',
|
|
740
|
+
].join(' ');
|
|
741
|
+
}}
|
|
742
|
+
>
|
|
743
|
+
{link.label}
|
|
744
|
+
</NavLink>
|
|
745
|
+
);
|
|
746
|
+
})}
|
|
747
|
+
</nav>
|
|
748
|
+
|
|
749
|
+
<div className="px-4 py-4 border-t border-slate-700">
|
|
750
|
+
{user && (
|
|
751
|
+
<p className="text-sm font-medium text-white truncate">{user.name}</p>
|
|
752
|
+
)}
|
|
753
|
+
{user && (
|
|
754
|
+
<p className="text-xs text-slate-400 truncate mt-0.5">{user.email}</p>
|
|
755
|
+
)}
|
|
756
|
+
<button
|
|
757
|
+
type="button"
|
|
758
|
+
onClick={function handleLogout() {
|
|
759
|
+
localStorage.removeItem('token');
|
|
760
|
+
localStorage.removeItem('user');
|
|
761
|
+
navigate('/login');
|
|
762
|
+
}}
|
|
763
|
+
className="mt-3 w-full text-sm font-medium text-slate-300 hover:text-white border border-slate-600 rounded-lg px-3 py-2 hover:bg-slate-800 transition-colors"
|
|
764
|
+
>
|
|
765
|
+
Log out
|
|
766
|
+
</button>
|
|
767
|
+
</div>
|
|
768
|
+
</aside>
|
|
769
|
+
|
|
770
|
+
<div className="flex-1 flex flex-col min-w-0">
|
|
771
|
+
<header className="bg-white border-b border-slate-200 px-6 py-4">
|
|
772
|
+
<p className="text-sm text-slate-500">Welcome back</p>
|
|
773
|
+
<h2 className="text-xl font-semibold text-slate-800">
|
|
774
|
+
{user ? `Hello, ${user.name}` : 'Dashboard'}
|
|
775
|
+
</h2>
|
|
776
|
+
</header>
|
|
777
|
+
|
|
778
|
+
<main className="flex-1 p-6 overflow-auto">
|
|
779
|
+
<Outlet />
|
|
780
|
+
</main>
|
|
781
|
+
</div>
|
|
782
|
+
</div>
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
export default DashboardLayout;
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
---
|
|
792
|
+
|
|
793
|
+
## FRONTEND PAGES
|
|
794
|
+
|
|
795
|
+
For **Login.jsx**, **Register.jsx**, **DashboardHome.jsx**, **Profile.jsx**, and **Items.jsx**: copy the **exact** contents from the current Blueprint repository files at:
|
|
796
|
+
|
|
797
|
+
- `frontend/src/pages/Login.jsx` (100 lines)
|
|
798
|
+
- `frontend/src/pages/Register.jsx` (118 lines)
|
|
799
|
+
- `frontend/src/pages/DashboardHome.jsx` (75 lines)
|
|
800
|
+
- `frontend/src/pages/Profile.jsx` (38 lines)
|
|
801
|
+
- `frontend/src/pages/Items.jsx` (210 lines)
|
|
802
|
+
|
|
803
|
+
**Patterns all pages must follow:**
|
|
804
|
+
- `const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:5000/api';`
|
|
805
|
+
- `import axios from 'axios';` — no `api/axios.js`
|
|
806
|
+
- Auth requests: `headers: { 'Content-Type': 'application/json' }`
|
|
807
|
+
- Protected requests add: `Authorization: \`Bearer ${localStorage.getItem('token')}\``
|
|
808
|
+
- Login/Register on success: `localStorage.setItem('token', data.token)`, `localStorage.setItem('user', JSON.stringify(data.user))`, `navigate('/dashboard')`
|
|
809
|
+
- Named inline handlers: `function handleX(e) { ... }` inside `onChange` / `onClick` / `onSubmit`
|
|
810
|
+
- `export default` at file end
|
|
811
|
+
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
## API CONTRACT (must match exactly)
|
|
815
|
+
|
|
816
|
+
| Method | Path | Auth | Body | Response |
|
|
817
|
+
|--------|------|------|------|----------|
|
|
818
|
+
| GET | /api/health | No | — | `{ status: 'ok' }` |
|
|
819
|
+
| POST | /api/auth/register | No | name, email, password | `{ token, user: { id, name, email } }` |
|
|
820
|
+
| POST | /api/auth/login | No | email, password | `{ token, user }` |
|
|
821
|
+
| GET | /api/auth/me | Bearer | — | `{ user }` |
|
|
822
|
+
| GET | /api/items | Bearer | — | `{ items: [...] }` |
|
|
823
|
+
| GET | /api/items/:id | Bearer | — | `{ item }` |
|
|
824
|
+
| POST | /api/items | Bearer | title, description? | `{ item }` |
|
|
825
|
+
| PUT | /api/items/:id | Bearer | title?, description? | `{ item }` |
|
|
826
|
+
| DELETE | /api/items/:id | Bearer | — | `{ message: 'Item deleted' }` |
|
|
827
|
+
|
|
828
|
+
---
|
|
829
|
+
|
|
830
|
+
## UI / ROUTING BEHAVIOR
|
|
831
|
+
|
|
832
|
+
1. **Public:** `/login`, `/register` — centered white card on slate background.
|
|
833
|
+
2. **Protected dashboard** at `/dashboard` with layout:
|
|
834
|
+
- Left sidebar (slate-900): brand "Blueprint" / "Dashboard", nav links Overview / Items / Profile, user name/email, Log out.
|
|
835
|
+
- Top header: "Welcome back" + "Hello, {name}".
|
|
836
|
+
- Main: `<Outlet />` for child routes.
|
|
837
|
+
3. **Redirects:** `/` → `/dashboard`, `/items` → `/dashboard/items`, unknown → `/dashboard`.
|
|
838
|
+
4. **NavLink** active style: `bg-blue-600 text-white`; inactive: `text-slate-300 hover:bg-slate-800`.
|
|
839
|
+
5. **Items page:** form (title, description) + list with Edit/Delete; delete uses `window.confirm`.
|
|
840
|
+
6. **Overview page:** item count card + quick links.
|
|
841
|
+
7. **Profile page:** name, email, user id from `localStorage` user object.
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## RUN INSTRUCTIONS (after all files exist)
|
|
846
|
+
|
|
847
|
+
```bash
|
|
848
|
+
# Terminal 1 — backend
|
|
849
|
+
cd backend
|
|
850
|
+
cp .env.example .env # Windows: Copy-Item .env.example .env
|
|
851
|
+
npm install
|
|
852
|
+
node src/server.js
|
|
853
|
+
|
|
854
|
+
# Terminal 2 — frontend
|
|
855
|
+
cd frontend
|
|
856
|
+
cp .env.example .env
|
|
857
|
+
npm install
|
|
858
|
+
npm run dev
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
- MongoDB must run at `mongodb://127.0.0.1:27017/blueprint_db`
|
|
862
|
+
- Frontend: http://localhost:5173
|
|
863
|
+
- Backend: http://localhost:5000/api
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
## VERIFICATION CHECKLIST
|
|
868
|
+
|
|
869
|
+
- [ ] No `frontend/src/api/` folder
|
|
870
|
+
- [ ] No `postcss.config.js` or `tailwind.config.js`
|
|
871
|
+
- [ ] `vite.config.js` uses `@tailwindcss/vite`
|
|
872
|
+
- [ ] Dashboard sidebar with 3 links + logout
|
|
873
|
+
- [ ] All components use `export default` at bottom
|
|
874
|
+
- [ ] `npm run build` succeeds in frontend
|
|
875
|
+
- [ ] Register → lands on `/dashboard` with sidebar visible
|
|
876
|
+
|
|
877
|
+
---
|
|
878
|
+
|
|
879
|
+
## README CONTENT
|
|
880
|
+
|
|
881
|
+
```markdown
|
|
882
|
+
# Blueprint
|
|
883
|
+
|
|
884
|
+
A reusable full-stack starter template with authentication, CRUD, and MongoDB. Copy or rename this folder to bootstrap any new project.
|
|
885
|
+
|
|
886
|
+
## Stack
|
|
887
|
+
|
|
888
|
+
- **Backend:** Node.js, Express, Mongoose, JWT, bcrypt
|
|
889
|
+
- **Frontend:** React (Vite), Tailwind CSS, React Router, axios
|
|
890
|
+
|
|
891
|
+
## Prerequisites
|
|
892
|
+
|
|
893
|
+
- [Node.js](https://nodejs.org/) (v18+ recommended)
|
|
894
|
+
- [MongoDB](https://www.mongodb.com/try/download/community) running locally (use [MongoDB Compass](https://www.mongodb.com/products/compass) to inspect `blueprint_db`)
|
|
895
|
+
|
|
896
|
+
## Project structure
|
|
897
|
+
|
|
898
|
+
\`\`\`
|
|
899
|
+
blueprint/
|
|
900
|
+
├── backend/ # API (port 5000)
|
|
901
|
+
├── frontend/ # React app (port 5173)
|
|
902
|
+
└── README.md
|
|
903
|
+
\`\`\`
|
|
904
|
+
|
|
905
|
+
## Setup
|
|
906
|
+
|
|
907
|
+
### 1. MongoDB
|
|
908
|
+
|
|
909
|
+
Start MongoDB on your machine. The default connection string targets a local instance:
|
|
910
|
+
|
|
911
|
+
\`\`\`
|
|
912
|
+
mongodb://127.0.0.1:27017/blueprint_db
|
|
913
|
+
\`\`\`
|
|
914
|
+
|
|
915
|
+
### 2. Backend
|
|
916
|
+
|
|
917
|
+
\`\`\`bash
|
|
918
|
+
cd backend
|
|
919
|
+
npm install
|
|
920
|
+
cp .env.example .env
|
|
921
|
+
\`\`\`
|
|
922
|
+
|
|
923
|
+
On Windows (PowerShell):
|
|
924
|
+
|
|
925
|
+
\`\`\`powershell
|
|
926
|
+
Copy-Item .env.example .env
|
|
927
|
+
\`\`\`
|
|
928
|
+
|
|
929
|
+
Edit `.env` and set a strong `JWT_SECRET`. Other defaults work for local development.
|
|
930
|
+
|
|
931
|
+
\`\`\`bash
|
|
932
|
+
node src/server.js
|
|
933
|
+
\`\`\`
|
|
934
|
+
|
|
935
|
+
API base: **http://localhost:5000/api**
|
|
936
|
+
|
|
937
|
+
- Health: `GET /api/health`
|
|
938
|
+
- Auth: `POST /api/auth/register`, `POST /api/auth/login`, `GET /api/auth/me`
|
|
939
|
+
- Items (protected): `GET/POST /api/items`, `GET/PUT/DELETE /api/items/:id`
|
|
940
|
+
|
|
941
|
+
### 3. Frontend
|
|
942
|
+
|
|
943
|
+
In a second terminal:
|
|
944
|
+
|
|
945
|
+
\`\`\`bash
|
|
946
|
+
cd frontend
|
|
947
|
+
npm install
|
|
948
|
+
cp .env.example .env
|
|
949
|
+
\`\`\`
|
|
950
|
+
|
|
951
|
+
Windows:
|
|
952
|
+
|
|
953
|
+
\`\`\`powershell
|
|
954
|
+
Copy-Item .env.example .env
|
|
955
|
+
\`\`\`
|
|
956
|
+
|
|
957
|
+
\`\`\`bash
|
|
958
|
+
npm run dev
|
|
959
|
+
\`\`\`
|
|
960
|
+
|
|
961
|
+
App URL: **http://localhost:5173**
|
|
962
|
+
|
|
963
|
+
## Test flow
|
|
964
|
+
|
|
965
|
+
1. Open **http://localhost:5173/register** and create an account (name, email, password).
|
|
966
|
+
2. You are redirected to **/dashboard** with a JWT stored in `localStorage`.
|
|
967
|
+
3. Use the sidebar to open **Items** — create, edit, and delete items.
|
|
968
|
+
4. Log out, then sign in again at **/login** — your items remain tied to your user only.
|
|
969
|
+
|
|
970
|
+
## Environment variables
|
|
971
|
+
|
|
972
|
+
**Backend (`backend/.env`)**
|
|
973
|
+
|
|
974
|
+
| Variable | Example |
|
|
975
|
+
|-------------|--------------------------------------|
|
|
976
|
+
| PORT | 5000 |
|
|
977
|
+
| MONGO_URI | mongodb://127.0.0.1:27017/blueprint_db |
|
|
978
|
+
| JWT_SECRET | (long random string) |
|
|
979
|
+
| CLIENT_URL | http://localhost:5173 |
|
|
980
|
+
|
|
981
|
+
**Frontend (`frontend/.env`)**
|
|
982
|
+
|
|
983
|
+
| Variable | Example |
|
|
984
|
+
|---------------|------------------------------|
|
|
985
|
+
| VITE_API_URL | http://localhost:5000/api |
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
---
|
|
989
|
+
|
|
990
|
+
## ALTERNATIVE (fastest 100% match)
|
|
991
|
+
|
|
992
|
+
If the AI has file access, instruct it instead:
|
|
993
|
+
|
|
994
|
+
> Clone or copy the Blueprint repo excluding `node_modules`, `dist`, and lockfiles. Run `npm install` in `backend` and `frontend`. Copy `.env.example` to `.env` in both folders. Start with `node backend/src/server.js` and `npm run dev` in frontend.
|
|
995
|
+
|
|
996
|
+
That guarantees a byte-identical copy without regeneration errors.
|