From 61fa98e26268f94f0be2c397bf3fd080d65327a1 Mon Sep 17 00:00:00 2001 From: DavisVT Date: Sun, 31 May 2026 20:59:43 +0100 Subject: [PATCH] feat: add favorite star icon to contract cards - add useFavorites hook to manage favorite contracts with localStorage - add clickable star icon in ContractTable - fill star when favorited - add show favorites filter --- .../contracts/components/ContractTable.tsx | 79 +++++++++++++++---- soroscan-frontend/app/contracts/page.tsx | 16 +++- soroscan-frontend/lib/hooks/useFavorites.ts | 35 ++++++++ 3 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 soroscan-frontend/lib/hooks/useFavorites.ts diff --git a/soroscan-frontend/app/contracts/components/ContractTable.tsx b/soroscan-frontend/app/contracts/components/ContractTable.tsx index 544d104ae..6734db767 100644 --- a/soroscan-frontend/app/contracts/components/ContractTable.tsx +++ b/soroscan-frontend/app/contracts/components/ContractTable.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { useRouter } from "next/navigation"; +import { Star } from "lucide-react"; import { Table, TableBody, @@ -13,21 +14,28 @@ import { import { Button } from "@/components/terminal/Button"; import type { Contract } from "@/components/ingest/contract-types"; import { ContractEmptyState } from "./ContractEmptyState"; +import { useFavorites } from "@/lib/hooks/useFavorites"; interface ContractTableProps { contracts: Contract[]; onDelete: (id: string) => void; onRegister: () => void; + showFavoritesOnly?: boolean; } -export function ContractTable({ contracts, onDelete, onRegister }: ContractTableProps) { +export function ContractTable({ contracts, onDelete, onRegister, showFavoritesOnly = false }: ContractTableProps) { const router = useRouter(); + const { isFavorite, toggleFavorite } = useFavorites(); + + const filteredContracts = showFavoritesOnly + ? contracts.filter((contract) => isFavorite(contract.id)) + : contracts; const handleRowClick = (id: string) => { router.push(`/contracts/${id}`); }; - if (contracts.length === 0) { + if (filteredContracts.length === 0) { return ; } @@ -35,7 +43,7 @@ export function ContractTable({ contracts, onDelete, onRegister }: ContractTable <> {/* ── Mobile card view (< 640px) ── */}
- {contracts.map((contract) => ( + {filteredContracts.map((contract) => (
handleRowClick(contract.id)} @@ -48,22 +56,40 @@ export function ContractTable({ contracts, onDelete, onRegister }: ContractTable {contract.contractId.slice(0, 8)}...
- +
+ - {contract.status.toUpperCase()} - + > + + {contract.status.toUpperCase()} + +
@@ -114,6 +140,7 @@ export function ContractTable({ contracts, onDelete, onRegister }: ContractTable + Contract ID Name Status @@ -123,12 +150,30 @@ export function ContractTable({ contracts, onDelete, onRegister }: ContractTable - {contracts.map((contract) => ( + {filteredContracts.map((contract) => ( handleRowClick(contract.id)} className="cursor-pointer" > + + + {contract.contractId.slice(0, 8)}... diff --git a/soroscan-frontend/app/contracts/page.tsx b/soroscan-frontend/app/contracts/page.tsx index 46f0cf7f0..d9fd72cc9 100644 --- a/soroscan-frontend/app/contracts/page.tsx +++ b/soroscan-frontend/app/contracts/page.tsx @@ -20,6 +20,7 @@ export default function ContractsPage() { const [deleteTarget, setDeleteTarget] = React.useState(null); const [isDeleting, setIsDeleting] = React.useState(false); const [error, setError] = React.useState(null); + const [showFavoritesOnly, setShowFavoritesOnly] = React.useState(false); const loadContracts = React.useCallback(async () => { try { @@ -77,9 +78,17 @@ export default function ContractsPage() { Manage tracked contracts and event monitoring

- +
+ + +
{error && ( @@ -100,6 +109,7 @@ export default function ContractsPage() { contracts={contracts} onDelete={handleDeleteClick} onRegister={() => setIsRegisterModalOpen(true)} + showFavoritesOnly={showFavoritesOnly} /> )} diff --git a/soroscan-frontend/lib/hooks/useFavorites.ts b/soroscan-frontend/lib/hooks/useFavorites.ts new file mode 100644 index 000000000..ef83f92e6 --- /dev/null +++ b/soroscan-frontend/lib/hooks/useFavorites.ts @@ -0,0 +1,35 @@ +"use client"; + +import * as React from "react"; + +const FAVORITES_KEY = "soroscan-contract-favorites"; + +export function useFavorites() { + const [favorites, setFavorites] = React.useState>(() => { + if (typeof window !== "undefined") { + const stored = localStorage.getItem(FAVORITES_KEY); + return stored ? new Set(JSON.parse(stored)) : new Set(); + } + return new Set(); + }); + + const toggleFavorite = React.useCallback((contractId: string) => { + setFavorites((prev) => { + const newFavorites = new Set(prev); + if (newFavorites.has(contractId)) { + newFavorites.delete(contractId); + } else { + newFavorites.add(contractId); + } + localStorage.setItem(FAVORITES_KEY, JSON.stringify([...newFavorites])); + return newFavorites; + }); + }, []); + + const isFavorite = React.useCallback( + (contractId: string) => favorites.has(contractId), + [favorites] + ); + + return { favorites, toggleFavorite, isFavorite }; +}