From 7b41f2ade79cb5a94034c18cca3aaed79bfdaead Mon Sep 17 00:00:00 2001
From: Hemant <133942800+hk2166@users.noreply.github.com>
Date: Sat, 11 Oct 2025 21:47:31 +0530
Subject: [PATCH 1/5] fixed issue #180
---
.env | 4 +
blockchain/.env.example | 6 -
client/.env.example | 4 -
client/WALLET_FEATURE.md | 249 ++++++++++++++++++
client/app/components/Header/Header.tsx | 2 +
client/app/components/Wallet/BalanceCard.tsx | 130 +++++++++
.../app/components/Wallet/NetworkSelector.tsx | 128 +++++++++
client/app/components/Wallet/SecurityTips.tsx | 135 ++++++++++
.../app/components/Wallet/VotingHistory.tsx | 173 ++++++++++++
client/app/components/Wallet/WalletInfo.tsx | 94 +++++++
client/app/wallet/page.tsx | 175 ++++++++++++
client/package-lock.json | 120 +++++++++
12 files changed, 1210 insertions(+), 10 deletions(-)
create mode 100644 .env
delete mode 100644 blockchain/.env.example
delete mode 100644 client/.env.example
create mode 100644 client/WALLET_FEATURE.md
create mode 100644 client/app/components/Wallet/BalanceCard.tsx
create mode 100644 client/app/components/Wallet/NetworkSelector.tsx
create mode 100644 client/app/components/Wallet/SecurityTips.tsx
create mode 100644 client/app/components/Wallet/VotingHistory.tsx
create mode 100644 client/app/components/Wallet/WalletInfo.tsx
create mode 100644 client/app/wallet/page.tsx
diff --git a/.env b/.env
new file mode 100644
index 00000000..14430698
--- /dev/null
+++ b/.env
@@ -0,0 +1,4 @@
+PRIVATE_KEY=011d4ba8e7b549ee9cd718df4c25abed
+RPC_URL_SEPOLIA=https://eth-mainnet.g.alchemy.com/v2/MBGDHRRT3_qjtqKzM2DZk
+RPC_URL_FUJI=https://avax-fuji.g.alchemy.com/v2/MBGDHRRT3_qjtqKzM2DZk
+ETHERSCAN_KEY=WCU9BD1WA1835GAU9VBTGSPA2DGEPQHYSF
\ No newline at end of file
diff --git a/blockchain/.env.example b/blockchain/.env.example
deleted file mode 100644
index d26c2bc2..00000000
--- a/blockchain/.env.example
+++ /dev/null
@@ -1,6 +0,0 @@
-PRIVATE_KEY = ENTER_YOUR_KEY_VALUE
-RPC_URL_SEPOLIA = ENTER_YOUR_KEY_VALUE
-RPC_URL_FUJI = ENTER_YOUR_KEY_VALUE
-RPC_URL_AMOY = ENTER_YOUR_KEY_VALUE
-RPC_URL_BSC = ENTER_YOUR_KEY_VALUE
-ETHERSCAN_KEY = ENTER_YOUR_KEY_VALUE
diff --git a/client/.env.example b/client/.env.example
deleted file mode 100644
index 16d49007..00000000
--- a/client/.env.example
+++ /dev/null
@@ -1,4 +0,0 @@
-NEXT_PUBLIC_SEPOLIA_RPC_URL = ENTER_RPC_URL
-NEXT_PUBLIC_AMOY_RPC_URL = ENTER_RPC_URL
-NEXT_PUBLIC_FUJI_RPC_URL = ENTER_RPC_URL
-NEXT_PUBLIC_PINATA_JWT = ENTER_PINATA_API_KEY
\ No newline at end of file
diff --git a/client/WALLET_FEATURE.md b/client/WALLET_FEATURE.md
new file mode 100644
index 00000000..fef62726
--- /dev/null
+++ b/client/WALLET_FEATURE.md
@@ -0,0 +1,249 @@
+# Wallet Management Feature
+
+## 📋 Overview
+
+A comprehensive Wallet Management page has been implemented for the Agora-Blockchain voting platform. This feature allows users to view, manage, and interact with their connected Web3 wallets.
+
+## ✨ Features Implemented
+
+### 1. **Wallet Connection & Display**
+- Real-time wallet connection status
+- Display of connected wallet address with copy-to-clipboard functionality
+- Wallet connector identification (MetaMask, WalletConnect, Coinbase Wallet, etc.)
+- ENS name and avatar support
+
+### 2. **Balance Overview**
+- Native token balance display (ETH, AVAX, etc.)
+- Real-time balance updates using wagmi hooks
+- Network-specific balance information
+- Token decimals and full balance details
+
+### 3. **Network Management**
+- Visual network selector with supported chains:
+ - Sepolia Testnet (Ethereum)
+ - Avalanche Fuji Testnet
+- One-click network switching
+- Clear indication of active network
+- Warning for unsupported networks
+
+### 4. **Wallet Information**
+- ENS identity display with avatar
+- Wallet type and address format verification
+- Connection status indicators
+- Security information about the connected wallet
+
+### 5. **Voting History**
+- Placeholder for voting transaction history
+- Ready for integration with blockchain voting records
+- Statistics summary (total votes, elections participated, networks used)
+- Transaction hash links to block explorers
+
+### 6. **Security Tips**
+- Expandable security tips section
+- Best practices for wallet security
+- Warnings about phishing and scams
+- Guidelines for safe Web3 interactions
+
+### 7. **User Experience**
+- Beautiful gradient UI with Tailwind CSS
+- Smooth animations using Framer Motion
+- Responsive design (mobile, tablet, desktop)
+- Toast notifications for user actions
+- Loading states and skeleton screens
+
+## 🏗️ Architecture
+
+### File Structure
+```
+client/app/
+├── wallet/
+│ └── page.tsx # Main wallet management page
+└── components/
+ └── Wallet/
+ ├── WalletInfo.tsx # Wallet details and ENS info
+ ├── BalanceCard.tsx # Token balance display
+ ├── NetworkSelector.tsx # Network switching UI
+ ├── VotingHistory.tsx # Voting transaction history
+ └── SecurityTips.tsx # Security guidelines
+```
+
+### Technology Stack
+- **Next.js 14** - App Router
+- **wagmi** - React Hooks for Ethereum
+- **RainbowKit** - Wallet connection UI
+- **viem** - Ethereum library
+- **Framer Motion** - Animations
+- **Heroicons** - Icons
+- **Tailwind CSS** - Styling
+- **react-hot-toast** - Notifications
+
+## 🎨 UI Components
+
+### WalletInfo
+Displays detailed wallet information including:
+- ENS avatar and name
+- Wallet type and status
+- Address format verification
+- Security information
+
+### BalanceCard
+Shows comprehensive balance information:
+- Native token balance with 4 decimal precision
+- Network-specific token symbols
+- Gradient backgrounds based on network
+- Full balance and decimals
+
+### NetworkSelector
+Provides network switching functionality:
+- Grid layout of supported networks
+- Visual indication of active network
+- One-click network switching
+- Warning for unsupported networks
+
+### VotingHistory
+Tracks user's voting participation:
+- List of voting transactions
+- Election names and timestamps
+- Transaction hash links
+- Statistics summary
+
+### SecurityTips
+Educational component for security:
+- Expandable accordion design
+- Key security guidelines
+- Warning about phishing
+- Platform-specific safety notes
+
+## 🔗 Integration Points
+
+### wagmi Hooks Used
+- `useAccount()` - Current wallet account info
+- `useBalance()` - Token balance
+- `useEnsName()` - ENS name resolution
+- `useEnsAvatar()` - ENS avatar
+- `useChainId()` - Current network ID
+- `useSwitchChain()` - Network switching
+- `useDisconnect()` - Wallet disconnection
+
+### RainbowKit Integration
+- ConnectButton for wallet connection
+- Automatic wallet provider detection
+- Support for multiple wallet types
+
+## 🚀 Usage
+
+### Accessing the Wallet Page
+1. Navigate to `/wallet` route
+2. Or click "Wallet" in the navigation menu
+
+### Wallet Connection
+If not connected:
+1. Click the "Connect Wallet" button
+2. Select your preferred wallet provider
+3. Approve the connection
+
+If connected:
+1. View wallet details, balance, and history
+2. Switch networks as needed
+3. Disconnect when finished
+
+### Network Switching
+1. Scroll to the "Network Selection" section
+2. Click on the desired network card
+3. Approve the network switch in your wallet
+
+## 🔧 Future Enhancements
+
+### Planned Features
+1. **Voting History Integration**
+ - Fetch actual voting records from blockchain
+ - Filter by date, network, or election
+ - Export voting history
+
+2. **Multi-Wallet Support**
+ - Connect multiple wallets simultaneously
+ - Set default wallet for transactions
+ - Switch between connected wallets
+
+3. **Token Management**
+ - Display ERC-20 token balances
+ - Token transfer functionality
+ - Token approval management
+
+4. **Transaction History**
+ - Complete transaction history
+ - Filter and search capabilities
+ - CSV export functionality
+
+5. **Advanced Security**
+ - Wallet health checks
+ - Connection permissions audit
+ - Security score indicators
+
+6. **Analytics Dashboard**
+ - Voting participation metrics
+ - Network usage statistics
+ - Gas fee analytics
+
+## 📝 Code Examples
+
+### Using Wallet Info in Other Components
+```tsx
+import { useAccount, useBalance } from 'wagmi';
+
+function MyComponent() {
+ const { address, isConnected } = useAccount();
+ const { data: balance } = useBalance({ address });
+
+ return (
+
+ {isConnected && (
+
Balance: {balance?.formatted} {balance?.symbol}
+ )}
+
+ );
+}
+```
+
+### Network Switching
+```tsx
+import { useSwitchChain } from 'wagmi';
+import { sepolia } from 'wagmi/chains';
+
+function SwitchNetwork() {
+ const { switchChain } = useSwitchChain();
+
+ const handleSwitch = () => {
+ switchChain({ chainId: sepolia.id });
+ };
+
+ return Switch to Sepolia ;
+}
+```
+
+## 🐛 Known Issues
+
+1. **Voting History**: Currently uses mock data - needs integration with blockchain indexer or backend API
+2. **ENS Resolution**: May be slow on some networks - consider caching
+3. **Balance Updates**: Real-time updates depend on RPC endpoint reliability
+
+## 🤝 Contributing
+
+When extending this feature:
+1. Follow the existing component structure
+2. Use wagmi hooks for blockchain interactions
+3. Maintain responsive design patterns
+4. Add loading states for async operations
+5. Include error handling with user-friendly messages
+
+## 📄 License
+
+This feature is part of the Agora-Blockchain project and follows the same license.
+
+## 👥 Credits
+
+Developed as part of the GSOC contribution to Agora-Blockchain.
+
+---
+
+For more information, see the main project README or visit the [documentation](../README.md).
diff --git a/client/app/components/Header/Header.tsx b/client/app/components/Header/Header.tsx
index 2fd84166..4352fc5b 100644
--- a/client/app/components/Header/Header.tsx
+++ b/client/app/components/Header/Header.tsx
@@ -10,6 +10,7 @@ import {
HomeIcon,
XMarkIcon,
Bars3Icon,
+ WalletIcon,
} from "@heroicons/react/24/outline";
import Web3Connect from "../Helper/Web3Connect";
import Image from "next/image";
@@ -17,6 +18,7 @@ import Image from "next/image";
const menuItems = [
{ name: "Home", href: "/", icon: HomeIcon },
{ name: "Create", href: "/create", icon: PlusCircleIcon },
+ { name: "Wallet", href: "/wallet", icon: WalletIcon },
{ name: "Profile", href: "/profile", icon: UserIcon },
];
diff --git a/client/app/components/Wallet/BalanceCard.tsx b/client/app/components/Wallet/BalanceCard.tsx
new file mode 100644
index 00000000..ff8bdf29
--- /dev/null
+++ b/client/app/components/Wallet/BalanceCard.tsx
@@ -0,0 +1,130 @@
+"use client";
+
+import React from "react";
+import { useBalance, useChainId } from "wagmi";
+import { sepolia, avalancheFuji, polygonAmoy } from "wagmi/chains";
+import { BanknotesIcon, ArrowTrendingUpIcon } from "@heroicons/react/24/outline";
+import { motion } from "framer-motion";
+
+interface BalanceCardProps {
+ address: `0x${string}` | undefined;
+}
+
+const BalanceCard: React.FC = ({ address }) => {
+ const chainId = useChainId();
+ const { data: balance, isLoading } = useBalance({
+ address,
+ });
+
+ const getChainName = (id: number) => {
+ switch (id) {
+ case sepolia.id:
+ return "Sepolia";
+ case avalancheFuji.id:
+ return "Avalanche Fuji";
+ case polygonAmoy.id:
+ return "Polygon Amoy";
+ default:
+ return "Unknown Network";
+ }
+ };
+
+ const getChainColor = (id: number) => {
+ switch (id) {
+ case sepolia.id:
+ return "from-blue-400 to-blue-600";
+ case avalancheFuji.id:
+ return "from-red-400 to-red-600";
+ case polygonAmoy.id:
+ return "from-purple-400 to-purple-600";
+ default:
+ return "from-gray-400 to-gray-600";
+ }
+ };
+
+ return (
+
+ Balance Overview
+
+ {isLoading ? (
+
+ ) : (
+
+ {/* Main Balance Display */}
+
+
+
+ {getChainName(chainId)} Balance
+
+
+
+
+
+ {balance
+ ? parseFloat(balance.formatted).toFixed(4)
+ : "0.0000"}
+
+
+ {balance?.symbol || "ETH"}
+
+
+
+
+ {/* Balance Details */}
+
+
+
+
+ {balance?.symbol || "ETH"}
+
+
+
+
+
+ Network
+
+
+
+ {getChainName(chainId)}
+
+
+
+
+ {/* Decimals Info */}
+
+
+ Decimals
+
+ {balance?.decimals || 18}
+
+
+
+ Full Balance
+
+ {balance?.formatted || "0"} {balance?.symbol || "ETH"}
+
+
+
+
+ )}
+
+ );
+};
+
+export default BalanceCard;
diff --git a/client/app/components/Wallet/NetworkSelector.tsx b/client/app/components/Wallet/NetworkSelector.tsx
new file mode 100644
index 00000000..ae512c9b
--- /dev/null
+++ b/client/app/components/Wallet/NetworkSelector.tsx
@@ -0,0 +1,128 @@
+"use client";
+
+import React from "react";
+import { useSwitchChain, useChainId } from "wagmi";
+import { sepolia, avalancheFuji, polygonAmoy } from "wagmi/chains";
+import {
+ GlobeAltIcon,
+ CheckCircleIcon,
+ ArrowPathIcon,
+} from "@heroicons/react/24/outline";
+import { motion } from "framer-motion";
+import toast from "react-hot-toast";
+
+const NetworkSelector: React.FC = () => {
+ const chainId = useChainId();
+ const { chains, switchChain, isPending } = useSwitchChain();
+
+ const supportedChains = [
+ {
+ id: sepolia.id,
+ name: "Sepolia Testnet",
+ icon: "🔵",
+ color: "from-blue-400 to-blue-600",
+ description: "Ethereum test network",
+ },
+ {
+ id: avalancheFuji.id,
+ name: "Avalanche Fuji",
+ icon: "🔴",
+ color: "from-red-400 to-red-600",
+ description: "Avalanche test network",
+ },
+ ];
+
+ const handleSwitchNetwork = async (targetChainId: number) => {
+ try {
+ await switchChain({ chainId: targetChainId });
+ toast.success(`Switched to ${supportedChains.find(c => c.id === targetChainId)?.name}`);
+ } catch (error) {
+ toast.error("Failed to switch network");
+ console.error("Network switch error:", error);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
Network Selection
+
+ Switch between supported blockchain networks
+
+
+
+
+
+ {supportedChains.map((chain) => {
+ const isActive = chainId === chain.id;
+ return (
+
!isActive && handleSwitchNetwork(chain.id)}
+ disabled={isActive || isPending}
+ whileHover={!isActive ? { scale: 1.02 } : {}}
+ whileTap={!isActive ? { scale: 0.98 } : {}}
+ className={`p-5 rounded-xl border-2 transition-all duration-200 ${
+ isActive
+ ? "border-indigo-500 bg-gradient-to-r from-indigo-50 to-purple-50"
+ : "border-gray-200 hover:border-indigo-300 bg-white hover:bg-gray-50"
+ } ${isPending ? "opacity-50 cursor-not-allowed" : ""}`}
+ >
+
+
+
{chain.icon}
+
+
{chain.name}
+
+ {chain.description}
+
+
+
+ {isActive && (
+
+ )}
+ {isPending && !isActive && (
+
+ )}
+
+ {isActive && (
+
+
+ Currently Active
+
+
+ )}
+
+ );
+ })}
+
+
+ {/* Warning for unsupported networks */}
+
+
+
⚠️
+
+
+ Important Notice
+
+
+ Only the networks listed above are supported for voting. Ensure
+ you're connected to the correct network before participating in
+ elections.
+
+
+
+
+
+ );
+};
+
+export default NetworkSelector;
diff --git a/client/app/components/Wallet/SecurityTips.tsx b/client/app/components/Wallet/SecurityTips.tsx
new file mode 100644
index 00000000..f11d0fdf
--- /dev/null
+++ b/client/app/components/Wallet/SecurityTips.tsx
@@ -0,0 +1,135 @@
+"use client";
+
+import React, { useState } from "react";
+import {
+ ShieldCheckIcon,
+ ChevronDownIcon,
+ ChevronUpIcon,
+ ExclamationTriangleIcon,
+ LockClosedIcon,
+ EyeSlashIcon,
+} from "@heroicons/react/24/outline";
+import { motion, AnimatePresence } from "framer-motion";
+
+const SecurityTips: React.FC = () => {
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ const securityTips = [
+ {
+ icon: LockClosedIcon,
+ title: "Never Share Your Private Keys",
+ description:
+ "Your private keys and seed phrases should never be shared with anyone. No legitimate service will ever ask for them.",
+ color: "text-red-600",
+ bgColor: "bg-red-50",
+ },
+ {
+ icon: ShieldCheckIcon,
+ title: "Verify Transaction Details",
+ description:
+ "Always double-check transaction details including recipient address, amount, and gas fees before confirming.",
+ color: "text-blue-600",
+ bgColor: "bg-blue-50",
+ },
+ {
+ icon: ExclamationTriangleIcon,
+ title: "Beware of Phishing",
+ description:
+ "Be cautious of suspicious links and websites. Always verify the URL and ensure you're on the official platform.",
+ color: "text-yellow-600",
+ bgColor: "bg-yellow-50",
+ },
+ {
+ icon: EyeSlashIcon,
+ title: "Use Hardware Wallets",
+ description:
+ "For large amounts, consider using a hardware wallet for enhanced security and protection against online threats.",
+ color: "text-green-600",
+ bgColor: "bg-green-50",
+ },
+ ];
+
+ return (
+
+ setIsExpanded(!isExpanded)}
+ className="w-full p-6 flex items-center justify-between hover:bg-white/30 transition-colors duration-200 rounded-2xl"
+ >
+
+
+
+
+
+
Security Tips
+
+ Important guidelines to keep your wallet secure
+
+
+
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+
+
+
+ {isExpanded && (
+
+
+ {securityTips.map((tip, index) => (
+
+
+
+
+
+ {tip.title}
+
+
{tip.description}
+
+
+
+ ))}
+
+ {/* Additional Warning */}
+
+
+
+
+
+ Stay Safe Online
+
+
+ This platform will never ask you to send cryptocurrency to
+ an external address. All voting transactions are handled
+ through smart contracts. If you encounter any suspicious
+ activity, please disconnect your wallet immediately.
+
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default SecurityTips;
diff --git a/client/app/components/Wallet/VotingHistory.tsx b/client/app/components/Wallet/VotingHistory.tsx
new file mode 100644
index 00000000..0ed8ab98
--- /dev/null
+++ b/client/app/components/Wallet/VotingHistory.tsx
@@ -0,0 +1,173 @@
+"use client";
+
+import React, { useState, useEffect } from "react";
+import {
+ ClockIcon,
+ CheckBadgeIcon,
+ ChartBarIcon,
+} from "@heroicons/react/24/outline";
+import { motion } from "framer-motion";
+import { useChainId } from "wagmi";
+
+interface VotingHistoryProps {
+ address: `0x${string}` | undefined;
+}
+
+interface VoteRecord {
+ id: string;
+ electionName: string;
+ timestamp: Date;
+ txHash: string;
+ network: string;
+}
+
+const VotingHistory: React.FC = ({ address }) => {
+ const chainId = useChainId();
+ const [votingHistory, setVotingHistory] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ // Simulated voting history - In production, this would fetch from blockchain
+ // or a backend service tracking user's voting activities
+ const fetchVotingHistory = async () => {
+ setIsLoading(true);
+ // Simulate API delay
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+
+ // Mock data - Replace with actual blockchain queries
+ const mockHistory: VoteRecord[] = [];
+
+ setVotingHistory(mockHistory);
+ setIsLoading(false);
+ };
+
+ if (address) {
+ fetchVotingHistory();
+ }
+ }, [address, chainId]);
+
+ const getNetworkName = (network: string) => {
+ switch (network) {
+ case "sepolia":
+ return "Sepolia";
+ case "avalancheFuji":
+ return "Avalanche Fuji";
+ case "polygonAmoy":
+ return "Polygon Amoy";
+ default:
+ return network;
+ }
+ };
+
+ const truncateHash = (hash: string) => {
+ return `${hash.slice(0, 6)}...${hash.slice(-4)}`;
+ };
+
+ return (
+
+
+
+
+
+
+
Voting History
+
+ Track your participation in elections
+
+
+
+
+ {isLoading ? (
+
+ {[1, 2, 3].map((i) => (
+
+ ))}
+
+ ) : votingHistory.length === 0 ? (
+
+
+
+
+
+ No Voting History Yet
+
+
+ You haven't participated in any elections yet. Your voting activity
+ will appear here once you cast your first vote.
+
+
+ ) : (
+
+ {votingHistory.map((vote, index) => (
+
+
+
+
+
+
+ {vote.electionName}
+
+
+ {vote.timestamp.toLocaleDateString()} at{" "}
+ {vote.timestamp.toLocaleTimeString()}
+
+
+
+
+
+
+ ))}
+
+ )}
+
+ {/* Statistics Summary */}
+
+
+
+ {votingHistory.length}
+
+
Total Votes
+
+
+
+ {new Set(votingHistory.map((v) => v.electionName)).size}
+
+
Elections
+
+
+
+ {new Set(votingHistory.map((v) => v.network)).size}
+
+
Networks
+
+
+
+ );
+};
+
+export default VotingHistory;
diff --git a/client/app/components/Wallet/WalletInfo.tsx b/client/app/components/Wallet/WalletInfo.tsx
new file mode 100644
index 00000000..d4e8c455
--- /dev/null
+++ b/client/app/components/Wallet/WalletInfo.tsx
@@ -0,0 +1,94 @@
+"use client";
+
+import React from "react";
+import { useEnsName, useEnsAvatar } from "wagmi";
+import { UserCircleIcon } from "@heroicons/react/24/outline";
+import Image from "next/image";
+import { motion } from "framer-motion";
+
+interface WalletInfoProps {
+ address: `0x${string}` | undefined;
+}
+
+const WalletInfo: React.FC = ({ address }) => {
+ const { data: ensName } = useEnsName({ address });
+ const { data: ensAvatar } = useEnsAvatar({ name: ensName || undefined });
+
+ return (
+
+ Wallet Details
+
+
+ {/* Avatar/Profile Section */}
+
+
+ {ensAvatar ? (
+
+ ) : (
+
+
+
+ )}
+
+
+
Identity
+
+ {ensName || "No ENS Name"}
+
+
+
+
+ {/* Wallet Type */}
+
+
+
+ Wallet Type
+
+
+ Web3
+
+
+
+ Your wallet is connected and ready for blockchain interactions
+
+
+
+ {/* Connection Status */}
+
+
+ {/* Address Checksum Info */}
+
+
+ Security Info
+
+
+ ✓ Address is checksummed
+ ✓ Valid Ethereum address format
+ ✓ Compatible with EVM chains
+
+
+
+
+ );
+};
+
+export default WalletInfo;
diff --git a/client/app/wallet/page.tsx b/client/app/wallet/page.tsx
new file mode 100644
index 00000000..521ea1a8
--- /dev/null
+++ b/client/app/wallet/page.tsx
@@ -0,0 +1,175 @@
+"use client";
+
+import React, { useState } from "react";
+import { motion } from "framer-motion";
+import { useAccount, useDisconnect, useBalance, useEnsName, useEnsAvatar } from "wagmi";
+import { ConnectButton } from "@rainbow-me/rainbowkit";
+import {
+ WalletIcon,
+ ArrowRightOnRectangleIcon,
+ CheckCircleIcon,
+ ExclamationTriangleIcon,
+ ClipboardDocumentIcon,
+ CheckIcon,
+} from "@heroicons/react/24/outline";
+import WalletInfo from "../components/Wallet/WalletInfo";
+import BalanceCard from "../components/Wallet/BalanceCard";
+import NetworkSelector from "../components/Wallet/NetworkSelector";
+import VotingHistory from "../components/Wallet/VotingHistory";
+import SecurityTips from "../components/Wallet/SecurityTips";
+import toast from "react-hot-toast";
+
+const WalletPage: React.FC = () => {
+ const { address, isConnected, connector } = useAccount();
+ const { disconnect } = useDisconnect();
+ const [copiedAddress, setCopiedAddress] = useState(false);
+
+ const handleCopyAddress = () => {
+ if (address) {
+ navigator.clipboard.writeText(address);
+ setCopiedAddress(true);
+ toast.success("Address copied to clipboard!");
+ setTimeout(() => setCopiedAddress(false), 2000);
+ }
+ };
+
+ const handleDisconnect = () => {
+ disconnect();
+ toast.success("Wallet disconnected successfully!");
+ };
+
+ const containerVariants = {
+ hidden: { opacity: 0, y: 20 },
+ visible: {
+ opacity: 1,
+ y: 0,
+ transition: {
+ duration: 0.5,
+ staggerChildren: 0.1,
+ },
+ },
+ };
+
+ const itemVariants = {
+ hidden: { opacity: 0, y: 10 },
+ visible: { opacity: 1, y: 0 },
+ };
+
+ if (!isConnected) {
+ return (
+
+
+
+
+ Wallet Management
+
+
+ Connect your wallet to view and manage your blockchain assets,
+ voting history, and more.
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {/* Header Section */}
+
+
+
+
+
+
+
+
+ Wallet Management
+
+
+
+
+ Connected via {connector?.name || "Wallet"}
+
+
+
+
+
+
+ Disconnect
+
+
+
+ {/* Address Display */}
+
+
+ Wallet Address
+
+
+
+ {address}
+
+
+ {copiedAddress ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ {/* Wallet Info and Balance */}
+
+
+
+
+
+
+
+
+
+ {/* Network Selector */}
+
+
+
+
+ {/* Voting History */}
+
+
+
+
+ {/* Security Tips */}
+
+
+
+
+
+
+ );
+};
+
+export default WalletPage;
diff --git a/client/package-lock.json b/client/package-lock.json
index 6143ee65..a3baa66c 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -16583,6 +16583,126 @@
"optional": true
}
}
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "14.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.4.tgz",
+ "integrity": "sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "14.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.4.tgz",
+ "integrity": "sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "14.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.4.tgz",
+ "integrity": "sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "14.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.4.tgz",
+ "integrity": "sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "14.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.4.tgz",
+ "integrity": "sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "14.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.4.tgz",
+ "integrity": "sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.4.tgz",
+ "integrity": "sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "14.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.4.tgz",
+ "integrity": "sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
}
}
}
From 90d1fb5d20c26f9c4766475f1696e063ea2b809b Mon Sep 17 00:00:00 2001
From: Hemant <133942800+hk2166@users.noreply.github.com>
Date: Sat, 11 Oct 2025 21:54:46 +0530
Subject: [PATCH 2/5] Update .env
---
.env | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.env b/.env
index 14430698..b40f5658 100644
--- a/.env
+++ b/.env
@@ -1,4 +1,4 @@
-PRIVATE_KEY=011d4ba8e7b549ee9cd718df4c25abed
-RPC_URL_SEPOLIA=https://eth-mainnet.g.alchemy.com/v2/MBGDHRRT3_qjtqKzM2DZk
-RPC_URL_FUJI=https://avax-fuji.g.alchemy.com/v2/MBGDHRRT3_qjtqKzM2DZk
-ETHERSCAN_KEY=WCU9BD1WA1835GAU9VBTGSPA2DGEPQHYSF
\ No newline at end of file
+PRIVATE_KEY=#####
+RPC_URL_SEPOLIA=####
+RPC_URL_FUJI=#####
+ETHERSCAN_KEY=###
From c20a9bf832dabf72024e259fafec2211d40cc485 Mon Sep 17 00:00:00 2001
From: Hemant <133942800+hk2166@users.noreply.github.com>
Date: Sat, 11 Oct 2025 21:55:19 +0530
Subject: [PATCH 3/5] Rename .env to .env.example
---
.env => .env.example | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename .env => .env.example (100%)
diff --git a/.env b/.env.example
similarity index 100%
rename from .env
rename to .env.example
From 5216270054f9c0957969206d4469f41b71612012 Mon Sep 17 00:00:00 2001
From: Hemant <133942800+hk2166@users.noreply.github.com>
Date: Sat, 11 Oct 2025 22:06:49 +0530
Subject: [PATCH 4/5] Update .env.example
---
.env.example | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.env.example b/.env.example
index b40f5658..09a9e9cb 100644
--- a/.env.example
+++ b/.env.example
@@ -1,4 +1,4 @@
-PRIVATE_KEY=#####
-RPC_URL_SEPOLIA=####
-RPC_URL_FUJI=#####
-ETHERSCAN_KEY=###
+PRIVATE_KEY=
+RPC_URL_SEPOLIA=
+RPC_URL_FUJI=
+ETHERSCAN_KEY=
From 27d0957fc1447a705fb1e4de74a924f7a8718c45 Mon Sep 17 00:00:00 2001
From: Hemant <133942800+hk2166@users.noreply.github.com>
Date: Sun, 12 Oct 2025 00:47:27 +0530
Subject: [PATCH 5/5] Created a Help & Support page issue #179
---
.DS_Store | Bin 8196 -> 8196 bytes
client/app/components/Cards/ElectionMini.tsx | 20 +-
client/app/components/Header/Header.tsx | 4 +-
client/app/components/Help/CategoryCard.tsx | 54 ++++
client/app/components/Help/ContactSupport.tsx | 227 +++++++++++++++++
client/app/components/Help/FAQSection.tsx | 204 +++++++++++++++
client/app/components/Help/HowToGuides.tsx | 176 +++++++++++++
.../components/Help/TroubleshootingGuide.tsx | 239 ++++++++++++++++++
client/app/create/page.tsx | 85 ++++++-
client/app/globals.css | 3 +-
client/app/help/page.tsx | 176 +++++++++++++
11 files changed, 1168 insertions(+), 20 deletions(-)
create mode 100644 client/app/components/Help/CategoryCard.tsx
create mode 100644 client/app/components/Help/ContactSupport.tsx
create mode 100644 client/app/components/Help/FAQSection.tsx
create mode 100644 client/app/components/Help/HowToGuides.tsx
create mode 100644 client/app/components/Help/TroubleshootingGuide.tsx
create mode 100644 client/app/help/page.tsx
diff --git a/.DS_Store b/.DS_Store
index 346f1030e880bf79920f31cac7a99ca9fbea3335..2993e81106e9df57c8b710256a77aaf0fb040eb9 100644
GIT binary patch
delta 611
zcmZp1XmOa}jIU^hRb_GBIbiF)R*p9L5g7+4te7}6Os8A@{VU0jlK@{@pK99?e>
zvSNLYJEF>`;FT}PFbq!4&n*DzVPFB7%)r17Gn*lUp_CyhCmo@Jp`Jn27E=kbBjUE`
zaz#QMfo4!{KEf%uRUtcwfgu6aK{=Br2^?Y%-2t{@a-*Q|qA6eZia}K|5#mAOce60;ys>aHpRfvt)vYFpXrLQ`94R4$qmYCpCf5n`Ff;5anA|Ju%fi5L
zk8AQhVL9En7E6TZl7Zoq$WX$N#E=h)2ZT9LRg*PE59VUB=c(U;J+s00wAtJ-B
z@#*rM7@z}pi^#&+_eErxdCx7F{9h!2QGarRs08~57i*A3lj}t#VeHNGMQ<=}W|#QJ
OGC5a7ojL|yWdr~~pRZj2
delta 419
zcmZp1XmOa}mJU^hRb`eYsfiFl?@57QYK7+4te7}6Os8A@{VU0jlK@{@pK9O-l8
zdCaYkIikv^;FT}PFbq!4&n*DzVPMwSFj-jeBD+nB<10S#W0M7igeU(K*xzSs@M
Ri3JljvrBwqVIop>F90(be|P`@
diff --git a/client/app/components/Cards/ElectionMini.tsx b/client/app/components/Cards/ElectionMini.tsx
index 3c731eb3..97b42e74 100644
--- a/client/app/components/Cards/ElectionMini.tsx
+++ b/client/app/components/Cards/ElectionMini.tsx
@@ -1,6 +1,6 @@
"use client";
-import React from "react";
+import React, { useEffect } from "react";
import { motion } from "framer-motion";
import SkeletonElection from "../Helper/SkeletonElection";
import { FaRegUser } from "react-icons/fa6";
@@ -30,11 +30,21 @@ const ElectionMini = ({
electionAddress: electionAddress,
});
- if (isLoading || electionInfo == undefined) return ;
- const isStarting = Math.floor(Date.now() / 1000) < Number(electionInfo[0]);
- const isEnded = Math.floor(Date.now() / 1000) > Number(electionInfo[1]);
+ // Calculate election status (safe to do even if electionInfo is undefined)
+ const isStarting = electionInfo ? Math.floor(Date.now() / 1000) < Number(electionInfo[0]) : false;
+ const isEnded = electionInfo ? Math.floor(Date.now() / 1000) > Number(electionInfo[1]) : false;
const electionStat = isStarting ? 1 : isEnded ? 3 : 2;
- update?.(electionAddress, electionStat);
+
+ // Move state update to useEffect to avoid updating parent during render
+ // This must be called before any conditional returns (Rules of Hooks)
+ useEffect(() => {
+ if (update && electionInfo) {
+ update(electionAddress, electionStat);
+ }
+ }, [electionAddress, electionStat, update, electionInfo]);
+
+ // Early return AFTER all hooks have been called
+ if (isLoading || electionInfo == undefined) return ;
return (
{
@@ -42,7 +44,7 @@ const Header = () => {
diff --git a/client/app/components/Help/CategoryCard.tsx b/client/app/components/Help/CategoryCard.tsx
new file mode 100644
index 00000000..acd5bb97
--- /dev/null
+++ b/client/app/components/Help/CategoryCard.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import React from "react";
+import { motion } from "framer-motion";
+
+interface Category {
+ id: string;
+ title: string;
+ icon: React.ComponentType>;
+ color: string;
+ description: string;
+}
+
+interface CategoryCardProps {
+ category: Category;
+ index: number;
+ isSelected: boolean;
+ onClick: () => void;
+}
+
+export default function CategoryCard({
+ category,
+ index,
+ isSelected,
+ onClick,
+}: CategoryCardProps) {
+ const Icon = category.icon;
+
+ return (
+
+
+
+
+
+ {category.title}
+
+ {category.description}
+
+ );
+}
diff --git a/client/app/components/Help/ContactSupport.tsx b/client/app/components/Help/ContactSupport.tsx
new file mode 100644
index 00000000..ed101d88
--- /dev/null
+++ b/client/app/components/Help/ContactSupport.tsx
@@ -0,0 +1,227 @@
+"use client";
+
+import React, { useState } from "react";
+import { motion } from "framer-motion";
+import {
+ EnvelopeIcon,
+ ChatBubbleLeftRightIcon,
+ DocumentTextIcon,
+ CodeBracketIcon,
+} from "@heroicons/react/24/outline";
+import toast from "react-hot-toast";
+
+export default function ContactSupport() {
+ const [formData, setFormData] = useState({
+ name: "",
+ email: "",
+ subject: "",
+ message: "",
+ });
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+
+ // In a real implementation, this would send to a backend or support system
+ toast.success(
+ "Thank you! Your message has been received. We'll get back to you soon."
+ );
+
+ setFormData({ name: "", email: "", subject: "", message: "" });
+ };
+
+ const handleChange = (
+ e: React.ChangeEvent
+ ) => {
+ setFormData({
+ ...formData,
+ [e.target.name]: e.target.value,
+ });
+ };
+
+ return (
+
+ Contact Support
+
+
+ {/* Contact Cards */}
+
+
+ Discord Community
+
+ Join our active community for real-time help and discussions
+
+
+
+
+
+ GitHub Issues
+
+ Report bugs or request features on our GitHub repository
+
+
+
+
+
+ Documentation
+
+ Explore detailed documentation and contribution guidelines
+
+
+
+
+ {/* Contact Form */}
+
+
+
+
+ Send Us a Message
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/client/app/components/Help/FAQSection.tsx b/client/app/components/Help/FAQSection.tsx
new file mode 100644
index 00000000..4d578557
--- /dev/null
+++ b/client/app/components/Help/FAQSection.tsx
@@ -0,0 +1,204 @@
+"use client";
+
+import React, { useState } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import { ChevronDownIcon } from "@heroicons/react/24/outline";
+
+interface FAQ {
+ id: string;
+ question: string;
+ answer: string;
+ category: string;
+}
+
+const faqs: FAQ[] = [
+ {
+ id: "1",
+ question: "What is Agora Blockchain?",
+ answer:
+ "Agora Blockchain is a decentralized voting platform that uses blockchain technology to ensure secure, transparent, and tamper-proof elections. It implements various voting algorithms like Moore's, Oklahoma, Borda, and IRV on the blockchain.",
+ category: "voting",
+ },
+ {
+ id: "2",
+ question: "How do I connect my wallet?",
+ answer:
+ "Click on the 'Connect Wallet' button in the header. You'll need MetaMask or another Web3 wallet installed. Follow the prompts to connect your wallet to the Sepolia testnet.",
+ category: "wallets",
+ },
+ {
+ id: "3",
+ question: "Which voting algorithms are supported?",
+ answer:
+ "Agora Blockchain supports multiple voting algorithms including: Moore's method, Oklahoma method, Borda count, Instant Runoff Voting (IRV), Kemeny-Young, Schulze method, Score voting, Ranked voting, and Quadratic voting.",
+ category: "voting",
+ },
+ {
+ id: "4",
+ question: "How do I create an election?",
+ answer:
+ "Navigate to the Create page, fill in the election details including title, description, voting algorithm, candidates, and time period. Make sure your wallet is connected and has enough test ETH for gas fees.",
+ category: "voting",
+ },
+ {
+ id: "5",
+ question: "Is my vote anonymous?",
+ answer:
+ "Votes are recorded on the blockchain and linked to wallet addresses. However, Agora Blockchain includes anonymous voting features using zero-knowledge proofs for enhanced privacy. Check the anonymousVoting directory for implementation details.",
+ category: "security",
+ },
+ {
+ id: "6",
+ question: "What networks are supported?",
+ answer:
+ "Agora Blockchain currently supports Sepolia testnet for Ethereum and Fuji testnet for Avalanche. Cross-chain voting is enabled through Chainlink CCIP.",
+ category: "wallets",
+ },
+ {
+ id: "7",
+ question: "How do I get test ETH?",
+ answer:
+ "You can get test ETH from Sepolia faucets like Alchemy Sepolia Faucet or Infura Faucet. Simply enter your wallet address to receive test tokens.",
+ category: "wallets",
+ },
+ {
+ id: "8",
+ question: "Can I vote from different blockchains?",
+ answer:
+ "Yes! Agora Blockchain supports cross-chain voting through Chainlink's Cross-Chain Interoperability Protocol (CCIP). You can vote from Sepolia or Fuji testnets.",
+ category: "smart-contracts",
+ },
+ {
+ id: "9",
+ question: "How are election results calculated?",
+ answer:
+ "Results are calculated on-chain using smart contracts specific to each voting algorithm. The ResultCalculator contract processes votes according to the selected algorithm and returns the winner.",
+ category: "voting",
+ },
+ {
+ id: "10",
+ question: "What are the gas fees?",
+ answer:
+ "Gas fees vary depending on network congestion and the complexity of the transaction. Creating an election typically costs more than casting a vote. Make sure you have enough testnet tokens.",
+ category: "smart-contracts",
+ },
+ {
+ id: "11",
+ question: "How secure are the smart contracts?",
+ answer:
+ "Agora Blockchain uses OpenZeppelin's audited smart contract libraries and follows security best practices. All contracts are tested thoroughly and deployed on testnets for verification.",
+ category: "security",
+ },
+ {
+ id: "12",
+ question: "Can I delete an election?",
+ answer:
+ "Yes, election creators can delete their elections through the smart contract. However, once voting has started, deletion may be restricted to preserve election integrity.",
+ category: "voting",
+ },
+ {
+ id: "13",
+ question: "What if my transaction fails?",
+ answer:
+ "Transaction failures can occur due to insufficient gas, network issues, or contract constraints. Check your wallet's gas settings and ensure you have enough testnet tokens. See the Troubleshooting section for more details.",
+ category: "smart-contracts",
+ },
+ {
+ id: "14",
+ question: "How do I view my voting history?",
+ answer:
+ "Navigate to your Profile page to see all elections you've created or participated in. You can also view transaction history in your wallet or on the blockchain explorer.",
+ category: "voting",
+ },
+ {
+ id: "15",
+ question: "Is the source code open?",
+ answer:
+ "Yes! Agora Blockchain is fully open source and available on GitHub. You can contribute to the project by following the contribution guidelines in the README.",
+ category: "security",
+ },
+];
+
+interface FAQSectionProps {
+ searchQuery: string;
+ selectedCategory: string | null;
+}
+
+export default function FAQSection({
+ searchQuery,
+ selectedCategory,
+}: FAQSectionProps) {
+ const [openFAQ, setOpenFAQ] = useState(null);
+
+ const filteredFAQs = faqs.filter((faq) => {
+ const matchesSearch =
+ searchQuery === "" ||
+ faq.question.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ faq.answer.toLowerCase().includes(searchQuery.toLowerCase());
+ const matchesCategory =
+ !selectedCategory || faq.category === selectedCategory;
+ return matchesSearch && matchesCategory;
+ });
+
+ const toggleFAQ = (id: string) => {
+ setOpenFAQ(openFAQ === id ? null : id);
+ };
+
+ return (
+
+
+ Frequently Asked Questions
+
+
+ {filteredFAQs.length > 0 ? (
+ filteredFAQs.map((faq) => (
+
+ toggleFAQ(faq.id)}
+ className="w-full px-6 py-4 text-left flex justify-between items-center hover:bg-gray-50 transition-colors"
+ >
+
+ {faq.question}
+
+
+
+
+ {openFAQ === faq.id && (
+
+
+
+ )}
+
+
+ ))
+ ) : (
+
+
No FAQs found matching your search.
+
+ )}
+
+
+ );
+}
diff --git a/client/app/components/Help/HowToGuides.tsx b/client/app/components/Help/HowToGuides.tsx
new file mode 100644
index 00000000..fe74f3b6
--- /dev/null
+++ b/client/app/components/Help/HowToGuides.tsx
@@ -0,0 +1,176 @@
+"use client";
+
+import React, { useState } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import {
+ PlayIcon,
+ CheckCircleIcon,
+ ChevronRightIcon,
+} from "@heroicons/react/24/outline";
+
+interface Guide {
+ id: string;
+ title: string;
+ description: string;
+ steps: string[];
+ videoUrl?: string;
+}
+
+const guides: Guide[] = [
+ {
+ id: "setup-wallet",
+ title: "Setting Up Your Wallet",
+ description: "Learn how to install and configure MetaMask for Agora Blockchain",
+ steps: [
+ "Install MetaMask browser extension from metamask.io",
+ "Create a new wallet or import an existing one",
+ "Save your seed phrase securely (never share it!)",
+ "Switch to Sepolia testnet in MetaMask",
+ "Get test ETH from a Sepolia faucet",
+ "Connect your wallet to Agora Blockchain",
+ ],
+ },
+ {
+ id: "create-election",
+ title: "Creating Your First Election",
+ description: "Step-by-step guide to creating a decentralized election",
+ steps: [
+ "Click 'Create' in the navigation menu",
+ "Enter election title and description",
+ "Select a voting algorithm (e.g., Borda, IRV, Moore's)",
+ "Add candidates with names and descriptions",
+ "Set start and end dates for voting period",
+ "Review election details and gas estimate",
+ "Confirm transaction in your wallet",
+ "Wait for blockchain confirmation",
+ "Share your election address with voters",
+ ],
+ },
+ {
+ id: "cast-vote",
+ title: "Casting a Vote",
+ description: "How to participate in an election and cast your vote",
+ steps: [
+ "Navigate to the election page",
+ "Review candidates and election details",
+ "Ensure you're on the correct network",
+ "Click 'Vote' on your preferred candidate(s)",
+ "For ranked voting: drag to reorder candidates",
+ "For score voting: assign points to each candidate",
+ "Review your vote selection",
+ "Confirm the transaction in your wallet",
+ "Wait for transaction confirmation",
+ "View confirmation receipt on the blockchain",
+ ],
+ },
+ {
+ id: "cross-chain-voting",
+ title: "Cross-Chain Voting with CCIP",
+ description: "Vote from different blockchain networks using Chainlink CCIP",
+ steps: [
+ "Connect your wallet to either Sepolia or Fuji testnet",
+ "Navigate to the election you want to vote in",
+ "Select 'Cross-Chain Vote' if available",
+ "Choose your source network (current network)",
+ "Choose destination network (where election is hosted)",
+ "Cast your vote as normal",
+ "Approve CCIP cross-chain transaction",
+ "Wait for cross-chain message confirmation",
+ "Verify vote on destination blockchain",
+ ],
+ },
+ {
+ id: "view-results",
+ title: "Viewing Election Results",
+ description: "How to check election outcomes and verify results on-chain",
+ steps: [
+ "Navigate to the completed election page",
+ "Wait for the voting period to end",
+ "Click 'Calculate Results' (if not auto-calculated)",
+ "View winner and vote distribution",
+ "Check detailed algorithm-specific results",
+ "Verify results on blockchain explorer",
+ "Export results as needed",
+ ],
+ },
+];
+
+export default function HowToGuides() {
+ const [selectedGuide, setSelectedGuide] = useState(null);
+
+ return (
+
+
+ How-To Guides
+
+
+ {guides.map((guide, index) => (
+
+
+ setSelectedGuide(selectedGuide === guide.id ? null : guide.id)
+ }
+ >
+
+
+
+
+
+ {guide.title}
+
+
{guide.description}
+
+
+
+
+
+
+
+ {selectedGuide === guide.id && (
+
+
+
+ {guide.steps.map((step, stepIndex) => (
+
+
+ {stepIndex + 1}
+
+ {step}
+
+ ))}
+
+
+
+ )}
+
+
+ ))}
+
+
+ );
+}
diff --git a/client/app/components/Help/TroubleshootingGuide.tsx b/client/app/components/Help/TroubleshootingGuide.tsx
new file mode 100644
index 00000000..f62ff7d1
--- /dev/null
+++ b/client/app/components/Help/TroubleshootingGuide.tsx
@@ -0,0 +1,239 @@
+"use client";
+
+import React, { useState } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import {
+ ExclamationTriangleIcon,
+ ChevronDownIcon,
+} from "@heroicons/react/24/outline";
+
+interface Issue {
+ id: string;
+ title: string;
+ symptoms: string[];
+ solutions: string[];
+}
+
+const commonIssues: Issue[] = [
+ {
+ id: "wallet-not-connecting",
+ title: "Wallet Not Connecting",
+ symptoms: [
+ "Can't see 'Connect Wallet' button response",
+ "MetaMask popup doesn't appear",
+ "Connection request times out",
+ ],
+ solutions: [
+ "Refresh the page and try again",
+ "Make sure MetaMask extension is installed and unlocked",
+ "Check if you're using a supported browser (Chrome, Firefox, Brave)",
+ "Clear browser cache and cookies",
+ "Try disabling other wallet extensions temporarily",
+ "Update MetaMask to the latest version",
+ ],
+ },
+ {
+ id: "transaction-failing",
+ title: "Transaction Keeps Failing",
+ symptoms: [
+ "Transaction reverts with an error",
+ "Gas estimation fails",
+ "Transaction stays pending forever",
+ ],
+ solutions: [
+ "Ensure you have enough test ETH for gas fees",
+ "Check if you're connected to the correct network (Sepolia/Fuji)",
+ "Try increasing gas limit manually in MetaMask",
+ "Wait for network congestion to decrease",
+ "Cancel pending transactions if stuck",
+ "Check contract requirements (e.g., voting period, eligibility)",
+ ],
+ },
+ {
+ id: "wrong-network",
+ title: "Connected to Wrong Network",
+ symptoms: [
+ "Can't see your elections",
+ "Transactions don't go through",
+ "Balance shows as zero",
+ ],
+ solutions: [
+ "Open MetaMask and switch to Sepolia testnet",
+ "Add custom network if using Fuji (Avalanche testnet)",
+ "Check network configuration in MetaMask settings",
+ "Verify RPC URLs match the documentation",
+ "Clear MetaMask activity and nonce data",
+ ],
+ },
+ {
+ id: "insufficient-funds",
+ title: "Insufficient Funds for Gas",
+ symptoms: [
+ "Error message about insufficient funds",
+ "Can't complete transactions",
+ "Balance too low warning",
+ ],
+ solutions: [
+ "Get test ETH from Sepolia faucet (Alchemy, Infura, or Chainlink)",
+ "Wait 24 hours if faucet has cooldown period",
+ "Use multiple faucets for more test tokens",
+ "Ask in Discord community for test tokens",
+ "Verify you're requesting from correct network faucet",
+ ],
+ },
+ {
+ id: "vote-not-recorded",
+ title: "My Vote Wasn't Recorded",
+ symptoms: [
+ "Transaction succeeded but vote not showing",
+ "Vote count didn't increase",
+ "Can't see vote in transaction history",
+ ],
+ solutions: [
+ "Wait a few minutes for blockchain confirmation",
+ "Check transaction on block explorer (Etherscan/Snowtrace)",
+ "Refresh the page to update vote count",
+ "Verify you voted before election ended",
+ "Check if you already voted (most elections allow one vote)",
+ "Ensure you're viewing the correct election",
+ ],
+ },
+ {
+ id: "election-not-loading",
+ title: "Election Page Not Loading",
+ symptoms: [
+ "Blank page or loading spinner",
+ "404 error",
+ "Election details not showing",
+ ],
+ solutions: [
+ "Verify the election address/ID is correct",
+ "Check if you're connected to the right network",
+ "Try refreshing the page",
+ "Clear browser cache",
+ "Check if election was deleted by creator",
+ "Verify blockchain connection is active",
+ ],
+ },
+ {
+ id: "cross-chain-issues",
+ title: "Cross-Chain Voting Not Working",
+ symptoms: [
+ "CCIP transaction fails",
+ "Vote doesn't appear on destination chain",
+ "Higher than expected fees",
+ ],
+ solutions: [
+ "Ensure both source and destination networks are supported",
+ "Have enough tokens for cross-chain fees (higher than normal)",
+ "Wait longer for cross-chain confirmation (can take minutes)",
+ "Check CCIP contract allowlist for supported routes",
+ "Verify both contracts are properly whitelisted",
+ "Try direct voting on the election's native chain instead",
+ ],
+ },
+ {
+ id: "metamask-errors",
+ title: "MetaMask Error Messages",
+ symptoms: [
+ '"User rejected the request"',
+ '"Nonce too high"',
+ '"Gas required exceeds allowance"',
+ ],
+ solutions: [
+ "User rejected: Click approve/confirm in MetaMask popup",
+ "Nonce too high: Reset account in MetaMask settings",
+ "Gas exceeds allowance: Increase gas limit or wait for lower network fees",
+ "Check MetaMask for specific error details",
+ "Update MetaMask to latest version",
+ "Report persistent errors on GitHub or Discord",
+ ],
+ },
+];
+
+export default function TroubleshootingGuide() {
+ const [openIssue, setOpenIssue] = useState(null);
+
+ const toggleIssue = (id: string) => {
+ setOpenIssue(openIssue === id ? null : id);
+ };
+
+ return (
+
+
+
+
+ Troubleshooting Guide
+
+
+
+ {commonIssues.map((issue) => (
+
+ toggleIssue(issue.id)}
+ className="w-full px-6 py-4 text-left flex justify-between items-center hover:bg-gray-50 transition-colors"
+ >
+ {issue.title}
+
+
+
+ {openIssue === issue.id && (
+
+
+
+
+ Symptoms:
+
+
+ {issue.symptoms.map((symptom, index) => (
+ {symptom}
+ ))}
+
+
+
+
+ Solutions:
+
+
+ {issue.solutions.map((solution, index) => (
+
+
+ {index + 1}
+
+
+ {solution}
+
+
+ ))}
+
+
+
+
+ )}
+
+
+ ))}
+
+
+ );
+}
diff --git a/client/app/create/page.tsx b/client/app/create/page.tsx
index d8f62dad..88858c23 100644
--- a/client/app/create/page.tsx
+++ b/client/app/create/page.tsx
@@ -17,8 +17,9 @@ import ElectionInfoPopup from "../components/Modal/ElectionInfoPopup";
const CreatePage: React.FC = () => {
const router = useRouter();
const [selectedBallot, setSelectedBallot] = useState(1);
+ const [isSubmitting, setIsSubmitting] = useState(false);
const { switchChain } = useSwitchChain();
- const { chain } = useAccount();
+ const { chain, isConnected, address } = useAccount();
const { writeContractAsync } = useWriteContract();
const [startTime, setStartTime] = useState(new Date());
const [endTime, setEndTime] = useState(new Date());
@@ -50,15 +51,31 @@ const CreatePage: React.FC = () => {
};
const createElection = async (event: FormEvent) => {
event.preventDefault();
+
+ // Check if wallet is connected
+ if (!isConnected || !address) {
+ toast.error("Please connect your wallet first!");
+ console.log("Wallet not connected. isConnected:", isConnected, "address:", address);
+ return;
+ }
+
+ // Check if on correct network
+ if (chain?.id !== sepolia.id) {
+ toast.error("Please switch to Sepolia network!");
+ console.log("Wrong network. Current chain:", chain?.id, "Required:", sepolia.id);
+ return;
+ }
+
const formData = new FormData(event.currentTarget);
const name = formData.get("name") as string;
const description = formData.get("description") as string;
const ballotType = BigInt(selectedBallot);
+ console.log("Form data:", { name, description, ballotType, candidateCount: candidates.length });
+
if (candidates.length<2){
toast.error("At least 2 candidates are required!");
return;
-
}
if (!startTime || !endTime) {
@@ -73,28 +90,65 @@ const CreatePage: React.FC = () => {
toast.error("Invalid timing. End time must be after start time.");
return;
}
+
if(candidates.length>0 && candidates.some(candidate=>!candidate.name || !candidate.description)){
toast.error("please enter all candidate information or remove empty candidates.")
return
}
- // passed candidates to the create election function
+
+ setIsSubmitting(true);
+ console.log("Submitting transaction...");
+ toast.loading("Waiting for MetaMask approval...", { id: "creating" });
+
try {
- await writeContractAsync({
+ console.log("Calling writeContractAsync with:", {
+ address: ELECTION_FACTORY_ADDRESS,
+ functionName: "createElection",
+ args: [
+ { startTime: start, endTime: end, name, description },
+ candidates.map((c, index) => ({ candidateID: BigInt(index), name: c.name, description: c.description })),
+ ballotType,
+ ballotType,
+ ]
+ });
+
+ const txHash = await writeContractAsync({
address: ELECTION_FACTORY_ADDRESS,
abi: ElectionFactory,
functionName: "createElection",
args: [
- { startTime: start, endTime: end, name, description }, // ElectionInfo object
+ { startTime: start, endTime: end, name, description },
candidates.map((c, index) => ({ candidateID: BigInt(index), name: c.name, description: c.description })),
ballotType,
ballotType,
],
});
- toast.success("Election created successfully!");
- router.push("/");
- } catch (error) {
+
+ console.log("Transaction submitted:", txHash);
+ toast.success("Election created successfully!", { id: "creating" });
+
+ setTimeout(() => {
+ router.push("/");
+ }, 1500);
+ } catch (error: any) {
console.error("Error creating election:", error);
- toast.error(ErrorMessage(error));
+ console.error("Error details:", {
+ message: error?.message,
+ cause: error?.cause,
+ shortMessage: error?.shortMessage,
+ });
+
+ toast.dismiss("creating");
+
+ if (error?.message?.includes("User rejected") || error?.message?.includes("User denied")) {
+ toast.error("Transaction rejected by user");
+ } else if (error?.message?.includes("Request expired")) {
+ toast.error("MetaMask request timed out. Please try again and approve quickly!");
+ } else {
+ toast.error(ErrorMessage(error));
+ }
+ } finally {
+ setIsSubmitting(false);
}
};
@@ -225,11 +279,16 @@ const CreatePage: React.FC = () => {
- Create Election
+ {isSubmitting ? "Creating... Check MetaMask" : "Create Election"}
diff --git a/client/app/globals.css b/client/app/globals.css
index c898587a..badd7bf5 100644
--- a/client/app/globals.css
+++ b/client/app/globals.css
@@ -5,7 +5,8 @@
body {
background-color: #ffffff;
margin: 0;
- overflow: hidden;
+ overflow-x: hidden;
+ overflow-y: auto;
}
html {
diff --git a/client/app/help/page.tsx b/client/app/help/page.tsx
new file mode 100644
index 00000000..bbeb0d54
--- /dev/null
+++ b/client/app/help/page.tsx
@@ -0,0 +1,176 @@
+"use client";
+
+import React, { useState } from "react";
+import { motion } from "framer-motion";
+import {
+ MagnifyingGlassIcon,
+ QuestionMarkCircleIcon,
+ ShieldCheckIcon,
+ WalletIcon,
+ DocumentTextIcon,
+ ChatBubbleLeftRightIcon,
+ EnvelopeIcon,
+ BookOpenIcon,
+ CommandLineIcon,
+} from "@heroicons/react/24/outline";
+import FAQSection from "../components/Help/FAQSection";
+import CategoryCard from "../components/Help/CategoryCard";
+import TroubleshootingGuide from "../components/Help/TroubleshootingGuide";
+import HowToGuides from "../components/Help/HowToGuides";
+import ContactSupport from "../components/Help/ContactSupport";
+
+const categories = [
+ {
+ id: "voting",
+ title: "Voting",
+ icon: QuestionMarkCircleIcon,
+ color: "bg-blue-100 text-blue-600",
+ description: "Learn about voting algorithms and casting votes",
+ },
+ {
+ id: "wallets",
+ title: "Wallets",
+ icon: WalletIcon,
+ color: "bg-purple-100 text-purple-600",
+ description: "Connect and manage your crypto wallet",
+ },
+ {
+ id: "security",
+ title: "Security",
+ icon: ShieldCheckIcon,
+ color: "bg-green-100 text-green-600",
+ description: "Best practices for keeping your account secure",
+ },
+ {
+ id: "smart-contracts",
+ title: "Smart Contracts",
+ icon: CommandLineIcon,
+ color: "bg-orange-100 text-orange-600",
+ description: "Understanding blockchain interactions",
+ },
+];
+
+export default function HelpPage() {
+ const [searchQuery, setSearchQuery] = useState("");
+ const [selectedCategory, setSelectedCategory] = useState(null);
+
+ return (
+
+
+ {/* Header Section */}
+
+
+
+ Help & Support
+
+
+ Find answers to your questions, explore guides, and get the support
+ you need to make the most of Agora Blockchain.
+
+
+
+ {/* Search Bar */}
+
+
+
+
+ setSearchQuery(e.target.value)}
+ className="w-full pl-12 pr-4 py-4 border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all"
+ />
+
+
+
+
+ {/* Categories Grid */}
+
+
+ Browse by Category
+
+
+ {categories.map((category, index) => (
+
+ setSelectedCategory(
+ selectedCategory === category.id ? null : category.id
+ )
+ }
+ />
+ ))}
+
+
+
+ {/* FAQ Section */}
+
+
+ {/* How-To Guides */}
+
+
+ {/* Troubleshooting Guide */}
+
+
+ {/* Contact Support */}
+
+
+ {/* Community Links */}
+
+
+
+
Join Our Community
+
+ Connect with other users, share experiences, and get help from our
+ community members on Discord.
+
+
+
+
+
+
+ );
+}