281 lines
14 KiB
TypeScript
281 lines
14 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { Wallet, HelpCircle, Check, AlertTriangle, Loader2 } from "lucide-react"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog"
|
|
import { useWalletStore } from "@/lib/wallet-store"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
const walletOptions = [
|
|
{
|
|
id: "metamask",
|
|
name: "MetaMask",
|
|
icon: "/images/wallets/metamask.svg",
|
|
description: "Connect to your MetaMask wallet",
|
|
popular: true,
|
|
verifiesAutomatically: false
|
|
},
|
|
{
|
|
id: "walletconnect",
|
|
name: "WalletConnect",
|
|
icon: "/images/wallets/walletconnect.svg",
|
|
description: "Connect using WalletConnect",
|
|
popular: true,
|
|
verifiesAutomatically: false
|
|
},
|
|
{
|
|
id: "coinbase",
|
|
name: "Coinbase Wallet",
|
|
icon: "/images/wallets/coinbase.svg",
|
|
description: "Connect to your Coinbase wallet",
|
|
popular: false,
|
|
verifiesAutomatically: false
|
|
},
|
|
{
|
|
id: "privadoid",
|
|
name: "Privado ID",
|
|
icon: "/images/wallets/privado.svg",
|
|
description: "Connect with automatic identity verification",
|
|
popular: true,
|
|
verifiesAutomatically: true
|
|
}
|
|
]
|
|
|
|
export function WalletConnectionModal() {
|
|
const { walletConnected, connectWallet } = useWalletStore()
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
const [selectedWallet, setSelectedWallet] = useState<string | null>(null)
|
|
const [connectionState, setConnectionState] = useState<"idle" | "connecting" | "success" | "error">("idle")
|
|
const [errorMessage, setErrorMessage] = useState("")
|
|
|
|
const handleWalletSelect = (walletId: string) => {
|
|
setSelectedWallet(walletId)
|
|
}
|
|
|
|
const handleConnect = async () => {
|
|
if (!selectedWallet) return
|
|
|
|
setConnectionState("connecting")
|
|
setErrorMessage("")
|
|
|
|
try {
|
|
// This would connect to the actual wallet in production
|
|
await new Promise(resolve => setTimeout(resolve, 2000)) // Simulate connection delay
|
|
|
|
// Simulate success or failure (in production, would use actual wallet connection)
|
|
const success = Math.random() > 0.2 // 80% success rate for demo
|
|
|
|
if (success) {
|
|
// Check if the selected wallet provides automatic verification
|
|
const selectedWalletDetails = walletOptions.find(w => w.id === selectedWallet);
|
|
const isVerified = selectedWalletDetails?.verifiesAutomatically || false;
|
|
|
|
// Connect the wallet and update verification status
|
|
connectWallet(undefined, undefined, isVerified);
|
|
|
|
setConnectionState("success")
|
|
setTimeout(() => {
|
|
setIsOpen(false)
|
|
setConnectionState("idle")
|
|
setSelectedWallet(null)
|
|
}, 1500)
|
|
} else {
|
|
throw new Error("Could not connect to wallet. Please try again.")
|
|
}
|
|
} catch (error) {
|
|
console.error("Wallet connection error:", error)
|
|
setConnectionState("error")
|
|
setErrorMessage(error instanceof Error ? error.message : "An unknown error occurred")
|
|
}
|
|
}
|
|
|
|
const closeAndReset = () => {
|
|
setIsOpen(false)
|
|
setTimeout(() => {
|
|
setConnectionState("idle")
|
|
setSelectedWallet(null)
|
|
setErrorMessage("")
|
|
}, 300)
|
|
}
|
|
|
|
return (
|
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
<DialogTrigger asChild>
|
|
<Button variant={walletConnected ? "outline" : "default"}>
|
|
<Wallet className="mr-2 h-4 w-4" />
|
|
{walletConnected ? "Wallet Connected" : "Connect Wallet"}
|
|
</Button>
|
|
</DialogTrigger>
|
|
|
|
<DialogContent className="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle>Connect your wallet</DialogTitle>
|
|
<DialogDescription>
|
|
Select a wallet to connect and authenticate with the platform.
|
|
Privado ID provides automatic identity verification.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
{connectionState === "success" ? (
|
|
<div className="flex flex-col items-center justify-center py-6">
|
|
<div className="rounded-full bg-green-100 p-3 mb-4">
|
|
<Check className="h-8 w-8 text-green-600" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold">Connected Successfully</h3>
|
|
<p className="text-muted-foreground mt-2">Your wallet is now connected</p>
|
|
|
|
{selectedWallet === "privadoid" && (
|
|
<div className="mt-2 text-green-600 text-sm flex items-center">
|
|
<Check className="h-4 w-4 mr-1" />
|
|
Identity verified automatically
|
|
</div>
|
|
)}
|
|
|
|
{selectedWallet && selectedWallet !== "privadoid" && (
|
|
<div className="mt-4">
|
|
<p className="text-sm text-amber-600 mb-2">
|
|
Your wallet is connected but your identity is not verified.
|
|
</p>
|
|
<Button size="sm" variant="outline" asChild>
|
|
<a href="/profile/verify">Verify Your Identity</a>
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : connectionState === "error" ? (
|
|
<div className="flex flex-col items-center justify-center py-6">
|
|
<div className="rounded-full bg-red-100 p-3 mb-4">
|
|
<AlertTriangle className="h-8 w-8 text-red-600" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold">Connection Failed</h3>
|
|
<p className="text-muted-foreground mt-2">{errorMessage}</p>
|
|
|
|
<Button variant="default" onClick={() => setConnectionState("idle")} className="mt-4">
|
|
Try Again
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="grid gap-4 py-4">
|
|
<div className="space-y-2">
|
|
<h3 className="text-sm font-medium">Popular Wallets</h3>
|
|
<div className="space-y-2">
|
|
{walletOptions.filter(w => w.popular).map((wallet) => (
|
|
<div
|
|
key={wallet.id}
|
|
className={cn(
|
|
"flex items-center space-x-4 rounded-md border p-4 cursor-pointer transition-colors",
|
|
selectedWallet === wallet.id
|
|
? "border-primary bg-primary/5"
|
|
: "hover:bg-muted"
|
|
)}
|
|
onClick={() => handleWalletSelect(wallet.id)}
|
|
>
|
|
<div className="h-10 w-10 flex-shrink-0 overflow-hidden rounded-full border">
|
|
<div className="h-full w-full flex items-center justify-center bg-muted">
|
|
<img
|
|
src={wallet.icon}
|
|
alt={wallet.name}
|
|
className="h-6 w-6"
|
|
onError={(e) => {
|
|
const target = e.target as HTMLImageElement;
|
|
target.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2' y='6' width='20' height='12' rx='2'/%3E%3Cpath d='M22 10H18a2 2 0 0 0-2 2v0a2 2 0 0 0 2 2h4'/%3E%3C/svg%3E";
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 space-y-1">
|
|
<div className="flex items-center gap-2">
|
|
<p className="font-medium leading-none">{wallet.name}</p>
|
|
{wallet.verifiesAutomatically && (
|
|
<span className="text-xs bg-green-100 text-green-800 px-1.5 py-0.5 rounded-full">
|
|
Verifies Identity
|
|
</span>
|
|
)}
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">{wallet.description}</p>
|
|
</div>
|
|
{selectedWallet === wallet.id && <Check className="h-5 w-5 text-primary" />}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<h3 className="text-sm font-medium">Other Wallets</h3>
|
|
<div className="space-y-2">
|
|
{walletOptions.filter(w => !w.popular).map((wallet) => (
|
|
<div
|
|
key={wallet.id}
|
|
className={cn(
|
|
"flex items-center space-x-4 rounded-md border p-4 cursor-pointer transition-colors",
|
|
selectedWallet === wallet.id
|
|
? "border-primary bg-primary/5"
|
|
: "hover:bg-muted"
|
|
)}
|
|
onClick={() => handleWalletSelect(wallet.id)}
|
|
>
|
|
<div className="h-10 w-10 flex-shrink-0 overflow-hidden rounded-full border">
|
|
<div className="h-full w-full flex items-center justify-center bg-muted">
|
|
<img
|
|
src={wallet.icon}
|
|
alt={wallet.name}
|
|
className="h-6 w-6"
|
|
onError={(e) => {
|
|
const target = e.target as HTMLImageElement;
|
|
target.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2' y='6' width='20' height='12' rx='2'/%3E%3Cpath d='M22 10H18a2 2 0 0 0-2 2v0a2 2 0 0 0 2 2h4'/%3E%3C/svg%3E";
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 space-y-1">
|
|
<p className="font-medium leading-none">{wallet.name}</p>
|
|
<p className="text-sm text-muted-foreground">{wallet.description}</p>
|
|
</div>
|
|
{selectedWallet === wallet.id && <Check className="h-5 w-5 text-primary" />}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<DialogFooter className="flex flex-col sm:flex-row gap-2">
|
|
<Button variant="outline" onClick={closeAndReset}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={handleConnect}
|
|
disabled={!selectedWallet || connectionState === "connecting"}
|
|
className="w-full sm:w-auto"
|
|
>
|
|
{connectionState === "connecting" ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Connecting...
|
|
</>
|
|
) : (
|
|
"Connect"
|
|
)}
|
|
</Button>
|
|
</DialogFooter>
|
|
</>
|
|
)}
|
|
|
|
<div className="text-xs text-center text-muted-foreground mt-2">
|
|
<Button variant="link" size="sm" className="h-auto p-0">
|
|
<HelpCircle className="h-3 w-3 mr-1" />
|
|
What is a wallet?
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|