396 lines
20 KiB
TypeScript
396 lines
20 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import Link from "next/link"
|
|
import { ArrowLeft, CheckCircle, Loader2, ShieldCheck, Upload, User, ChevronRight, Info, Shield } from "lucide-react"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"
|
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
|
|
import { Checkbox } from "@/components/ui/checkbox"
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
|
import { useWalletStore } from "@/lib/wallet-store"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Separator } from "@/components/ui/separator"
|
|
import { usePrivadoId } from "@/lib/privado-id"
|
|
import { countries } from "@/lib/countries"
|
|
import { verifyIdentity } from "@/lib/identity-client"
|
|
|
|
export default function VerifyPage() {
|
|
const {
|
|
walletConnected,
|
|
isVerified,
|
|
username,
|
|
connectWallet,
|
|
setVerificationDetails
|
|
} = useWalletStore()
|
|
|
|
const [country, setCountry] = useState<string>("")
|
|
const [privadoID, setPrivadoID] = useState<string>("")
|
|
const [selfAttestedEligible, setSelfAttestedEligible] = useState<boolean>(false)
|
|
const [isVerifying, setIsVerifying] = useState<boolean>(false)
|
|
const [verificationComplete, setVerificationComplete] = useState<boolean>(false)
|
|
const [verificationError, setVerificationError] = useState<string | null>(null)
|
|
const [citizenshipVerified, setCitizenshipVerified] = useState<boolean>(false)
|
|
const [eligibilityVerified, setEligibilityVerified] = useState<boolean>(false)
|
|
const { verifyIdentity: verifyWithPrivadoId, isLoading: isPrivadoVerifying } = usePrivadoId()
|
|
|
|
const handleCountryChange = (value: string) => {
|
|
setCountry(value);
|
|
// Reset verification state when country changes
|
|
setSelfAttestedEligible(false);
|
|
setCitizenshipVerified(false);
|
|
};
|
|
|
|
const handlePrivadoVerification = async () => {
|
|
try {
|
|
if (!privadoID) {
|
|
setVerificationError("Please enter your Privado ID");
|
|
return false;
|
|
}
|
|
|
|
const isVerified = await verifyWithPrivadoId();
|
|
setCitizenshipVerified(isVerified);
|
|
return isVerified;
|
|
} catch (error) {
|
|
setVerificationError("Failed to verify with Privado ID");
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const handleVerification = async () => {
|
|
setIsVerifying(true);
|
|
setVerificationError(null);
|
|
|
|
try {
|
|
// Build userData based on country selection
|
|
const userData: Record<string, string> = {};
|
|
|
|
if (country === "US") {
|
|
// For US verification, we need Privado ID and self-attestation
|
|
if (!citizenshipVerified) {
|
|
const privadoVerified = await handlePrivadoVerification();
|
|
if (!privadoVerified) {
|
|
setIsVerifying(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
userData.privadoID = privadoID;
|
|
userData.selfAttestedEligible = selfAttestedEligible ? "true" : "false";
|
|
} else {
|
|
// For other countries, just use self-attestation
|
|
userData.selfAttestedEligible = selfAttestedEligible ? "true" : "false";
|
|
}
|
|
|
|
// Call the verification API
|
|
const result = await verifyIdentity({
|
|
country,
|
|
userData
|
|
});
|
|
|
|
// Update local state to reflect verification result
|
|
setCitizenshipVerified(result.citizenshipVerified);
|
|
setEligibilityVerified(result.eligibilityVerified);
|
|
|
|
// Update wallet store with verification status
|
|
setVerificationDetails(
|
|
country,
|
|
result.citizenshipVerified,
|
|
result.eligibilityVerified,
|
|
result.status
|
|
);
|
|
|
|
setVerificationComplete(true);
|
|
} catch (error) {
|
|
console.error("Verification failed:", error);
|
|
setVerificationError("Verification failed. Please try again.");
|
|
} finally {
|
|
setIsVerifying(false);
|
|
}
|
|
};
|
|
|
|
if (!walletConnected) {
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="mx-auto max-w-2xl text-center">
|
|
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl">Connect Your Wallet</h1>
|
|
<p className="mt-4 text-lg text-muted-foreground">
|
|
Please connect your wallet before verifying your identity.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (isVerified && !verificationComplete) {
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="mx-auto max-w-2xl text-center">
|
|
<div className="mb-4 flex justify-center">
|
|
<CheckCircle className="h-12 w-12 text-green-500 dark:text-green-400" />
|
|
</div>
|
|
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl">Already Verified</h1>
|
|
<p className="mt-4 text-lg text-muted-foreground">
|
|
Your account is already verified and has full access to submit perspectives and influence legislation.
|
|
</p>
|
|
<div className="mt-8 flex justify-center">
|
|
<Button asChild>
|
|
<Link href="/submit">
|
|
Submit a Perspective
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (verificationComplete) {
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="mx-auto max-w-2xl text-center">
|
|
<div className="mb-4 flex justify-center">
|
|
<CheckCircle className="h-12 w-12 text-green-500 dark:text-green-400" />
|
|
</div>
|
|
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl">Verification Complete!</h1>
|
|
|
|
{citizenshipVerified && eligibilityVerified ? (
|
|
<div>
|
|
<p className="mt-4 text-lg text-green-700 dark:text-green-400 font-medium">
|
|
You're fully verified and can influence outcomes.
|
|
</p>
|
|
<p className="mt-2 text-muted-foreground">
|
|
Your account now has full access to submit perspectives, vote on insights,
|
|
and influence legislation.
|
|
</p>
|
|
</div>
|
|
) : citizenshipVerified ? (
|
|
<div>
|
|
<p className="mt-4 text-lg text-amber-700 dark:text-amber-400 font-medium">
|
|
Citizenship confirmed. You can participate fully once eligibility is attested.
|
|
</p>
|
|
<p className="mt-2 text-muted-foreground">
|
|
Your citizenship is verified. Complete your eligibility attestation to gain
|
|
full participation privileges.
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<p className="mt-4 text-lg text-muted-foreground">
|
|
Your identity has been verified. You now have access to submit perspectives and influence legislation.
|
|
</p>
|
|
)}
|
|
|
|
<div className="mt-8 flex flex-col sm:flex-row justify-center gap-4">
|
|
<Button asChild>
|
|
<Link href="/submit">
|
|
Submit a Perspective
|
|
</Link>
|
|
</Button>
|
|
<Button variant="outline" asChild>
|
|
<Link href="/profile">
|
|
View Profile
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="mx-auto max-w-2xl">
|
|
<Button variant="ghost" size="sm" asChild className="mb-4">
|
|
<Link href="/profile">
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
Back to Profile
|
|
</Link>
|
|
</Button>
|
|
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl">Verify Your Identity</h1>
|
|
<p className="mt-4 text-lg text-muted-foreground">
|
|
Verification allows your perspectives to influence official metrics and legislation.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mb-6 rounded-lg p-6 border bg-muted/40 dark:bg-muted/20">
|
|
<div className="flex items-start gap-4">
|
|
<ShieldCheck className="h-5 w-5 text-primary mt-0.5" />
|
|
<div className="space-y-2">
|
|
<h3 className="text-lg font-semibold tracking-tight">Why Verify?</h3>
|
|
<p className="text-muted-foreground">
|
|
Verified users have unlimited submissions, their perspectives count towards
|
|
official metrics, and they can influence the consensus process for legislation.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Card className="dark:border-border">
|
|
<CardHeader>
|
|
<CardTitle>Identity Verification</CardTitle>
|
|
<CardDescription>Complete verification to gain full access to the platform</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{verificationError && (
|
|
<Alert variant="destructive" className="mb-4">
|
|
<AlertDescription>{verificationError}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="country">Country of Citizenship</Label>
|
|
<Select value={country} onValueChange={handleCountryChange} required>
|
|
<SelectTrigger id="country" className="w-full">
|
|
<SelectValue placeholder="Select your country" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{countries.map((c) => (
|
|
<SelectItem key={c.code} value={c.code}>
|
|
{c.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<p className="text-xs text-muted-foreground">
|
|
Select the country where you are a citizen and eligible to vote.
|
|
</p>
|
|
</div>
|
|
|
|
{country && (
|
|
<div className="pt-4 border-t">
|
|
{country === "US" ? (
|
|
<div className="space-y-4">
|
|
<div className="rounded-md border p-4">
|
|
<h3 className="text-sm font-medium mb-2">United States Verification</h3>
|
|
<p className="text-sm text-muted-foreground mb-4">
|
|
Verify your citizenship with Privado ID and attest to your eligibility.
|
|
</p>
|
|
|
|
<div className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="privado-id">Privado ID</Label>
|
|
<Input
|
|
id="privado-id"
|
|
placeholder="Enter your Privado ID"
|
|
value={privadoID}
|
|
onChange={(e) => setPrivadoID(e.target.value)}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
|
|
<Button
|
|
onClick={handlePrivadoVerification}
|
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
|
|
disabled={isPrivadoVerifying || !privadoID || citizenshipVerified}
|
|
>
|
|
{isPrivadoVerifying ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Verifying...
|
|
</>
|
|
) : citizenshipVerified ? (
|
|
<>
|
|
<CheckCircle className="mr-2 h-4 w-4" />
|
|
Citizenship Verified
|
|
</>
|
|
) : (
|
|
"Verify Citizenship"
|
|
)}
|
|
</Button>
|
|
</div>
|
|
|
|
{citizenshipVerified && (
|
|
<div className="mt-6">
|
|
<div className="flex items-center space-x-2 mb-4">
|
|
<Checkbox
|
|
id="eligibility"
|
|
checked={selfAttestedEligible}
|
|
onCheckedChange={(checked) => setSelfAttestedEligible(!!checked)}
|
|
/>
|
|
<label
|
|
htmlFor="eligibility"
|
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
>
|
|
I attest I'm an eligible voter (e.g., not disenfranchised by felony)
|
|
</label>
|
|
</div>
|
|
|
|
<div className="rounded-md bg-amber-50 p-3 text-amber-800 text-xs dark:bg-amber-950 dark:text-amber-300">
|
|
<p className="flex items-start">
|
|
<Info className="h-4 w-4 mr-1.5 mt-0.5 flex-shrink-0" />
|
|
False statements may result in legal consequences under penalty of perjury.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
<div className="rounded-md border p-4">
|
|
<h3 className="text-sm font-medium mb-2">{countries.find(c => c.code === country)?.name} Verification</h3>
|
|
<p className="text-sm text-muted-foreground mb-4">
|
|
Verification for your country is coming soon. Please self-attest.
|
|
</p>
|
|
|
|
<div className="mt-6">
|
|
<div className="flex items-center space-x-2 mb-4">
|
|
<Checkbox
|
|
id="self-attestation"
|
|
checked={selfAttestedEligible}
|
|
onCheckedChange={(checked) => setSelfAttestedEligible(!!checked)}
|
|
/>
|
|
<label
|
|
htmlFor="self-attestation"
|
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
>
|
|
I attest I'm a citizen and eligible voter in my country
|
|
</label>
|
|
</div>
|
|
|
|
<div className="rounded-md bg-amber-50 p-3 text-amber-800 text-xs dark:bg-amber-950 dark:text-amber-300">
|
|
<p className="flex items-start">
|
|
<Info className="h-4 w-4 mr-1.5 mt-0.5 flex-shrink-0" />
|
|
False statements may have legal consequences.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
<CardFooter className="flex justify-end">
|
|
<Button
|
|
onClick={handleVerification}
|
|
disabled={
|
|
isVerifying ||
|
|
!country ||
|
|
(country === "US" && (!citizenshipVerified || !selfAttestedEligible)) ||
|
|
(country !== "US" && !selfAttestedEligible)
|
|
}
|
|
>
|
|
{isVerifying ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Verifying...
|
|
</>
|
|
) : (
|
|
<>
|
|
Complete Verification
|
|
<ChevronRight className="ml-2 h-4 w-4" />
|
|
</>
|
|
)}
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|