diff --git a/backend/src/controllers/user.js b/backend/src/controllers/user.js index 6fb8f11..33074f1 100644 --- a/backend/src/controllers/user.js +++ b/backend/src/controllers/user.js @@ -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({ diff --git a/backend/src/middleware/validators.js b/backend/src/middleware/validators.js index 756031b..481002e 100644 --- a/backend/src/middleware/validators.js +++ b/backend/src/middleware/validators.js @@ -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, ]; diff --git a/backend/src/routes/users.js b/backend/src/routes/users.js index cd05e59..6466eb2 100644 --- a/backend/src/routes/users.js +++ b/backend/src/routes/users.js @@ -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, diff --git a/frontend/src/pages/ProfilePage.jsx b/frontend/src/pages/ProfilePage.jsx index f3cd7d3..9ba5fec 100644 --- a/frontend/src/pages/ProfilePage.jsx +++ b/frontend/src/pages/ProfilePage.jsx @@ -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 = () => { + {/* WSDC ID */} +
+ +
+
+ +
+ { + 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} + /> +
+

+ Your World Swing Dance Council ID (optional) +

+
+ {/* Email */}