diff --git a/.hintrc b/.hintrc new file mode 100644 index 0000000..9024f9c --- /dev/null +++ b/.hintrc @@ -0,0 +1,13 @@ +{ + "extends": [ + "development" + ], + "hints": { + "axe/name-role-value": [ + "default", + { + "button-name": "off" + } + ] + } +} \ No newline at end of file diff --git a/package.json b/package.json index f155ed9..752d896 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,8 @@ "celo-composer", "celo" ], - "packageManager": "yarn@4.5.1" + "packageManager": "yarn@4.5.1", + "dependencies": { + "@wagmi/core": "^2.18.1" + } } diff --git a/packages/react-app/index.html b/packages/react-app/index.html index e0e8e66..394495c 100644 --- a/packages/react-app/index.html +++ b/packages/react-app/index.html @@ -4,7 +4,7 @@ - Trust squared + Trust2
diff --git a/packages/react-app/package.json b/packages/react-app/package.json index e479da9..69ef6ef 100644 --- a/packages/react-app/package.json +++ b/packages/react-app/package.json @@ -21,6 +21,7 @@ "@radix-ui/react-toast": "^1.2.2", "@tanstack/react-query": "^5.60.5", "@wagmi/connectors": "^5.4.0", + "@wagmi/core": "^2.18.1", "@yudiel/react-qr-scanner": "^2.0.8", "algosdk": "^3.0.0", "class-variance-authority": "^0.7.0", @@ -45,13 +46,13 @@ "@types/react-blockies": "^1.4.4", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", - "autoprefixer": "^10.4.20", + "autoprefixer": "^10.4.21", "eslint": "^9.13.0", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.14", "globals": "^15.11.0", - "postcss": "^8.4.49", - "tailwindcss": "^3.4.15", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", "typescript": "~5.6.2", "typescript-eslint": "^8.11.0", "vite": "^5.4.10" diff --git a/packages/react-app/public/logo1.svg b/packages/react-app/public/logo1.svg index 7b4f3ec..ff6115d 100644 --- a/packages/react-app/public/logo1.svg +++ b/packages/react-app/public/logo1.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/react-app/public/logo2.svg b/packages/react-app/public/logo2.svg index 623d666..3a7017d 100644 --- a/packages/react-app/public/logo2.svg +++ b/packages/react-app/public/logo2.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/react-app/src/App.tsx b/packages/react-app/src/App.tsx index 6b451ab..f5dfde5 100644 --- a/packages/react-app/src/App.tsx +++ b/packages/react-app/src/App.tsx @@ -2,13 +2,14 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; import { useAccount } from "wagmi"; import "./App.css"; import BottomNavbar from "./components/BottomNavbar"; -import Navbar from "./components/Navbar"; +// import Navbar from "./components/Navbar"; import Trustees from "./screens/Trustees"; import Home from "./screens/Home"; import Layout from "./screens/Layout"; import Login from "./screens/Login"; +import Dashborad from "./screens/Dashborad"; import { QrScan } from "./screens/TrustAction"; -import Trusters from "./screens/Trusters"; + import { useIsLoggedIn } from "@dynamic-labs/sdk-react-core"; function App() { @@ -28,7 +29,7 @@ function App() { path="/*" element={ <> - + {/* */} {/* Add your other routes here */} - + } /> diff --git a/packages/react-app/src/components/BottomNavbar.tsx b/packages/react-app/src/components/BottomNavbar.tsx index 647019b..0d7c963 100644 --- a/packages/react-app/src/components/BottomNavbar.tsx +++ b/packages/react-app/src/components/BottomNavbar.tsx @@ -1,97 +1,147 @@ -import { CiHome as IconHome, CiUser as IconTrusters } from "react-icons/ci"; -import { - FaHandHoldingUsd as IconTrustees, - FaChartArea as IconHistory, -} from "react-icons/fa"; -import { Link } from "react-router-dom"; - -const navbarItems = { - home: { - icon: , - label: "Home", - route: "/", - }, - profile: { - icon: , - label: "Profile", - route: "/profile", - }, - history: { - icon: , - label: "History", - route: "/history", - }, - trustees: { - icon: , - label: "Trustees", - route: "/trustees", - }, -}; +import { Home, Users, BarChart3, Heart, Settings, LogOut } from "lucide-react"; +import { Link, useLocation } from "react-router-dom"; +import { useState } from "react"; +import { useAccount, useDisconnect } from "wagmi"; +import { useDynamicContext } from "@dynamic-labs/sdk-react-core"; export default function BottomNavbar() { + const location = useLocation(); + const [showLogoutMenu, setShowLogoutMenu] = useState(false); + const { disconnect } = useDisconnect(); + const { handleLogOut } = useDynamicContext(); + + const isActive = (route: string) => { + if (route === "/") { + return location.pathname === "/"; + } + if (route === "/trust") { + return location.pathname === "/trust"; + } + return location.pathname.startsWith(route); + }; + + const handleLogout = async () => { + try { + disconnect(); + await handleLogOut(); + setShowLogoutMenu(false); + } catch (error) { + console.error("Logout error:", error); + } + }; + return ( -
-
-
+
+
+
+ {/* Home */} -
- - Supporters -
+ + {/* Dashboard */} - profile + + {/* QR Scan Button */} -
- - Trustees +
+
+
+
+
+
+
+
+
+
+
+
- {/* - $200 */} - {/* {Object.entries(navbarItems).map(([key, item]) => ( - + + */} + + {/* Trustees */} + + + + + {/* Settings with Logout Dropdown */} +
+ + + {/* Logout Dropdown */} + {showLogoutMenu && ( +
+ +
+ )} +
- {/* */}
+ + {/* Click outside to close dropdown */} + {showLogoutMenu && ( +
setShowLogoutMenu(false)} + /> + )}
); -} +} \ No newline at end of file diff --git a/packages/react-app/src/components/TrustAccount.tsx b/packages/react-app/src/components/TrustAccount.tsx index 2ecc086..6ce2916 100644 --- a/packages/react-app/src/components/TrustAccount.tsx +++ b/packages/react-app/src/components/TrustAccount.tsx @@ -1,5 +1,4 @@ import { getAddressLink } from "@/utils"; -import { useMemo } from "react"; import Blockies from "react-blockies"; import { Link } from "react-router-dom"; @@ -7,20 +6,21 @@ interface ProfileCardProps { address: string; name?: string; } -// export const randomWalletAddress = () => { -// const chars = "0123456789abcdef"; -// let address = "0x"; -// for (let i = 0; i < 40; i++) { -// address += chars[Math.floor(Math.random() * chars.length)]; -// } -// return address; -// }; + +// Helper function to truncate address +function truncateAddress(address: string): string { + return `${address.slice(0, 6)}...${address.slice(-4)}`; +} export default function TrustAccount({ address, name = "" }: ProfileCardProps) { + console.log("TrustAccount received:", { address, name }); // للـ debugging + + const displayName = name?.trim() || truncateAddress(address); + return ( -
+
{/* Profile Picture */} - - {name || truncateAddress(address)} + {displayName} {truncateAddress(address)} @@ -44,9 +43,4 @@ export default function TrustAccount({ address, name = "" }: ProfileCardProps) {
); -} - -// Helper function to truncate address -function truncateAddress(address: string): string { - return `${address.slice(0, 6)}...${address.slice(-4)}`; -} +} \ No newline at end of file diff --git a/packages/react-app/src/hooks/queries/useGenericQuery.ts b/packages/react-app/src/hooks/queries/useGenericQuery.ts index 7c44a4b..0777912 100644 --- a/packages/react-app/src/hooks/queries/useGenericQuery.ts +++ b/packages/react-app/src/hooks/queries/useGenericQuery.ts @@ -2,7 +2,8 @@ import { useQuery } from "@tanstack/react-query"; export const useGenericQuery = ( queryKey: string[], - queryFn: () => Promise + queryFn: () => Promise, + enabled: boolean = true ) => { const { data, @@ -12,6 +13,7 @@ export const useGenericQuery = ( } = useQuery({ queryKey: queryKey, queryFn: queryFn, + enabled: enabled, }); const refetch = async () => { diff --git a/packages/react-app/src/hooks/queries/useGetMember.tsx b/packages/react-app/src/hooks/queries/useGetMember.tsx index 2874011..ddba622 100644 --- a/packages/react-app/src/hooks/queries/useGetMember.tsx +++ b/packages/react-app/src/hooks/queries/useGetMember.tsx @@ -88,8 +88,7 @@ export const useGetMemberTrusters = (memberId: string) => { }; export const useGetMember = (memberId: string) => { - const queryKey = ["member", memberId]; const queryFn = () => fetchMemberData(memberId); - return useGenericQuery(queryKey, queryFn); + return useGenericQuery(queryKey, queryFn, !!memberId && memberId.length > 0); }; diff --git a/packages/react-app/src/index.css b/packages/react-app/src/index.css index 1070b8e..473ff59 100644 --- a/packages/react-app/src/index.css +++ b/packages/react-app/src/index.css @@ -3,14 +3,15 @@ @tailwind utilities; #root { - background-color: #F1FFF3; + background-color: #000000ff; + color: #f0f0f0ff; } /* ... */ @layer base { :root { - --background: 0 0% 100%; + --background: 0 0% 0%; --foreground: 240 10% 3.9%; --card: 0 0% 100%; --card-foreground: 240 10% 3.9%; @@ -38,7 +39,7 @@ } .dark { - --background: 20 14.3% 4.1%; + --background: 0 0% 0%; --foreground: 0 0% 95%; --card: 24 9.8% 10%; --card-foreground: 0 0% 95%; diff --git a/packages/react-app/src/main.tsx b/packages/react-app/src/main.tsx index 113bbc2..3d158a0 100644 --- a/packages/react-app/src/main.tsx +++ b/packages/react-app/src/main.tsx @@ -12,3 +12,4 @@ createRoot(document.getElementById("root")!).render( ); + diff --git a/packages/react-app/src/screens/Dashborad.tsx b/packages/react-app/src/screens/Dashborad.tsx new file mode 100644 index 0000000..03dfced --- /dev/null +++ b/packages/react-app/src/screens/Dashborad.tsx @@ -0,0 +1,221 @@ +import TrustAccount from "@/components/TrustAccount"; +import { useGetMember, useGetMemberTrustees, useGetMemberTrusters } from "@/hooks/queries/useGetMember"; + +import { useBalanceStream } from "@/hooks/useBalanceStream"; +import { formatScore, formatFlow } from "@/utils"; +import { useDynamicContext } from "@dynamic-labs/sdk-react-core"; +import { useAccount, useDisconnect } from "wagmi"; + + +import { useState } from "react"; + +export default function Dashboard() { + const [showLogoutMenu, setShowLogoutMenu] = useState(false); + const [selectedPeriod, setSelectedPeriod] = useState<'Day' | 'Week' | 'Month' | 'Year'>('Day'); + + const account = useAccount(); + const { disconnect } = useDisconnect(); + const { data: memberData } = useGetMember(account.address as string); + const { data: trusteesData } = useGetMemberTrustees(account.address ?? ""); + const { data: trustersData } = useGetMemberTrusters(account.address ?? ""); + + + const balance = useBalanceStream( + account.address, + + BigInt(memberData?.data?.member?.inFlowRate || 0) - + BigInt(memberData?.data?.member?.outFlowRate || 0) + ); + + const { user = {}, handleLogOut } = useDynamicContext(); + + const supporters = trusteesData?.data?.member?.trustees?.length || 0; + const delegates = trustersData?.data?.member?.trusters?.length || 0; + const totalInflow = trusteesData?.data?.member?.trustees?.reduce((acc, curr) => acc + Number(curr.flowRate), 0) || 0; + const totalOutflow = trustersData?.data?.member?.trusters?.reduce((acc, curr) => acc + Number(curr.flowRate), 0) || 0; + const netFlow = totalInflow - totalOutflow; + + const formattedBalance = balance?.toString() || "0"; + const trustScore = formatScore(memberData?.data?.member?.trustScore || ""); + + // const handleLogout = async () => { + // try { + // disconnect(); + // await handleLogOut(); + // setShowLogoutMenu(false); + // } catch (error) { + // console.error("Logout error:", error); + // } + // }; + + function generateChartData() { + const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + const maxDataValue = Math.max(supporters, delegates, 1); + return days.map((day, index) => ({ + day, + supporters: Math.floor(Math.random() * (maxDataValue + 1)), + delegates: Math.floor(Math.random() * (maxDataValue + 1)), + })); + } + + const chartData = generateChartData(); + const chartMaxValue = Math.max(supporters, delegates, 20); + + return ( +
+
+ {account.address && ( +
+ +
+ Trust Score + {trustScore || "0"} +
+
+ )} + + {/*
+ + + {showLogoutMenu && ( +
+ +
+ )} +
*/} +
+ {showLogoutMenu && ( + + ))} +
+ +
+
+
+
+
+

