Spaces:
Sleeping
Sleeping
| import { useState, useEffect } from 'react'; | |
| import { Upload, CheckCircle, AlertCircle, Loader2, FileCheck } from 'lucide-react'; | |
| import api from '../api/api'; | |
| const VerificationPanel = ({ photographerId }) => { | |
| const [verification, setVerification] = useState(null); | |
| const [loading, setLoading] = useState(true); | |
| const [uploadingID, setUploadingID] = useState(false); | |
| const [uploadingPortfolio, setUploadingPortfolio] = useState(false); | |
| useEffect(() => { | |
| fetchVerificationStatus(); | |
| }, []); | |
| const fetchVerificationStatus = async () => { | |
| try { | |
| const res = await api.get(`/verification/status/${photographerId}`); | |
| setVerification(res.data); | |
| } catch (err) { | |
| console.error('Failed to load verification status:', err); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const handleIDUpload = async (e) => { | |
| const file = e.target.files?.[0]; | |
| if (!file) return; | |
| // In real app, upload to Cloudinary or similar | |
| setUploadingID(true); | |
| try { | |
| // Mock upload - in production use FormData with multipart | |
| const idUrl = URL.createObjectURL(file); | |
| const res = await api.post('/verification/submit', { | |
| idDocumentUrl: idUrl, | |
| }); | |
| setVerification(res.data); | |
| alert('ID uploaded successfully!'); | |
| } catch (err) { | |
| alert('Failed to upload ID'); | |
| } finally { | |
| setUploadingID(false); | |
| } | |
| }; | |
| const handlePortfolioUpload = async (e) => { | |
| const files = e.target.files; | |
| if (!files || files.length === 0) return; | |
| setUploadingPortfolio(true); | |
| try { | |
| const portfolioUrls = Array.from(files).map(file => URL.createObjectURL(file)); | |
| const res = await api.post('/verification/submit', { | |
| portfolioUrls, | |
| }); | |
| setVerification(res.data); | |
| alert(`${files.length} portfolio images uploaded!`); | |
| } catch (err) { | |
| alert('Failed to upload portfolio images'); | |
| } finally { | |
| setUploadingPortfolio(false); | |
| } | |
| }; | |
| if (loading) { | |
| return ( | |
| <div className="flex items-center justify-center py-12"> | |
| <Loader2 className="animate-spin text-blue-600" size={32} /> | |
| </div> | |
| ); | |
| } | |
| const statusColor = { | |
| verified: 'green', | |
| pending: 'amber', | |
| rejected: 'red', | |
| not_started: 'gray', | |
| }; | |
| const statusIcon = { | |
| verified: <CheckCircle className="text-green-600" size={32} />, | |
| pending: <AlertCircle className="text-amber-600" size={32} />, | |
| rejected: <AlertCircle className="text-red-600" size={32} />, | |
| not_started: <AlertCircle className="text-gray-600" size={32} />, | |
| }; | |
| const status = verification?.status || 'not_started'; | |
| const color = statusColor[status]; | |
| return ( | |
| <div> | |
| <h2 className="text-2xl font-bold mb-6">📋 Photographer Verification</h2> | |
| {/* Status Card */} | |
| <div className={`bg-${color}-50 border border-${color}-200 rounded-lg p-6 mb-6`}> | |
| <div className="flex items-center gap-4"> | |
| <div>{statusIcon[status]}</div> | |
| <div> | |
| <h3 className={`text-lg font-bold text-${color}-900 capitalize`}> | |
| Status: {status.replace('_', ' ')} | |
| </h3> | |
| {verification?.submittedAt && ( | |
| <p className={`text-sm text-${color}-700`}> | |
| Submitted: {new Date(verification.submittedAt).toLocaleDateString()} | |
| </p> | |
| )} | |
| {verification?.verifiedAt && ( | |
| <p className={`text-sm text-${color}-700`}> | |
| Verified: {new Date(verification.verifiedAt).toLocaleDateString()} | |
| </p> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| {/* ID Verification Section */} | |
| <div className="bg-white border border-gray-200 rounded-lg p-6 mb-6"> | |
| <h3 className="text-lg font-bold mb-4 flex items-center gap-2"> | |
| <FileCheck size={20} /> | |
| Step 1: ID Verification | |
| </h3> | |
| {verification?.idDocument ? ( | |
| <div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-4"> | |
| <div className="flex items-center gap-2 text-green-700 font-semibold"> | |
| <CheckCircle size={20} /> | |
| ID Document Verified ✓ | |
| </div> | |
| {verification?.idVerifiedAt && ( | |
| <p className="text-sm text-green-600 mt-1"> | |
| Verified on {new Date(verification.idVerifiedAt).toLocaleDateString()} | |
| </p> | |
| )} | |
| </div> | |
| ) : ( | |
| <div> | |
| <p className="text-gray-700 mb-4"> | |
| Upload a clear copy of your government-issued ID (Aadhar, PAN, Passport, etc.) | |
| </p> | |
| <label className="flex items-center justify-center border-2 border-dashed border-gray-300 rounded-lg p-8 hover:border-blue-500 cursor-pointer transition"> | |
| <div className="text-center"> | |
| <Upload className="mx-auto mb-2 text-gray-400" size={32} /> | |
| <p className="font-semibold text-gray-700">Click to upload ID</p> | |
| <p className="text-sm text-gray-500">or drag and drop</p> | |
| </div> | |
| <input | |
| type="file" | |
| onChange={handleIDUpload} | |
| disabled={uploadingID} | |
| className="hidden" | |
| accept="image/*,.pdf" | |
| /> | |
| </label> | |
| {uploadingID && <p className="text-sm text-blue-600 mt-2">Uploading...</p>} | |
| </div> | |
| )} | |
| </div> | |
| {/* Portfolio Verification Section */} | |
| <div className="bg-white border border-gray-200 rounded-lg p-6 mb-6"> | |
| <h3 className="text-lg font-bold mb-4 flex items-center gap-2"> | |
| <FileCheck size={20} /> | |
| Step 2: Portfolio Verification | |
| </h3> | |
| {verification?.portfolioSamples?.length > 0 ? ( | |
| <div> | |
| <p className="text-green-700 font-semibold mb-4"> | |
| ✓ {verification.portfolioSamples.length} portfolio images uploaded | |
| </p> | |
| <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4"> | |
| {verification.portfolioSamples.map((sample, idx) => ( | |
| <div key={idx} className="aspect-square bg-gray-200 rounded-lg overflow-hidden"> | |
| <img | |
| src={sample.url} | |
| alt={`Portfolio ${idx + 1}`} | |
| className="w-full h-full object-cover" | |
| /> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| ) : ( | |
| <div> | |
| <p className="text-gray-700 mb-4"> | |
| Upload 3-5 of your best portfolio samples to showcase your work quality. | |
| </p> | |
| <label className="flex items-center justify-center border-2 border-dashed border-gray-300 rounded-lg p-8 hover:border-blue-500 cursor-pointer transition"> | |
| <div className="text-center"> | |
| <Upload className="mx-auto mb-2 text-gray-400" size={32} /> | |
| <p className="font-semibold text-gray-700">Click to upload portfolio</p> | |
| <p className="text-sm text-gray-500">Upload multiple images (PNG, JPG)</p> | |
| </div> | |
| <input | |
| type="file" | |
| onChange={handlePortfolioUpload} | |
| disabled={uploadingPortfolio} | |
| multiple | |
| className="hidden" | |
| accept="image/*" | |
| /> | |
| </label> | |
| {uploadingPortfolio && <p className="text-sm text-blue-600 mt-2">Uploading...</p>} | |
| </div> | |
| )} | |
| </div> | |
| {/* Admin Notes */} | |
| {verification?.adminNotes && ( | |
| <div className="bg-blue-50 border border-blue-200 rounded-lg p-6"> | |
| <h3 className="font-bold text-blue-900 mb-2">Admin Notes</h3> | |
| <p className="text-blue-800">{verification.adminNotes}</p> | |
| </div> | |
| )} | |
| {/* Rejection Info */} | |
| {status === 'rejected' && verification?.rejectionReason && ( | |
| <div className="bg-red-50 border border-red-200 rounded-lg p-6 mb-6"> | |
| <h3 className="font-bold text-red-900 mb-2">Rejection Reason</h3> | |
| <p className="text-red-800 mb-4">{verification.rejectionReason}</p> | |
| {verification?.resubmissionAllowed && ( | |
| <button className="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 font-medium"> | |
| Resubmit Application | |
| </button> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default VerificationPanel; | |