create-fullstack-boilerplate 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/README.md +390 -0
- package/index.js +78 -0
- package/lib/addDB.js +77 -0
- package/lib/addRoute.js +264 -0
- package/lib/copyProject.js +25 -0
- package/lib/dataTypes.js +79 -0
- package/lib/installDeps.js +11 -0
- package/lib/prompts.js +289 -0
- package/lib/setupExtraDB.js +172 -0
- package/lib/setupMainDB.js +9 -0
- package/lib/testDBConnection.js +31 -0
- package/lib/utils.js +39 -0
- package/package.json +45 -0
- package/template/Backend/.env +7 -0
- package/template/Backend/DB/DBInit.js +28 -0
- package/template/Backend/DB/dbConfigs.js +4 -0
- package/template/Backend/Models/index.js +54 -0
- package/template/Backend/README.md +535 -0
- package/template/Backend/middleware/authMiddleware.js +19 -0
- package/template/Backend/package-lock.json +2997 -0
- package/template/Backend/package.json +32 -0
- package/template/Backend/routes/authRoutes.js +15 -0
- package/template/Backend/routes/dashboardRoutes.js +13 -0
- package/template/Backend/routes/index.js +15 -0
- package/template/Backend/routes/settingsRoutes.js +9 -0
- package/template/Backend/server.js +70 -0
- package/template/Backend/services/authService.js +68 -0
- package/template/Backend/services/cryptoService.js +14 -0
- package/template/Backend/services/dashboardService.js +39 -0
- package/template/Backend/services/settingsService.js +43 -0
- package/template/Frontend/.env +3 -0
- package/template/Frontend/README.md +576 -0
- package/template/Frontend/eslint.config.js +29 -0
- package/template/Frontend/index.html +13 -0
- package/template/Frontend/package-lock.json +3690 -0
- package/template/Frontend/package.json +39 -0
- package/template/Frontend/public/PMDLogo.png +0 -0
- package/template/Frontend/public/pp.jpg +0 -0
- package/template/Frontend/public/tabicon.png +0 -0
- package/template/Frontend/src/App.jsx +71 -0
- package/template/Frontend/src/assets/fonts/ArticulatCFDemiBold/font.woff +0 -0
- package/template/Frontend/src/assets/fonts/ArticulatCFDemiBold/font.woff2 +0 -0
- package/template/Frontend/src/assets/fonts/ArticulatCFNormal/font.woff +0 -0
- package/template/Frontend/src/assets/fonts/ArticulatCFNormal/font.woff2 +0 -0
- package/template/Frontend/src/assets/fonts/ArticulatCFRegular/font.woff +0 -0
- package/template/Frontend/src/assets/fonts/ArticulatCFRegular/font.woff2 +0 -0
- package/template/Frontend/src/assets/fonts/MixtaProRegularItalic/font.woff +0 -0
- package/template/Frontend/src/assets/fonts/MixtaProRegularItalic/font.woff2 +0 -0
- package/template/Frontend/src/assets/fonts/fonts_sohne/OTF/S/303/266hneMono-Buch.otf +0 -0
- package/template/Frontend/src/assets/fonts/fonts_sohne/OTF/S/303/266hneMono-Leicht.otf +0 -0
- package/template/Frontend/src/assets/fonts/fonts_sohne/WOFF2/soehne-mono-buch.woff2 +0 -0
- package/template/Frontend/src/assets/fonts/fonts_sohne/WOFF2/soehne-mono-leicht.woff2 +0 -0
- package/template/Frontend/src/components/Layout.jsx +61 -0
- package/template/Frontend/src/components/Loader.jsx +19 -0
- package/template/Frontend/src/components/ProtectedRoute.jsx +19 -0
- package/template/Frontend/src/components/Sidebar.jsx +286 -0
- package/template/Frontend/src/components/ThemeToggle.jsx +30 -0
- package/template/Frontend/src/config/axiosClient.js +46 -0
- package/template/Frontend/src/config/encryption.js +11 -0
- package/template/Frontend/src/config/routes.js +65 -0
- package/template/Frontend/src/contexts/AuthContext.jsx +144 -0
- package/template/Frontend/src/contexts/ThemeContext.jsx +69 -0
- package/template/Frontend/src/index.css +88 -0
- package/template/Frontend/src/main.jsx +11 -0
- package/template/Frontend/src/pages/Dashboard.jsx +137 -0
- package/template/Frontend/src/pages/Login.jsx +195 -0
- package/template/Frontend/src/pages/NotFound.jsx +70 -0
- package/template/Frontend/src/pages/Settings.jsx +69 -0
- package/template/Frontend/tailwind.config.js +90 -0
- package/template/Frontend/vite.config.js +37 -0
- package/template/Readme.md +0 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
# Frontend Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This is the frontend of your fullstack application built with **React 19**, **Vite**, **Tailwind CSS**, and **DaisyUI**. It provides a modern, fast development experience with hot module replacement and optimized production builds.
|
|
6
|
+
|
|
7
|
+
## 🏗️ Project Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
frontend/
|
|
11
|
+
├── public/ # Static assets
|
|
12
|
+
│ └── vite.svg # Favicon and public files
|
|
13
|
+
├── src/ # Source code
|
|
14
|
+
│ ├── assets/ # Images, fonts, etc.
|
|
15
|
+
│ ├── components/ # Reusable React components
|
|
16
|
+
│ ├── pages/ # Page components
|
|
17
|
+
│ ├── services/ # API service functions
|
|
18
|
+
│ ├── utils/ # Utility functions
|
|
19
|
+
│ ├── App.jsx # Main App component
|
|
20
|
+
│ ├── main.jsx # Application entry point
|
|
21
|
+
│ └── index.css # Global styles and Tailwind imports
|
|
22
|
+
├── .env # Environment variables
|
|
23
|
+
├── index.html # HTML template
|
|
24
|
+
├── package.json # Dependencies and scripts
|
|
25
|
+
├── vite.config.js # Vite configuration
|
|
26
|
+
├── tailwind.config.js # Tailwind CSS configuration
|
|
27
|
+
└── eslint.config.js # ESLint configuration
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 🚀 Getting Started
|
|
31
|
+
|
|
32
|
+
### Prerequisites
|
|
33
|
+
|
|
34
|
+
- Node.js >= 20
|
|
35
|
+
- npm or yarn
|
|
36
|
+
|
|
37
|
+
### Installation
|
|
38
|
+
|
|
39
|
+
Dependencies are automatically installed during project creation. To reinstall:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Environment Variables
|
|
46
|
+
|
|
47
|
+
Configure your environment variables in the `.env` file:
|
|
48
|
+
|
|
49
|
+
```env
|
|
50
|
+
VITE_API_URL=http://localhost:5000/api
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Note:** All environment variables in Vite must be prefixed with `VITE_` to be exposed to your application.
|
|
54
|
+
|
|
55
|
+
### Running the Development Server
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm run dev
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The application will start on `http://localhost:5173` with hot module replacement (HMR) enabled.
|
|
62
|
+
|
|
63
|
+
### Building for Production
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm run build
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This creates an optimized production build in the `dist/` folder.
|
|
70
|
+
|
|
71
|
+
### Preview Production Build
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm run preview
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Serves the production build locally for testing.
|
|
78
|
+
|
|
79
|
+
### Linting
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run lint
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 📦 Tech Stack
|
|
86
|
+
|
|
87
|
+
### Core Technologies
|
|
88
|
+
|
|
89
|
+
- **React 19** - UI library with latest features
|
|
90
|
+
- **Vite** - Next-generation frontend tooling
|
|
91
|
+
- **React Router** - Client-side routing
|
|
92
|
+
|
|
93
|
+
### Styling
|
|
94
|
+
|
|
95
|
+
- **Tailwind CSS 4** - Utility-first CSS framework
|
|
96
|
+
- **DaisyUI** - Tailwind CSS component library
|
|
97
|
+
|
|
98
|
+
### Additional Libraries
|
|
99
|
+
|
|
100
|
+
- **Axios** - HTTP client for API calls
|
|
101
|
+
- **JWT Decode** - Decode JWT tokens
|
|
102
|
+
- **Crypto-JS** - Encryption utilities
|
|
103
|
+
- **Lucide React** / **Heroicons** - Icon libraries
|
|
104
|
+
- **React Spinners** - Loading indicators
|
|
105
|
+
- **Recharts** - Charting library
|
|
106
|
+
- **XLSX** - Excel file handling
|
|
107
|
+
|
|
108
|
+
## 🧩 Component Structure
|
|
109
|
+
|
|
110
|
+
### Creating a New Component
|
|
111
|
+
|
|
112
|
+
Create components in `src/components/`:
|
|
113
|
+
|
|
114
|
+
```jsx
|
|
115
|
+
// src/components/Button.jsx
|
|
116
|
+
import React from 'react';
|
|
117
|
+
|
|
118
|
+
const Button = ({ children, onClick, variant = 'primary', ...props }) => {
|
|
119
|
+
return (
|
|
120
|
+
<button
|
|
121
|
+
onClick={onClick}
|
|
122
|
+
className={`btn btn-${variant}`}
|
|
123
|
+
{...props}
|
|
124
|
+
>
|
|
125
|
+
{children}
|
|
126
|
+
</button>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export default Button;
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Creating a Page
|
|
134
|
+
|
|
135
|
+
Create pages in `src/pages/`:
|
|
136
|
+
|
|
137
|
+
```jsx
|
|
138
|
+
// src/pages/Dashboard.jsx
|
|
139
|
+
import React, { useEffect, useState } from 'react';
|
|
140
|
+
import { getDashboardData } from '../services/dashboardService';
|
|
141
|
+
|
|
142
|
+
const Dashboard = () => {
|
|
143
|
+
const [data, setData] = useState(null);
|
|
144
|
+
const [loading, setLoading] = useState(true);
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
const fetchData = async () => {
|
|
148
|
+
try {
|
|
149
|
+
const result = await getDashboardData();
|
|
150
|
+
setData(result);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error('Error fetching dashboard data:', error);
|
|
153
|
+
} finally {
|
|
154
|
+
setLoading(false);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
fetchData();
|
|
159
|
+
}, []);
|
|
160
|
+
|
|
161
|
+
if (loading) return <div>Loading...</div>;
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="container mx-auto p-4">
|
|
165
|
+
<h1 className="text-3xl font-bold mb-4">Dashboard</h1>
|
|
166
|
+
{/* Your dashboard content */}
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export default Dashboard;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## 🔌 API Integration
|
|
175
|
+
|
|
176
|
+
### Setting Up API Services
|
|
177
|
+
|
|
178
|
+
Create service files in `src/services/`:
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
// src/services/api.js
|
|
182
|
+
import axios from 'axios';
|
|
183
|
+
|
|
184
|
+
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000/api';
|
|
185
|
+
|
|
186
|
+
const api = axios.create({
|
|
187
|
+
baseURL: API_URL,
|
|
188
|
+
headers: {
|
|
189
|
+
'Content-Type': 'application/json'
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Add token to requests if it exists
|
|
194
|
+
api.interceptors.request.use(
|
|
195
|
+
(config) => {
|
|
196
|
+
const token = localStorage.getItem('token');
|
|
197
|
+
if (token) {
|
|
198
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
199
|
+
}
|
|
200
|
+
return config;
|
|
201
|
+
},
|
|
202
|
+
(error) => Promise.reject(error)
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Handle response errors
|
|
206
|
+
api.interceptors.response.use(
|
|
207
|
+
(response) => response,
|
|
208
|
+
(error) => {
|
|
209
|
+
if (error.response?.status === 401) {
|
|
210
|
+
// Handle unauthorized access
|
|
211
|
+
localStorage.removeItem('token');
|
|
212
|
+
window.location.href = '/login';
|
|
213
|
+
}
|
|
214
|
+
return Promise.reject(error);
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
export default api;
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Creating API Service Functions
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
// src/services/userService.js
|
|
225
|
+
import api from './api';
|
|
226
|
+
|
|
227
|
+
export const getUsers = async () => {
|
|
228
|
+
const response = await api.get('/users');
|
|
229
|
+
return response.data;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const getUserById = async (id) => {
|
|
233
|
+
const response = await api.get(`/users/${id}`);
|
|
234
|
+
return response.data;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const createUser = async (userData) => {
|
|
238
|
+
const response = await api.post('/users', userData);
|
|
239
|
+
return response.data;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const updateUser = async (id, userData) => {
|
|
243
|
+
const response = await api.put(`/users/${id}`, userData);
|
|
244
|
+
return response.data;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export const deleteUser = async (id) => {
|
|
248
|
+
const response = await api.delete(`/users/${id}`);
|
|
249
|
+
return response.data;
|
|
250
|
+
};
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## 📡 Routing
|
|
254
|
+
|
|
255
|
+
### Setting Up Routes
|
|
256
|
+
|
|
257
|
+
```jsx
|
|
258
|
+
// src/App.jsx
|
|
259
|
+
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
|
260
|
+
import Dashboard from './pages/Dashboard';
|
|
261
|
+
import Login from './pages/Login';
|
|
262
|
+
import NotFound from './pages/NotFound';
|
|
263
|
+
|
|
264
|
+
function App() {
|
|
265
|
+
return (
|
|
266
|
+
<BrowserRouter>
|
|
267
|
+
<Routes>
|
|
268
|
+
<Route path="/" element={<Navigate to="/dashboard" />} />
|
|
269
|
+
<Route path="/login" element={<Login />} />
|
|
270
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
271
|
+
<Route path="*" element={<NotFound />} />
|
|
272
|
+
</Routes>
|
|
273
|
+
</BrowserRouter>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export default App;
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Protected Routes
|
|
281
|
+
|
|
282
|
+
```jsx
|
|
283
|
+
// src/components/ProtectedRoute.jsx
|
|
284
|
+
import { Navigate } from 'react-router-dom';
|
|
285
|
+
|
|
286
|
+
const ProtectedRoute = ({ children }) => {
|
|
287
|
+
const token = localStorage.getItem('token');
|
|
288
|
+
|
|
289
|
+
if (!token) {
|
|
290
|
+
return <Navigate to="/login" replace />;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return children;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export default ProtectedRoute;
|
|
297
|
+
|
|
298
|
+
// Usage in App.jsx
|
|
299
|
+
<Route
|
|
300
|
+
path="/dashboard"
|
|
301
|
+
element={
|
|
302
|
+
<ProtectedRoute>
|
|
303
|
+
<Dashboard />
|
|
304
|
+
</ProtectedRoute>
|
|
305
|
+
}
|
|
306
|
+
/>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## 🎨 Styling with Tailwind & DaisyUI
|
|
310
|
+
|
|
311
|
+
### Tailwind Utility Classes
|
|
312
|
+
|
|
313
|
+
```jsx
|
|
314
|
+
// Layout
|
|
315
|
+
<div className="container mx-auto px-4">
|
|
316
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
317
|
+
{/* Content */}
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
// Flexbox
|
|
322
|
+
<div className="flex items-center justify-between">
|
|
323
|
+
<span>Left</span>
|
|
324
|
+
<span>Right</span>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
// Responsive Text
|
|
328
|
+
<h1 className="text-2xl md:text-3xl lg:text-4xl font-bold">
|
|
329
|
+
Responsive Heading
|
|
330
|
+
</h1>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### DaisyUI Components
|
|
334
|
+
|
|
335
|
+
```jsx
|
|
336
|
+
// Buttons
|
|
337
|
+
<button className="btn btn-primary">Primary</button>
|
|
338
|
+
<button className="btn btn-secondary">Secondary</button>
|
|
339
|
+
<button className="btn btn-accent">Accent</button>
|
|
340
|
+
<button className="btn btn-ghost">Ghost</button>
|
|
341
|
+
<button className="btn btn-link">Link</button>
|
|
342
|
+
|
|
343
|
+
// Cards
|
|
344
|
+
<div className="card bg-base-100 shadow-xl">
|
|
345
|
+
<div className="card-body">
|
|
346
|
+
<h2 className="card-title">Card Title</h2>
|
|
347
|
+
<p>Card content goes here</p>
|
|
348
|
+
<div className="card-actions justify-end">
|
|
349
|
+
<button className="btn btn-primary">Action</button>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
// Alerts
|
|
355
|
+
<div className="alert alert-info">
|
|
356
|
+
<span>Info alert</span>
|
|
357
|
+
</div>
|
|
358
|
+
<div className="alert alert-success">
|
|
359
|
+
<span>Success alert</span>
|
|
360
|
+
</div>
|
|
361
|
+
<div className="alert alert-warning">
|
|
362
|
+
<span>Warning alert</span>
|
|
363
|
+
</div>
|
|
364
|
+
<div className="alert alert-error">
|
|
365
|
+
<span>Error alert</span>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
// Modal
|
|
369
|
+
<input type="checkbox" id="my-modal" className="modal-toggle" />
|
|
370
|
+
<div className="modal">
|
|
371
|
+
<div className="modal-box">
|
|
372
|
+
<h3 className="font-bold text-lg">Modal Title</h3>
|
|
373
|
+
<p className="py-4">Modal content</p>
|
|
374
|
+
<div className="modal-action">
|
|
375
|
+
<label htmlFor="my-modal" className="btn">Close</label>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Customizing Tailwind Config
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
// tailwind.config.js
|
|
385
|
+
export default {
|
|
386
|
+
content: [
|
|
387
|
+
"./index.html",
|
|
388
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
389
|
+
],
|
|
390
|
+
theme: {
|
|
391
|
+
extend: {
|
|
392
|
+
colors: {
|
|
393
|
+
'brand': '#your-color',
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
plugins: [require("daisyui")],
|
|
398
|
+
daisyui: {
|
|
399
|
+
themes: ["light", "dark", "cupcake"],
|
|
400
|
+
},
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## 🔐 Authentication
|
|
405
|
+
|
|
406
|
+
### Login Example
|
|
407
|
+
|
|
408
|
+
```jsx
|
|
409
|
+
// src/pages/Login.jsx
|
|
410
|
+
import { useState } from 'react';
|
|
411
|
+
import { useNavigate } from 'react-router-dom';
|
|
412
|
+
import api from '../services/api';
|
|
413
|
+
|
|
414
|
+
const Login = () => {
|
|
415
|
+
const [credentials, setCredentials] = useState({ email: '', password: '' });
|
|
416
|
+
const [error, setError] = useState('');
|
|
417
|
+
const navigate = useNavigate();
|
|
418
|
+
|
|
419
|
+
const handleSubmit = async (e) => {
|
|
420
|
+
e.preventDefault();
|
|
421
|
+
try {
|
|
422
|
+
const response = await api.post('/auth/login', credentials);
|
|
423
|
+
localStorage.setItem('token', response.data.token);
|
|
424
|
+
navigate('/dashboard');
|
|
425
|
+
} catch (err) {
|
|
426
|
+
setError(err.response?.data?.message || 'Login failed');
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
return (
|
|
431
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
432
|
+
<div className="card w-96 bg-base-100 shadow-xl">
|
|
433
|
+
<div className="card-body">
|
|
434
|
+
<h2 className="card-title">Login</h2>
|
|
435
|
+
{error && <div className="alert alert-error">{error}</div>}
|
|
436
|
+
<form onSubmit={handleSubmit}>
|
|
437
|
+
<input
|
|
438
|
+
type="email"
|
|
439
|
+
placeholder="Email"
|
|
440
|
+
className="input input-bordered w-full mb-4"
|
|
441
|
+
value={credentials.email}
|
|
442
|
+
onChange={(e) => setCredentials({...credentials, email: e.target.value})}
|
|
443
|
+
/>
|
|
444
|
+
<input
|
|
445
|
+
type="password"
|
|
446
|
+
placeholder="Password"
|
|
447
|
+
className="input input-bordered w-full mb-4"
|
|
448
|
+
value={credentials.password}
|
|
449
|
+
onChange={(e) => setCredentials({...credentials, password: e.target.value})}
|
|
450
|
+
/>
|
|
451
|
+
<button type="submit" className="btn btn-primary w-full">
|
|
452
|
+
Login
|
|
453
|
+
</button>
|
|
454
|
+
</form>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
export default Login;
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
## 📦 State Management
|
|
465
|
+
|
|
466
|
+
For simple state management, use React's built-in hooks:
|
|
467
|
+
|
|
468
|
+
### Context API Example
|
|
469
|
+
|
|
470
|
+
```jsx
|
|
471
|
+
// src/contexts/AuthContext.jsx
|
|
472
|
+
import { createContext, useContext, useState, useEffect } from 'react';
|
|
473
|
+
import { jwtDecode } from 'jwt-decode';
|
|
474
|
+
|
|
475
|
+
const AuthContext = createContext();
|
|
476
|
+
|
|
477
|
+
export const AuthProvider = ({ children }) => {
|
|
478
|
+
const [user, setUser] = useState(null);
|
|
479
|
+
|
|
480
|
+
useEffect(() => {
|
|
481
|
+
const token = localStorage.getItem('token');
|
|
482
|
+
if (token) {
|
|
483
|
+
try {
|
|
484
|
+
const decoded = jwtDecode(token);
|
|
485
|
+
setUser(decoded);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
localStorage.removeItem('token');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}, []);
|
|
491
|
+
|
|
492
|
+
const login = (token) => {
|
|
493
|
+
localStorage.setItem('token', token);
|
|
494
|
+
setUser(jwtDecode(token));
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const logout = () => {
|
|
498
|
+
localStorage.removeItem('token');
|
|
499
|
+
setUser(null);
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
<AuthContext.Provider value={{ user, login, logout }}>
|
|
504
|
+
{children}
|
|
505
|
+
</AuthContext.Provider>
|
|
506
|
+
);
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
export const useAuth = () => useContext(AuthContext);
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
## 📊 Charts & Visualization
|
|
513
|
+
|
|
514
|
+
### Using Recharts
|
|
515
|
+
|
|
516
|
+
```jsx
|
|
517
|
+
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts';
|
|
518
|
+
|
|
519
|
+
const data = [
|
|
520
|
+
{ name: 'Jan', value: 400 },
|
|
521
|
+
{ name: 'Feb', value: 300 },
|
|
522
|
+
{ name: 'Mar', value: 600 },
|
|
523
|
+
];
|
|
524
|
+
|
|
525
|
+
const Chart = () => (
|
|
526
|
+
<LineChart width={600} height={300} data={data}>
|
|
527
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
528
|
+
<XAxis dataKey="name" />
|
|
529
|
+
<YAxis />
|
|
530
|
+
<Tooltip />
|
|
531
|
+
<Legend />
|
|
532
|
+
<Line type="monotone" dataKey="value" stroke="#8884d8" />
|
|
533
|
+
</LineChart>
|
|
534
|
+
);
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## ✨ Best Practices
|
|
538
|
+
|
|
539
|
+
1. **Component Organization**: Keep components small and focused
|
|
540
|
+
2. **Custom Hooks**: Extract reusable logic into custom hooks
|
|
541
|
+
3. **Error Handling**: Always handle errors in async operations
|
|
542
|
+
4. **Loading States**: Show loading indicators for async operations
|
|
543
|
+
5. **Accessibility**: Use semantic HTML and ARIA labels
|
|
544
|
+
6. **Performance**: Use React.memo for expensive components
|
|
545
|
+
7. **Environment Variables**: Never commit sensitive data
|
|
546
|
+
|
|
547
|
+
## 📚 Additional Resources
|
|
548
|
+
|
|
549
|
+
- [React Documentation](https://react.dev/)
|
|
550
|
+
- [Vite Documentation](https://vite.dev/)
|
|
551
|
+
- [Tailwind CSS Documentation](https://tailwindcss.com/)
|
|
552
|
+
- [DaisyUI Documentation](https://daisyui.com/)
|
|
553
|
+
- [React Router Documentation](https://reactrouter.com/)
|
|
554
|
+
|
|
555
|
+
## 🐛 Troubleshooting
|
|
556
|
+
|
|
557
|
+
### Module Not Found
|
|
558
|
+
```bash
|
|
559
|
+
npm install
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Port Already in Use
|
|
563
|
+
Change the port in `vite.config.js` or kill the process:
|
|
564
|
+
```bash
|
|
565
|
+
# Windows
|
|
566
|
+
netstat -ano | findstr :5173
|
|
567
|
+
taskkill /PID <PID> /F
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Build Errors
|
|
571
|
+
Clear cache and rebuild:
|
|
572
|
+
```bash
|
|
573
|
+
rm -rf node_modules dist .vite
|
|
574
|
+
npm install
|
|
575
|
+
npm run build
|
|
576
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
6
|
+
|
|
7
|
+
export default defineConfig([
|
|
8
|
+
globalIgnores(['dist']),
|
|
9
|
+
{
|
|
10
|
+
files: ['**/*.{js,jsx}'],
|
|
11
|
+
extends: [
|
|
12
|
+
js.configs.recommended,
|
|
13
|
+
reactHooks.configs['recommended-latest'],
|
|
14
|
+
reactRefresh.configs.vite,
|
|
15
|
+
],
|
|
16
|
+
languageOptions: {
|
|
17
|
+
ecmaVersion: 2020,
|
|
18
|
+
globals: globals.browser,
|
|
19
|
+
parserOptions: {
|
|
20
|
+
ecmaVersion: 'latest',
|
|
21
|
+
ecmaFeatures: { jsx: true },
|
|
22
|
+
sourceType: 'module',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
rules: {
|
|
26
|
+
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/PMDLogo.png" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>PMD - Reports</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|