diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx index 7592365..d3da5cf 100644 --- a/src/app/groups/page.tsx +++ b/src/app/groups/page.tsx @@ -1,67 +1,125 @@ "use client"; -import { Navbar } from "@/components/Navbar"; import { GroupCard } from "@/components/GroupCard"; -import { SavingsGroup, GroupStatus } from "@sorosave/sdk"; - -// Placeholder data for development — will be replaced with contract queries -const PLACEHOLDER_GROUPS: SavingsGroup[] = [ - { - id: 1, - name: "Lagos Savings Circle", - admin: "GABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFG", - token: "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC", - contributionAmount: 1000000000n, - cycleLength: 604800, - maxMembers: 5, - members: ["GABCD...", "GEFGH...", "GIJKL..."], - payoutOrder: [], - currentRound: 0, - totalRounds: 0, - status: GroupStatus.Forming, - createdAt: 1700000000, - }, - { - id: 2, - name: "DeFi Builders Fund", - admin: "GABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFG", - token: "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC", - contributionAmount: 5000000000n, - cycleLength: 2592000, - maxMembers: 10, - members: ["GABCD...", "GEFGH...", "GIJKL...", "GMNOP...", "GQRST..."], - payoutOrder: ["GABCD...", "GEFGH...", "GIJKL...", "GMNOP...", "GQRST..."], - currentRound: 2, - totalRounds: 5, - status: GroupStatus.Active, - createdAt: 1699000000, - }, -]; +import { Navbar } from "@/components/Navbar"; +import { SavingsGroup } from "@sorosave/sdk"; +import { useState } from "react"; +import { GroupCompare } from "@/components/GroupCompare"; export default function GroupsPage() { - // TODO: Replace with actual contract queries - const groups = PLACEHOLDER_GROUPS; + const [selectedGroups, setSelectedGroups] = useState([]); + const [showCompareModal, setShowCompareModal] = useState(false); + + // Mock data - replace with actual data fetching + const groups: SavingsGroup[] = [ + { + id: "1", + name: "Community Savings Group", + status: "Active", + contributionAmount: 100, + cycleLength: 30, + members: Array(8).fill({}), + maxMembers: 10, + currentRound: 3, + totalRounds: 10, + }, + { + id: "2", + name: "Tech Professionals Group", + status: "Forming", + contributionAmount: 200, + cycleLength: 15, + members: Array(5).fill({}), + maxMembers: 12, + currentRound: 0, + totalRounds: 12, + }, + { + id: "3", + name: "Students Savings Club", + status: "Active", + contributionAmount: 50, + cycleLength: 45, + members: Array(15).fill({}), + maxMembers: 20, + currentRound: 2, + totalRounds: 20, + }, + { + id: "4", + name: "Investors Network", + status: "Completed", + contributionAmount: 500, + cycleLength: 7, + members: Array(10).fill({}), + maxMembers: 10, + currentRound: 10, + totalRounds: 10, + }, + ]; + + const handleGroupSelect = (group: SavingsGroup) => { + setSelectedGroups((prev) => { + const isAlreadySelected = prev.some((g) => g.id === group.id); + if (isAlreadySelected) { + return prev.filter((g) => g.id !== group.id); + } + if (prev.length >= 3) { + return prev; + } + return [...prev, group]; + }); + }; + + const handleCompareClick = () => { + if (selectedGroups.length > 0) { + setShowCompareModal(true); + } + }; return ( - <> +
-

Savings Groups

+

Groups

