feat: add country and city fields to user profile

- Add country and city fields to User model
- Create database migration for location fields
- Add validation for country and city (max 100 characters)
- Create countries.js with complete list of 195 countries
- Add country dropdown select and city text input to profile page
- Include country and city in GET /api/users/me response
- Update profile form to support location data

Users can now select their country from a dropdown list of all
countries and enter their city name.
This commit is contained in:
Radosław Gierwiało
2025-11-13 20:57:43 +01:00
parent 48f9dfe1b4
commit 144b13a0cf
7 changed files with 276 additions and 2 deletions

View File

@@ -2,7 +2,8 @@ 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, Hash, Youtube, Instagram, Facebook } from 'lucide-react';
import { User, Mail, Lock, Save, AlertCircle, CheckCircle, Loader2, Hash, Youtube, Instagram, Facebook, MapPin, Globe } from 'lucide-react';
import { COUNTRIES } from '../data/countries';
const ProfilePage = () => {
const { user, updateUser } = useAuth();
@@ -18,6 +19,8 @@ const ProfilePage = () => {
instagramUrl: '',
facebookUrl: '',
tiktokUrl: '',
country: '',
city: '',
});
// Load user data when component mounts or user changes
@@ -32,6 +35,8 @@ const ProfilePage = () => {
instagramUrl: user.instagramUrl || '',
facebookUrl: user.facebookUrl || '',
tiktokUrl: user.tiktokUrl || '',
country: user.country || '',
city: user.city || '',
});
}
}, [user]);
@@ -231,6 +236,54 @@ const ProfilePage = () => {
</div>
</div>
{/* Location Section */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Country */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Country
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Globe className="h-5 w-5 text-gray-400" />
</div>
<select
name="country"
value={profileData.country}
onChange={handleProfileChange}
className="pl-10 w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-primary-500 focus:border-primary-500"
>
<option value="">Select a country</option>
{COUNTRIES.map((country) => (
<option key={country} value={country}>
{country}
</option>
))}
</select>
</div>
</div>
{/* City */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
City
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<MapPin className="h-5 w-5 text-gray-400" />
</div>
<input
type="text"
name="city"
value={profileData.city}
onChange={handleProfileChange}
className="pl-10 w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-primary-500 focus:border-primary-500"
placeholder="Somewhere"
/>
</div>
</div>
</div>
{/* WSDC ID */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">