T.ME/BIBIL_0DAY
CasperSecurity


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/admin/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/private_html/src/pages/admin/business-profiles.tsx
import React, { useState, useEffect } 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, CheckCircle, XCircle, Clock, Eye, 
  User, Mail, Phone, Globe, MapPin, Shield
} from 'lucide-react';
import toast from 'react-hot-toast';

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;
  verificationStatus: 'PENDING' | 'VERIFIED' | 'REJECTED';
  verificationNote?: string;
  verifiedAt?: string;
  createdAt: string;
  updatedAt: string;
  user: {
    id: string;
    name: string;
    email: string;
    role: string;
  };
}

const BusinessProfilesAdminPage: React.FC = () => {
  const { data: session, status } = useSession();
  const router = useRouter();
  const [businessProfiles, setBusinessProfiles] = useState<BusinessProfile[]>([]);
  const [loading, setLoading] = useState(true);
  const [selectedProfile, setSelectedProfile] = useState<BusinessProfile | null>(null);
  const [verificationNote, setVerificationNote] = useState('');
  const [processing, setProcessing] = useState<string | null>(null);

  useEffect(() => {
    if (status === 'authenticated' && session?.user?.role === 'ADMIN') {
      fetchBusinessProfiles();
    }
  }, [status, session]);

  const fetchBusinessProfiles = async () => {
    try {
      setLoading(true);
      const response = await fetch('/api/admin/business-profiles');
      if (response.ok) {
        const data = await response.json();
        setBusinessProfiles(data);
      } else {
        toast.error('Failed to fetch business profiles');
      }
    } catch (error) {
      console.error('Error fetching business profiles:', error);
      toast.error('Error fetching business profiles');
    } finally {
      setLoading(false);
    }
  };

  const handleVerification = async (profileId: string, status: 'VERIFIED' | 'REJECTED') => {
    setProcessing(profileId);
    try {
      const response = await fetch(`/api/admin/business-profiles/${profileId}/verify`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          status,
          note: verificationNote
        })
      });

      if (response.ok) {
        toast.success(`Business profile ${status.toLowerCase()}`);
        setVerificationNote('');
        setSelectedProfile(null);
        fetchBusinessProfiles();
      } else {
        const error = await response.json();
        toast.error(error.message || 'Failed to update verification status');
      }
    } catch (error) {
      toast.error('Error updating verification status');
    } finally {
      setProcessing(null);
    }
  };

  const getStatusIcon = (status: string) => {
    switch (status) {
      case 'VERIFIED':
        return <CheckCircle className="h-5 w-5 text-green-500" />;
      case 'REJECTED':
        return <XCircle className="h-5 w-5 text-red-500" />;
      default:
        return <Clock className="h-5 w-5 text-yellow-500" />;
    }
  };

  const getStatusColor = (status: string) => {
    switch (status) {
      case 'VERIFIED':
        return 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400';
      case 'REJECTED':
        return 'bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400';
      default:
        return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400';
    }
  };

  const pendingProfiles = businessProfiles.filter(p => p.verificationStatus === 'PENDING');
  const verifiedProfiles = businessProfiles.filter(p => p.verificationStatus === 'VERIFIED');
  const rejectedProfiles = businessProfiles.filter(p => p.verificationStatus === 'REJECTED');

  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-7xl mx-auto px-4 py-8">
        {/* Header */}
        <div className="mb-8">
          <h1 className="text-3xl font-bold text-gray-900 dark:text-white">Business Profile Verification</h1>
          <p className="text-gray-600 dark:text-gray-400 mt-2">
            Review and verify business profiles submitted by users
          </p>
        </div>

        {/* Statistics */}
        <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
          <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"
          >
            <div className="flex items-center">
              <Clock className="h-8 w-8 text-yellow-500 mr-4" />
              <div>
                <p className="text-sm font-medium text-gray-600 dark:text-gray-400">Pending Review</p>
                <p className="text-2xl font-bold text-gray-900 dark:text-white">{pendingProfiles.length}</p>
              </div>
            </div>
          </motion.div>

          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ delay: 0.1 }}
            className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6"
          >
            <div className="flex items-center">
              <CheckCircle className="h-8 w-8 text-green-500 mr-4" />
              <div>
                <p className="text-sm font-medium text-gray-600 dark:text-gray-400">Verified</p>
                <p className="text-2xl font-bold text-gray-900 dark:text-white">{verifiedProfiles.length}</p>
              </div>
            </div>
          </motion.div>

          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ delay: 0.2 }}
            className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6"
          >
            <div className="flex items-center">
              <XCircle className="h-8 w-8 text-red-500 mr-4" />
              <div>
                <p className="text-sm font-medium text-gray-600 dark:text-gray-400">Rejected</p>
                <p className="text-2xl font-bold text-gray-900 dark:text-white">{rejectedProfiles.length}</p>
              </div>
            </div>
          </motion.div>
        </div>

        {/* Pending Profiles */}
        <div className="mb-8">
          <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">Pending Review</h2>
          {pendingProfiles.length === 0 ? (
            <div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6 text-center">
              <Clock className="h-12 w-12 text-gray-400 mx-auto mb-4" />
              <p className="text-gray-600 dark:text-gray-400">No business profiles pending review</p>
            </div>
          ) : (
            <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
              {pendingProfiles.map((profile) => (
                <motion.div
                  key={profile.id}
                  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"
                >
                  <div className="flex items-start justify-between mb-4">
                    <div className="flex items-center">
                      {profile.logo ? (
                        <img
                          src={profile.logo}
                          alt={profile.businessName}
                          className="w-12 h-12 rounded-lg object-cover mr-3"
                        />
                      ) : (
                        <div className="w-12 h-12 bg-gray-100 dark:bg-gray-700 rounded-lg flex items-center justify-center mr-3">
                          <Building2 className="h-6 w-6 text-gray-400" />
                        </div>
                      )}
                      <div>
                        <h3 className="font-semibold text-gray-900 dark:text-white">{profile.businessName}</h3>
                        <p className="text-sm text-gray-600 dark:text-gray-400">{profile.businessType}</p>
                      </div>
                    </div>
                    <span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(profile.verificationStatus)}`}>
                      {getStatusIcon(profile.verificationStatus)}
                      <span className="ml-1">{profile.verificationStatus}</span>
                    </span>
                  </div>

                  <div className="space-y-2 mb-4">
                    <div className="flex items-center text-sm text-gray-600 dark:text-gray-400">
                      <User className="h-4 w-4 mr-2" />
                      {profile.user.name} ({profile.user.email})
                    </div>
                    {profile.industry && (
                      <div className="flex items-center text-sm text-gray-600 dark:text-gray-400">
                        <Building2 className="h-4 w-4 mr-2" />
                        {profile.industry}
                      </div>
                    )}
                    {profile.website && (
                      <div className="flex items-center text-sm text-gray-600 dark:text-gray-400">
                        <Globe className="h-4 w-4 mr-2" />
                        {profile.website}
                      </div>
                    )}
                    {profile.phone && (
                      <div className="flex items-center text-sm text-gray-600 dark:text-gray-400">
                        <Phone className="h-4 w-4 mr-2" />
                        {profile.phone}
                      </div>
                    )}
                  </div>

                  {profile.description && (
                    <p className="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">
                      {profile.description}
                    </p>
                  )}

                  <div className="flex space-x-2">
                    <button
                      onClick={() => setSelectedProfile(profile)}
                      className="flex-1 px-3 py-2 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
                    >
                      <Eye className="h-4 w-4 mr-1 inline" />
                      Review
                    </button>
                  </div>
                </motion.div>
              ))}
            </div>
          )}
        </div>

        {/* Verification Modal */}
        {selectedProfile && (
          <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
            <motion.div
              initial={{ opacity: 0, scale: 0.9 }}
              animate={{ opacity: 1, scale: 1 }}
              className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto"
            >
              <div className="p-6">
                <div className="flex items-center justify-between mb-6">
                  <h2 className="text-xl font-semibold text-gray-900 dark:text-white">
                    Review Business Profile
                  </h2>
                  <button
                    onClick={() => setSelectedProfile(null)}
                    className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
                  >
                    <XCircle className="h-6 w-6" />
                  </button>
                </div>

                <div className="space-y-6">
                  {/* Business Info */}
                  <div>
                    <h3 className="font-semibold text-gray-900 dark:text-white mb-3">Business Information</h3>
                    <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">Business Name</label>
                        <p className="text-sm text-gray-900 dark:text-white">{selectedProfile.businessName}</p>
                      </div>
                      <div>
                        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Business Type</label>
                        <p className="text-sm text-gray-900 dark:text-white">{selectedProfile.businessType}</p>
                      </div>
                      {selectedProfile.industry && (
                        <div>
                          <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Industry</label>
                          <p className="text-sm text-gray-900 dark:text-white">{selectedProfile.industry}</p>
                        </div>
                      )}
                      {selectedProfile.employeeCount && (
                        <div>
                          <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Employees</label>
                          <p className="text-sm text-gray-900 dark:text-white">{selectedProfile.employeeCount}</p>
                        </div>
                      )}
                    </div>
                  </div>

                  {/* Contact Info */}
                  <div>
                    <h3 className="font-semibold text-gray-900 dark:text-white mb-3">Contact Information</h3>
                    <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                      {selectedProfile.website && (
                        <div>
                          <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Website</label>
                          <p className="text-sm text-gray-900 dark:text-white">{selectedProfile.website}</p>
                        </div>
                      )}
                      {selectedProfile.phone && (
                        <div>
                          <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Phone</label>
                          <p className="text-sm text-gray-900 dark:text-white">{selectedProfile.phone}</p>
                        </div>
                      )}
                      {selectedProfile.email && (
                        <div>
                          <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Email</label>
                          <p className="text-sm text-gray-900 dark:text-white">{selectedProfile.email}</p>
                        </div>
                      )}
                      {selectedProfile.address && (
                        <div className="md:col-span-2">
                          <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Address</label>
                          <p className="text-sm text-gray-900 dark:text-white">{selectedProfile.address}</p>
                        </div>
                      )}
                    </div>
                  </div>

                  {/* Description */}
                  {selectedProfile.description && (
                    <div>
                      <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Description</label>
                      <p className="text-sm text-gray-900 dark:text-white">{selectedProfile.description}</p>
                    </div>
                  )}

                  {/* Verification Note */}
                  <div>
                    <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
                      Verification Note (Optional)
                    </label>
                    <textarea
                      value={verificationNote}
                      onChange={(e) => setVerificationNote(e.target.value)}
                      rows={3}
                      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="Add a note about the verification decision..."
                    />
                  </div>

                  {/* Action Buttons */}
                  <div className="flex space-x-3 pt-4">
                    <button
                      onClick={() => handleVerification(selectedProfile.id, 'VERIFIED')}
                      disabled={processing === selectedProfile.id}
                      className="flex-1 px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
                    >
                      <CheckCircle className="h-4 w-4 mr-2" />
                      {processing === selectedProfile.id ? 'Processing...' : 'Approve'}
                    </button>
                    <button
                      onClick={() => handleVerification(selectedProfile.id, 'REJECTED')}
                      disabled={processing === selectedProfile.id}
                      className="flex-1 px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
                    >
                      <XCircle className="h-4 w-4 mr-2" />
                      {processing === selectedProfile.id ? 'Processing...' : 'Reject'}
                    </button>
                  </div>
                </div>
              </div>
            </motion.div>
          </div>
        )}
      </div>
    </LayoutWithSidebar>
  );
};

export default BusinessProfilesAdminPage; 

CasperSecurity Mini