![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/backups/lavocat.quebec/backup-20250730-021618/src/pages/judge/ |
'use client';
import React, { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import LayoutWithSidebar from '@/components/LayoutWithSidebar';
import { toast } from 'react-hot-toast';
import { motion } from 'framer-motion';
import {
User,
Upload,
Phone,
Mail,
MapPin,
Globe,
Linkedin,
Eye,
EyeOff,
Calendar,
Clock,
DollarSign,
Building,
GraduationCap,
Trophy,
Scale,
Gavel,
FileText,
CheckCircle,
XCircle,
Edit,
X,
Check,
Camera,
} from 'lucide-react';
interface JudgeProfile {
id: string;
name: string;
email: string;
role: string;
username?: string;
profilePicture?: string;
bio?: string;
title?: string;
specialization?: string;
availability?: string;
location?: string;
phone?: string;
website?: string;
linkedin?: string;
isPublic: boolean;
// Judge-specific fields
court?: string;
jurisdiction?: string;
appointmentDate?: string;
yearsOnBench?: number;
education?: string[];
certifications?: string[];
languages?: string[];
areasOfExpertise?: string[];
notableCases?: string[];
publications?: string[];
awards?: string[];
memberships?: string[];
speakingEngagements?: string[];
availabilitySchedule?: string;
consultationRate?: number;
currency?: string;
experience?: string;
references?: string[];
portfolio?: string;
socialMedia?: {
linkedin?: string;
twitter?: string;
};
}
const JudgeProfilePage: React.FC = () => {
const { data: session, status } = useSession();
const router = useRouter();
const [profile, setProfile] = useState<JudgeProfile | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [uploading, setUploading] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [formData, setFormData] = useState<Partial<JudgeProfile>>({});
useEffect(() => {
if (status === 'loading') return;
if (!session) {
router.push('/auth/login');
return;
}
if (session.user.role !== 'JUDGE') {
router.push('/unauthorized');
return;
}
fetchProfile();
}, [session, status, router]);
const fetchProfile = async () => {
try {
const response = await fetch('/api/judge/profile');
if (response.ok) {
const data = await response.json();
setProfile(data);
setFormData(data);
} else {
toast.error('Failed to load profile');
}
} catch (error) {
console.error('Error fetching profile:', error);
toast.error('Error loading profile');
} finally {
setLoading(false);
}
};
const handleInputChange = (field: keyof JudgeProfile, value: any) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleArrayInputChange = (field: keyof JudgeProfile, value: string) => {
const array = value.split(',').map(item => item.trim()).filter(item => item);
setFormData(prev => ({
...prev,
[field]: array
}));
};
const handleSave = async () => {
setSaving(true);
try {
const response = await fetch('/api/judge/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
if (response.ok) {
const updatedProfile = await response.json();
setProfile(updatedProfile);
setIsEditing(false);
toast.success('Profile updated successfully');
} else {
const error = await response.json();
toast.error(error.message || 'Failed to update profile');
}
} catch (error) {
console.error('Error updating profile:', error);
toast.error('Error updating profile');
} finally {
setSaving(false);
}
};
const handleCancel = () => {
setFormData(profile || {});
setIsEditing(false);
};
const handleProfilePictureUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
if (file.size > 5 * 1024 * 1024) {
toast.error('File size must be less than 5MB');
return;
}
setUploading(true);
const formData = new FormData();
formData.append('profilePicture', file);
try {
const response = await fetch('/api/judge/profile/picture', {
method: 'POST',
body: formData,
});
if (response.ok) {
const data = await response.json();
setProfile(prev => prev ? { ...prev, profilePicture: data.profilePicture } : null);
setFormData(prev => ({ ...prev, profilePicture: data.profilePicture }));
toast.success('Profile picture updated successfully');
} else {
toast.error('Failed to upload profile picture');
}
} catch (error) {
console.error('Error uploading profile picture:', error);
toast.error('Error uploading profile picture');
} finally {
setUploading(false);
}
};
const togglePublicProfile = async () => {
const newIsPublic = !formData.isPublic;
setFormData(prev => ({ ...prev, isPublic: newIsPublic }));
try {
const response = await fetch('/api/judge/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ...formData, isPublic: newIsPublic }),
});
if (response.ok) {
const updatedProfile = await response.json();
setProfile(updatedProfile);
toast.success(`Profile is now ${newIsPublic ? 'public' : 'private'}`);
} else {
toast.error('Failed to update profile visibility');
}
} catch (error) {
console.error('Error updating profile visibility:', error);
toast.error('Error updating profile visibility');
}
};
if (loading) {
return (
<LayoutWithSidebar>
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-4 text-gray-600 dark:text-gray-400">Loading profile...</p>
</div>
</div>
</LayoutWithSidebar>
);
}
if (!profile) {
return (
<LayoutWithSidebar>
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800 flex items-center justify-center">
<div className="text-center">
<XCircle className="h-12 w-12 text-red-500 mx-auto mb-4" />
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">Profile Not Found</h1>
<p className="text-gray-600 dark:text-gray-400">Unable to load your profile information.</p>
</div>
</div>
</LayoutWithSidebar>
);
}
return (
<LayoutWithSidebar>
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="mb-8"
>
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white flex items-center">
<Scale className="h-8 w-8 text-blue-600 mr-3" />
Judicial Profile
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-2">
Manage your judicial profile and professional information
</p>
</div>
<div className="flex items-center space-x-4">
{/* Public/Private Toggle */}
<button
onClick={togglePublicProfile}
className={`flex items-center space-x-2 px-4 py-2 rounded-lg border transition-colors ${
formData.isPublic
? 'bg-green-50 border-green-200 text-green-700 hover:bg-green-100'
: 'bg-gray-50 border-gray-200 text-gray-700 hover:bg-gray-100'
}`}
>
{formData.isPublic ? (
<>
<Eye className="h-4 w-4" />
<span>Public</span>
</>
) : (
<>
<EyeOff className="h-4 w-4" />
<span>Private</span>
</>
)}
</button>
{/* Edit/Save Buttons */}
{isEditing ? (
<div className="flex space-x-2">
<button
onClick={handleSave}
disabled={saving}
className="flex items-center space-x-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
>
<Check className="h-4 w-4" />
<span>{saving ? 'Saving...' : 'Save'}</span>
</button>
<button
onClick={handleCancel}
className="flex items-center space-x-2 bg-gray-600 text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors"
>
<X className="h-4 w-4" />
<span>Cancel</span>
</button>
</div>
) : (
<button
onClick={() => setIsEditing(true)}
className="flex items-center space-x-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
<Edit className="h-4 w-4" />
<span>Edit Profile</span>
</button>
)}
</div>
</div>
</motion.div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Profile Picture and Basic Info */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className="lg:col-span-1"
>
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
{/* Profile Picture */}
<div className="text-center mb-6">
<div className="relative inline-block">
<div className="w-32 h-32 rounded-full overflow-hidden bg-gradient-to-br from-blue-500 to-purple-600 mx-auto">
{formData.profilePicture ? (
<img
src={formData.profilePicture}
alt="Profile"
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center text-white text-4xl font-bold">
{profile.name.split(' ').map(n => n[0]).join('')}
</div>
)}
</div>
{isEditing && (
<label className="absolute bottom-0 right-0 bg-blue-600 text-white p-2 rounded-full cursor-pointer hover:bg-blue-700 transition-colors">
<Camera className="h-4 w-4" />
<input
type="file"
accept="image/*"
onChange={handleProfilePictureUpload}
className="hidden"
disabled={uploading}
/>
</label>
)}
</div>
{uploading && (
<p className="text-sm text-gray-500 mt-2">Uploading...</p>
)}
</div>
{/* Basic Information */}
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Full Name
</label>
{isEditing ? (
<input
type="text"
value={formData.name || ''}
onChange={(e) => handleInputChange('name', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-900 dark:text-white font-medium">{profile.name}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Email
</label>
<p className="text-gray-600 dark:text-gray-400">{profile.email}</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Role
</label>
<p className="text-gray-600 dark:text-gray-400">Judge</p>
</div>
{isEditing && (
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Title/Position
</label>
<input
type="text"
value={formData.title || ''}
onChange={(e) => handleInputChange('title', e.target.value)}
placeholder="e.g., Honorable Judge, Chief Justice"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
)}
{profile.title && (
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Title
</label>
<p className="text-gray-600 dark:text-gray-400">{profile.title}</p>
</div>
)}
</div>
</div>
</motion.div>
{/* Main Content */}
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
className="lg:col-span-2 space-y-6"
>
{/* Judicial Information */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
<Scale className="h-5 w-5 text-blue-600 mr-2" />
Judicial Information
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Court
</label>
{isEditing ? (
<input
type="text"
value={formData.court || ''}
onChange={(e) => handleInputChange('court', e.target.value)}
placeholder="e.g., Superior Court, Federal Court"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">{profile.court || 'Not specified'}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Jurisdiction
</label>
{isEditing ? (
<input
type="text"
value={formData.jurisdiction || ''}
onChange={(e) => handleInputChange('jurisdiction', e.target.value)}
placeholder="e.g., Criminal, Civil, Family Law"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">{profile.jurisdiction || 'Not specified'}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Appointment Date
</label>
{isEditing ? (
<input
type="date"
value={formData.appointmentDate || ''}
onChange={(e) => handleInputChange('appointmentDate', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">{profile.appointmentDate || 'Not specified'}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Years on Bench
</label>
{isEditing ? (
<input
type="number"
min="0"
max="50"
value={formData.yearsOnBench || ''}
onChange={(e) => handleInputChange('yearsOnBench', parseInt(e.target.value))}
placeholder="e.g., 15"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">{profile.yearsOnBench || 'Not specified'}</p>
)}
</div>
</div>
<div className="mt-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Areas of Expertise
</label>
{isEditing ? (
<input
type="text"
value={Array.isArray(formData.areasOfExpertise) ? formData.areasOfExpertise.join(', ') : ''}
onChange={(e) => handleArrayInputChange('areasOfExpertise', e.target.value)}
placeholder="e.g., Criminal Law, Civil Litigation, Family Law, Constitutional Law"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">
{profile.areasOfExpertise?.length ? profile.areasOfExpertise.join(', ') : 'Not specified'}
</p>
)}
</div>
</div>
{/* Education and Experience */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
<GraduationCap className="h-5 w-5 text-green-600 mr-2" />
Education & Experience
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Education
</label>
{isEditing ? (
<textarea
value={Array.isArray(formData.education) ? formData.education.join('\n') : ''}
onChange={(e) => handleArrayInputChange('education', e.target.value)}
placeholder="List your educational background..."
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">
{profile.education?.length ? profile.education.join(', ') : 'Not specified'}
</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Experience
</label>
{isEditing ? (
<textarea
value={formData.experience || ''}
onChange={(e) => handleInputChange('experience', e.target.value)}
placeholder="Describe your legal and judicial experience..."
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">{profile.experience || 'Not specified'}</p>
)}
</div>
</div>
<div className="mt-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Notable Cases
</label>
{isEditing ? (
<textarea
value={Array.isArray(formData.notableCases) ? formData.notableCases.join('\n') : ''}
onChange={(e) => handleArrayInputChange('notableCases', e.target.value)}
placeholder="List notable cases you've presided over..."
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">
{profile.notableCases?.length ? profile.notableCases.join(', ') : 'Not specified'}
</p>
)}
</div>
<div className="mt-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Publications
</label>
{isEditing ? (
<textarea
value={Array.isArray(formData.publications) ? formData.publications.join('\n') : ''}
onChange={(e) => handleArrayInputChange('publications', e.target.value)}
placeholder="List your legal publications and writings..."
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">
{profile.publications?.length ? profile.publications.join(', ') : 'Not specified'}
</p>
)}
</div>
</div>
{/* Contact Information */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
<Mail className="h-5 w-5 text-purple-600 mr-2" />
Contact Information
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Phone
</label>
{isEditing ? (
<input
type="tel"
value={formData.phone || ''}
onChange={(e) => handleInputChange('phone', e.target.value)}
placeholder="+1 (555) 123-4567"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">{profile.phone || 'Not specified'}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Location
</label>
{isEditing ? (
<input
type="text"
value={formData.location || ''}
onChange={(e) => handleInputChange('location', e.target.value)}
placeholder="e.g., Montreal, QC"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">{profile.location || 'Not specified'}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Website
</label>
{isEditing ? (
<input
type="url"
value={formData.website || ''}
onChange={(e) => handleInputChange('website', e.target.value)}
placeholder="https://yourwebsite.com"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">{profile.website || 'Not specified'}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
LinkedIn
</label>
{isEditing ? (
<input
type="url"
value={formData.linkedin || ''}
onChange={(e) => handleInputChange('linkedin', e.target.value)}
placeholder="https://linkedin.com/in/yourprofile"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">{profile.linkedin || 'Not specified'}</p>
)}
</div>
</div>
</div>
{/* Bio */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
<User className="h-5 w-5 text-indigo-600 mr-2" />
About Me
</h2>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Bio
</label>
{isEditing ? (
<textarea
value={formData.bio || ''}
onChange={(e) => handleInputChange('bio', e.target.value)}
placeholder="Tell us about yourself, your judicial philosophy, and what drives you in the legal system..."
rows={4}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
) : (
<p className="text-gray-600 dark:text-gray-400">{profile.bio || 'No bio provided'}</p>
)}
</div>
</div>
</motion.div>
</div>
</div>
</div>
</LayoutWithSidebar>
);
};
export default JudgeProfilePage;