Skip to content

Commit a9c5528

Browse files
committed
Remove public registration flow and implement admin-only Add User panel
1 parent 1dd6885 commit a9c5528

File tree

4 files changed

+194
-16
lines changed

4 files changed

+194
-16
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import express from 'express';
22
import { registerUser, loginUser } from '../controllers/userController.js';
3+
import { authenticateToken, isAdmin } from '../middleware/authMiddleware.js';
34

45
const router = express.Router();
56

6-
router.post('/register', registerUser);
7+
router.post('/register', authenticateToken, isAdmin, registerUser);
78
router.post('/login', loginUser);
89

910
export default router;

Treasure_Hunt_Frontend/src/pages/admin/Admin.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import QuestionPanel from './components/QuestionPanel';
44
import TeamPanel from './components/TeamPanel';
55
import ResultPanel from './components/ResultPanel';
66
import ViewQuestions from './components/ViewQuestions';
7+
import AddUserPanel from './components/AddUserPanel';
78

89
/* ── SVG decorations ──────────────────────────────────────── */
910
const MapX = ({ size = 48, style = {} }) => (
@@ -53,6 +54,7 @@ const TAB_META = [
5354
{ id: 'View Questions', label: 'View Questions', icon: '📜', desc: 'Edit & remove clues' },
5455
{ id: 'Teams', label: 'Teams', icon: '🏴‍☠️', desc: 'Review submissions' },
5556
{ id: 'Results', label: 'Results', icon: '🏆', desc: 'Leaderboard & export' },
57+
{ id: 'Add User', label: 'Add User', icon: '🧑‍🤝‍🧑', desc: 'Manage players' },
5658
];
5759

5860
const Admin = () => {
@@ -163,6 +165,7 @@ const Admin = () => {
163165
{activeTab === 'View Questions' && <ViewQuestions />}
164166
{activeTab === 'Teams' && <TeamPanel />}
165167
{activeTab === 'Results' && <ResultPanel />}
168+
{activeTab === 'Add User' && <AddUserPanel />}
166169
</motion.div>
167170
</AnimatePresence>
168171
</div>
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { useState } from 'react';
2+
import { registerUser } from '../../../services/api';
3+
4+
const s = {
5+
label: {
6+
display: 'block',
7+
fontSize: '0.85rem',
8+
fontWeight: 800,
9+
color: 'var(--color-green)',
10+
fontFamily: 'var(--font-body)',
11+
marginBottom: '0.5rem',
12+
letterSpacing: '0.5px',
13+
textTransform: 'uppercase',
14+
},
15+
input: {
16+
width: '100%',
17+
padding: '0.75rem 1rem',
18+
borderRadius: '10px',
19+
border: '2.5px solid var(--color-green)',
20+
fontSize: '1rem',
21+
fontFamily: 'var(--font-body)',
22+
background: 'var(--color-bg-primary)',
23+
color: 'var(--color-text-primary)',
24+
outline: 'none',
25+
boxSizing: 'border-box',
26+
transition: 'border-color 0.2s',
27+
},
28+
select: {
29+
width: '100%',
30+
padding: '0.75rem 1rem',
31+
borderRadius: '10px',
32+
border: '2.5px solid var(--color-green)',
33+
fontSize: '1rem',
34+
fontFamily: 'var(--font-body)',
35+
background: 'var(--color-bg-primary)',
36+
color: 'var(--color-text-primary)',
37+
outline: 'none',
38+
boxSizing: 'border-box',
39+
transition: 'border-color 0.2s',
40+
},
41+
successBar: {
42+
padding: '0.75rem 1rem',
43+
borderRadius: '10px',
44+
border: '2px solid #2e7d32',
45+
background: '#e8f5e9',
46+
color: '#2e7d32',
47+
fontWeight: 700,
48+
fontSize: '0.9rem',
49+
marginBottom: '1rem',
50+
},
51+
errorBar: {
52+
padding: '0.75rem 1rem',
53+
borderRadius: '10px',
54+
border: '2px solid var(--color-red)',
55+
background: '#ffebee',
56+
color: 'var(--color-red)',
57+
fontWeight: 700,
58+
fontSize: '0.9rem',
59+
marginBottom: '1rem',
60+
},
61+
};
62+
63+
const AddUserPanel = () => {
64+
const [formData, setFormData] = useState({
65+
username: '',
66+
password: '',
67+
role: 'participant',
68+
});
69+
const [error, setError] = useState('');
70+
const [success, setSuccess] = useState('');
71+
const [loading, setLoading] = useState(false);
72+
73+
const handleSubmit = async (e) => {
74+
e.preventDefault();
75+
setError('');
76+
setSuccess('');
77+
if (!formData.username || !formData.password) {
78+
setError('Username and password are required');
79+
return;
80+
}
81+
setLoading(true);
82+
83+
try {
84+
// registerUser handles the standard POST /users/register call from frontend
85+
// which now requires a Bearer token (supplied by interceptor)
86+
const response = await registerUser(formData);
87+
if (response.success) {
88+
setSuccess('✅ User account created successfully!');
89+
setFormData({ ...formData, username: '', password: '' });
90+
} else {
91+
setError(response.message || 'Failed to create user');
92+
}
93+
} catch {
94+
setError('An error occurred while creating the user account');
95+
} finally {
96+
setLoading(false);
97+
}
98+
};
99+
100+
return (
101+
<div style={{ maxWidth: 720, margin: '0 auto' }}>
102+
{/* Section heading */}
103+
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: '1.5rem' }}>
104+
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
105+
<rect x="2" y="2" width="24" height="24" rx="6" fill="var(--color-bg-secondary)" stroke="var(--color-green)" strokeWidth="2.5" />
106+
<path d="M14 13C16.2 13 18 11.2 18 9C18 6.8 16.2 5 14 5C11.8 5 10 6.8 10 9C10 11.2 11.8 13 14 13ZM14 15C11 15 5 16.5 5 19.5V21H23V19.5C23 16.5 17 15 14 15Z" fill="var(--color-green)" />
107+
</svg>
108+
<h2 style={{ fontFamily: 'var(--font-heading)', fontSize: '1.5rem', color: 'var(--color-green)', margin: 0 }}>
109+
Create New Participant / Admin
110+
</h2>
111+
</div>
112+
113+
<div style={{ background: '#fff', padding: '2rem', borderRadius: '16px', boxShadow: '0 4px 6px rgba(0,0,0,0.05)' }}>
114+
{error && <div style={s.errorBar}>{error}</div>}
115+
{success && <div style={s.successBar}>{success}</div>}
116+
117+
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
118+
119+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.5rem' }}>
120+
<div>
121+
<label style={s.label}>Username</label>
122+
<input
123+
type="text"
124+
value={formData.username}
125+
onChange={e => setFormData({ ...formData, username: e.target.value })}
126+
placeholder="e.g. thecluemafia"
127+
style={s.input}
128+
/>
129+
</div>
130+
131+
<div>
132+
<label style={s.label}>Password</label>
133+
<input
134+
type="text"
135+
value={formData.password}
136+
onChange={e => setFormData({ ...formData, password: e.target.value })}
137+
placeholder="Passcode..."
138+
style={s.input}
139+
/>
140+
</div>
141+
</div>
142+
143+
<div>
144+
<label style={s.label}>Account Role</label>
145+
<select
146+
value={formData.role}
147+
onChange={e => setFormData({ ...formData, role: e.target.value })}
148+
style={s.select}
149+
>
150+
<option value="participant">Participant</option>
151+
<option value="admin">Admin</option>
152+
</select>
153+
</div>
154+
155+
<div style={{ paddingTop: '1rem', borderTop: '2px dashed var(--color-bg-secondary)' }}>
156+
<button
157+
type="submit"
158+
disabled={loading}
159+
style={{
160+
width: '100%',
161+
padding: '1rem',
162+
fontSize: '1.1rem',
163+
borderRadius: '12px',
164+
background: 'var(--color-red)',
165+
color: '#fff',
166+
fontWeight: 800,
167+
border: 'none',
168+
cursor: loading ? 'not-allowed' : 'pointer',
169+
opacity: loading ? 0.7 : 1,
170+
boxShadow: '4px 4px 0 var(--color-brown-dim)',
171+
transition: 'transform 0.1s',
172+
}}
173+
onMouseDown={e => { if (!loading) e.currentTarget.style.transform = 'translate(2px, 2px)' }}
174+
onMouseUp={e => { if (!loading) e.currentTarget.style.transform = 'none' }}
175+
onMouseLeave={e => { if (!loading) e.currentTarget.style.transform = 'none' }}
176+
>
177+
{loading ? 'CREATING...' : 'CREATE ACCOUNT'}
178+
</button>
179+
</div>
180+
</form>
181+
</div>
182+
</div>
183+
);
184+
};
185+
186+
export default AddUserPanel;

Treasure_Hunt_Frontend/src/pages/login/Login.jsx

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { motion, AnimatePresence } from "framer-motion";
44
import { loginUser, registerUser } from "../../services/api";
55

66
const Login = () => {
7-
const [isLogin, setIsLogin] = useState(true);
87
const [username, setUsername] = useState("");
98
const [password, setPassword] = useState("");
109
const [role, setRole] = useState("participant");
@@ -17,9 +16,7 @@ const Login = () => {
1716
setLoading(true);
1817
setError("");
1918
try {
20-
const response = isLogin
21-
? await loginUser({ username, password })
22-
: await registerUser({ username, password, role });
19+
const response = await loginUser({ username, password });
2320
if (response.success) {
2421
localStorage.setItem("token", response.token);
2522
localStorage.setItem("userRole", response.user.role);
@@ -72,7 +69,7 @@ const Login = () => {
7269
>
7370
<div className="flex-col-center" style={{ marginBottom: '1.5rem', textAlign: 'center' }}>
7471
<h2 style={{ fontSize: '2.2rem', lineHeight: 1.1, marginBottom: '0.4rem' }}>
75-
{isLogin ? "WELCOME EXPLORER" : "ENLIST NOW"}
72+
WELCOME EXPLORER
7673
</h2>
7774
<p style={{ fontSize: '1rem', fontWeight: 600, color: 'var(--color-green-light)', margin: 0 }}>
7875
Are you up for the challenge?
@@ -129,19 +126,10 @@ const Login = () => {
129126
style={{ padding: '1rem', borderRadius: '12px', border: '3px solid var(--color-green)', fontSize: '1.1rem', fontWeight: 'bold', outline: 'none', background: 'var(--color-bg-primary)', color: 'var(--color-green)' }}
130127
/>
131128
<button type="submit" disabled={loading} style={{ marginTop: '0.8rem', fontSize: '1.2rem', padding: '1rem' }}>
132-
{loading ? "VERIFYING..." : (isLogin ? "START ADVENTURE" : "JOIN CREW")}
129+
{loading ? "VERIFYING..." : "START ADVENTURE"}
133130
</button>
134131
</form>
135132

136-
<div style={{ textAlign: 'center', marginTop: '1.5rem' }}>
137-
<span
138-
onClick={() => { setIsLogin(!isLogin); setError(""); }}
139-
style={{ color: 'var(--color-brown)', fontWeight: 800, cursor: 'pointer', fontSize: '1rem', textDecoration: 'underline' }}
140-
>
141-
{isLogin ? "New Explorer? Enlist Here!" : "Already Enlisted? Resume Journey!"}
142-
</span>
143-
</div>
144-
145133
</motion.div>
146134
</div>
147135
);

0 commit comments

Comments
 (0)