"use client" import type React from "react" import { useState, useEffect, useRef } from "react" import { useSearchParams } from "next/navigation" import Link from "next/link" import { FileText, Upload, CheckCircle, Loader2, ArrowLeft, ShieldAlert, ShieldCheck, AlertTriangle, Info, RefreshCw } from "lucide-react" import { CoinsStacked } from "@/icons" 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 { Textarea } from "@/components/ui/textarea" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { useWalletStore } from "@/lib/wallet-store" import { Badge } from "@/components/ui/badge" import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { ethers } from "ethers" import { usePrivadoId } from "@/lib/privado-id" import { PERSPECTIVE_CONTRACT_ABI, PERSPECTIVE_CONTRACT_ADDRESS, FEE_CONTRACT_ADDRESS, FEE_CONTRACT_ABI } from "@/lib/constants" import { useTrustData } from "@/hooks/use-trust-data" // Mock issues data for the dropdown const mockIssues = [ { id: "climate-action", title: "Climate Action Policies", category: "Environmental Policy" }, { id: "education-reform", title: "Education System Reform", category: "Education" }, { id: "healthcare-access", title: "Healthcare Accessibility", category: "Healthcare" }, { id: "housing-affordability", title: "Housing Affordability Crisis", category: "Infrastructure" }, { id: "digital-privacy", title: "Digital Privacy Regulations", category: "Technology" } ] // Generate a simple CAPTCHA const generateCaptcha = () => { const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789'; let captcha = ''; for (let i = 0; i < 6; i++) { captcha += chars.charAt(Math.floor(Math.random() * chars.length)); } return captcha; }; export default function SubmitPage() { const { walletConnected, walletAddress, isVerified, citizenshipVerified, eligibilityVerified, verificationStatus, perspectivesSubmittedToday: perspectivesSubmitted, incrementPerspectiveCount } = useWalletStore() const { trustScore, dailyLimit, isLoading, updateUserTrust } = useTrustData() const searchParams = useSearchParams() const issueParam = searchParams.get('issue') const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitted, setIsSubmitted] = useState(false) const [ipfsHash, setIpfsHash] = useState("") const [selectedIssue, setSelectedIssue] = useState(null) const [title, setTitle] = useState("") const [content, setContent] = useState("") const [captchaInput, setCaptchaInput] = useState("") const [captchaValue, setCaptchaValue] = useState("") const [captchaVerified, setCaptchaVerified] = useState(false) const [selectedFile, setSelectedFile] = useState(null) const [fileError, setFileError] = useState("") const fileInputRef = useRef(null) const [submissionError, setSubmissionError] = useState(null) const [uploadingFile, setUploadingFile] = useState(false) const { verifyIdentity } = usePrivadoId() // Economic barrier states const [showFeeModal, setShowFeeModal] = useState(false) const [transactionState, setTransactionState] = useState<'idle' | 'pending' | 'confirmed' | 'error'>('idle') const [transactionHash, setTransactionHash] = useState(null) const [contentToSubmit, setContentToSubmit] = useState<{ issueId: string; text: string; fileUrl?: string; } | null>(null) // Fee amount in POL const FEE_AMOUNT = "0.01" // Check rate limit for unverified users const isRateLimited = !isVerified && perspectivesSubmitted >= 5 // Set the selected issue from URL parameter if available useEffect(() => { if (issueParam) { setSelectedIssue(issueParam) } }, [issueParam]) // Generate CAPTCHA on load useEffect(() => { if (!isVerified) { refreshCaptcha(); } }, [isVerified]); // Get the selected issue details const selectedIssueDetails = selectedIssue ? mockIssues.find(issue => issue.id === selectedIssue) : null const refreshCaptcha = () => { setCaptchaValue(generateCaptcha()); setCaptchaInput(""); setCaptchaVerified(false); }; const validateCaptcha = () => { const isValid = captchaInput.trim() === captchaValue; setCaptchaVerified(isValid); return isValid; }; // Add file validation function const validateFile = (file: File): boolean => { // Reset error message setFileError(""); // Check file type const validTypes = ['application/pdf', 'image/jpeg', 'image/png']; if (!validTypes.includes(file.type)) { setFileError("Invalid file type. Please upload a PDF, JPEG, or PNG file."); return false; } // Check file size (5MB limit) const maxSize = 5 * 1024 * 1024; // 5MB in bytes if (file.size > maxSize) { setFileError("File is too large. Maximum size is 5MB."); return false; } return true; } // Add file change handler const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file) { if (validateFile(file)) { setSelectedFile(file) } else { e.target.value = '' // Reset input setSelectedFile(null) } } } // Add smart contract submission function const submitToBlockchain = async ( issueId: string, text: string, fileUrl?: string ) => { try { const provider = new ethers.providers.Web3Provider( window.ethereum as ethers.providers.ExternalProvider || undefined ); const signer = provider.getSigner() const contract = new ethers.Contract( PERSPECTIVE_CONTRACT_ADDRESS, PERSPECTIVE_CONTRACT_ABI, signer ) // Prepare metadata including verification status const metadata = { isVerified, timestamp: Date.now(), fileUrl, submitterAddress: await signer.getAddress() } // Submit perspective to smart contract const tx = await contract.submitPerspective( issueId, text, JSON.stringify(metadata), { gasLimit: 500000 } ) // Wait for transaction confirmation await tx.wait() return tx.hash } catch (error) { console.error("Blockchain submission error:", error) throw error } } // Update file upload handler with additional CAPTCHA check const handleFileUpload = async (file: File): Promise => { if (!isVerified && !captchaVerified) { setFileError("Please complete CAPTCHA verification first"); document.getElementById('captcha-section')?.scrollIntoView({ behavior: 'smooth' }); return ""; } setUploadingFile(true); try { // TODO: Replace with actual IPFS upload await new Promise(resolve => setTimeout(resolve, 1000)); const mockIpfsUrl = `ipfs://Qm...${file.name}`; return mockIpfsUrl; } catch (error) { console.error("File upload error:", error); setFileError("Failed to upload file. Please try again."); return ""; } finally { setUploadingFile(false); } }; // Handle fee payment and submission const handleFeePayment = async () => { if (!walletConnected || !contentToSubmit) return; setTransactionState('pending'); try { const provider = new ethers.providers.Web3Provider( window.ethereum as ethers.providers.ExternalProvider || undefined ); const signer = provider.getSigner(); const contract = new ethers.Contract( FEE_CONTRACT_ADDRESS, FEE_CONTRACT_ABI, signer ); // Convert the fee amount to wei const feeInWei = ethers.utils.parseEther(FEE_AMOUNT); // Call the payFeeAndSubmit function from our smart contract const tx = await contract.payFeeAndSubmit( contentToSubmit.issueId, contentToSubmit.text, contentToSubmit.fileUrl || "", isVerified, { value: feeInWei, gasLimit: 500000 } ); // Wait for transaction confirmation await tx.wait(); setTransactionHash(tx.hash); setTransactionState('confirmed'); // Update user trust score if unverified if (!isVerified) { try { await updateUserTrust('submit_perspective'); } catch (trustError) { console.error("Failed to update trust score:", trustError); } } setIpfsHash(tx.hash); setIsSubmitted(true); incrementPerspectiveCount(); setShowFeeModal(false); } catch (error: any) { console.error("Fee payment failed:", error); setTransactionState('error'); setSubmissionError(error.message || "Failed to process fee payment. Please try again."); } }; // Update submit handler to check for verification const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setSubmissionError(null); if (!walletConnected || !selectedIssue) return; // Verify identity status if needed if (!isVerified) { if (!validateCaptcha()) { setSubmissionError("CAPTCHA validation failed. Please try again."); refreshCaptcha(); return; } if (isRateLimited) { setSubmissionError("Daily submission limit reached. Please verify your identity for unlimited access."); return; } } setIsSubmitting(true); try { // Handle file upload if present let fileUrl = ""; if (selectedFile) { fileUrl = await handleFileUpload(selectedFile); if (!fileUrl) { setIsSubmitting(false); return; // Upload failed } } // For unverified users, prompt for fee payment if (!isVerified) { setContentToSubmit({ issueId: selectedIssue, text: content, fileUrl: fileUrl || undefined }); setShowFeeModal(true); setIsSubmitting(false); return; } // For verified users, proceed with normal submission const txHash = await submitToBlockchain(selectedIssue, content, fileUrl || undefined); setIpfsHash(txHash); setIsSubmitted(true); incrementPerspectiveCount(); } catch (error: any) { console.error("Submission failed:", error); setSubmissionError(error.message || "Failed to submit perspective. Please try again."); } finally { setIsSubmitting(false); } }; // Render verification banner based on status const renderVerificationBanner = () => { if (!isVerified) { return ( Limited Functionality As an unverified user, you're in read-only mode with limited actions. You can submit up to {dailyLimit} perspectives per day. ); } else if (citizenshipVerified && !eligibilityVerified) { return ( Citizenship Verified Your citizenship is confirmed. You can submit up to 20 perspectives per day. Complete your eligibility attestation to unlock unlimited submissions and full voting power. ); } else if (citizenshipVerified && eligibilityVerified) { return ( Fully Verified You're fully verified with unlimited perspective submissions and enhanced influence on outcomes. ); } return null; }; if (!walletConnected) { return (

