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 +1 -1
- package/templates/client/src/components/Loading.jsx +2 -5
- package/templates/client/src/components/Navbar.jsx +20 -29
- package/templates/client/src/index.css +1 -244
- package/templates/client/src/pages/CreateResource.jsx +63 -85
- package/templates/client/src/pages/Dashboard.jsx +34 -75
- package/templates/client/src/pages/EditResource.jsx +64 -86
- package/templates/client/src/pages/Login.jsx +38 -52
- package/templates/client/src/pages/NotFound.jsx +4 -11
- package/templates/client/src/pages/Profile.jsx +10 -40
- package/templates/client/src/pages/Register.jsx +64 -80
- package/templates/client/src/pages/ResourceDetails.jsx +45 -82
- package/templates/client/src/pages/Resources.jsx +86 -122
- package/templates/client/src/routes/ProtectedRoute.jsx +3 -7
- package/templates/server/middleware/errorMiddleware.js +3 -3
- package/templates/server/server.js +9 -8
- package/templates/server/views/dashboard.ejs +99 -108
- package/templates/server/views/login.ejs +34 -40
- package/templates/server/views/partials/footer.ejs +2 -1
- package/templates/server/views/partials/header.ejs +6 -42
package/package.json
CHANGED
|
@@ -2,11 +2,8 @@ import React from 'react';
|
|
|
2
2
|
|
|
3
3
|
const Loading = ({ message = 'Loading...' }) => {
|
|
4
4
|
return (
|
|
5
|
-
<div
|
|
6
|
-
<
|
|
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,
|
|
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
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
<
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
</NavLink>
|
|
34
|
+
<Link to="/login">Login</Link>
|
|
35
|
+
{' | '}
|
|
36
|
+
<Link to="/register">Register</Link>
|
|
42
37
|
</>
|
|
43
38
|
)}
|
|
44
|
-
|
|
45
|
-
<a
|
|
46
|
-
|
|
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) →
|
|
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
|
-
/*
|
|
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
|
|
53
|
-
<
|
|
54
|
-
|
|
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
|
-
|
|
55
|
+
{error && <p><strong>Error: {error}</strong></p>}
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
{userResources.
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
</
|
|
111
|
-
|
|
112
|
-
|
|
69
|
+
</tbody>
|
|
70
|
+
</table>
|
|
71
|
+
)}
|
|
113
72
|
</div>
|
|
114
73
|
);
|
|
115
74
|
};
|