| import { useIdentityVerification } from "@/hooks/useIdentityVerification"; | |
| import { useProfile } from "@/hooks/useProfile"; | |
| import { Mail, Building, Upload, CheckCircle2, XCircle, Loader2 } from "lucide-react"; | |
| import { useRef, useState } from "react"; | |
| import { toast } from "react-toastify"; | |
| interface IdentityVerificationPanelProps { | |
| username: string; | |
| } | |
| export function IdentityVerificationPanel({ username }: IdentityVerificationPanelProps) { | |
| const { profile, isLoading: profileLoading, error: profileError } = useProfile(username); | |
| const { verifying, verifyEmail, uploadCVForVetting } = useIdentityVerification(username); | |
| const fileRef = useRef<HTMLInputElement>(null); | |
| const [uploading, setUploading] = useState(false); | |
| if (profileLoading) { | |
| return ( | |
| <div className="space-y-4"> | |
| <h2 className="text-xl font-bold gradient-text">Identity Verification</h2> | |
| <div className="glass rounded-xl p-8 flex justify-center"> | |
| <Loader2 className="w-6 h-6 animate-spin text-primary" /> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| if (profileError || !profile) { | |
| return ( | |
| <div className="space-y-4"> | |
| <h2 className="text-xl font-bold gradient-text">Identity Verification</h2> | |
| <div className="glass rounded-xl p-4 text-red-500 text-center"> | |
| <XCircle className="w-8 h-8 mx-auto mb-2" /> | |
| <p>Failed to load profile data. Please try again later.</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const file = e.target.files?.[0]; | |
| if (!file) return; | |
| const allowedTypes = ["application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]; | |
| if (!allowedTypes.includes(file.type)) { | |
| toast.error("Please upload a PDF or Word document (.pdf, .doc, .docx)"); | |
| return; | |
| } | |
| if (file.size > 5 * 1024 * 1024) { | |
| toast.error("File size must be less than 5MB"); | |
| return; | |
| } | |
| setUploading(true); | |
| try { | |
| await uploadCVForVetting(file); | |
| toast.success("CV uploaded successfully. Our team will review it shortly."); | |
| } catch { | |
| toast.error("Failed to upload CV. Please try again."); | |
| } finally { | |
| setUploading(false); | |
| if (fileRef.current) fileRef.current.value = ""; | |
| } | |
| }; | |
| return ( | |
| <div className="space-y-4"> | |
| <h2 className="text-xl font-bold gradient-text">Identity Verification</h2> | |
| <div className="glass rounded-xl p-4 flex items-center justify-between"> | |
| <div className="flex items-center gap-3"> | |
| <Mail className="w-5 h-5 text-primary" /> | |
| <div> | |
| <p className="font-semibold text-sm">Email</p> | |
| <p className="text-xs text-gray-500">{profile.email || "No email provided"}</p> | |
| </div> | |
| </div> | |
| {profile.isEmailVerified ? ( | |
| <span className="flex items-center gap-1 text-green-600 text-sm"> | |
| <CheckCircle2 className="w-4 h-4" /> Verified | |
| </span> | |
| ) : ( | |
| <button onClick={verifyEmail} disabled={verifying} className="btn btn-secondary btn-sm"> | |
| {verifying ? <Loader2 className="w-4 h-4 animate-spin" /> : "Verify"} | |
| </button> | |
| )} | |
| </div> | |
| <div className="glass rounded-xl p-4 flex items-center justify-between"> | |
| <div className="flex items-center gap-3"> | |
| <img src="https://orcid.org/sites/default/files/images/orcid_16x16.png" className="w-5 h-5" alt="ORCID" /> | |
| <div> | |
| <p className="font-semibold text-sm">ORCID</p> | |
| <p className="text-xs text-gray-500">{profile.orcid || "Not connected"}</p> | |
| </div> | |
| </div> | |
| {profile.orcid ? ( | |
| <span className="flex items-center gap-1 text-green-600 text-sm"> | |
| <CheckCircle2 className="w-4 h-4" /> Connected | |
| </span> | |
| ) : ( | |
| <a href="/oauth2/authorization/orcid" className="btn btn-secondary btn-sm"> | |
| Connect ORCID | |
| </a> | |
| )} | |
| </div> | |
| <div className="glass rounded-xl p-4"> | |
| <div className="flex items-center gap-3 mb-3"> | |
| <Upload className="w-5 h-5 text-primary" /> | |
| <div> | |
| <p className="font-semibold text-sm">Institutional Vetting</p> | |
| <p className="text-xs text-gray-500">Upload your CV for manual verification</p> | |
| </div> | |
| </div> | |
| <input | |
| ref={fileRef} | |
| type="file" | |
| accept=".pdf,.doc,.docx" | |
| className="hidden" | |
| onChange={handleFileUpload} | |
| disabled={uploading} | |
| /> | |
| <button onClick={() => fileRef.current?.click()} disabled={verifying || uploading} className="btn btn-secondary btn-sm"> | |
| {(verifying || uploading) ? <Loader2 className="w-4 h-4 animate-spin" /> : "Upload CV"} | |
| </button> | |
| {(profile as any).isVetted && ( | |
| <div className="mt-3 text-xs text-green-600 flex items-center gap-1"> | |
| <CheckCircle2 className="w-3 h-3" /> Your institution has verified your identity | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 5.16 kB
- Xet hash:
- 75aa1320005f0b3b03fb80a746648c31ee7c7e35fb09ce713ba71b282dcd3be4
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.