initial version of the app
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { User, Edit, Mail, Calendar, Save, X } from 'lucide-react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
surname: string;
|
||||
role: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface ProfileFormData {
|
||||
name: string;
|
||||
surname: string;
|
||||
}
|
||||
|
||||
export function UserProfile() {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [formData, setFormData] = useState<ProfileFormData>({
|
||||
name: '',
|
||||
surname: '',
|
||||
});
|
||||
const { toast } = useToast();
|
||||
|
||||
const updateFormData = (field: keyof ProfileFormData, value: string) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserProfile();
|
||||
}, []);
|
||||
|
||||
const fetchUserProfile = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/users/profile');
|
||||
if (response.ok) {
|
||||
const userData = await response.json();
|
||||
setUser(userData.user);
|
||||
setFormData({
|
||||
name: userData.user.name,
|
||||
surname: userData.user.surname,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to fetch user profile',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching user profile:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to fetch user profile',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!formData.name.trim() || !formData.surname.trim()) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Name and surname are required',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
const response = await fetch('/api/users/profile', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: formData.name.trim(),
|
||||
surname: formData.surname.trim(),
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Profile updated successfully!',
|
||||
});
|
||||
setIsEditing(false);
|
||||
await fetchUserProfile(); // Refresh user data
|
||||
} else {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: data.error || 'Failed to update profile',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating profile:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to update profile',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
if (user) {
|
||||
setFormData({
|
||||
name: user.name,
|
||||
surname: user.surname,
|
||||
});
|
||||
}
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className='p-6'>
|
||||
<div className='flex items-center justify-center'>
|
||||
<div className='animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900'></div>
|
||||
<p className='ml-2'>Loading profile...</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className='p-6'>
|
||||
<div className='text-center text-gray-500'>
|
||||
<p>Unable to load user profile</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className='flex items-center gap-2'>
|
||||
<User className='h-5 w-5' />
|
||||
User Profile
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-6'>
|
||||
{/* Profile Information */}
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
|
||||
{/* Name */}
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor='name'>First Name</Label>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
id='name'
|
||||
value={formData.name}
|
||||
onChange={(e) => updateFormData('name', e.target.value)}
|
||||
placeholder='Enter your first name'
|
||||
/>
|
||||
) : (
|
||||
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded'>
|
||||
<span>{user.name}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Surname */}
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor='surname'>Last Name</Label>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
id='surname'
|
||||
value={formData.surname}
|
||||
onChange={(e) => updateFormData('surname', e.target.value)}
|
||||
placeholder='Enter your last name'
|
||||
/>
|
||||
) : (
|
||||
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded'>
|
||||
<span>{user.surname}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Email (Read-only) */}
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor='email'>Email Address</Label>
|
||||
<div className='flex items-center gap-2 p-2 bg-gray-100 rounded text-gray-600'>
|
||||
<Mail className='h-4 w-4' />
|
||||
<span>{user.email}</span>
|
||||
<span className='text-xs text-gray-500 ml-auto'>(Read-only)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Member Since */}
|
||||
<div className='space-y-2'>
|
||||
<Label>Member Since</Label>
|
||||
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded'>
|
||||
<Calendar className='h-4 w-4' />
|
||||
<span>
|
||||
{new Date(user.createdAt).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className='flex gap-2 pt-4 border-t'>
|
||||
{isEditing ? (
|
||||
<>
|
||||
<Button onClick={handleSave} disabled={saving} className='flex items-center gap-2'>
|
||||
<Save className='h-4 w-4' />
|
||||
{saving ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
<Button
|
||||
variant='outline'
|
||||
onClick={handleCancel}
|
||||
disabled={saving}
|
||||
className='flex items-center gap-2'
|
||||
>
|
||||
<X className='h-4 w-4' />
|
||||
Cancel
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button onClick={() => setIsEditing(true)} className='flex items-center gap-2'>
|
||||
<Edit className='h-4 w-4' />
|
||||
Edit Profile
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Account Information Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Account Information</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-4'>
|
||||
<div className='space-y-2'>
|
||||
<Label>Account Type</Label>
|
||||
<div className='p-2 bg-blue-50 rounded text-blue-800 capitalize font-medium'>
|
||||
{user.role}
|
||||
</div>
|
||||
</div>
|
||||
<div className='space-y-2'>
|
||||
<Label>User ID</Label>
|
||||
<div className='p-2 bg-gray-50 rounded text-gray-600 font-mono text-sm'>{user.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user