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 ; +} +``` + +## 🐛 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 */} +
+
+
+ + + Native Token + +
+

+ {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 ( + + + + + {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()} +

+
+ + Network: {getNetworkName(vote.network)} + + + Tx: {truncateHash(vote.txHash)} + +
+
+
+
+
+ ))} +
+ )} + + {/* 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 ? ( + ENS Avatar + ) : ( +
+ +
+ )} +
+
+

Identity

+

+ {ensName || "No ENS Name"} +

+
+
+ + {/* Wallet Type */} +
+
+ + Wallet Type + + + Web3 + +
+

+ Your wallet is connected and ready for blockchain interactions +

+
+ + {/* Connection Status */} +
+
+

Status

+

Active

+
+
+

Address Type

+

EOA

+
+
+ + {/* 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"} + +
+
+
+ +
+ + {/* Address Display */} +
+

+ Wallet Address +

+
+ + {address} + + +
+
+
+ + {/* 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 = () => { Agora Blockchain 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 +

+
+ +
+
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ +