![]() 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/lawyer/ |
import React, { useEffect, useState } from 'react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import LayoutWithSidebar from '@/components/LayoutWithSidebar';
import { useRequireRole, USER_ROLES } from '@/lib/auth-utils';
import { toast } from 'react-hot-toast';
import {
User,
Briefcase,
GraduationCap,
MapPin,
Phone,
Globe,
DollarSign,
Clock,
Award,
Star,
Save,
Upload,
Edit3,
CheckCircle,
AlertCircle,
BarChart2
} from 'lucide-react';
interface LawyerProfile {
id: string;
name: string;
email: string;
role: string;
profilePicture?: string;
bio?: string;
title?: string;
specialization?: string;
barNumber?: string;
yearsOfExperience?: number;
education?: string;
certifications?: string;
officeLocation?: string;
workPhone?: string;
linkedinUrl?: string;
websiteUrl?: string;
availability?: string;
timezone?: string;
pronouns?: string;
isProfilePublic?: boolean;
hourlyRate?: number;
proBono?: boolean;
boldnessRating?: number;
transparencyRating?: number;
winRate?: number;
totalCases?: number;
wonCases?: number;
lostCases?: number;
averageRating?: number;
isVerified?: boolean;
}
const LawyerProfilePage: React.FC = () => {
const { data: session, status } = useSession();
const router = useRouter();
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [profile, setProfile] = useState<LawyerProfile | null>(null);
const [isEditing, setIsEditing] = useState(false);
// Role-based access control
const { isAuthorized } = useRequireRole([
USER_ROLES.LAWYER,
USER_ROLES.ADMIN,
USER_ROLES.SUPERADMIN, USER_ROLES.SUPERADMIN
], '/');
useEffect(() => {
if (status === 'loading') return;
if (!session || !isAuthorized) {
router.push('/');
return;
}
fetchProfile();
}, [session, status, router, isAuthorized]);
const fetchProfile = async () => {
try {
const response = await fetch('/api/lawyer/profile');
if (response.ok) {
const data = await response.json();
setProfile(data);
} else {
toast.error('Failed to load profile');
}
} catch (error) {
toast.error('Failed to load profile');
} finally {
setLoading(false);
}
};
const handleSave = async () => {
if (!profile) return;
setSaving(true);
try {
const response = await fetch('/api/lawyer/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(profile),
});
if (response.ok) {
toast.success('Profile updated successfully!');
setIsEditing(false);
} else {
toast.error('Failed to update profile');
}
} catch (error) {
toast.error('Failed to update profile');
} finally {
setSaving(false);
}
};
const handleInputChange = (field: keyof LawyerProfile, value: any) => {
if (!profile) return;
setProfile({ ...profile, [field]: value });
};
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
const formData = new FormData();
formData.append('profilePicture', file);
try {
const response = await fetch('/api/upload/profile-picture', {
method: 'POST',
body: formData,
});
if (response.ok) {
const data = await response.json();
setProfile(prev => prev ? { ...prev, profilePicture: data.url } : null);
toast.success('Profile picture updated!');
} else {
toast.error('Failed to upload profile picture');
}
} catch (error) {
toast.error('Failed to upload profile picture');
}
};
if (status === 'loading' || loading) {
return (
<LayoutWithSidebar>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
</LayoutWithSidebar>
);
}
if (!profile) {
return (
<LayoutWithSidebar>
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<AlertCircle className="h-12 w-12 text-red-500 mx-auto mb-4" />
<h2 className="text-xl font-semibold text-gray-900 mb-2">Profile Not Found</h2>
<p className="text-gray-600">Unable to load your profile information.</p>
</div>
</div>
</LayoutWithSidebar>
);
}
return (
<LayoutWithSidebar>
<div className="max-w-4xl mx-auto px-4 py-8">
{/* Header */}
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-3xl font-bold text-gray-900">Professional Profile</h1>
<p className="text-gray-600 mt-2">Manage your professional information and credentials</p>
</div>
<div className="flex gap-3">
{isEditing ? (
<>
<button
onClick={() => setIsEditing(false)}
className="px-4 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50"
>
Cancel
</button>
<button
onClick={handleSave}
disabled={saving}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 flex items-center gap-2"
>
<Save className="h-4 w-4" />
{saving ? 'Saving...' : 'Save Changes'}
</button>
</>
) : (
<button
onClick={() => setIsEditing(true)}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center gap-2"
>
<Edit3 className="h-4 w-4" />
Edit Profile
</button>
)}
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Profile Picture Section */}
<div className="lg:col-span-1">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<User className="h-5 w-5" />
Profile Picture
</h2>
<div className="text-center">
<div className="relative inline-block">
<img
src={profile.profilePicture || '/images/default-avatar.png'}
alt={profile.name}
className="w-32 h-32 rounded-full object-cover border-4 border-gray-200"
/>
{isEditing && (
<label className="absolute bottom-0 right-0 bg-blue-600 text-white p-2 rounded-full cursor-pointer hover:bg-blue-700">
<Upload className="h-4 w-4" />
<input
type="file"
accept="image/*"
onChange={handleFileUpload}
className="hidden"
/>
</label>
)}
</div>
<h3 className="text-xl font-semibold text-gray-900 mt-4">{profile.name}</h3>
<p className="text-gray-600">{profile.title || 'Lawyer'}</p>
{profile.isVerified && (
<div className="flex items-center justify-center gap-1 mt-2 text-green-600">
<CheckCircle className="h-4 w-4" />
<span className="text-sm font-medium">Verified</span>
</div>
)}
</div>
</div>
{/* Quick Stats */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mt-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<BarChart2 className="h-5 w-5" />
Professional Stats
</h2>
<div className="space-y-3">
<div className="flex justify-between">
<span className="text-gray-600">Total Cases</span>
<span className="font-semibold">{profile.totalCases || 0}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Win Rate</span>
<span className="font-semibold">{profile.winRate || 0}%</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Average Rating</span>
<span className="font-semibold flex items-center gap-1">
{profile.averageRating || 0}
<Star className="h-4 w-4 text-yellow-500 fill-current" />
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Years Experience</span>
<span className="font-semibold">{profile.yearsOfExperience || 0}</span>
</div>
</div>
</div>
</div>
{/* Main Profile Form */}
<div className="lg:col-span-2">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2">
<Briefcase className="h-5 w-5" />
Professional Information
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Basic Information */}
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Professional Title
</label>
<input
type="text"
value={profile.title || ''}
onChange={(e) => handleInputChange('title', e.target.value)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="e.g., Senior Partner, Associate Attorney"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Specialization
</label>
<input
type="text"
value={profile.specialization || ''}
onChange={(e) => handleInputChange('specialization', e.target.value)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="e.g., Criminal Law, Family Law, Corporate Law"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Bar Number
</label>
<input
type="text"
value={profile.barNumber || ''}
onChange={(e) => handleInputChange('barNumber', e.target.value)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="Your bar association number"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Years of Experience
</label>
<input
type="number"
value={profile.yearsOfExperience || ''}
onChange={(e) => handleInputChange('yearsOfExperience', parseInt(e.target.value) || 0)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="0"
/>
</div>
</div>
{/* Contact Information */}
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Work Phone
</label>
<input
type="tel"
value={profile.workPhone || ''}
onChange={(e) => handleInputChange('workPhone', e.target.value)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="(555) 123-4567"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Office Location
</label>
<input
type="text"
value={profile.officeLocation || ''}
onChange={(e) => handleInputChange('officeLocation', e.target.value)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="City, Province"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
LinkedIn URL
</label>
<input
type="url"
value={profile.linkedinUrl || ''}
onChange={(e) => handleInputChange('linkedinUrl', e.target.value)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="https://linkedin.com/in/yourprofile"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Website URL
</label>
<input
type="url"
value={profile.websiteUrl || ''}
onChange={(e) => handleInputChange('websiteUrl', e.target.value)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="https://yourwebsite.com"
/>
</div>
</div>
</div>
{/* Hourly Rate */}
<div className="mt-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Hourly Rate (CAD)
</label>
<div className="flex items-center gap-2">
<DollarSign className="h-5 w-5 text-gray-400" />
<input
type="number"
value={profile.hourlyRate || ''}
onChange={(e) => handleInputChange('hourlyRate', parseInt(e.target.value) || 0)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="200"
/>
<span className="text-gray-500">/hour</span>
</div>
</div>
{/* Bio */}
<div className="mt-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Professional Bio
</label>
<textarea
value={profile.bio || ''}
onChange={(e) => handleInputChange('bio', e.target.value)}
disabled={!isEditing}
rows={4}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="Tell clients about your expertise, experience, and approach to legal practice..."
/>
</div>
{/* Education & Certifications */}
<div className="mt-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Education
</label>
<textarea
value={profile.education || ''}
onChange={(e) => handleInputChange('education', e.target.value)}
disabled={!isEditing}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="List your degrees, institutions, and graduation years..."
/>
</div>
<div className="mt-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Certifications & Memberships
</label>
<textarea
value={profile.certifications || ''}
onChange={(e) => handleInputChange('certifications', e.target.value)}
disabled={!isEditing}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="List your professional certifications, bar memberships, and associations..."
/>
</div>
{/* Availability & Settings */}
<div className="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Availability
</label>
<input
type="text"
value={profile.availability || ''}
onChange={(e) => handleInputChange('availability', e.target.value)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
placeholder="e.g., Mon-Fri 9AM-5PM"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Timezone
</label>
<select
value={profile.timezone || ''}
onChange={(e) => handleInputChange('timezone', e.target.value)}
disabled={!isEditing}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50"
>
<option value="">Select timezone</option>
<option value="America/Toronto">Eastern Time (ET)</option>
<option value="America/Vancouver">Pacific Time (PT)</option>
<option value="America/Edmonton">Mountain Time (MT)</option>
<option value="America/Winnipeg">Central Time (CT)</option>
<option value="America/Halifax">Atlantic Time (AT)</option>
</select>
</div>
</div>
{/* Pro Bono Toggle */}
<div className="mt-6">
<label className="flex items-center">
<input
type="checkbox"
checked={profile.proBono || false}
onChange={(e) => handleInputChange('proBono', e.target.checked)}
disabled={!isEditing}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<span className="ml-2 text-sm text-gray-700">
Accept pro bono cases
</span>
</label>
</div>
{/* Public Profile Toggle */}
<div className="mt-4">
<label className="flex items-center">
<input
type="checkbox"
checked={profile.isProfilePublic || false}
onChange={(e) => handleInputChange('isProfilePublic', e.target.checked)}
disabled={!isEditing}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<span className="ml-2 text-sm text-gray-700">
Make profile public to potential clients
</span>
</label>
</div>
</div>
</div>
</div>
{/* Quick Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<div className="flex items-center">
<div className="p-2 bg-blue-100 rounded-lg">
<Briefcase className="h-6 w-6 text-blue-600" />
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-600">Total Cases</p>
<p className="text-2xl font-bold text-gray-900">{profile.totalCases || 0}</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<div className="flex items-center">
<div className="p-2 bg-green-100 rounded-lg">
<CheckCircle className="h-6 w-6 text-green-600" />
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-600">Win Rate</p>
<p className="text-2xl font-bold text-gray-900">{profile.winRate || 0}%</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<div className="flex items-center">
<div className="p-2 bg-yellow-100 rounded-lg">
<Star className="h-6 w-6 text-yellow-600" />
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-600">Rating</p>
<p className="text-2xl font-bold text-gray-900">{profile.averageRating || 0}/5</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<div className="flex items-center">
<div className="p-2 bg-purple-100 rounded-lg">
<Clock className="h-6 w-6 text-purple-600" />
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-600">Experience</p>
<p className="text-2xl font-bold text-gray-900">{profile.yearsOfExperience || 0}y</p>
</div>
</div>
</div>
</div>
</div>
</LayoutWithSidebar>
);
};
export default LawyerProfilePage;