mern-template-tool 1.0.0 → 1.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mern-template-tool",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Exam-oriented MERN stack project generator with JWT, sessions, EJS, React Context, Multer, and generic CRUD",
5
5
  "main": "bin/index.js",
6
6
  "bin": {
@@ -2,11 +2,8 @@ import React from 'react';
2
2
 
3
3
  const Loading = ({ message = 'Loading...' }) => {
4
4
  return (
5
- <div className="spinner-container">
6
- <div style={{ textAlign: 'center' }}>
7
- <div className="spinner" style={{ margin: '0 auto 1rem auto' }}></div>
8
- <p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem' }}>{message}</p>
9
- </div>
5
+ <div>
6
+ <p><em>{message}</em></p>
10
7
  </div>
11
8
  );
12
9
  };
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Link, NavLink, useNavigate } from 'react-router-dom';
2
+ import { Link, useNavigate } from 'react-router-dom';
3
3
  import { useAuth } from '../context/AuthContext';
4
4
 
5
5
  const Navbar = () => {
@@ -12,45 +12,36 @@ const Navbar = () => {
12
12
  };
13
13
 
14
14
  return (
15
- <nav className="navbar">
16
- <Link to="/" className="nav-brand">
17
- 🚀 MERN App
18
- </Link>
19
-
20
- <div className="nav-links">
21
- <NavLink to="/resources">Resources</NavLink>
22
-
15
+ <nav>
16
+ <h1>MERN Application</h1>
17
+ <div>
18
+ <Link to="/resources">Resources</Link>
19
+ {' | '}
23
20
  {isAuthenticated ? (
24
21
  <>
25
- <NavLink to="/dashboard">Dashboard</NavLink>
26
- <NavLink to="/resources/new">Create Resource</NavLink>
27
- <NavLink to="/profile">Profile ({user?.name})</NavLink>
28
- <button
29
- onClick={handleLogout}
30
- className="btn btn-secondary btn-sm"
31
- style={{ marginLeft: '0.5rem' }}
32
- >
22
+ <Link to="/dashboard">Dashboard</Link>
23
+ {' | '}
24
+ <Link to="/resources/new">Create Resource</Link>
25
+ {' | '}
26
+ <Link to="/profile">Profile ({user?.name})</Link>
27
+ {' | '}
28
+ <button type="button" onClick={handleLogout}>
33
29
  Logout
34
30
  </button>
35
31
  </>
36
32
  ) : (
37
33
  <>
38
- <NavLink to="/login">Login</NavLink>
39
- <NavLink to="/register" className="btn btn-primary btn-sm">
40
- Register
41
- </NavLink>
34
+ <Link to="/login">Login</Link>
35
+ {' | '}
36
+ <Link to="/register">Register</Link>
42
37
  </>
43
38
  )}
44
-
45
- <a
46
- href="http://localhost:5000/admin/login"
47
- target="_blank"
48
- rel="noreferrer"
49
- style={{ fontSize: '0.8rem', color: '#a5b4fc', borderLeft: '1px solid #334155', paddingLeft: '0.75rem' }}
50
- >
51
- Admin Portal (EJS) &rarr;
39
+ {' | '}
40
+ <a href="http://localhost:5000/admin/login" target="_blank" rel="noreferrer">
41
+ Admin Portal (EJS)
52
42
  </a>
53
43
  </div>
44
+ <hr />
54
45
  </nav>
55
46
  );
56
47
  };