Supporters

+

{supporters}

+
+
+ +
+
+
+

Delegates

+

{delegates}

+
+
+
+ +
+ + + + +
+

+ $ {Math.abs(netFlow) > 0 ? formatFlow(Math.abs(netFlow).toString()) : formattedBalance || '0'} +

+

Net Flow

+
+
+
+ +
+
+ {chartMaxValue} + {Math.floor(chartMaxValue * 0.75)} + {Math.floor(chartMaxValue * 0.5)} + {Math.floor(chartMaxValue * 0.25)} + 0 +
+ +
+ {chartData.map((data, index) => ( +
+
+
+
+
+ {data.day} +
+ ))} +
+
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/packages/react-app/src/screens/Home.tsx b/packages/react-app/src/screens/Home.tsx index 1e2fa30..f37724b 100644 --- a/packages/react-app/src/screens/Home.tsx +++ b/packages/react-app/src/screens/Home.tsx @@ -1,104 +1,130 @@ import TrustAccount from "@/components/TrustAccount"; import { useGetMember } from "@/hooks/queries/useGetMember"; -import { useVerifier } from "@/hooks/queries/useVerifier"; + import { useBalanceStream } from "@/hooks/useBalanceStream"; import { formatScore } from "@/utils"; import { useDynamicContext } from "@dynamic-labs/sdk-react-core"; import { QRCodeSVG } from "qrcode.react"; -import { useAccount } from "wagmi"; +import { useAccount, useDisconnect } from "wagmi"; import { useVerifiedIdentities } from "@/hooks/useVerifiedIdentities"; import { BadgeCheck } from "lucide-react"; - -// const formatScore = (rate: string) => { -// const score = ((Number(rate) / 1e18) * 1e5).toFixed(2); -// return score + " ☘️"; -// }; +import { useState } from "react"; export default function Home() { - //this will try to get user verified by the backened - const verifierResult = useVerifier(); + const [showLogoutMenu, setShowLogoutMenu] = useState(false); + const account = useAccount(); + const { disconnect } = useDisconnect(); const { data } = useGetMember(account.address as string); - // console.log({data, addr: account.address}) const identities = useVerifiedIdentities(account.address); - // console.log({identities}) - // @ts-ignore + const balance = useBalanceStream( account.address, - // @ts-ignore + BigInt(data?.data?.member?.inFlowRate || 0) - - BigInt(data?.data?.member?.outFlowRate || 0) + BigInt(data?.data?.member?.outFlowRate || 0) ); - const { user = {} } = useDynamicContext(); + const { user = {}, handleLogOut } = useDynamicContext(); + + const supporters = data?.data?.member?.trustees?.length; + const trustees = data?.data?.member?.trusters?.length; + + const formattedBalance = balance?.toString() + " G$"; + const trustScore = formatScore(data?.data?.member?.trustScore || ""); - // return
home
; - // return return ( -
-
+
+ {/* Header with Profile and Settings */} +
{account.address && ( )} - + + {/* Click outside to close dropdown */} + {showLogoutMenu && ( +
setShowLogoutMenu(false)} /> -
- {Object.entries(identities || {}).map(([k, v]) => { - if (v) - return ( -
- {k} -
- ); - })} + )} + + {/* Main Content Container */} +
+ {/* QR Code Section */} +
+ +

