![]() 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/domains/lavocat.ca/public_html/src/pages/ |
import React, { useState, useEffect } from 'react';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
import LayoutWithSidebar from '../components/LayoutWithSidebar';
import { motion } from 'framer-motion';
import {
Building2, Users, Award, TrendingUp, MapPin,
Phone, Mail, Globe, Star, Target, Shield,
Calendar, DollarSign, Clock, Filter, Search
} from 'lucide-react';
import Link from 'next/link';
import HireLawyerButton from '../components/HireLawyerButton';
import { prisma } from '../lib/prisma';
import BarreauBadge from '../components/BarreauBadge';
interface LawyerStats {
totalCases: number;
wonCases: number;
lostCases: number;
winRate: number;
averageRating: number;
yearsOfExperience: number;
hourlyRate: number;
totalBadges: number;
level: number;
}
interface BusinessProfile {
id: string;
businessName: string;
businessType: string;
industry?: string;
description?: string;
logo?: string;
website?: string;
phone?: string;
email?: string;
address?: string;
employeeCount?: string;
annualRevenue?: string;
isVerified: boolean;
createdAt: string;
updatedAt: string;
user: {
id: string;
name: string;
email: string;
role: string;
};
lawyers: Array<{
id: string;
name: string;
role: string;
profilePicture?: string;
specialization?: string;
yearsOfExperience?: number;
totalCases: number;
wonCases: number;
lostCases: number;
winRate: number;
averageRating: number;
hourlyRate?: number;
totalBadges: number;
level: number;
isVerified: boolean;
verificationStatus?: string;
}>;
firmStats: {
totalLawyers: number;
totalCases: number;
totalWonCases: number;
totalLostCases: number;
averageWinRate: number;
averageRating: number;
averageHourlyRate: number;
totalBadges: number;
averageLevel: number;
};
}
interface BusinessProfilesPageProps {
businesses: BusinessProfile[];
}
const BusinessProfilesPage: React.FC<BusinessProfilesPageProps> = ({ businesses }) => {
const [filteredBusinesses, setFilteredBusinesses] = useState<BusinessProfile[]>(businesses);
const [searchTerm, setSearchTerm] = useState('');
const [sortBy, setSortBy] = useState('name');
const [filterVerified, setFilterVerified] = useState(false);
useEffect(() => {
let filtered = businesses;
// Search filter
if (searchTerm) {
filtered = filtered.filter(business =>
business.businessName.toLowerCase().includes(searchTerm.toLowerCase()) ||
business.industry?.toLowerCase().includes(searchTerm.toLowerCase()) ||
business.description?.toLowerCase().includes(searchTerm.toLowerCase())
);
}
// Verified filter
if (filterVerified) {
filtered = filtered.filter(business => business.isVerified);
}
// Sort
filtered.sort((a, b) => {
switch (sortBy) {
case 'name':
return a.businessName.localeCompare(b.businessName);
case 'winRate':
return b.firmStats.averageWinRate - a.firmStats.averageWinRate;
case 'rating':
return (b.firmStats.averageRating || 0) - (a.firmStats.averageRating || 0);
case 'cases':
return b.firmStats.totalCases - a.firmStats.totalCases;
case 'lawyers':
return b.firmStats.totalLawyers - a.firmStats.totalLawyers;
default:
return 0;
}
});
setFilteredBusinesses(filtered);
}, [businesses, searchTerm, sortBy, filterVerified]);
const getWinRateColor = (winRate: number) => {
if (winRate >= 80) return 'text-green-600';
if (winRate >= 60) return 'text-yellow-600';
return 'text-red-600';
};
const getRatingColor = (rating: number) => {
if (rating >= 4.5) return 'text-yellow-500';
if (rating >= 4.0) return 'text-gray-600';
return 'text-gray-400';
};
return (
<LayoutWithSidebar>
<Head>
<title>Business Profiles - Legal Firms & Lawyers</title>
<meta name="description" content="Discover verified legal firms and their expert lawyers with detailed statistics, win rates, and client reviews." />
</Head>
<div className="max-w-7xl mx-auto px-4 py-8">
{/* Quick Navigation */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="bg-gradient-to-r from-blue-50 to-purple-50 dark:from-blue-900/20 dark:to-purple-900/20 rounded-xl p-6 border border-blue-200 dark:border-blue-800 mb-8"
>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
🏛️ Judicial Ecosystem Navigation
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Link
href="/judicial-directory"
className="flex items-center space-x-3 p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-md transition-all duration-200 group"
>
<div className="w-10 h-10 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
<span className="text-blue-600 text-lg">⚖️</span>
</div>
<div>
<div className="font-medium text-gray-900 dark:text-white group-hover:text-blue-600 transition-colors">
Judicial Directory
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Legal professionals & expertise
</div>
</div>
</Link>
<Link
href="/business-profiles"
className="flex items-center space-x-3 p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-md transition-all duration-200 group border-2 border-blue-500"
>
<div className="w-10 h-10 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
<span className="text-purple-600 text-lg">🏢</span>
</div>
<div>
<div className="font-medium text-gray-900 dark:text-white group-hover:text-purple-600 transition-colors">
Law Firms & Businesses
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Professional organizations
</div>
</div>
</Link>
<Link
href="/profiles"
className="flex items-center space-x-3 p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-md transition-all duration-200 group"
>
<div className="w-10 h-10 bg-green-100 dark:bg-green-900/30 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
<span className="text-green-600 text-lg">👥</span>
</div>
<div>
<div className="font-medium text-gray-900 dark:text-white group-hover:text-green-600 transition-colors">
Society Members
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Community directory
</div>
</div>
</Link>
</div>
</motion.div>
{/* Header */}
<div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
⚖️ Law Firms Directory
</h1>
<p className="text-xl text-gray-600 dark:text-gray-400">
Discover verified law firms and legal organizations with detailed statistics,
team information, and performance metrics in our judicial ecosystem
</p>
</div>
{/* Filters */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6 mb-8">
<div className="flex flex-col md:flex-row gap-4">
{/* Search */}
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-gray-400" />
<input
type="text"
placeholder="Search firms, lawyers, or specializations..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
/>
</div>
</div>
{/* Sort */}
<div className="flex gap-4">
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
>
<option value="name">Sort by Name</option>
<option value="winRate">Sort by Win Rate</option>
<option value="rating">Sort by Rating</option>
<option value="cases">Sort by Total Cases</option>
<option value="lawyers">Sort by Lawyers</option>
</select>
<label className="flex items-center">
<input
type="checkbox"
checked={filterVerified}
onChange={(e) => setFilterVerified(e.target.checked)}
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 dark:text-gray-300">Verified Only</span>
</label>
</div>
</div>
</div>
{/* Results */}
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{filteredBusinesses.map((business, index) => (
<motion.div
key={business.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
whileHover={{ y: -5, scale: 1.02 }}
className="bg-white dark:bg-gray-800 rounded-xl shadow-lg hover:shadow-2xl transition-all duration-300 overflow-hidden border border-gray-100 dark:border-gray-700 cursor-pointer group"
onClick={() => window.open(`/business/${business.id}`, '_blank')}
>
<div className="p-6">
{/* Header */}
<div className="flex items-start justify-between mb-4">
<div className="flex items-center">
<div className="relative">
{business.logo ? (
<img
src={business.logo}
alt={business.businessName}
className="w-16 h-16 rounded-lg object-cover mr-4 group-hover:scale-110 transition-transform"
/>
) : (
<div className="w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center mr-4 group-hover:scale-110 transition-transform">
<Building2 className="h-8 w-8 text-white" />
</div>
)}
{business.isVerified && (
<div className="absolute -top-1 -right-1 w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center">
<Shield className="h-3 w-3 text-white" />
</div>
)}
</div>
<div>
<h3 className="font-semibold text-lg text-gray-900 dark:text-white flex items-center group-hover:text-blue-600 transition-colors">
{business.businessName}
{business.isVerified && (
<Shield className="h-5 w-5 ml-2 text-blue-600" />
)}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">{business.businessType}</p>
{business.industry && (
<p className="text-xs text-gray-500">{business.industry}</p>
)}
</div>
</div>
<div className="opacity-0 group-hover:opacity-100 transition-opacity">
<span className="text-blue-600 text-sm font-medium">🏢 View Firm</span>
</div>
</div>
{/* Firm Stats */}
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="text-center p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg group-hover:bg-blue-100 dark:group-hover:bg-blue-900/30 transition-colors">
<div className="text-2xl font-bold text-blue-600">
{business.firmStats.totalLawyers > 0 ? business.firmStats.totalLawyers : <span className="text-gray-400 text-base">No lawyers yet</span>}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400">Lawyers</div>
</div>
<div className="text-center p-3 bg-green-50 dark:bg-green-900/20 rounded-lg group-hover:bg-green-100 dark:group-hover:bg-green-900/30 transition-colors">
<div className={`text-2xl font-bold ${getWinRateColor(business.firmStats.averageWinRate)}`}>
{business.firmStats.totalCases > 0 && typeof business.firmStats.averageWinRate === 'number' ? business.firmStats.averageWinRate.toFixed(1) + '%' : <span className="text-gray-400 text-base">N/A</span>}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400">Win Rate</div>
</div>
<div className="text-center p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg group-hover:bg-purple-100 dark:group-hover:bg-purple-900/30 transition-colors">
<div className="text-2xl font-bold text-purple-600">
{business.firmStats.totalCases > 0 ? business.firmStats.totalCases : <span className="text-gray-400 text-base">No cases yet</span>}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400">Total Cases</div>
</div>
<div className="text-center p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg group-hover:bg-yellow-100 dark:group-hover:bg-yellow-900/30 transition-colors">
<div className={`text-2xl font-bold ${getRatingColor(business.firmStats.averageRating || 0)}`}>
{business.firmStats.averageRating && business.firmStats.averageRating > 0 ? business.firmStats.averageRating.toFixed(1) : <span className="text-gray-400 text-base">N/A</span>}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400">Rating</div>
</div>
</div>
{/* Description */}
{business.description && (
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">
{business.description}
</p>
)}
{/* Contact Info */}
<div className="space-y-2 mb-4">
{business.website && (
<div className="flex items-center text-sm text-gray-600 dark:text-gray-400">
<Globe className="h-4 w-4 mr-2" />
<span className="truncate">{business.website}</span>
</div>
)}
{business.phone && (
<div className="flex items-center text-sm text-gray-600 dark:text-gray-400">
<Phone className="h-4 w-4 mr-2" />
{business.phone}
</div>
)}
{business.address && (
<div className="flex items-center text-sm text-gray-600 dark:text-gray-400">
<MapPin className="h-4 w-4 mr-2" />
<span className="truncate">{business.address}</span>
</div>
)}
</div>
{/* Top Lawyers Preview */}
{business.lawyers.length > 0 ? (
<div className="border-t border-gray-200 dark:border-gray-700 pt-4">
<h4 className="text-sm font-medium text-gray-900 dark:text-white mb-2">Top Lawyers</h4>
<div className="space-y-2">
{business.lawyers.slice(0, 3).map((lawyer) => (
<div key={lawyer.id} className="flex items-center justify-between group/lawyer">
<div className="flex items-center">
{lawyer.profilePicture ? (
<img
src={lawyer.profilePicture}
alt={lawyer.name}
className="w-8 h-8 rounded-full object-cover mr-2 group-hover/lawyer:scale-110 transition-transform"
/>
) : (
<div className="w-8 h-8 bg-gray-300 dark:bg-gray-600 rounded-full flex items-center justify-center mr-2 group-hover/lawyer:scale-110 transition-transform">
<Users className="h-4 w-4 text-gray-500" />
</div>
)}
<div>
<div className="flex items-center gap-2 mb-2">
<span className="text-sm font-medium text-gray-900">{lawyer.name}</span>
<BarreauBadge
verificationStatus={lawyer.verificationStatus}
isVerified={lawyer.isVerified}
size="sm"
showText={false}
/>
</div>
<p className="text-xs text-gray-600 dark:text-gray-400">{lawyer.specialization}</p>
</div>
</div>
<div className="text-right">
<p className={`text-sm font-medium ${getWinRateColor(lawyer.winRate)}`}>
{typeof lawyer.winRate === 'number' ? lawyer.winRate.toFixed(1) + '%' : 'N/A'}
</p>
<p className="text-xs text-gray-600 dark:text-gray-400">{lawyer.totalCases} cases</p>
</div>
</div>
))}
</div>
</div>
) : (
<div className="border-t border-gray-200 dark:border-gray-700 pt-4 text-center text-gray-400 text-sm">
No lawyers yet
</div>
)}
{/* Hire Button */}
{business.lawyers.length > 0 && (
<div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
<HireLawyerButton
lawyer={business.lawyers[0]}
businessProfile={business}
className="w-full"
/>
</div>
)}
{/* Quick Action Buttons */}
<div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700 flex gap-2">
<button
onClick={(e) => {
e.stopPropagation();
window.open(`/business/${business.id}`, '_blank');
}}
className="flex-1 bg-gradient-to-r from-blue-500 to-purple-600 text-white px-3 py-2 rounded-md text-sm font-medium hover:from-blue-600 hover:to-purple-700 transition-all"
>
🏢 View Firm Profile
</button>
<button
onClick={(e) => {
e.stopPropagation();
window.open(`/hire?firm=${business.id}`, '_blank');
}}
className="bg-green-600 text-white px-3 py-2 rounded-md text-sm font-medium hover:bg-green-700 transition-colors"
>
💼 Hire Firm
</button>
</div>
</div>
</motion.div>
))}
</div>
{/* Empty State */}
{filteredBusinesses.length === 0 && (
<div className="text-center py-12">
<Building2 className="h-16 w-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">No businesses found</h3>
<p className="text-gray-600 dark:text-gray-400">
Try adjusting your search criteria or filters.
</p>
</div>
)}
</div>
</LayoutWithSidebar>
);
};
function serializeDates(obj: any): any {
if (obj === null || obj === undefined) return obj;
if (obj instanceof Date) return obj.toISOString();
if (Array.isArray(obj)) return obj.map(serializeDates);
if (typeof obj === 'object') {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, serializeDates(v)])
);
}
return obj;
}
export const getServerSideProps: GetServerSideProps = async (context) => {
let businesses = [];
if (process.env.NODE_ENV === 'development') {
// Direct DB call in dev to avoid self-signed cert issues
const businessProfiles = await prisma.businessProfile.findMany({
where: { isPublic: true },
include: {
owner: {
select: {
id: true,
name: true,
email: true,
role: true,
isProfilePublic: true,
profilePicture: true,
specialization: true,
yearsOfExperience: true,
totalCases: true,
wonCases: true,
lostCases: true,
averageRating: true,
hourlyRate: true,
totalBadges: true,
level: true,
isVerified: true,
verificationStatus: true
}
},
members: {
select: {
id: true,
name: true,
email: true,
role: true,
profilePicture: true,
specialization: true,
yearsOfExperience: true,
totalCases: true,
wonCases: true,
lostCases: true,
averageRating: true,
hourlyRate: true,
totalBadges: true,
level: true,
isVerified: true,
verificationStatus: true,
isProfilePublic: true
}
}
},
orderBy: { createdAt: 'desc' }
});
businesses = businessProfiles.map((business) => {
const allLawyers = [business.owner, ...business.members]
.filter(lawyer => lawyer.isProfilePublic)
.filter((lawyer, index, self) =>
index === self.findIndex(l => l.id === lawyer.id)
);
const lawyersWithStats = allLawyers.map(lawyer => ({
...lawyer,
winRate: lawyer.totalCases > 0 ? (lawyer.wonCases / lawyer.totalCases) * 100 : 0
}));
const firmStats = {
totalLawyers: lawyersWithStats.length,
totalCases: lawyersWithStats.reduce((sum, lawyer) => sum + lawyer.totalCases, 0),
totalWonCases: lawyersWithStats.reduce((sum, lawyer) => sum + lawyer.wonCases, 0),
totalLostCases: lawyersWithStats.reduce((sum, lawyer) => sum + lawyer.lostCases, 0),
averageWinRate: lawyersWithStats.length > 0 ? lawyersWithStats.reduce((sum, lawyer) => sum + lawyer.winRate, 0) / lawyersWithStats.length : 0,
averageRating: lawyersWithStats.length > 0 ? lawyersWithStats.reduce((sum, lawyer) => sum + (lawyer.averageRating || 0), 0) / lawyersWithStats.length : 0,
averageHourlyRate: lawyersWithStats.length > 0 ? lawyersWithStats.reduce((sum, lawyer) => sum + (lawyer.hourlyRate || 0), 0) / lawyersWithStats.length : 0,
totalBadges: lawyersWithStats.reduce((sum, lawyer) => sum + lawyer.totalBadges, 0),
averageLevel: lawyersWithStats.length > 0 ? lawyersWithStats.reduce((sum, lawyer) => sum + lawyer.level, 0) / lawyersWithStats.length : 0
};
return { ...business, lawyers: lawyersWithStats, firmStats };
});
} else {
try {
const response = await fetch(`${process.env.NEXTAUTH_URL || 'http://localhost:3000'}/api/public/business-profiles`);
businesses = await response.json();
} catch (error) {
businesses = [];
}
}
return { props: { businesses: serializeDates(businesses) } };
};
export default BusinessProfilesPage;