Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 111 additions & 0 deletions src/app/(dashboard)/claim/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useState } from 'react';
import { AlertTriangle, CheckCircle } from 'lucide-react';
import { Dispute } from '@/types/dispute';
import { OpenDisputeModal } from '@/components/modals/OpenDisputeModal';
import { DisputeVotingCard } from '@/components/claims/DisputeVotingCard';


export default function ClaimDetail() {
const [isDisputeModalOpen, setDisputeModalOpen] = useState(false);


const [dispute, setDispute] = useState<Dispute | null>(null);

const handleOpenDispute = async (payload: any) => {

console.log('Opening dispute:', payload);


setDispute({
id: 'dsp_1',
claimId: 'claim_123',
reason: payload.reason,
status: 'VOTING',
proVotes: 0,
conVotes: 0,
totalStaked: payload.initialStake,
createdAt: new Date().toISOString()
});
};

const handleVote = async (id: string, side: string, amount: number) => {
// Call POST /disputes/:id/vote API here
console.log(`Voting ${side} with ${amount}`);
};

return (
<div className="min-h-screen bg-[#0A0A0A] p-8 text-zinc-300">


<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">

<div className="lg:col-span-2 space-y-6">
<div className="rounded-xl border border-zinc-800 bg-[#111111] p-6">


<div className="flex justify-between items-start mb-4">
<div className="space-y-1">
<div className="flex gap-2">
<span className="px-2 py-0.5 rounded text-xs font-medium bg-zinc-800 text-zinc-400">Climate</span>
<span className="text-xs text-zinc-500 font-mono">0x1a2b...3c4d</span>
</div>
<h1 className="text-xl font-bold text-white">Global average temperatures increased by 1.1°C...</h1>
</div>


{dispute ? (
<span className="flex items-center gap-1 px-3 py-1 rounded-full bg-red-900/20 text-red-500 border border-red-900/50 text-xs font-bold">
<AlertTriangle size={14} /> Dispute Active
</span>
) : (
<span className="flex items-center gap-1 px-3 py-1 rounded-full bg-green-900/20 text-green-500 border border-green-900/50 text-xs font-bold">
<CheckCircle size={14} /> Verified
</span>
)}
</div>


{!dispute && (
<div className="grid grid-cols-2 gap-4 mt-8">
<button className="py-3 rounded-lg bg-green-600 hover:bg-green-700 text-white font-bold transition-colors">
Verify (Stake + Vote)
</button>


<button
onClick={() => setDisputeModalOpen(true)}
className="py-3 rounded-lg border border-red-900/50 text-red-500 hover:bg-red-900/10 font-bold flex items-center justify-center gap-2 transition-colors"
>
<AlertTriangle size={18} /> Dispute
</button>
</div>
)}


{dispute && (
<DisputeVotingCard
disputeId={dispute.id}
currentStaked={dispute.totalStaked}
onVote={handleVote}
/>
)}
</div>


</div>


<div className="space-y-6">

</div>
</div>

<OpenDisputeModal
claimId="claim_123"
isOpen={isDisputeModalOpen}
onClose={() => setDisputeModalOpen(false)}
onSubmit={handleOpenDispute}
/>
</div>
);
}
62 changes: 62 additions & 0 deletions src/components/claims/DisputeVotingCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useState } from 'react';

interface DisputeVotingCardProps {
disputeId: string;
currentStaked: number;
onVote: (disputeId: string, side: 'PRO' | 'CON', amount: number) => Promise<void>;
}