+ {selectedGroups.length > 0 && ( + + )}
- {groups.length === 0 ? ( -
- No groups found. Create the first one! -
- ) : ( -
- {groups.map((group) => ( - - ))} -
- )} +
+ {groups.map((group) => ( +
handleGroupSelect(group)}> + +
+ ))} +
- + + {showCompareModal && ( + setShowCompareModal(false)} + /> + )} +
); -} +} \ No newline at end of file diff --git a/src/components/GroupCard.tsx b/src/components/GroupCard.tsx index 34e0616..b3e3525 100644 --- a/src/components/GroupCard.tsx +++ b/src/components/GroupCard.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import { SavingsGroup, formatAmount, getStatusLabel } from "@sorosave/sdk"; +import { useState } from "react"; interface GroupCardProps { group: SavingsGroup; @@ -16,41 +17,57 @@ const statusColors: Record = { }; export function GroupCard({ group }: GroupCardProps) { + const [isSelected, setIsSelected] = useState(false); + return ( - -
-
-

{group.name}

- - {getStatusLabel(group.status)} +
setIsSelected(!isSelected)} + > +
+

{group.name}

+ + {getStatusLabel(group.status)} + +
+ +
+
+ Contribution + + {formatAmount(group.contributionAmount)} tokens
- -
-
- Contribution - - {formatAmount(group.contributionAmount)} tokens - -
-
- Members - - {group.members.length} / {group.maxMembers} - -
-
- Round - - {group.currentRound} / {group.totalRounds || group.maxMembers} - -
+
+ Members + + {group.members.length} / {group.maxMembers} + +
+
+ Round + + {group.currentRound} / {group.totalRounds || group.maxMembers} +
- + +
+ +
+
); -} +} \ No newline at end of file diff --git a/src/components/GroupCompare.tsx b/src/components/GroupCompare.tsx new file mode 100644 index 0000000..2149639 --- /dev/null +++ b/src/components/GroupCompare.tsx @@ -0,0 +1,206 @@ +"use client"; + +import { SavingsGroup, formatAmount, getStatusLabel } from "@sorosave/sdk"; +import { useState } from "react"; + +interface GroupCompareProps { + groups: SavingsGroup[]; + onClose: () => void; +} + +const statusColors: Record = { + Forming: "bg-blue-100 text-blue-800", + Active: "bg-green-100 text-green-800", + Completed: "bg-gray-100 text-gray-800", + Disputed: "bg-red-100 text-red-800", + Paused: "bg-yellow-100 text-yellow-800", +}; + +const getDifferenceClass = (value1: any, value2: any): string => { + if (value1 === value2) return "text-green-600"; + return "text-red-600 font-semibold"; +}; + +export function GroupCompare({ groups, onClose }: GroupCompareProps) { + const [activeTab, setActiveTab] = useState(0); + + if (groups.length === 0) { + return ( +
+
+

Compare Groups

+

No groups selected for comparison.

+ +
+
+ ); + } + + return ( +
+
+
+
+

Compare Groups

+ +
+ +
+ {groups.map((group, index) => ( + + ))} +
+ +
+ {/* Group Details */} + {groups.map((group, index) => ( +
+
+

{group.name}

+
+
+ Status + + {getStatusLabel(group.status)} + +
+
+ Contribution Amount + + {formatAmount(group.contributionAmount)} tokens + +
+
+ Cycle Length + + {group.cycleLength || "N/A"} days + +
+
+ Members + + {group.members.length} / {group.maxMembers} + +
+
+ Current Round + {group.currentRound} +
+
+ Total Rounds + + {group.totalRounds || group.maxMembers} + +
+
+
+
+ ))} +
+ + {/* Comparison Table */} +
+

Comparison

+
+ + + + + {groups.map((group) => ( + + ))} + + + + + + {groups.map((group) => ( + + ))} + + + + {groups.map((group) => ( + + ))} + + + + {groups.map((group) => ( + + ))} + + + + {groups.map((group) => ( + + ))} + + +
+ Attribute + + {group.name} +
+ Status + + + {getStatusLabel(group.status)} + +
+ Contribution Amount + + {formatAmount(group.contributionAmount)} tokens +
+ Cycle Length + + {group.cycleLength || "N/A"} days +
+ Members + + {group.members.length} / {group.maxMembers} +
+
+
+ +
+ +
+
+
+
+ ); +} \ No newline at end of file