+ Scan to receive your funds +

-
-
- Trust Score - - {formatScore(data?.data?.member?.trustScore || "")} - -
-
- Balance - - {balance?.toString()} G$ - + {/* Stats Section */} +
+ {/* Supporters and Trustees Row */} +
+
+

Supporters

+

{supporters || 0}

+
+
+

Trustees

+

{trustees || 0}

+
- {/*
-
- -

Total Supporters

+ + {/* Balance */} +
+ Balance + {formattedBalance || "0 G$"}
-

15

-
*/} - {/*
-
- -

Total Amount

+ {/* Trust Score */} +
+ Trust Score + {trustScore || "0"}
-

$12,345

-
*/}
+ + {/* Verified Identities */} + {Object.entries(identities || {}).some(([k, v]) => v) && ( +
+

Verified Identities

+
+ {Object.entries(identities || {}).map(([k, v]) => { + if (v) + return ( +
+ {k} + +
+ ); + return null; + })} +
+
+ )}
+ + {/* Bottom padding for navigation */} +
); -} +} \ No newline at end of file diff --git a/packages/react-app/src/screens/Login.tsx b/packages/react-app/src/screens/Login.tsx index cedd25f..d6a0841 100644 --- a/packages/react-app/src/screens/Login.tsx +++ b/packages/react-app/src/screens/Login.tsx @@ -2,19 +2,24 @@ import { Button } from "@/components/ui/button"; import { DynamicConnectButton } from "@dynamic-labs/sdk-react-core"; import { injected } from "@wagmi/connectors"; import { useConnect } from "wagmi"; -import { useAccount } from "wagmi"; + export default function Login() { const { connect } = useConnect(); let isMiniPlay = false; if (window && window.ethereum) { - // User has a injected wallet + - // @ts-expect-error - if (window.ethereum.isMiniPay) { - isMiniPlay = true; - } + + + +if (window.ethereum && 'isMiniPay' in window.ethereum) { + + if (window.ethereum.isMiniPay) { + isMiniPlay = true; + } +} } const onConnectMiniPay = () => { @@ -28,16 +33,40 @@ export default function Login() { }; return ( -
- logo - {isMiniPlay ? ( - - ) : ( - - Connect Wallet - - )} - {/* Connect Wallet */} +
+
+ {/* Logo Section */} +
+ + logo + + + {/* Subtitle */} +

+ Build your reputation through trust +
+ and contributions. +

+
+ + {/* Sign In Button */} +
+ {isMiniPlay ? ( + + ) : ( + + Sign in + + )} +
+
); -} +} \ No newline at end of file diff --git a/packages/react-app/src/screens/TrustAction.tsx b/packages/react-app/src/screens/TrustAction.tsx index 0b23af3..aabcf3c 100644 --- a/packages/react-app/src/screens/TrustAction.tsx +++ b/packages/react-app/src/screens/TrustAction.tsx @@ -22,15 +22,16 @@ import { truncateAddress } from "@/utils"; // @ts-expect-error const isMiniPay = window?.ethereum?.isMiniPay; const gasOpts = isMiniPay ? {} : { - maxFeePerGas: BigInt(5e9), - maxPriorityFeePerGas: BigInt(0) -} - + maxFeePerGas: BigInt(60e9), + maxPriorityFeePerGas: BigInt(2e9) +}; + const useGetFlowRate = (sender: string | undefined) => { - if (!sender) return undefined; - const memberData = useGetMember(sender); + const memberData = useGetMember(sender || ""); + if (!sender) return 0n; // @ts-ignore - return memberData.status === "success" ? BigInt(memberData.data?.data?.outFlowRate || 0) : undefined + const flowRate = memberData.data?.data?.outFlowRate; + return flowRate ? BigInt(flowRate) : 0n; }; export const QrScan = () => { @@ -38,7 +39,6 @@ export const QrScan = () => { const account = useAccount(); const existingFlowRate = useGetFlowRate(account.address); - console.log({ existingFlowRate }); const { writeContractAsync } = useWriteContract(); const [result, setResult] = useState(undefined); @@ -50,17 +50,15 @@ export const QrScan = () => { const validRecipient = isAddress(result || ""); const handleScan = (result: IDetectedBarcode[]) => { - console.log(result); if (result.length > 0) { setResult(result[0].rawValue); } }; const trust = async () => { - if (existingFlowRate !== undefined && result) { + if (result) { const monthlyTrustRate = parseEther(amount.toString()) / BigInt(60 * 60 * 24 * 30); - console.log("Trusting:", { existingFlowRate, result, monthlyTrustRate }); const newFlowRate = existingFlowRate + monthlyTrustRate; const userData = encodeAbiParameters( @@ -90,7 +88,6 @@ export const QrScan = () => { }); navigation("/"); } catch (e: any) { - console.log({ e }) setLoading(false); toast({ title: "Transaction failed", diff --git a/packages/react-app/src/screens/Trustees.tsx b/packages/react-app/src/screens/Trustees.tsx index 6f79a01..2add537 100644 --- a/packages/react-app/src/screens/Trustees.tsx +++ b/packages/react-app/src/screens/Trustees.tsx @@ -1,122 +1,181 @@ import TrustAccount from "@/components/TrustAccount"; -import { useGetMemberTrustees } from "@/hooks/queries/useGetMember"; +import { useGetMemberTrustees, useGetMemberTrusters } from "@/hooks/queries/useGetMember"; import { formatFlow, getAddressLink, truncateAddress } from "@/utils"; -import { ExternalLink } from "lucide-react"; +import { ExternalLink, Settings, LogOut } from "lucide-react"; import Blockies from "react-blockies"; -import { CiLocationArrow1, CiUser } from "react-icons/ci"; import { Link } from "react-router-dom"; -import { useAccount } from "wagmi"; +import { useAccount, useDisconnect } from "wagmi"; +import { useState } from "react"; +import { useDynamicContext } from "@dynamic-labs/sdk-react-core"; -export const stats = { - score: { - icon: , - label: "Score", - value: 1, - }, - netFlow: { - icon: , - label: "Net Flow", - value: 1, - }, - supporters: { - icon: , - label: "Supporters", - value: 1, - }, +export default function History() { + const { address } = useAccount(); + const { disconnect } = useDisconnect(); + const { user = {}, handleLogOut } = useDynamicContext(); + const [activeTab, setActiveTab] = useState<'trustees' | 'delegates'>('trustees'); + const [showLogoutMenu, setShowLogoutMenu] = useState(false); - trustees: { - icon: , - label: "Trustees", - value: 1, - }, -}; + // Get both trustees and trusters data + const { data: trusteesData } = useGetMemberTrustees(address ?? ""); + const { data: trustersData } = useGetMemberTrusters(address ?? ""); -export function Stat({ - label, - value, - icon, -}: (typeof stats)[keyof typeof stats]) { - return ( -
- {icon} - -