export const DisputeVotingCard = ({ disputeId, currentStaked, onVote }: DisputeVotingCardProps) => {
const [amount, setAmount] = useState<string>('');
const [loading, setLoading] = useState(false);

const handleVote = async (side: 'PRO' | 'CON') => {
if (!amount) return;
setLoading(true);
await onVote(disputeId, side, Number(amount));
setLoading(false);
setAmount('');
};

return (
<div className="rounded-xl border border-red-900/50 bg-red-950/10 p-5 mt-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-white font-bold flex items-center gap-2">
<span className="relative flex h-3 w-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
</span>
Active Dispute Voting
</h3>
<span className="text-sm text-red-400 font-mono">${currentStaked.toLocaleString()} Staked</span>
</div>

<div className="mb-4">
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Enter stake amount"
className="w-full rounded-lg border border-zinc-700 bg-zinc-900 p-3 text-white focus:border-red-500 outline-none"
/>
</div>

<div className="grid grid-cols-2 gap-3">
<button
onClick={() => handleVote('PRO')}
disabled={loading || !amount}
className="py-2.5 rounded-lg bg-green-900/30 border border-green-800 text-green-400 hover:bg-green-900/50 font-bold transition-all"
>
Vote Valid
</button>
<button
onClick={() => handleVote('CON')}
disabled={loading || !amount}
className="py-2.5 rounded-lg bg-red-900/30 border border-red-800 text-red-400 hover:bg-red-900/50 font-bold transition-all"
>
Vote Invalid
</button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"use client";
import React, { useState } from "react";

export interface ClaimFormData {
Expand Down
91 changes: 91 additions & 0 deletions src/components/modals/OpenDisputeModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useState } from 'react';
import { X, AlertTriangle } from 'lucide-react';
import { CreateDisputePayload } from '../../types/dispute';

interface OpenDisputeModalProps {
claimId: string;
isOpen: boolean;
onClose: () => void;
onSubmit: (payload: CreateDisputePayload) => Promise<void>;
}

export const OpenDisputeModal = ({ claimId, isOpen, onClose, onSubmit }: OpenDisputeModalProps) => {
const [reason, setReason] = useState('');
const [stake, setStake] = useState<string>('');
const [loading, setLoading] = useState(false);

if (!isOpen) return null;

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
await onSubmit({ claimId, reason, initialStake: Number(stake) });
onClose();
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="w-full max-w-md rounded-xl border border-zinc-800 bg-[#111111] p-6 shadow-2xl">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2 text-red-500">
<AlertTriangle size={20} />
<h2 className="text-lg font-bold text-white">Open Dispute</h2>
</div>
<button onClick={onClose} className="text-zinc-500 hover:text-white">
<X size={20} />
</button>
</div>

<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-zinc-400 mb-1">Reason for Dispute</label>
<textarea
className="w-full rounded-lg border border-zinc-700 bg-zinc-900/50 p-3 text-white placeholder:text-zinc-600 focus:border-red-500 focus:outline-none"
rows={4}
placeholder="Why is this claim inaccurate?"
value={reason}
onChange={(e) => setReason(e.target.value)}
required
/>
</div>

<div>
<label className="block text-sm font-medium text-zinc-400 mb-1">Stake Amount (USDC)</label>
<input
type="number"
className="w-full rounded-lg border border-zinc-700 bg-zinc-900/50 p-3 text-white placeholder:text-zinc-600 focus:border-red-500 focus:outline-none"
placeholder="0.00"
value={stake}
onChange={(e) => setStake(e.target.value)}
min="1"
required
/>
</div>

<div className="flex justify-end gap-3 mt-6">
<button
type="button"
onClick={onClose}
className="px-4 py-2 rounded-lg text-sm font-medium text-zinc-400 hover:text-white hover:bg-zinc-800 transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={loading}
className="px-4 py-2 rounded-lg bg-red-600 text-sm font-bold text-white hover:bg-red-700 disabled:opacity-50 transition-colors"
>
{loading ? 'Submitting...' : 'Confirm Dispute'}
</button>
</div>
</form>
</div>
</div>
);
};
16 changes: 16 additions & 0 deletions src/types/dispute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface Dispute {
id: string;
claimId: string;
reason: string;
status: 'OPEN' | 'VOTING' | 'RESOLVED' | 'FAILED';
proVotes: number;
conVotes: number;
totalStaked: number;
createdAt: string;
}

export interface CreateDisputePayload {
claimId: string;
reason: string;
initialStake: number;
}