import { useState, useCallback, useEffect } from "react"; import { useParams, useNavigate } from "react-router"; import { Masthead } from "../components/Masthead"; import { ChainList } from "../components/chains/ChainList"; import { ChainEditor } from "../components/chains/ChainEditor"; import { useChains } from "../hooks/useChains"; import { useAuth } from "../contexts/AuthContext"; import type { ChainSummary, ChainWithContracts, ChainGraphResponse, CreateChainRequest, } from "../lib/api"; export default function ChainsPage() { const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth(); const navigate = useNavigate(); // Redirect to login if not authenticated (when auth is configured) useEffect(() => { if (!authLoading && isAuthConfigured && !isAuthenticated) { navigate("/login"); } }, [authLoading, isAuthConfigured, isAuthenticated, navigate]); // Show loading while checking auth if (authLoading) { return (

Loading...

); } // Don't render if not authenticated (will redirect) if (isAuthConfigured && !isAuthenticated) { return null; } return ; } function ChainsPageContent() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { chains, loading, error, createNewChain, archiveExistingChain, getChainById, getGraph, } = useChains(); const [chainDetail, setChainDetail] = useState(null); const [chainGraph, setChainGraph] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [isCreating, setIsCreating] = useState(false); // Load chain detail when ID changes useEffect(() => { if (id) { setDetailLoading(true); Promise.all([getChainById(id), getGraph(id)]).then(([chain, graph]) => { setChainDetail(chain); setChainGraph(graph); setDetailLoading(false); }); } else { setChainDetail(null); setChainGraph(null); } }, [id, getChainById, getGraph]); const handleSelect = useCallback( (chainId: string) => { navigate(`/chains/${chainId}`); }, [navigate] ); const handleBack = useCallback(() => { navigate("/chains"); }, [navigate]); const handleCreate = useCallback(() => { setIsCreating(true); }, []); const handleCreateSubmit = useCallback( async (name: string, description: string) => { const data: CreateChainRequest = { name: name.trim(), description: description.trim() || undefined, }; try { const result = await createNewChain(data); if (result) { setIsCreating(false); navigate(`/chains/${result.id}`); } } catch (err) { console.error("Failed to create chain:", err); } }, [createNewChain, navigate] ); const handleCreateCancel = useCallback(() => { setIsCreating(false); }, []); const handleArchive = useCallback( async (chain: ChainSummary) => { if (confirm(`Are you sure you want to archive "${chain.name}"?`)) { const success = await archiveExistingChain(chain.id); if (success && chain.id === id) { navigate("/chains"); } } }, [archiveExistingChain, id, navigate] ); const handleRefresh = useCallback(async () => { if (id) { const [chain, graph] = await Promise.all([getChainById(id), getGraph(id)]); setChainDetail(chain); setChainGraph(graph); } }, [id, getChainById, getGraph]); const handleContractClick = useCallback( (contractId: string) => { navigate(`/contracts/${contractId}`); }, [navigate] ); return (
{error && (
{error}
)} {/* Create chain modal */} {isCreating && ( )}
{/* Chain list */} {/* Chain detail/editor or empty state */} {chainDetail ? ( ) : (

Select a chain or create a new one

)}
); } interface CreateChainModalProps { onSubmit: (name: string, description: string) => void; onCancel: () => void; } function CreateChainModal({ onSubmit, onCancel }: CreateChainModalProps) { const [name, setName] = useState(""); const [description, setDescription] = useState(""); const handleSubmit = () => { if (name.trim()) { onSubmit(name.trim(), description.trim()); } }; return (

Create Chain

{/* Chain name */}
setName(e.target.value)} placeholder="e.g., Feature Implementation" className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]" autoFocus />
{/* Description */}