fix: profile page form pre-population and WSDC ID editing

- Add useEffect to pre-fill profile form with current user data
- Add WSDC ID field to profile edit form with numeric validation
- Update backend to accept wsdcId in profile updates with null handling
- Add wsdcId validation to updateProfileValidation middleware
- Include firstName, lastName, wsdcId in GET /api/users/me response

Fixes issue where profile inputs were empty on page load and allows
users to update their WSDC ID.
This commit is contained in:
Radosław Gierwiało
2025-11-13 20:38:36 +01:00
parent 7c2ed687c1
commit ebf4b84ed2
4 changed files with 55 additions and 6 deletions

View File

@@ -10,12 +10,13 @@ const { sanitizeForEmail } = require('../utils/sanitize');
async function updateProfile(req, res, next) { async function updateProfile(req, res, next) {
try { try {
const userId = req.user.id; const userId = req.user.id;
const { firstName, lastName, email } = req.body; const { firstName, lastName, email, wsdcId } = req.body;
// Build update data // Build update data
const updateData = {}; const updateData = {};
if (firstName !== undefined) updateData.firstName = firstName; if (firstName !== undefined) updateData.firstName = firstName;
if (lastName !== undefined) updateData.lastName = lastName; if (lastName !== undefined) updateData.lastName = lastName;
if (wsdcId !== undefined) updateData.wsdcId = wsdcId || null; // Allow empty string to clear WSDC ID
// Check if email is being changed // Check if email is being changed
const currentUser = await prisma.user.findUnique({ const currentUser = await prisma.user.findUnique({

View File

@@ -131,6 +131,11 @@ const updateProfileValidation = [
.isEmail() .isEmail()
.withMessage('Must be a valid email address') .withMessage('Must be a valid email address')
.normalizeEmail(), .normalizeEmail(),
body('wsdcId')
.optional()
.trim()
.matches(/^\d{0,10}$/)
.withMessage('WSDC ID must be numeric and up to 10 digits'),
handleValidationErrors, handleValidationErrors,
]; ];

View File

@@ -17,6 +17,9 @@ router.get('/me', authenticate, async (req, res, next) => {
username: true, username: true,
email: true, email: true,
emailVerified: true, emailVerified: true,
firstName: true,
lastName: true,
wsdcId: true,
avatar: true, avatar: true,
createdAt: true, createdAt: true,
updatedAt: true, updatedAt: true,

View File

@@ -1,8 +1,8 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
import { authAPI } from '../services/api'; import { authAPI } from '../services/api';
import Layout from '../components/layout/Layout'; import Layout from '../components/layout/Layout';
import { User, Mail, Lock, Save, AlertCircle, CheckCircle, Loader2 } from 'lucide-react'; import { User, Mail, Lock, Save, AlertCircle, CheckCircle, Loader2, Hash } from 'lucide-react';
const ProfilePage = () => { const ProfilePage = () => {
const { user, updateUser } = useAuth(); const { user, updateUser } = useAuth();
@@ -10,10 +10,23 @@ const ProfilePage = () => {
// Profile edit state // Profile edit state
const [profileData, setProfileData] = useState({ const [profileData, setProfileData] = useState({
firstName: user?.firstName || '', firstName: '',
lastName: user?.lastName || '', lastName: '',
email: user?.email || '', email: '',
wsdcId: '',
}); });
// Load user data when component mounts or user changes
useEffect(() => {
if (user) {
setProfileData({
firstName: user.firstName || '',
lastName: user.lastName || '',
email: user.email || '',
wsdcId: user.wsdcId || '',
});
}
}, [user]);
const [profileLoading, setProfileLoading] = useState(false); const [profileLoading, setProfileLoading] = useState(false);
const [profileMessage, setProfileMessage] = useState(''); const [profileMessage, setProfileMessage] = useState('');
const [profileError, setProfileError] = useState(''); const [profileError, setProfileError] = useState('');
@@ -210,6 +223,33 @@ const ProfilePage = () => {
</div> </div>
</div> </div>
{/* WSDC ID */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
WSDC ID
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Hash className="h-5 w-5 text-gray-400" />
</div>
<input
type="text"
name="wsdcId"
value={profileData.wsdcId}
onChange={(e) => {
const value = e.target.value.replace(/\D/g, '');
setProfileData({ ...profileData, wsdcId: value });
}}
className="pl-10 w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-primary-500 focus:border-primary-500"
placeholder="12345"
maxLength={10}
/>
</div>
<p className="text-xs text-gray-500 mt-1">
Your World Swing Dance Council ID (optional)
</p>
</div>
{/* Email */} {/* Email */}
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">