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) {
try {
const userId = req.user.id;
const { firstName, lastName, email } = req.body;
const { firstName, lastName, email, wsdcId } = req.body;
// Build update data
const updateData = {};
if (firstName !== undefined) updateData.firstName = firstName;
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
const currentUser = await prisma.user.findUnique({

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { authAPI } from '../services/api';
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 { user, updateUser } = useAuth();
@@ -10,10 +10,23 @@ const ProfilePage = () => {
// Profile edit state
const [profileData, setProfileData] = useState({
firstName: user?.firstName || '',
lastName: user?.lastName || '',
email: user?.email || '',
firstName: '',
lastName: '',
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 [profileMessage, setProfileMessage] = useState('');
const [profileError, setProfileError] = useState('');
@@ -210,6 +223,33 @@ const ProfilePage = () => {
</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 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">