mern-template-tool 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 (47) hide show
  1. package/README.md +93 -0
  2. package/bin/index.js +112 -0
  3. package/package.json +33 -0
  4. package/templates/README.md +160 -0
  5. package/templates/_gitignore +8 -0
  6. package/templates/client/index.html +13 -0
  7. package/templates/client/package.json +20 -0
  8. package/templates/client/src/App.jsx +72 -0
  9. package/templates/client/src/components/Loading.jsx +14 -0
  10. package/templates/client/src/components/Navbar.jsx +58 -0
  11. package/templates/client/src/context/AuthContext.jsx +109 -0
  12. package/templates/client/src/index.css +244 -0
  13. package/templates/client/src/main.jsx +16 -0
  14. package/templates/client/src/pages/CreateResource.jsx +142 -0
  15. package/templates/client/src/pages/Dashboard.jsx +117 -0
  16. package/templates/client/src/pages/EditResource.jsx +166 -0
  17. package/templates/client/src/pages/Login.jsx +101 -0
  18. package/templates/client/src/pages/NotFound.jsx +21 -0
  19. package/templates/client/src/pages/Profile.jsx +52 -0
  20. package/templates/client/src/pages/Register.jsx +134 -0
  21. package/templates/client/src/pages/ResourceDetails.jsx +156 -0
  22. package/templates/client/src/pages/Resources.jsx +181 -0
  23. package/templates/client/src/routes/ProtectedRoute.jsx +34 -0
  24. package/templates/client/src/services/api.js +71 -0
  25. package/templates/client/vite.config.js +20 -0
  26. package/templates/server/.env.example +5 -0
  27. package/templates/server/config/db.js +13 -0
  28. package/templates/server/controllers/authController.js +131 -0
  29. package/templates/server/controllers/resourceController.js +254 -0
  30. package/templates/server/middleware/adminMiddleware.js +14 -0
  31. package/templates/server/middleware/authMiddleware.js +53 -0
  32. package/templates/server/middleware/errorMiddleware.js +55 -0
  33. package/templates/server/middleware/sessionAuthMiddleware.js +12 -0
  34. package/templates/server/middleware/uploadMiddleware.js +61 -0
  35. package/templates/server/middleware/validateMiddleware.js +61 -0
  36. package/templates/server/models/Resource.js +43 -0
  37. package/templates/server/models/User.js +48 -0
  38. package/templates/server/package.json +25 -0
  39. package/templates/server/routes/adminRoutes.js +138 -0
  40. package/templates/server/routes/authRoutes.js +21 -0
  41. package/templates/server/routes/resourceRoutes.js +27 -0
  42. package/templates/server/server.js +89 -0
  43. package/templates/server/utils/jwt.js +18 -0
  44. package/templates/server/views/dashboard.ejs +128 -0
  45. package/templates/server/views/login.ejs +48 -0
  46. package/templates/server/views/partials/footer.ejs +6 -0
  47. package/templates/server/views/partials/header.ejs +60 -0
