Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 62 additions & 17 deletions soroscan-frontend/app/contracts/components/ContractTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import * as React from "react";
import { useRouter } from "next/navigation";
import { Star } from "lucide-react";
import {
Table,
TableBody,
Expand All @@ -13,29 +14,36 @@ 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 <ContractEmptyState onRegister={onRegister} />;
}

return (
<>
{/* ── Mobile card view (< 640px) ── */}
<div className="flex flex-col gap-3 sm:hidden">
{contracts.map((contract) => (
{filteredContracts.map((contract) => (
<div
key={contract.id}
onClick={() => handleRowClick(contract.id)}
Expand All @@ -48,22 +56,40 @@ export function ContractTable({ contracts, onDelete, onRegister }: ContractTable
{contract.contractId.slice(0, 8)}...
</div>
</div>
<span
className={`inline-flex items-center gap-2 px-2 py-1 text-xs font-mono ${
contract.status === "active"
? "text-terminal-green border border-terminal-green/30 bg-terminal-green/10"
: "text-terminal-gray border border-terminal-gray/30 bg-terminal-gray/10"
}`}
>
<div className="flex items-center gap-2">
<button
onClick={(e) => {
e.stopPropagation();
toggleFavorite(contract.id);
}}
className="focus:outline-none"
>
<Star
size={20}
className={
isFavorite(contract.id)
? "fill-yellow-400 text-yellow-400"
: "text-terminal-gray hover:text-yellow-400"
}
/>
</button>
<span
className={`w-2 h-2 rounded-full ${
className={`inline-flex items-center gap-2 px-2 py-1 text-xs font-mono ${
contract.status === "active"
? "bg-terminal-green animate-pulse"
: "bg-terminal-gray"
? "text-terminal-green border border-terminal-green/30 bg-terminal-green/10"
: "text-terminal-gray border border-terminal-gray/30 bg-terminal-gray/10"
}`}
/>
{contract.status.toUpperCase()}
</span>
>
<span
className={`w-2 h-2 rounded-full ${
contract.status === "active"
? "bg-terminal-green animate-pulse"
: "bg-terminal-gray"
}`}
/>
{contract.status.toUpperCase()}
</span>
</div>
</div>

<div>
Expand Down Expand Up @@ -114,6 +140,7 @@ export function ContractTable({ contracts, onDelete, onRegister }: ContractTable
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-10"></TableHead>
<TableHead>Contract ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
Expand All @@ -123,12 +150,30 @@ export function ContractTable({ contracts, onDelete, onRegister }: ContractTable
</TableRow>
</TableHeader>
<TableBody>
{contracts.map((contract) => (
{filteredContracts.map((contract) => (
<TableRow
key={contract.id}
onClick={() => handleRowClick(contract.id)}
className="cursor-pointer"
>
<TableCell>
<button
onClick={(e) => {
e.stopPropagation();
toggleFavorite(contract.id);
}}
className="focus:outline-none"
>
<Star
size={20}
className={
isFavorite(contract.id)
? "fill-yellow-400 text-yellow-400"
: "text-terminal-gray hover:text-yellow-400"
}
/>
</button>
</TableCell>
<TableCell className="font-mono text-terminal-cyan">
{contract.contractId.slice(0, 8)}...
</TableCell>
Expand Down
16 changes: 13 additions & 3 deletions soroscan-frontend/app/contracts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default function ContractsPage() {
const [deleteTarget, setDeleteTarget] = React.useState<Contract | null>(null);
const [isDeleting, setIsDeleting] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
const [showFavoritesOnly, setShowFavoritesOnly] = React.useState(false);

const loadContracts = React.useCallback(async () => {
try {
Expand Down Expand Up @@ -77,9 +78,17 @@ export default function ContractsPage() {
Manage tracked contracts and event monitoring
</p>
</div>
<Button variant="primary" onClick={() => setIsRegisterModalOpen(true)}>
Register Contract
</Button>
<div className="flex gap-2">
<Button
variant={showFavoritesOnly ? "primary" : "secondary"}
onClick={() => setShowFavoritesOnly(!showFavoritesOnly)}
>
{showFavoritesOnly ? "Show All" : "Show Favorites"}
</Button>
<Button variant="primary" onClick={() => setIsRegisterModalOpen(true)}>
Register Contract
</Button>
</div>
</div>

{error && (
Expand All @@ -100,6 +109,7 @@ export default function ContractsPage() {
contracts={contracts}
onDelete={handleDeleteClick}
onRegister={() => setIsRegisterModalOpen(true)}
showFavoritesOnly={showFavoritesOnly}
/>
)}
</Card>
Expand Down
35 changes: 35 additions & 0 deletions soroscan-frontend/lib/hooks/useFavorites.ts
Original file line number Diff line number Diff line change
@@ -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<Set<string>>(() => {
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 };
}