Connect Your Wallet

Please connect your wallet to submit a perspective.

) } if (isSubmitted) { return (

Perspective Submitted!

Your perspective has been successfully submitted and stored on IPFS.

IPFS Hash: {ipfsHash}

{!isVerified && (

Your fee of {FEE_AMOUNT} POL is refundable if your submission remains unflagged for 24 hours.

)}
{selectedIssue && ( )}
) } return (
{renderVerificationBanner()} {/* Fee payment modal */} Fee Required for Unverified Users This action requires a {FEE_AMOUNT} POL fee, which is refundable if your submission is not flagged within 24 hours.

This small fee helps prevent spam and abuse. The fee will be automatically refunded after 24 hours if your submission isn't flagged for moderation.

{transactionState === 'pending' && (

Processing Transaction...

Please confirm in your wallet and wait for confirmation

)} {transactionState === 'error' && (
Transaction Error

{submissionError}

)}

Submit Your Perspective

Share your thoughts on important topics and contribute to the discourse

{/* Daily submission limit indicator - only for unverified users */} {!isVerified && (

Daily Submission Limit

{perspectivesSubmitted} / {dailyLimit}

{trustScore < 100 ? "Increase your trust score to get more daily submissions." : "You've reached the maximum trust score for an unverified user."} {" "} Verify your identity {" "} for unlimited submissions.

)} {/* Fee notice for unverified users */} {!isVerified && ( Fee Required for Unverified Users Submitting a perspective requires a {FEE_AMOUNT} POL fee, which will be refunded if your submission remains unflagged for 24 hours. )} {perspectivesSubmitted >= dailyLimit && !isVerified ? (

Daily submission limit reached

You've reached your daily submission limit. Come back tomorrow or{" "} verify your identity {" "} for unlimited submissions.

) : ( New Perspective Fill out the form below to submit your perspective. Be specific and constructive in your feedback.
{/* Issue Selection */}
{selectedIssueDetails ? (

{selectedIssueDetails.title}

{selectedIssueDetails.category}

Your perspective will be linked to this issue

) : (

Can't find the issue you're looking for?

)}
{!selectedIssue && ( Note All perspectives must be linked to a specific issue. This helps organize discussions and generate meaningful insights. )} {/* Title */}
setTitle(e.target.value)} placeholder="Give your perspective a clear, concise title" className="w-full" required />
{/* Content */}