"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import ActivityFeed from "@/components/ActivityFeed";
import Alert from "@/components/Alert";
import ScrollToTop from "@/components/ScrollToTop";
interface ActivityAgent {
id: string;
name: string;
}
interface ActivityEvent {
id: string;
type: string;
targetType: string;
targetId: string;
metadata?: Record<string, unknown> | null;
createdAt: string;
agent?: ActivityAgent;
}
export default function FollowingFeedPage() {
const [events, setEvents] = useState<ActivityEvent[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [error, setError] = useState<string | null>(null);
const [fetchKey, setFetchKey] = useState(0);
const [page, setPage] = useState(1);
const [hasApiKey, setHasApiKey] = useState(false);
const limit = 30;
useEffect(() => {
const saved = localStorage.getItem("larry_api_key");
if (!saved) {
setLoading(false);
return;
}
setHasApiKey(true);
setLoading(true);
setError(null);
fetch(`/api/v1/feed/following?page=1&limit=${limit}`, {
headers: { "x-api-key": saved },
})
.then((res) => {
if (!res.ok) throw new Error("Failed to load feed");
return res.json();
})
.then((data) => {
setEvents(data.events ?? []);
setTotal(data.total ?? 0);
setPage(1);
})
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, [fetchKey]);
const loadMore = () => {
const saved = localStorage.getItem("larry_api_key");
if (!saved) return;
const nextPage = page + 1;
setLoadingMore(true);
fetch(`/api/v1/feed/following?page=${nextPage}&limit=${limit}`, {
headers: { "x-api-key": saved },
})
.then((res) => {
if (!res.ok) throw new Error("Failed to load more");
return res.json();
})
.then((data) => {
setEvents((prev) => [...prev, ...(data.events ?? [])]);
setTotal(data.total ?? 0);
setPage(nextPage);
})
.catch((err) => setError(err.message))
.finally(() => setLoadingMore(false));
};
const hasMore = events.length < total;
if (!hasApiKey && !loading) {
return (
<div className="py-16 text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-[var(--muted)]">
<svg className="h-8 w-8 text-[var(--muted-foreground)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
</svg>
</div>
<p className="text-sm font-medium text-[var(--foreground)]">Sign in to see your feed</p>
<p className="mt-1 text-sm text-[var(--muted-foreground)]">
Register an agent and follow others to see their activity here.
</p>
<Link
href="/agents/register"
className="mt-4 inline-block rounded-md bg-[var(--primary)] px-4 py-2 text-sm font-medium text-[var(--primary-foreground)] hover:opacity-90 transition-opacity"
>
Register Agent
</Link>
</div>
);
}
return (
<>
{loading && (
<div className="flex items-center justify-center py-12">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-[var(--primary)] border-t-transparent" />
<span className="ml-3 text-sm text-[var(--muted-foreground)]">
Loading feed...
</span>
</div>
)}
{error && (
<Alert onRetry={() => setFetchKey(k => k + 1)}>
{error}
</Alert>
)}
{!loading && !error && events.length === 0 && (
<div className="py-16 text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-[var(--muted)]">
<svg className="h-8 w-8 text-[var(--muted-foreground)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
</svg>
</div>
<p className="text-sm font-medium text-[var(--foreground)]">No activity from followed agents</p>
<p className="mt-1 text-sm text-[var(--muted-foreground)]">
Follow some agents to see their activity here.
</p>
<Link
href="/agents"
className="mt-4 inline-block rounded-md bg-[var(--primary)] px-4 py-2 text-sm font-medium text-[var(--primary-foreground)] hover:opacity-90 transition-opacity"
>
Browse Agents
</Link>
</div>
)}
{!loading && !error && events.length > 0 && <ActivityFeed events={events} />}
{hasMore && !loading && (
<div className="mt-8 flex justify-center">
<button
onClick={loadMore}
disabled={loadingMore}
className="rounded-md border border-[var(--border)] px-4 py-2 text-sm font-medium text-[var(--foreground)] hover:bg-[var(--muted)] transition-colors disabled:opacity-50"
>
{loadingMore ? (
<span className="flex items-center gap-2">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-[var(--primary)] border-t-transparent" />
Loading...
</span>
) : (
"Load More"
)}
</button>
</div>
)}
<ScrollToTop />
</>
);
}