@@ -1,244 +1 @@
1
- /* Base CSS Reset and Variables */
2
- :root {
3
- --primary: #6366f1;
4
- --primary-hover: #4f46e5;
5
- --primary-light: #eef2ff;
6
- --bg-color: #0f172a;
7
- --card-bg: #1e293b;
8
- --card-border: #334155;
9
- --text-primary: #f8fafc;
10
- --text-secondary: #94a3b8;
11
- --danger: #ef4444;
12
- --success: #10b981;
13
- --warning: #f59e0b;
14
- --radius: 8px;
15
- }
16
-
17
- * {
18
- box-sizing: border-box;
19
- margin: 0;
20
- padding: 0;
21
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
22
- }
23
-
24
- body {
25
- background-color: var(--bg-color);
26
- color: var(--text-primary);
27
- min-height: 100vh;
28
- display: flex;
29
- flex-direction: column;
30
- }
31
-
32
- #root {
33
- min-height: 100vh;
34
- display: flex;
35
- flex-direction: column;
36
- }
37
-
38
- .container {
39
- max-width: 1100px;
40
- margin: 0 auto;
41
- padding: 2rem 1rem;
42
- flex: 1;
43
- width: 100%;
44
- }
45
-
46
- /* Typography */
47
- h1, h2, h3, h4 {
48
- color: var(--text-primary);
49
- font-weight: 700;
50
- margin-bottom: 0.75rem;
51
- }
52
-
53
- p {
54
- color: var(--text-secondary);
55
- line-height: 1.6;
56
- }
57
-
58
- /* Navigation */
59
- .navbar {
60
- background-color: var(--card-bg);
61
- border-bottom: 1px solid var(--card-border);
62
- padding: 1rem 2rem;
63
- display: flex;
64
- justify-content: space-between;
65
- align-items: center;
66
- }
67
-
68
- .nav-brand {
69
- font-size: 1.25rem;
70
- font-weight: 700;
71
- color: #fff;
72
- text-decoration: none;
73
- display: flex;
74
- align-items: center;
75
- gap: 0.5rem;
76
- }
77
-
78
- .nav-links {
79
- display: flex;
80
- align-items: center;
81
- gap: 1.25rem;
82
- }
83
-
84
- .nav-links a {
85
- color: var(--text-secondary);
86
- text-decoration: none;
87
- font-weight: 500;
88
- font-size: 0.95rem;
89
- transition: color 0.2s;
90
- }
91
-
92
- .nav-links a:hover,
93
- .nav-links a.active {
94
- color: #fff;
95
- }
96
-
97
- /* Cards & Layout */
98
- .card {
99
- background-color: var(--card-bg);
100
- border: 1px solid var(--card-border);
101
- border-radius: var(--radius);
102
- padding: 1.5rem;
103
- margin-bottom: 1.5rem;
104
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
105
- }
106
-
107
- .grid-cols-3 {
108
- display: grid;
109
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
110
- gap: 1.5rem;
111
- }
112
-
113
- /* Buttons */
114
- .btn {
115
- display: inline-flex;
116
- align-items: center;
117
- justify-content: center;
118
- padding: 0.6rem 1.2rem;
119
- border-radius: var(--radius);
120
- font-weight: 600;
121
- font-size: 0.9rem;
122
- cursor: pointer;
123
- border: none;
124
- transition: all 0.2s ease;
125
- text-decoration: none;
126
- }
127
-
128
- .btn-primary {
129
- background-color: var(--primary);
130
- color: white;
131
- }
132
-
133
- .btn-primary:hover {
134
- background-color: var(--primary-hover);
135
- }
136
-
137
- .btn-secondary {
138
- background-color: transparent;
139
- border: 1px solid var(--card-border);
140
- color: var(--text-primary);
141
- }
142
-
143
- .btn-secondary:hover {
144
- background-color: var(--card-border);
145
- }
146
-
147
- .btn-danger {
148
- background-color: var(--danger);
149
- color: white;
150
- }
151
-
152
- .btn-danger:hover {
153
- opacity: 0.9;
154
- }
155
-
156
- .btn-sm {
157
- padding: 0.35rem 0.75rem;
158
- font-size: 0.8rem;
159
- }
160
-
161
- /* Forms */
162
- .form-group {
163
- margin-bottom: 1.25rem;
164
- }
165
-
166
- .form-group label {
167
- display: block;
168
- font-size: 0.875rem;
169
- font-weight: 500;
170
- color: var(--text-secondary);
171
- margin-bottom: 0.5rem;
172
- }
173
-
174
- .form-control {
175
- width: 100%;
176
- padding: 0.7rem 0.9rem;
177
- background-color: #0b1120;
178
- border: 1px solid var(--card-border);
179
- border-radius: var(--radius);
180
- color: var(--text-primary);
181
- font-size: 0.95rem;
182
- outline: none;
183
- transition: border-color 0.2s;
184
- }
185
-
186
- .form-control:focus {
187
- border-color: var(--primary);
188
- }
189
-
190
- /* Alerts */
191
- .alert {
192
- padding: 0.8rem 1rem;
193
- border-radius: var(--radius);
194
- font-size: 0.9rem;
195
- margin-bottom: 1.25rem;
196
- }
197
-
198
- .alert-error {
199
- background-color: rgba(239, 68, 68, 0.15);
200
- border: 1px solid var(--danger);
201
- color: #fca5a5;
202
- }
203
-
204
- .alert-success {
205
- background-color: rgba(16, 185, 129, 0.15);
206
- border: 1px solid var(--success);
207
- color: #6ee7b7;
208
- }
209
-
210
- /* Badges */
211
- .badge {
212
- display: inline-block;
213
- padding: 0.2rem 0.6rem;
214
- border-radius: 9999px;
215
- font-size: 0.75rem;
216
- font-weight: 600;
217
- text-transform: uppercase;
218
- }
219
-
220
- .badge-primary {
221
- background: rgba(99, 102, 241, 0.2);
222
- color: #a5b4fc;
223
- }
224
-
225
- /* Spinner */
226
- .spinner-container {
227
- display: flex;
228
- justify-content: center;
229
- align-items: center;
230
- padding: 3rem;
231
- }
232
-
233
- .spinner {
234
- width: 40px;
235
- height: 40px;
236
- border: 4px solid var(--card-border);
237
- border-top-color: var(--primary);
238
- border-radius: 50%;
239
- animation: spin 0.8s linear infinite;
240
- }
241
-
242
- @keyframes spin {
243
- to { transform: rotate(360deg); }
244
- }
1
+ /* Pure HTML - No CSS styles applied */
@@ -24,7 +24,6 @@ const CreateResource = () => {
24
24
  setError(null);
25
25
 
26
26
  try {
27
- // Use FormData to support multipart/form-data with file upload
28
27
  const formData = new FormData();
29
28
  formData.append('title', title);
30
29
  formData.append('description', description);
@@ -49,92 +48,71 @@ const CreateResource = () => {
49
48
  };
50
49
 
51
50
  return (
52
- <div className="container" style={{ maxWidth: '650px' }}>
53
- <div className="card">
54
- <h2>Create New Resource</h2>
55
- <p style={{ marginBottom: '1.5rem', fontSize: '0.9rem' }}>
56
- Demonstrates Multer file upload, JWT-protected POST endpoint, and validation.
57
- </p>
51
+ <div>
52
+ <h2>Create New Resource</h2>
53
+ <p>Demonstrates generic CRUD entity creation with Multer file upload</p>
58
54
 
59
- {error && <div className="alert alert-error">{error}</div>}
55
+ {error && <p><strong>Error: {error}</strong></p>}
60
56
 
61
- <form onSubmit={handleSubmit}>
62
- <div className="form-group">
63
- <label htmlFor="title">Title *</label>
64
- <input
65
- id="title"
66
- type="text"
67
- required
68
- className="form-control"
69
- placeholder="e.g. Node.js Architecture Notes"
70
- value={title}
71
- onChange={(e) => setTitle(e.target.value)}
72
- />
73
- </div>
74
-
75
- <div className="form-group">
76
- <label htmlFor="category">Category</label>
77
- <select
78
- id="category"
79
- className="form-control"
80
- value={category}
81
- onChange={(e) => setCategory(e.target.value)}
82
- >
83
- <option value="General">General</option>
84
- <option value="Documentation">Documentation</option>
85
- <option value="Assignment">Assignment</option>
86
- <option value="Project">Project</option>
87
- <option value="Exam">Exam</option>
88
- </select>
89
- </div>
90
-
91
- <div className="form-group">
92
- <label htmlFor="description">Description</label>
93
- <textarea
94
- id="description"
95
- rows={4}
96
- className="form-control"
97
- placeholder="Provide context or details about this item..."
98
- value={description}
99
- onChange={(e) => setDescription(e.target.value)}
100
- />
101
- </div>
102
-
103
- <div className="form-group">
104
- <label htmlFor="file">File Upload (Optional - PDF, Images, DOCX, max 10MB)</label>
105
- <input
106
- id="file"
107
- type="file"
108
- className="form-control"
109
- onChange={handleFileChange}
110
- accept=".pdf,.png,.jpg,.jpeg,.doc,.docx"
111
- />
112
- {file && (
113
- <small style={{ display: 'block', marginTop: '0.4rem', color: 'var(--text-secondary)' }}>
114
- Selected: {file.name} ({(file.size / 1024).toFixed(1)} KB)
115
- </small>
116
- )}
117
- </div>
118
-
119
- <div style={{ display: 'flex', gap: '1rem', marginTop: '1.5rem' }}>
120
- <button
121
- type="submit"
122
- className="btn btn-primary"
123
- disabled={loading}
124
- style={{ flex: 1 }}
125
- >
126
- {loading ? 'Creating...' : 'Create Resource'}
127
- </button>
128
- <button
129
- type="button"
130
- onClick={() => navigate('/resources')}
131
- className="btn btn-secondary"
132
- >
133
- Cancel
134
- </button>
135
- </div>
136
- </form>
137
- </div>
57
+ <form onSubmit={handleSubmit}>
58
+ <div>
59
+ <label htmlFor="title">Title *: </label>
60
+ <input
61
+ id="title"
62
+ type="text"
63
+ required
64
+ placeholder="Resource Title"
65
+ value={title}
66
+ onChange={(e) => setTitle(e.target.value)}
67
+ />
68
+ </div>
69
+ <br />
70
+ <div>
71
+ <label htmlFor="category">Category: </label>
72
+ <select
73
+ id="category"
74
+ value={category}
75
+ onChange={(e) => setCategory(e.target.value)}
76
+ >
77
+ <option value="General">General</option>
78
+ <option value="Documentation">Documentation</option>
79
+ <option value="Assignment">Assignment</option>
80
+ <option value="Project">Project</option>
81
+ <option value="Exam">Exam</option>
82
+ </select>
83
+ </div>
84
+ <br />
85
+ <div>
86
+ <label htmlFor="description">Description: </label>
87
+ <br />
88
+ <textarea
89
+ id="description"
90
+ rows={4}
91
+ cols={50}
92
+ placeholder="Write details..."
93
+ value={description}
94
+ onChange={(e) => setDescription(e.target.value)}
95
+ />
96
+ </div>
97
+ <br />
98
+ <div>
99
+ <label htmlFor="file">File Upload (Optional): </label>
100
+ <input
101
+ id="file"
102
+ type="file"
103
+ onChange={handleFileChange}
104
+ accept=".pdf,.png,.jpg,.jpeg,.doc,.docx"
105
+ />
106
+ </div>
107
+ <br />
108
+ <button type="submit" disabled={loading}>
109
+ {loading ? 'Creating...' : 'Create Resource'}
110
+ </button>
111
+ {' '}
112
+ <button type="button" onClick={() => navigate('/resources')}>
113
+ Cancel
114
+ </button>
115
+ </form>
138
116
  </div>
139
117
  );
140
118
  };
@@ -14,7 +14,6 @@ const Dashboard = () => {
14
14
  try {
15
15
  const response = await api.getResources();
16
16
  if (response.success) {
17
- // Filter resources created by current user
18
17
  const myResources = response.data.filter(
19
18
  (r) => r.createdBy && (r.createdBy._id === user?.id || r.createdBy === user?.id)
20
19
  );
@@ -33,83 +32,43 @@ const Dashboard = () => {
33
32
  if (loading) return <Loading message="Loading dashboard..." />;
34
33
 
35
34
  return (
36
- <div className="container">
37
- <div
38
- style={{
39
- display: 'flex',
40
- justifyContent: 'space-between',
41
- alignItems: 'center',
42
- marginBottom: '2rem',
43
- }}
44
- >
45
- <div>
46
- <h2>Welcome, {user?.name}!</h2>
47
- <p>You are logged in with role: <span className="badge badge-primary">{user?.role}</span></p>
48
- </div>
49
- <Link to="/resources/new" className="btn btn-primary">
50
- + Create New Resource
51
- </Link>
52
- </div>
35
+ <div>
36
+ <h2>User Dashboard</h2>
37
+ <p>Welcome, <strong>{user?.name}</strong>!</p>
38
+ <p>Role: <strong>{user?.role}</strong> | Email: <strong>{user?.email}</strong></p>
39
+ <p>
40
+ <Link to="/resources/new">[ + Create New Resource ]</Link>
41
+ </p>
53
42
 
54
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', gap: '1rem', marginBottom: '2rem' }}>
55
- <div className="card">
56
- <p style={{ fontSize: '0.85rem' }}>Your Email</p>
57
- <h3 style={{ fontSize: '1.1rem', marginTop: '0.25rem' }}>{user?.email}</h3>
58
- </div>
59
- <div className="card">
60
- <p style={{ fontSize: '0.85rem' }}>Resources Created</p>
61
- <h3 style={{ fontSize: '1.8rem', color: 'var(--primary)', marginTop: '0.25rem' }}>
62
- {userResources.length}
63
- </h3>
64
- </div>
65
- <div className="card">
66
- <p style={{ fontSize: '0.85rem' }}>Account Status</p>
67
- <h3 style={{ fontSize: '1.2rem', color: 'var(--success)', marginTop: '0.25rem' }}>
68
- Active
69
- </h3>
70
- </div>
71
- </div>
43
+ <hr />
72
44
 
73
- <div className="card">
74
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
75
- <h3>Your Recent Items</h3>
76
- <Link to="/resources" style={{ color: 'var(--primary)', fontSize: '0.9rem', textDecoration: 'none' }}>
77
- View All Resources &rarr;
78
- </Link>
79
- </div>
80
-
81
- {userResources.length === 0 ? (
82
- <p style={{ padding: '1.5rem 0', textAlign: 'center' }}>
83
- You haven't created any resources yet. Click "+ Create New Resource" to add one!
84
- </p>
85
- ) : (
86
- <div className="grid-cols-3">
87
- {userResources.slice(0, 3).map((item) => (
88
- <div
89
- key={item._id}
90
- style={{
91
- background: 'rgba(255,255,255,0.03)',
92
- padding: '1rem',
93
- borderRadius: '6px',
94
- border: '1px solid var(--card-border)',
95
- }}
96
- >
97
- <h4>{item.title}</h4>
98
- <p style={{ fontSize: '0.85rem', margin: '0.5rem 0' }}>
99
- Category: <span className="badge badge-primary">{item.category}</span>
100
- </p>
101
- <Link
102
- to={`/resources/${item._id}`}
103
- className="btn btn-secondary btn-sm"
104
- style={{ marginTop: '0.5rem' }}
105
- >
106
- View Details
107
- </Link>
108
- </div>
45
+ <h3>Your Created Resources ({userResources.length})</h3>
46
+ {userResources.length === 0 ? (
47
+ <p>You haven't created any resources yet.</p>
48
+ ) : (
49
+ <table border="1" cellPadding="6" cellSpacing="0">
50
+ <thead>
51
+ <tr>
52
+ <th>Title</th>
53
+ <th>Category</th>
54
+ <th>Created At</th>
55
+ <th>Action</th>
56
+ </tr>
57
+ </thead>
58
+ <tbody>
59
+ {userResources.map((item) => (
60
+ <tr key={item._id}>
61
+ <td>{item.title}</td>
62
+ <td>{item.category}</td>
63
+ <td>{new Date(item.createdAt).toLocaleDateString()}</td>
64
+ <td>
65
+ <Link to={`/resources/${item._id}`}>View Details</Link>
66
+ </td>
67
+ </tr>
109
68
  ))}
110
- </div>
111
- )}
112
- </div>
69
+ </tbody>
70
+ </table>
71
+ )}
113
72
  </div>
114
73
  );
115
74
  };