{value}

-
- ); -} + // Calculate stats based on active tab + const currentData = activeTab === 'trustees' ? trusteesData : trustersData; + const listData = activeTab === 'trustees' + ? trusteesData?.data?.member?.trustees + : trustersData?.data?.member?.trusters; -export default function History() { - const { address } = useAccount(); - const { data, status } = useGetMemberTrustees(address ?? ""); + const totalCount = listData?.length || 0; + const totalFlow = listData?.reduce((acc, curr) => acc + Number(curr.flowRate), 0) || 0; - const totalTrustees = data?.data?.member?.trustees.length; - const totalFlow = data?.data?.member?.trustees.reduce( - (acc, curr) => acc + Number(curr.flowRate), - 0 - ); + const handleLogout = async () => { + try { + disconnect(); + await handleLogOut(); + setShowLogoutMenu(false); + } catch (error) { + console.error("Logout error:", error); + } + }; return ( -
-
- +
+ {/* Header with Profile and Settings */} +
+ + + {/* Settings with Logout Dropdown */} +
+ + + {/* Logout Dropdown */} + {showLogoutMenu && ( +
+ +
+ )} +
+
-
-
- -
{"Total Trustees"}
-
{totalTrustees}
-
+ {/* Click outside to close dropdown */} + {showLogoutMenu && ( +
setShowLogoutMenu(false)} + /> + )} -
- -
{"Total Outflow"}
-
- {totalFlow ? formatFlow(totalFlow.toString()) : "0"} -
-
+ {/* Tab Navigation */} +
+
+ +
- {/* Headers */} -
-
Address
-
Amount Trusted
+ {/* Stats Cards */} +
+
+ + {activeTab === 'trustees' ? 'Total Supporters' : 'Total Delegates'} + + {totalCount} +
+ +
+ + {activeTab === 'trustees' ? 'Total Inflow' : 'Total Outflow'} + + + $ {totalFlow ? formatFlow(totalFlow.toString()) : '0'} + +
- {/* List */} -
- {data?.data?.member?.trustees.map((t) => - { - const account = t.id.split("_")[1] - return( -
-
- -
- {/*
{trustee.name}
*/} -
- - {truncateAddress(account)} - + {/* Table Headers */} +
+
+ Name + Amount +
+
+ + {/* List Items */} +
+ {!listData || listData.length === 0 ? ( +
+

No {activeTab === 'trustees' ? 'trustees' : 'delegates'} yet

+
+ ) : ( +
+ {listData.map((item) => { + // Handle different ID formats for trustees vs trusters + const account = activeTab === 'trustees' + ? item.id.split("_")[1] + : item.id.split("_")[0]; + + return ( +
+
+
+ +
+
+ {truncateAddress(account)} +
+
+
+
+ $ {formatFlow(item.flowRate.toString())} +
+
-
-
-
- {formatFlow(t.flowRate.toString())} -
+ ); + })}
- )})} + )}
+ + {/* Add bottom padding to account for fixed navigation */} +
); -} +} \ No newline at end of file diff --git a/packages/react-app/tailwind.config.js b/packages/react-app/tailwind.config.js index 3fc12b0..543e7e3 100644 --- a/packages/react-app/tailwind.config.js +++ b/packages/react-app/tailwind.config.js @@ -1,57 +1,57 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - darkMode: ["class"], - content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"], + darkMode: ["class"], + content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"], theme: { - extend: { - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - }, - colors: { - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))' - } - } - } + extend: { + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + } + } }, plugins: [require("tailwindcss-animate")], } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..3fc12b0 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,57 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"], + theme: { + extend: { + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + } + } + }, + plugins: [require("tailwindcss-animate")], +}