![]() 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/private_html/src/pages/user/ |
import React, { useState, useEffect, useRef } from 'react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import LayoutWithSidebar from '../../components/LayoutWithSidebar';
import { motion } from 'framer-motion';
import {
Building2, Globe, Phone, Mail, MapPin, FileText,
Users, DollarSign, Shield, Eye, EyeOff, Save, Upload
} from 'lucide-react';
import toast from 'react-hot-toast';
import ImageModal from '../../components/ui/ImageModal';
import { useRequireRole, USER_ROLES } from '../../lib/auth-utils';
interface BusinessProfile {
id: string;
userId: string;
businessName: string;
businessType: string;
industry?: string;
description?: string;
logo?: string;
website?: string;
phone?: string;
email?: string;
address?: string;
registrationNumber?: string;
taxId?: string;
employeeCount?: string;
annualRevenue?: string;
isPublic: boolean;
isVerified: boolean;
createdAt: string;
updatedAt: string;
}
const BusinessProfilePage: React.FC = () => {
const { data: session, status } = useSession();
const router = useRouter();
const fileInputRef = useRef<HTMLInputElement>(null);
const [businessProfile, setBusinessProfile] = useState<BusinessProfile | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [uploadingLogo, setUploadingLogo] = useState(false);
const [selectedImage, setSelectedImage] = useState<string | null>(null);
const [formData, setFormData] = useState({
businessName: '',
businessType: 'CORPORATION',
industry: '',
description: '',
website: '',
phone: '',
email: '',
address: '',
registrationNumber: '',
taxId: '',
employeeCount: '',
annualRevenue: '',
isPublic: false
});
// Add permission guard
const { isAuthorized } = useRequireRole([
USER_ROLES.CLIENT,
USER_ROLES.USER,
USER_ROLES.LAWYER,
USER_ROLES.ADMIN,
USER_ROLES.SUPERADMIN
], '/auth/login');
useEffect(() => {
if (status === 'authenticated') {
fetchBusinessProfile();
}
}, [status]);
const fetchBusinessProfile = async () => {
try {
setLoading(true);
const response = await fetch('/api/user/business-profile');
if (response.ok) {
const data = await response.json();
setBusinessProfile(data);
if (data) {
setFormData({
businessName: data.businessName || '',
businessType: data.businessType || 'CORPORATION',
industry: data.industry || '',
description: data.description || '',
website: data.website || '',
phone: data.phone || '',
email: data.email || '',
address: data.address || '',
registrationNumber: data.registrationNumber || '',
taxId: data.taxId || '',
employeeCount: data.employeeCount || '',
annualRevenue: data.annualRevenue || '',
isPublic: data.isPublic || false
});
}
}
} catch (error) {
console.error('Error fetching business profile:', error);
} finally {
setLoading(false);
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value, type } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? (e.target as HTMLInputElement).checked : value
}));
};
const handleLogoUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
if (file.size > 5 * 1024 * 1024) {
toast.error('Logo file size must be less than 5MB');
return;
}
setUploadingLogo(true);
const formData = new FormData();
formData.append('logo', file);
try {
const response = await fetch('/api/user/business-profile/logo', {
method: 'POST',
body: formData
});
if (response.ok) {
const data = await response.json();
setBusinessProfile(prev => prev ? { ...prev, logo: data.logoUrl } : null);
toast.success('Logo uploaded successfully');
} else {
toast.error('Failed to upload logo');
}
} catch (error) {
toast.error('Error uploading logo');
} finally {
setUploadingLogo(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSaving(true);
try {
const response = await fetch('/api/user/business-profile', {
method: businessProfile ? 'PUT' : 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
if (response.ok) {
const data = await response.json();
setBusinessProfile(data);
toast.success(businessProfile ? 'Business profile updated successfully' : 'Business profile created successfully');
fetchBusinessProfile();
} else {
const error = await response.json();
toast.error(error.message || 'Failed to save business profile');
}
} catch (error) {
toast.error('Error saving business profile');
} finally {
setSaving(false);
}
};
const businessTypes = [
{ value: 'CORPORATION', label: 'Corporation' },
{ value: 'LLC', label: 'Limited Liability Company (LLC)' },
{ value: 'PARTNERSHIP', label: 'Partnership' },
{ value: 'NONPROFIT', label: 'Non-Profit Organization' },
{ value: 'SOLE_PROPRIETORSHIP', label: 'Sole Proprietorship' }
];
const employeeCounts = [
{ value: '1-10', label: '1-10 employees' },
{ value: '11-50', label: '11-50 employees' },
{ value: '51-200', label: '51-200 employees' },
{ value: '201-500', label: '201-500 employees' },
{ value: '500+', label: '500+ employees' }
];
const revenueRanges = [
{ value: 'Under $100K', label: 'Under $100,000' },
{ value: '$100K-$500K', label: '$100,000 - $500,000' },
{ value: '$500K-$1M', label: '$500,000 - $1,000,000' },
{ value: '$1M-$5M', label: '$1,000,000 - $5,000,000' },
{ value: '$5M+', label: '$5,000,000+' }
];
if (loading) {
return (
<LayoutWithSidebar>
<div className="flex items-center justify-center min-h-96">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-purple-600"></div>
</div>
</LayoutWithSidebar>
);
}
return (
<LayoutWithSidebar>
<div className="max-w-4xl mx-auto px-4 py-8">
{/* Header */}
<div className="mb-8">
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Business Profile</h1>
<p className="text-gray-600 dark:text-gray-400 mt-2">
{businessProfile ? 'Manage your business profile' : 'Create your business profile'}
</p>
</div>
{businessProfile && (
<div className="flex space-x-3">
<button
type="button"
onClick={() => router.push(`/business/${businessProfile.id}`)}
className="px-4 py-2 text-blue-600 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md hover:bg-blue-100 dark:hover:bg-blue-900/30 transition-colors"
>
👁️ View Public Profile
</button>
</div>
)}
</div>
</div>
<form onSubmit={handleSubmit}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6"
>
{/* Logo Section */}
<div className="mb-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
<Building2 className="h-5 w-5 mr-2" />
Business Logo
</h2>
<div className="flex items-center space-x-6">
<div className="relative">
{businessProfile?.logo ? (
<div
className="cursor-pointer"
onClick={() => setSelectedImage(businessProfile.logo!)}
>
<img
src={businessProfile.logo}
alt="Business Logo"
className="w-24 h-24 rounded-lg border border-gray-200 dark:border-gray-600 object-cover"
/>
</div>
) : (
<div className="w-24 h-24 bg-gray-100 dark:bg-gray-700 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600 flex items-center justify-center">
<Building2 className="h-8 w-8 text-gray-400" />
</div>
)}
{uploadingLogo && (
<div className="absolute inset-0 bg-black bg-opacity-50 rounded-lg flex items-center justify-center">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-white"></div>
</div>
)}
</div>
<div>
<button
type="button"
onClick={() => fileInputRef.current?.click()}
disabled={uploadingLogo}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed text-sm flex items-center"
>
<Upload className="h-4 w-4 mr-2" />
{businessProfile?.logo ? 'Change Logo' : 'Upload Logo'}
</button>
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleLogoUpload}
className="hidden"
/>
<p className="text-xs text-gray-500 mt-2">Max 5MB. JPG, PNG, or GIF.</p>
</div>
</div>
</div>
{/* Basic Information */}
<div className="mb-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
<FileText className="h-5 w-5 mr-2" />
Basic Information
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Business Name *
</label>
<input
type="text"
name="businessName"
value={formData.businessName}
onChange={handleInputChange}
required
className="w-full px-3 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"
placeholder="Enter business name"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Business Type *
</label>
<select
name="businessType"
value={formData.businessType}
onChange={handleInputChange}
required
className="w-full px-3 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"
>
{businessTypes.map(type => (
<option key={type.value} value={type.value}>
{type.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Industry
</label>
<input
type="text"
name="industry"
value={formData.industry}
onChange={handleInputChange}
className="w-full px-3 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"
placeholder="e.g., Legal Services, Technology"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Number of Employees
</label>
<select
name="employeeCount"
value={formData.employeeCount}
onChange={handleInputChange}
className="w-full px-3 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="">Select employee count</option>
{employeeCounts.map(count => (
<option key={count.value} value={count.value}>
{count.label}
</option>
))}
</select>
</div>
</div>
<div className="mt-6">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Business Description
</label>
<textarea
name="description"
value={formData.description}
onChange={handleInputChange}
rows={4}
className="w-full px-3 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"
placeholder="Describe your business, services, and mission..."
/>
</div>
</div>
{/* Contact Information */}
<div className="mb-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
<Phone className="h-5 w-5 mr-2" />
Contact Information
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Website
</label>
<div className="relative">
<Globe className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<input
type="url"
name="website"
value={formData.website}
onChange={handleInputChange}
className="w-full pl-10 pr-3 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"
placeholder="https://example.com"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Phone Number
</label>
<div className="relative">
<Phone className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<input
type="tel"
name="phone"
value={formData.phone}
onChange={handleInputChange}
className="w-full pl-10 pr-3 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"
placeholder="(555) 123-4567"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Email Address
</label>
<div className="relative">
<Mail className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<input
type="email"
name="email"
value={formData.email}
onChange={handleInputChange}
className="w-full pl-10 pr-3 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"
placeholder="contact@business.com"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Annual Revenue
</label>
<select
name="annualRevenue"
value={formData.annualRevenue}
onChange={handleInputChange}
className="w-full px-3 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="">Select revenue range</option>
{revenueRanges.map(revenue => (
<option key={revenue.value} value={revenue.value}>
{revenue.label}
</option>
))}
</select>
</div>
</div>
<div className="mt-6">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Address
</label>
<div className="relative">
<MapPin className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<textarea
name="address"
value={formData.address}
onChange={handleInputChange}
rows={3}
className="w-full pl-10 pr-3 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"
placeholder="Enter business address..."
/>
</div>
</div>
</div>
{/* Legal Information */}
<div className="mb-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
<Shield className="h-5 w-5 mr-2" />
Legal Information
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Registration Number
</label>
<input
type="text"
name="registrationNumber"
value={formData.registrationNumber}
onChange={handleInputChange}
className="w-full px-3 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"
placeholder="Business registration number"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Tax ID
</label>
<input
type="text"
name="taxId"
value={formData.taxId}
onChange={handleInputChange}
className="w-full px-3 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"
placeholder="Tax identification number"
/>
</div>
</div>
</div>
{/* Privacy Settings */}
<div className="mb-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
{formData.isPublic ? <Eye className="h-5 w-5 mr-2" /> : <EyeOff className="h-5 w-5 mr-2" />}
Privacy Settings
</h2>
<div className="flex items-center">
<input
type="checkbox"
name="isPublic"
checked={formData.isPublic}
onChange={handleInputChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label className="ml-2 block text-sm text-gray-700 dark:text-gray-300">
Make this business profile public
</label>
</div>
<p className="text-xs text-gray-500 mt-2">
Public profiles are visible to all users. Private profiles are only visible to you and your team.
</p>
</div>
{/* Submit Button */}
<div className="flex justify-end">
<button
type="submit"
disabled={saving}
className="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
>
<Save className="h-4 w-4 mr-2" />
{saving ? 'Saving...' : (businessProfile ? 'Update Profile' : 'Create Profile')}
</button>
</div>
</motion.div>
</form>
</div>
{/* Image Modal */}
<ImageModal
isOpen={!!selectedImage}
onClose={() => setSelectedImage(null)}
imageSrc={selectedImage || ''}
imageAlt="Business Logo"
title="Business Logo"
/>
</LayoutWithSidebar>
);
};
export default BusinessProfilePage;