@@ -0,0 +1,138 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const bcrypt = require('bcryptjs');
4
+ const User = require('../models/User');
5
+ const Resource = require('../models/Resource');
6
+ const { sessionAuth } = require('../middleware/sessionAuthMiddleware');
7
+
8
+ // GET /admin/login - Render Admin Login Page
9
+ router.get('/login', (req, res) => {
10
+ if (req.session && req.session.user && req.session.user.role === 'admin') {
11
+ return res.redirect('/admin/dashboard');
12
+ }
13
+ res.render('login', {
14
+ error: req.query.error || null,
15
+ success: req.query.success || null,
16
+ });
17
+ });
18
+
19
+ // POST /admin/login - Authenticate Admin User and establish Session
20
+ router.post('/login', async (req, res, next) => {
21
+ try {
22
+ const { email, password } = req.body;
23
+
24
+ if (!email || !password) {
25
+ return res.render('login', {
26
+ error: 'Please provide both email and password.',
27
+ success: null,
28
+ });
29
+ }
30
+
31
+ const user = await User.findOne({ email: email.toLowerCase() });
32
+
33
+ if (!user) {
34
+ return res.render('login', {
35
+ error: 'Invalid admin credentials.',
36
+ success: null,
37
+ });
38
+ }
39
+
40
+ if (user.role !== 'admin') {
41
+ return res.render('login', {
42
+ error: 'Access denied: Admin role required.',
43
+ success: null,
44
+ });
45
+ }
46
+
47
+ if (!user.isActive) {
48
+ return res.render('login', {
49
+ error: 'This admin account has been deactivated.',
50
+ success: null,
51
+ });
52
+ }
53
+
54
+ const isMatch = await bcrypt.compare(password, user.password);
55
+ if (!isMatch) {
56
+ return res.render('login', {
57
+ error: 'Invalid admin credentials.',
58
+ success: null,
59
+ });
60
+ }
61
+
62
+ // Save user info in express-session
63
+ req.session.user = {
64
+ id: user._id,
65
+ name: user.name,
66
+ email: user.email,
67
+ role: user.role,
68
+ };
69
+
70
+ res.redirect('/admin/dashboard');
71
+ } catch (error) {
72
+ next(error);
73
+ }
74
+ });
75
+
76
+ // POST /admin/logout - Destroy Session
77
+ router.post('/logout', (req, res) => {
78
+ req.session.destroy((err) => {
79
+ if (err) {
80
+ console.error('Session destroy error:', err);
81
+ }
82
+ res.redirect('/admin/login?success=Logged%20out%20successfully');
83
+ });
84
+ });
85
+
86
+ // GET /admin/dashboard - Render Protected Admin Dashboard
87
+ router.get('/dashboard', sessionAuth, async (req, res, next) => {
88
+ try {
89
+ const users = await User.find().sort({ createdAt: -1 });
90
+ const resources = await Resource.find()
91
+ .populate('createdBy', 'name email')
92
+ .sort({ createdAt: -1 });
93
+
94
+ const totalStudents = users.filter((u) => u.role === 'student').length;
95
+ const totalAdmins = users.filter((u) => u.role === 'admin').length;
96
+
97
+ res.render('dashboard', {
98
+ adminUser: req.session.user,
99
+ users,
100
+ resources,
101
+ stats: {
102
+ totalUsers: users.length,
103
+ totalStudents,
104
+ totalAdmins,
105
+ totalResources: resources.length,
106
+ },
107
+ message: req.query.message || null,
108
+ });
109
+ } catch (error) {
110
+ next(error);
111
+ }
112
+ });
113
+
114
+ // POST /admin/users/:id/toggle - Toggle user active status (demonstrates admin action in EJS)
115
+ router.post('/users/:id/toggle', sessionAuth, async (req, res, next) => {
116
+ try {
117
+ const user = await User.findById(req.params.id);
118
+ if (user) {
119
+ user.isActive = !user.isActive;
120
+ await user.save();
121
+ }
122
+ res.redirect('/admin/dashboard?message=User%20status%20updated');
123
+ } catch (error) {
124
+ next(error);
125
+ }
126
+ });
127
+
128
+ // POST /admin/resources/:id/delete - Admin delete resource
129
+ router.post('/resources/:id/delete', sessionAuth, async (req, res, next) => {
130
+ try {
131
+ await Resource.findByIdAndDelete(req.params.id);
132
+ res.redirect('/admin/dashboard?message=Resource%20deleted%20by%20admin');
133
+ } catch (error) {
134
+ next(error);
135
+ }
136
+ });
137
+
138
+ module.exports = router;
@@ -0,0 +1,21 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const {
4
+ registerUser,
5
+ loginUser,
6
+ getMe,
7
+ } = require('../controllers/authController');
8
+ const { protect } = require('../middleware/authMiddleware');
9
+ const {
10
+ registerValidationRules,
11
+ loginValidationRules,
12
+ } = require('../middleware/validateMiddleware');
13
+
14
+ // Public routes
15
+ router.post('/register', registerValidationRules, registerUser);
16
+ router.post('/login', loginValidationRules, loginUser);
17
+
18
+ // Protected routes
19
+ router.get('/me', protect, getMe);
20
+
21
+ module.exports = router;
@@ -0,0 +1,27 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const {
4
+ getResources,
5
+ getResourceById,
6
+ createResource,
7
+ updateResource,
8
+ deleteResource,
9
+ downloadResourceFile,
10
+ } = require('../controllers/resourceController');
11
+ const { protect } = require('../middleware/authMiddleware');
12
+ const upload = require('../middleware/uploadMiddleware');
13
+ const {
14
+ resourceValidationRules,
15
+ } = require('../middleware/validateMiddleware');
16
+
17
+ // Public read routes
18
+ router.get('/', getResources);
19
+ router.get('/:id', getResourceById);
20
+ router.get('/:id/download', downloadResourceFile);
21
+
22
+ // Protected mutation routes (with optional Multer file upload)
23
+ router.post('/', protect, upload.single('file'), resourceValidationRules, createResource);
24
+ router.put('/:id', protect, upload.single('file'), updateResource);
25
+ router.delete('/:id', protect, deleteResource);
26
+
27
+ module.exports = router;
@@ -0,0 +1,89 @@
1
+ const express = require('express');
2
+ const path = require('path');
3
+ const dotenv = require('dotenv');
4
+ const cors = require('cors');
5
+ const session = require('express-session');
6
+ const connectDB = require('./config/db');
7
+ const { notFound, errorHandler } = require('./middleware/errorMiddleware');
8
+
9
+ // Load environment variables
10
+ dotenv.config();
11
+
12
+ // Connect to MongoDB
13
+ connectDB();
14
+
15
+ const app = express();
16
+
17
+ // Body Parser Middleware
18
+ app.use(express.json());
19
+ app.use(express.urlencoded({ extended: true }));
20
+
21
+ // CORS configuration (allow client requests)
22
+ app.use(
23
+ cors({
24
+ origin: '*',
25
+ credentials: true,
26
+ })
27
+ );
28
+
29
+ // Express Session Configuration (For EJS Admin Portal)
30
+ app.use(
31
+ session({
32
+ secret: process.env.SESSION_SECRET || 'mern_exam_session_secret_key_12345',
33
+ resave: false,
34
+ saveUninitialized: false,
35
+ cookie: {
36
+ httpOnly: true,
37
+ maxAge: 24 * 60 * 60 * 1000, // 1 day
38
+ },
39
+ })
40
+ );
41
+
42
+ // EJS View Engine Setup
43
+ app.set('view engine', 'ejs');
44
+ app.set('views', path.join(__dirname, 'views'));
45
+
46
+ // Static Folders
47
+ app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
48
+ app.use(express.static(path.join(__dirname, 'public')));
49
+
50
+ // API Routes
51
+ app.use('/api/auth', require('./routes/authRoutes'));
52
+ app.use('/api/resources', require('./routes/resourceRoutes'));
53
+
54
+ // EJS Admin Routes
55
+ app.use('/admin', require('./routes/adminRoutes'));
56
+
57
+ // Health check route
58
+ app.get('/api/health', (req, res) => {
59
+ res.status(200).json({
60
+ status: 'ok',
61
+ timestamp: new Date().toISOString(),
62
+ uptime: process.uptime(),
63
+ });
64
+ });
65
+
66
+ // Root route redirect / info
67
+ app.get('/', (req, res) => {
68
+ res.send(`
69
+ <div style="font-family: sans-serif; padding: 2rem; max-width: 600px; margin: 0 auto; text-align: center;">
70
+ <h2>🚀 MERN Template Backend Server Running</h2>
71
+ <p style="color: #64748b; margin: 1rem 0;">Exam-ready architecture with JWT API & EJS Admin portal.</p>
72
+ <div style="display: flex; gap: 1rem; justify-content: center; margin-top: 1.5rem;">
73
+ <a href="/admin/login" style="padding: 0.6rem 1.2rem; background: #4f46e5; color: white; text-decoration: none; border-radius: 6px; font-weight: 500;">Admin EJS Panel</a>
74
+ <a href="/api/health" style="padding: 0.6rem 1.2rem; background: #0f172a; color: white; text-decoration: none; border-radius: 6px; font-weight: 500;">API Health</a>
75
+ </div>
76
+ </div>
77
+ `);
78
+ });
79
+
80
+ // Error handling middleware
81
+ app.use(notFound);
82
+ app.use(errorHandler);
83
+
84
+ const PORT = process.env.PORT || 5000;
85
+
86
+ app.listen(PORT, () => {
87
+ console.log(`[Express Server] Server running on http://localhost:${PORT}`);
88
+ console.log(`[EJS Admin Portal] Admin portal at http://localhost:${PORT}/admin/login`);
89
+ });
@@ -0,0 +1,18 @@
1
+ const jwt = require('jsonwebtoken');
2
+
3
+ const generateToken = (user) => {
4
+ return jwt.sign(
5
+ {
6
+ id: user._id,
7
+ role: user.role,
8
+ },
9
+ process.env.JWT_SECRET || 'fallback_jwt_secret_for_exam',
10
+ {
11
+ expiresIn: '1d',
12
+ }
13
+ );
14
+ };
15
+
16
+ module.exports = {
17
+ generateToken,
18
+ };
@@ -0,0 +1,128 @@
1
+ <%- include('partials/header', { title: 'Admin Dashboard - MERN Exam' }) %>
2
+
3
+ <h2 style="margin-bottom: 1.5rem;">System Overview & Statistics</h2>
4
+
5
+ <% if (typeof message !== 'undefined' && message) { %>
6
+ <div class="alert alert-success"><%= message %></div>
7
+ <% } %>
8
+
9
+ <!-- Stats Grid -->
10
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem;">
11
+ <div class="card" style="margin-bottom: 0; text-align: center;">
12
+ <p style="color: var(--text-muted); font-size: 0.85rem;">Total Users</p>
13
+ <h3 style="font-size: 1.8rem; color: var(--primary);"><%= stats.totalUsers %></h3>
14
+ </div>
15
+ <div class="card" style="margin-bottom: 0; text-align: center;">
16
+ <p style="color: var(--text-muted); font-size: 0.85rem;">Students</p>
17
+ <h3 style="font-size: 1.8rem; color: #10b981;"><%= stats.totalStudents %></h3>
18
+ </div>
19
+ <div class="card" style="margin-bottom: 0; text-align: center;">
20
+ <p style="color: var(--text-muted); font-size: 0.85rem;">Admins</p>
21
+ <h3 style="font-size: 1.8rem; color: #6366f1;"><%= stats.totalAdmins %></h3>
22
+ </div>
23
+ <div class="card" style="margin-bottom: 0; text-align: center;">
24
+ <p style="color: var(--text-muted); font-size: 0.85rem;">Total Resources</p>
25
+ <h3 style="font-size: 1.8rem; color: #f59e0b;"><%= stats.totalResources %></h3>
26
+ </div>
27
+ </div>
28
+
29
+ <!-- Users Management Table -->
30
+ <div class="card">
31
+ <h3>Registered Users</h3>
32
+ <p style="color: var(--text-muted); font-size: 0.85rem; margin-bottom: 1rem;">
33
+ Demonstrates EJS looping, conditionals, and server-side state toggling.
34
+ </p>
35
+ <table>
36
+ <thead>
37
+ <tr>
38
+ <th>Name</th>
39
+ <th>Email</th>
40
+ <th>Role</th>
41
+ <th>Status</th>
42
+ <th>Joined</th>
43
+ <th>Actions</th>
44
+ </tr>
45
+ </thead>
46
+ <tbody>
47
+ <% if (users.length === 0) { %>
48
+ <tr>
49
+ <td colspan="6" style="text-align: center; color: var(--text-muted);">No users registered yet.</td>
50
+ </tr>
51
+ <% } else { %>
52
+ <% users.forEach(function(u) { %>
53
+ <tr>
54
+ <td><strong><%= u.name %></strong></td>
55
+ <td><%= u.email %></td>
56
+ <td>
57
+ <span class="badge <%= u.role === 'admin' ? 'badge-admin' : 'badge-student' %>">
58
+ <%= u.role %>
59
+ </span>
60
+ </td>
61
+ <td>
62
+ <span class="badge <%= u.isActive ? 'badge-active' : 'badge-inactive' %>">
63
+ <%= u.isActive ? 'Active' : 'Inactive' %>
64
+ </span>
65
+ </td>
66
+ <td><%= new Date(u.createdAt).toLocaleDateString() %></td>
67
+ <td>
68
+ <form action="/admin/users/<%= u._id %>/toggle" method="POST" style="display:inline;">
69
+ <button type="submit" class="btn btn-sm <%= u.isActive ? 'btn-danger' : 'btn-primary' %>">
70
+ <%= u.isActive ? 'Deactivate' : 'Activate' %>
71
+ </button>
72
+ </form>
73
+ </td>
74
+ </tr>
75
+ <% }); %>
76
+ <% } %>
77
+ </tbody>
78
+ </table>
79
+ </div>
80
+
81
+ <!-- Resources Table -->
82
+ <div class="card">
83
+ <h3>Created Resources</h3>
84
+ <table>
85
+ <thead>
86
+ <tr>
87
+ <th>Title</th>
88
+ <th>Category</th>
89
+ <th>Created By</th>
90
+ <th>File Attachment</th>
91
+ <th>Created At</th>
92
+ <th>Actions</th>
93
+ </tr>
94
+ </thead>
95
+ <tbody>
96
+ <% if (resources.length === 0) { %>
97
+ <tr>
98
+ <td colspan="6" style="text-align: center; color: var(--text-muted);">No resources found.</td>
99
+ </tr>
100
+ <% } else { %>
101
+ <% resources.forEach(function(r) { %>
102
+ <tr>
103
+ <td><strong><%= r.title %></strong></td>
104
+ <td><%= r.category %></td>
105
+ <td><%= r.createdBy ? r.createdBy.name : 'Unknown' %></td>
106
+ <td>
107
+ <% if (r.fileUrl) { %>
108
+ <a href="/api/resources/<%= r._id %>/download" target="_blank" style="color: var(--primary);">
109
+ 📥 <%= r.fileName || 'Download' %>
110
+ </a>
111
+ <% } else { %>
112
+ <span style="color: var(--text-muted);">None</span>
113
+ <% } %>
114
+ </td>
115
+ <td><%= new Date(r.createdAt).toLocaleDateString() %></td>
116
+ <td>
117
+ <form action="/admin/resources/<%= r._id %>/delete" method="POST" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete this resource?');">
118
+ <button type="submit" class="btn btn-danger btn-sm">Delete</button>
119
+ </form>
120
+ </td>
121
+ </tr>
122
+ <% }); %>
123
+ <% } %>
124
+ </tbody>
125
+ </table>
126
+ </div>
127
+
128
+ <%- include('partials/footer') %>
@@ -0,0 +1,48 @@
1
+ <%- include('partials/header', { title: 'Admin Login - MERN Exam' }) %>
2
+
3
+ <div style="max-width: 420px; margin: 3rem auto;">
4
+ <div class="card">
5
+ <h2 style="margin-bottom: 0.5rem; text-align: center;">Admin Sign In</h2>
6
+ <p style="color: var(--text-muted); font-size: 0.875rem; text-align: center; margin-bottom: 1.5rem;">
7
+ Server-rendered session authentication demo
8
+ </p>
9
+
10
+ <% if (typeof error !== 'undefined' && error) { %>
11
+ <div class="alert alert-error"><%= error %></div>
12
+ <% } %>
13
+
14
+ <% if (typeof success !== 'undefined' && success) { %>
15
+ <div class="alert alert-success"><%= success %></div>
16
+ <% } %>
17
+
18
+ <form action="/admin/login" method="POST">
19
+ <div style="margin-bottom: 1rem;">
20
+ <label style="display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: 500;">Admin Email</label>
21
+ <input
22
+ type="email"
23
+ name="email"
24
+ required
25
+ placeholder="admin@example.com"
26
+ style="width: 100%; padding: 0.6rem; border: 1px solid var(--border); border-radius: 6px; font-size: 0.9rem;"
27
+ />
28
+ </div>
29
+
30
+ <div style="margin-bottom: 1.5rem;">
31
+ <label style="display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: 500;">Password</label>
32
+ <input
33
+ type="password"
34
+ name="password"
35
+ required
36
+ placeholder="••••••••"
37
+ style="width: 100%; padding: 0.6rem; border: 1px solid var(--border); border-radius: 6px; font-size: 0.9rem;"
38
+ />
39
+ </div>
40
+
41
+ <button type="submit" class="btn btn-primary" style="width: 100%; padding: 0.75rem;">
42
+ Log In to Admin Panel
43
+ </button>
44
+ </form>
45
+ </div>
46
+ </div>
47
+
48
+ <%- include('partials/footer') %>
@@ -0,0 +1,6 @@
1
+ </main>
2
+ <footer>
3
+ <p>MERN Stack Project Generator &bull; EJS Server-Side Rendered Admin Panel</p>
4
+ </footer>
5
+ </body>
6
+ </html>
@@ -0,0 +1,60 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= typeof title !== 'undefined' ? title : 'MERN Admin Portal' %></title>
7
+ <style>
8
+ :root {
9
+ --primary: #4f46e5;
10
+ --primary-hover: #4338ca;
11
+ --bg: #f8fafc;
12
+ --card-bg: #ffffff;
13
+ --text: #1e293b;
14
+ --text-muted: #64748b;
15
+ --border: #e2e8f0;
16
+ --danger: #ef4444;
17
+ --success: #10b981;
18
+ }
19
+ * { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
20
+ body { background-color: var(--bg); color: var(--text); min-height: 100vh; display: flex; flex-direction: column; }
21
+ header { background: #1e1e2e; color: #fff; padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; }
22
+ header h1 { font-size: 1.25rem; font-weight: 700; }
23
+ header .nav-links { display: flex; gap: 1rem; align-items: center; }
24
+ header a, header button { color: #fff; text-decoration: none; font-size: 0.9rem; }
25
+ .btn { padding: 0.5rem 1rem; border-radius: 6px; font-weight: 500; cursor: pointer; border: none; transition: 0.2s; }
26
+ .btn-primary { background: var(--primary); color: #fff; }
27
+ .btn-primary:hover { background: var(--primary-hover); }
28
+ .btn-danger { background: var(--danger); color: #fff; }
29
+ .btn-sm { padding: 0.25rem 0.5rem; font-size: 0.8rem; }
30
+ .container { max-width: 1100px; margin: 2rem auto; padding: 0 1rem; flex: 1; }
31
+ .card { background: var(--card-bg); border-radius: 8px; border: 1px solid var(--border); padding: 1.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.05); margin-bottom: 1.5rem; }
32
+ .alert { padding: 0.75rem 1rem; border-radius: 6px; margin-bottom: 1rem; font-size: 0.9rem; }
33
+ .alert-error { background: #fee2e2; color: #991b1b; border: 1px solid #f87171; }
34
+ .alert-success { background: #d1fae5; color: #065f46; border: 1px solid #34d399; }
35
+ table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
36
+ th, td { text-align: left; padding: 0.75rem; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
37
+ th { background: #f1f5f9; color: var(--text-muted); font-weight: 600; }
38
+ .badge { display: inline-block; padding: 0.2rem 0.5rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; }
39
+ .badge-admin { background: #e0e7ff; color: #4338ca; }
40
+ .badge-student { background: #f1f5f9; color: #475569; }
41
+ .badge-active { background: #d1fae5; color: #065f46; }
42
+ .badge-inactive { background: #fee2e2; color: #991b1b; }
43
+ footer { background: #fff; border-top: 1px solid var(--border); padding: 1rem; text-align: center; color: var(--text-muted); font-size: 0.85rem; margin-top: auto; }
44
+ </style>
45
+ </head>
46
+ <body>
47
+ <header>
48
+ <h1>âš¡ MERN Admin Portal (EJS Session Auth)</h1>
49
+ <div class="nav-links">
50
+ <% if (typeof adminUser !== 'undefined' && adminUser) { %>
51
+ <span>Logged in as: <strong><%= adminUser.name %></strong> (<%= adminUser.role %>)</span>
52
+ <form action="/admin/logout" method="POST" style="display:inline;">
53
+ <button type="submit" class="btn btn-danger btn-sm">Logout</button>
54
+ </form>
55
+ <% } else { %>
56
+ <a href="/admin/login">Admin Login</a>
57
+ <% } %>
58
+ </div>
59
+ </header>
60
+ <main class="container">