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
|
@@ -73,92 +73,70 @@ const EditResource = () => {
|
|
|
73
73
|
if (loading) return <Loading message="Loading resource..." />;
|
|
74
74
|
|
|
75
75
|
return (
|
|
76
|
-
<div
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
</
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
/>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
<div style={{ display: 'flex', gap: '1rem', marginTop: '1.5rem' }}>
|
|
144
|
-
<button
|
|
145
|
-
type="submit"
|
|
146
|
-
className="btn btn-primary"
|
|
147
|
-
disabled={saving}
|
|
148
|
-
style={{ flex: 1 }}
|
|
149
|
-
>
|
|
150
|
-
{saving ? 'Saving changes...' : 'Save Changes'}
|
|
151
|
-
</button>
|
|
152
|
-
<button
|
|
153
|
-
type="button"
|
|
154
|
-
onClick={() => navigate(`/resources/${id}`)}
|
|
155
|
-
className="btn btn-secondary"
|
|
156
|
-
>
|
|
157
|
-
Cancel
|
|
158
|
-
</button>
|
|
159
|
-
</div>
|
|
160
|
-
</form>
|
|
161
|
-
</div>
|
|
76
|
+
<div>
|
|
77
|
+
<h2>Edit Resource</h2>
|
|
78
|
+
<p>Modify resource details</p>
|
|
79
|
+
|
|
80
|
+
{error && <p><strong>Error: {error}</strong></p>}
|
|
81
|
+
|
|
82
|
+
<form onSubmit={handleSubmit}>
|
|
83
|
+
<div>
|
|
84
|
+
<label htmlFor="title">Title *: </label>
|
|
85
|
+
<input
|
|
86
|
+
id="title"
|
|
87
|
+
type="text"
|
|
88
|
+
required
|
|
89
|
+
value={title}
|
|
90
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
<br />
|
|
94
|
+
<div>
|
|
95
|
+
<label htmlFor="category">Category: </label>
|
|
96
|
+
<select
|
|
97
|
+
id="category"
|
|
98
|
+
value={category}
|
|
99
|
+
onChange={(e) => setCategory(e.target.value)}
|
|
100
|
+
>
|
|
101
|
+
<option value="General">General</option>
|
|
102
|
+
<option value="Documentation">Documentation</option>
|
|
103
|
+
<option value="Assignment">Assignment</option>
|
|
104
|
+
<option value="Project">Project</option>
|
|
105
|
+
<option value="Exam">Exam</option>
|
|
106
|
+
</select>
|
|
107
|
+
</div>
|
|
108
|
+
<br />
|
|
109
|
+
<div>
|
|
110
|
+
<label htmlFor="description">Description: </label>
|
|
111
|
+
<br />
|
|
112
|
+
<textarea
|
|
113
|
+
id="description"
|
|
114
|
+
rows={4}
|
|
115
|
+
cols={50}
|
|
116
|
+
value={description}
|
|
117
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
<br />
|
|
121
|
+
<div>
|
|
122
|
+
<label htmlFor="file">Replace Attached File (Optional): </label>
|
|
123
|
+
{existingFileName && <p>Current: <em>{existingFileName}</em></p>}
|
|
124
|
+
<input
|
|
125
|
+
id="file"
|
|
126
|
+
type="file"
|
|
127
|
+
onChange={handleFileChange}
|
|
128
|
+
accept=".pdf,.png,.jpg,.jpeg,.doc,.docx"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
<br />
|
|
132
|
+
<button type="submit" disabled={saving}>
|
|
133
|
+
{saving ? 'Saving...' : 'Save Changes'}
|
|
134
|
+
</button>
|
|
135
|
+
{' '}
|
|
136
|
+
<button type="button" onClick={() => navigate(`/resources/${id}`)}>
|
|
137
|
+
Cancel
|
|
138
|
+
</button>
|
|
139
|
+
</form>
|
|
162
140
|
</div>
|
|
163
141
|
);
|
|
164
142
|
};
|
|
@@ -39,61 +39,47 @@ const Login = () => {
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
|
-
<div
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
<p style={{ textAlign: 'center', marginBottom: '1.5rem', fontSize: '0.9rem' }}>
|
|
46
|
-
Sign in to access your MERN dashboard
|
|
47
|
-
</p>
|
|
42
|
+
<div>
|
|
43
|
+
<h2>User Login</h2>
|
|
44
|
+
<p>Sign in to access your MERN account</p>
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
{error && <p><strong>Error: {error}</strong></p>}
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
48
|
+
<form onSubmit={handleSubmit}>
|
|
49
|
+
<div>
|
|
50
|
+
<label htmlFor="email">Email Address: </label>
|
|
51
|
+
<input
|
|
52
|
+
id="email"
|
|
53
|
+
type="email"
|
|
54
|
+
name="email"
|
|
55
|
+
required
|
|
56
|
+
value={formData.email}
|
|
57
|
+
onChange={handleChange}
|
|
58
|
+
placeholder="user@example.com"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
<br />
|
|
62
|
+
<div>
|
|
63
|
+
<label htmlFor="password">Password: </label>
|
|
64
|
+
<input
|
|
65
|
+
id="password"
|
|
66
|
+
type="password"
|
|
67
|
+
name="password"
|
|
68
|
+
required
|
|
69
|
+
value={formData.password}
|
|
70
|
+
onChange={handleChange}
|
|
71
|
+
placeholder="password"
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
<br />
|
|
75
|
+
<button type="submit" disabled={loading}>
|
|
76
|
+
{loading ? 'Signing in...' : 'Sign In'}
|
|
77
|
+
</button>
|
|
78
|
+
</form>
|
|
65
79
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
id="password"
|
|
70
|
-
type="password"
|
|
71
|
-
name="password"
|
|
72
|
-
required
|
|
73
|
-
className="form-control"
|
|
74
|
-
value={formData.password}
|
|
75
|
-
onChange={handleChange}
|
|
76
|
-
placeholder="••••••••"
|
|
77
|
-
/>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
<button
|
|
81
|
-
type="submit"
|
|
82
|
-
className="btn btn-primary"
|
|
83
|
-
style={{ width: '100%', marginTop: '0.5rem' }}
|
|
84
|
-
disabled={loading}
|
|
85
|
-
>
|
|
86
|
-
{loading ? 'Signing in...' : 'Sign In'}
|
|
87
|
-
</button>
|
|
88
|
-
</form>
|
|
89
|
-
|
|
90
|
-
<p style={{ textAlign: 'center', marginTop: '1.5rem', fontSize: '0.9rem' }}>
|
|
91
|
-
Don't have an account?{' '}
|
|
92
|
-
<Link to="/register" style={{ color: 'var(--primary)' }}>
|
|
93
|
-
Register here
|
|
94
|
-
</Link>
|
|
95
|
-
</p>
|
|
96
|
-
</div>
|
|
80
|
+
<p>
|
|
81
|
+
Don't have an account? <Link to="/register">Register here</Link>
|
|
82
|
+
</p>
|
|
97
83
|
</div>
|
|
98
84
|
);
|
|
99
85
|
};
|
|
@@ -3,17 +3,10 @@ import { Link } from 'react-router-dom';
|
|
|
3
3
|
|
|
4
4
|
const NotFound = () => {
|
|
5
5
|
return (
|
|
6
|
-
<div
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<p style={{ margin: '1rem 0 2rem 0' }}>
|
|
11
|
-
The page you are looking for does not exist or has been moved.
|
|
12
|
-
</p>
|
|
13
|
-
<Link to="/" className="btn btn-primary">
|
|
14
|
-
Return to Home
|
|
15
|
-
</Link>
|
|
16
|
-
</div>
|
|
6
|
+
<div>
|
|
7
|
+
<h2>404 - Page Not Found</h2>
|
|
8
|
+
<p>The requested page does not exist.</p>
|
|
9
|
+
<Link to="/">← Return to Home</Link>
|
|
17
10
|
</div>
|
|
18
11
|
);
|
|
19
12
|
};
|
|
@@ -5,46 +5,16 @@ const Profile = () => {
|
|
|
5
5
|
const { user } = useAuth();
|
|
6
6
|
|
|
7
7
|
return (
|
|
8
|
-
<div
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
</
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<p style={{ fontSize: '1.1rem', fontWeight: '600', color: 'var(--text-primary)' }}>{user?.name}</p>
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
<div>
|
|
22
|
-
<label style={{ fontSize: '0.8rem', color: 'var(--text-secondary)' }}>Email Address</label>
|
|
23
|
-
<p style={{ fontSize: '1.1rem', fontWeight: '600', color: 'var(--text-primary)' }}>{user?.email}</p>
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
<div>
|
|
27
|
-
<label style={{ fontSize: '0.8rem', color: 'var(--text-secondary)' }}>Assigned Role</label>
|
|
28
|
-
<p style={{ marginTop: '0.25rem' }}>
|
|
29
|
-
<span className="badge badge-primary">{user?.role}</span>
|
|
30
|
-
</p>
|
|
31
|
-
</div>
|
|
32
|
-
|
|
33
|
-
<div>
|
|
34
|
-
<label style={{ fontSize: '0.8rem', color: 'var(--text-secondary)' }}>User ID (MongoDB ObjectId)</label>
|
|
35
|
-
<p style={{ fontFamily: 'monospace', fontSize: '0.9rem', color: 'var(--text-secondary)' }}>
|
|
36
|
-
{user?.id || user?._id}
|
|
37
|
-
</p>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<div>
|
|
41
|
-
<label style={{ fontSize: '0.8rem', color: 'var(--text-secondary)' }}>Account Created At</label>
|
|
42
|
-
<p style={{ fontSize: '0.9rem', color: 'var(--text-secondary)' }}>
|
|
43
|
-
{user?.createdAt ? new Date(user.createdAt).toLocaleString() : 'Active Session'}
|
|
44
|
-
</p>
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
8
|
+
<div>
|
|
9
|
+
<h2>User Profile</h2>
|
|
10
|
+
<p>Current authenticated user details (from GET /api/auth/me):</p>
|
|
11
|
+
<ul>
|
|
12
|
+
<li><strong>Full Name:</strong> {user?.name}</li>
|
|
13
|
+
<li><strong>Email:</strong> {user?.email}</li>
|
|
14
|
+
<li><strong>Role:</strong> {user?.role}</li>
|
|
15
|
+
<li><strong>User ID:</strong> {user?.id || user?._id}</li>
|
|
16
|
+
<li><strong>Created:</strong> {user?.createdAt ? new Date(user.createdAt).toLocaleString() : 'N/A'}</li>
|
|
17
|
+
</ul>
|
|
48
18
|
</div>
|
|
49
19
|
);
|
|
50
20
|
};
|
|
@@ -43,90 +43,74 @@ const Register = () => {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
|
-
<div
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
<p style={{ textAlign: 'center', marginBottom: '1.5rem', fontSize: '0.9rem' }}>
|
|
50
|
-
Register for student or admin privileges
|
|
51
|
-
</p>
|
|
46
|
+
<div>
|
|
47
|
+
<h2>User Registration</h2>
|
|
48
|
+
<p>Register as a student or admin</p>
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
{error && <p><strong>Error: {error}</strong></p>}
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
name="role"
|
|
104
|
-
className="form-control"
|
|
105
|
-
value={formData.role}
|
|
106
|
-
onChange={handleChange}
|
|
107
|
-
>
|
|
108
|
-
<option value="student">Student (Standard User)</option>
|
|
109
|
-
<option value="admin">Admin (Elevated Permissions)</option>
|
|
110
|
-
</select>
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
|
-
<button
|
|
114
|
-
type="submit"
|
|
115
|
-
className="btn btn-primary"
|
|
116
|
-
style={{ width: '100%', marginTop: '0.5rem' }}
|
|
117
|
-
disabled={loading}
|
|
52
|
+
<form onSubmit={handleSubmit}>
|
|
53
|
+
<div>
|
|
54
|
+
<label htmlFor="name">Full Name: </label>
|
|
55
|
+
<input
|
|
56
|
+
id="name"
|
|
57
|
+
type="text"
|
|
58
|
+
name="name"
|
|
59
|
+
required
|
|
60
|
+
value={formData.name}
|
|
61
|
+
onChange={handleChange}
|
|
62
|
+
placeholder="Your name"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
<br />
|
|
66
|
+
<div>
|
|
67
|
+
<label htmlFor="email">Email Address: </label>
|
|
68
|
+
<input
|
|
69
|
+
id="email"
|
|
70
|
+
type="email"
|
|
71
|
+
name="email"
|
|
72
|
+
required
|
|
73
|
+
value={formData.email}
|
|
74
|
+
onChange={handleChange}
|
|
75
|
+
placeholder="user@example.com"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
<br />
|
|
79
|
+
<div>
|
|
80
|
+
<label htmlFor="password">Password (min 6 characters): </label>
|
|
81
|
+
<input
|
|
82
|
+
id="password"
|
|
83
|
+
type="password"
|
|
84
|
+
name="password"
|
|
85
|
+
required
|
|
86
|
+
minLength={6}
|
|
87
|
+
value={formData.password}
|
|
88
|
+
onChange={handleChange}
|
|
89
|
+
placeholder="password"
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
<br />
|
|
93
|
+
<div>
|
|
94
|
+
<label htmlFor="role">Role: </label>
|
|
95
|
+
<select
|
|
96
|
+
id="role"
|
|
97
|
+
name="role"
|
|
98
|
+
value={formData.role}
|
|
99
|
+
onChange={handleChange}
|
|
118
100
|
>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
101
|
+
<option value="student">student</option>
|
|
102
|
+
<option value="admin">admin</option>
|
|
103
|
+
</select>
|
|
104
|
+
</div>
|
|
105
|
+
<br />
|
|
106
|
+
<button type="submit" disabled={loading}>
|
|
107
|
+
{loading ? 'Registering...' : 'Register'}
|
|
108
|
+
</button>
|
|
109
|
+
</form>
|
|
122
110
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
Login here
|
|
127
|
-
</Link>
|
|
128
|
-
</p>
|
|
129
|
-
</div>
|
|
111
|
+
<p>
|
|
112
|
+
Already have an account? <Link to="/login">Login here</Link>
|
|
113
|
+
</p>
|
|
130
114
|
</div>
|
|
131
115
|
);
|
|
132
116
|
};
|
|
@@ -49,14 +49,10 @@ const ResourceDetails = () => {
|
|
|
49
49
|
if (loading) return <Loading message="Loading resource details..." />;
|
|
50
50
|
if (error) {
|
|
51
51
|
return (
|
|
52
|
-
<div
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<Link to="/resources" className="btn btn-secondary" style={{ marginTop: '1rem' }}>
|
|
57
|
-
Back to Resources
|
|
58
|
-
</Link>
|
|
59
|
-
</div>
|
|
52
|
+
<div>
|
|
53
|
+
<h2>Error</h2>
|
|
54
|
+
<p>{error}</p>
|
|
55
|
+
<Link to="/resources">← Back to Resources</Link>
|
|
60
56
|
</div>
|
|
61
57
|
);
|
|
62
58
|
}
|
|
@@ -70,85 +66,52 @@ const ResourceDetails = () => {
|
|
|
70
66
|
const canModify = isOwner || isAdmin;
|
|
71
67
|
|
|
72
68
|
return (
|
|
73
|
-
<div
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
</
|
|
69
|
+
<div>
|
|
70
|
+
<p>
|
|
71
|
+
<Link to="/resources">← Back to all resources</Link>
|
|
72
|
+
</p>
|
|
77
73
|
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<h1 style={{ marginTop: '0.5rem', fontSize: '1.8rem' }}>{resource.title}</h1>
|
|
83
|
-
</div>
|
|
84
|
-
{canModify && (
|
|
85
|
-
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
|
86
|
-
<Link to={`/resources/${resource._id}/edit`} className="btn btn-secondary btn-sm">
|
|
87
|
-
Edit
|
|
88
|
-
</Link>
|
|
89
|
-
<button
|
|
90
|
-
onClick={handleDelete}
|
|
91
|
-
className="btn btn-danger btn-sm"
|
|
92
|
-
disabled={deleteLoading}
|
|
93
|
-
>
|
|
94
|
-
{deleteLoading ? 'Deleting...' : 'Delete'}
|
|
95
|
-
</button>
|
|
96
|
-
</div>
|
|
97
|
-
)}
|
|
98
|
-
</div>
|
|
74
|
+
<h2>{resource.title}</h2>
|
|
75
|
+
<p><strong>Category:</strong> {resource.category}</p>
|
|
76
|
+
<p><strong>Description:</strong></p>
|
|
77
|
+
<p>{resource.description || 'No description provided.'}</p>
|
|
99
78
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
79
|
+
{resource.fileUrl && (
|
|
80
|
+
<fieldset>
|
|
81
|
+
<legend>Attached File</legend>
|
|
82
|
+
<p>File Name: <strong>{resource.fileName || 'Attached file'}</strong></p>
|
|
83
|
+
{resource.fileSize && (
|
|
84
|
+
<p>File Size: {(resource.fileSize / 1024).toFixed(1)} KB</p>
|
|
85
|
+
)}
|
|
86
|
+
<p>
|
|
87
|
+
<a
|
|
88
|
+
href={`http://localhost:5000/api/resources/${resource._id}/download`}
|
|
89
|
+
target="_blank"
|
|
90
|
+
rel="noreferrer"
|
|
91
|
+
>
|
|
92
|
+
[ Download File ]
|
|
93
|
+
</a>
|
|
94
|
+
</p>
|
|
95
|
+
</fieldset>
|
|
96
|
+
)}
|
|
103
97
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
border: '1px solid var(--card-border)',
|
|
111
|
-
marginBottom: '1.5rem',
|
|
112
|
-
}}
|
|
113
|
-
>
|
|
114
|
-
<h4 style={{ marginBottom: '0.5rem' }}>Attached File:</h4>
|
|
115
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
116
|
-
<div>
|
|
117
|
-
<p style={{ color: '#fff', fontWeight: '500' }}>📄 {resource.fileName || 'Attached document'}</p>
|
|
118
|
-
{resource.fileSize && (
|
|
119
|
-
<p style={{ fontSize: '0.8rem' }}>Size: {(resource.fileSize / 1024).toFixed(1)} KB</p>
|
|
120
|
-
)}
|
|
121
|
-
</div>
|
|
122
|
-
<a
|
|
123
|
-
href={`http://localhost:5000/api/resources/${resource._id}/download`}
|
|
124
|
-
target="_blank"
|
|
125
|
-
rel="noreferrer"
|
|
126
|
-
className="btn btn-primary btn-sm"
|
|
127
|
-
>
|
|
128
|
-
Download File
|
|
129
|
-
</a>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
)}
|
|
98
|
+
<br />
|
|
99
|
+
<p>
|
|
100
|
+
<strong>Created by:</strong> {resource.createdBy?.name || 'Unknown'} ({resource.createdBy?.email})
|
|
101
|
+
{' | '}
|
|
102
|
+
<strong>Date:</strong> {new Date(resource.createdAt).toLocaleString()}
|
|
103
|
+
</p>
|
|
133
104
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}}
|
|
143
|
-
>
|
|
144
|
-
<div>
|
|
145
|
-
<span>Created by: <strong>{resource.createdBy?.name || 'Unknown'}</strong> ({resource.createdBy?.email})</span>
|
|
146
|
-
</div>
|
|
147
|
-
<div>
|
|
148
|
-
<span>Date: {new Date(resource.createdAt).toLocaleString()}</span>
|
|
149
|
-
</div>
|
|
105
|
+
{canModify && (
|
|
106
|
+
<div>
|
|
107
|
+
<hr />
|
|
108
|
+
<Link to={`/resources/${resource._id}/edit`}>[ Edit Resource ]</Link>
|
|
109
|
+
{' '}
|
|
110
|
+
<button type="button" onClick={handleDelete} disabled={deleteLoading}>
|
|
111
|
+
{deleteLoading ? 'Deleting...' : 'Delete Resource'}
|
|
112
|
+
</button>
|
|
150
113
|
</div>
|
|
151
|
-
|
|
114
|
+
)}
|
|
152
115
|
</div>
|
|
153
116
|
);
|
|
154
117
|
};
|