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 (
);
}
// 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
+ New Chain
)}
);
}
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 */}
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 */}
Description (optional)
A chain links multiple contracts together in a directed acyclic graph (DAG).
Contracts can depend on each other, and dependent contracts start automatically
when their dependencies complete.
{/* Actions */}
Cancel
Create
);
}