Files
frontend-helper/src/dev/FeedPreview.tsx
T

95 lines
3.4 KiB
TypeScript

"use client";
// Dev-only: the real signed-in shell with fixture data and no network calls.
// Likes, search and the composer work on local state so the panels can be
// checked by hand. Used by src/app/dev/feed/page.tsx.
import { useEffect, useMemo, useState } from "react";
import { AppShell } from "@/components/AppShell";
import { DiscoverRailsView } from "@/components/DiscoverRails";
import { ExpertSearchView } from "@/components/ExpertSearch";
import { FeedView } from "@/components/FeedView";
import { toComments } from "@/lib/comment-model";
import { toCardPosts, type CardPost } from "@/lib/feed-model";
import {
FIXTURE_CATEGORIES,
FIXTURE_COMMENTS,
FIXTURE_DISCOVER,
FIXTURE_FEED_ITEMS,
FIXTURE_NOW,
FIXTURE_PERSON,
FIXTURE_SEARCH_RESULTS,
} from "./fixtures";
export function FeedPreview() {
const [posts, setPosts] = useState<CardPost[]>(() => toCardPosts(FIXTURE_FEED_ITEMS));
const [draft, setDraft] = useState("");
const [query, setQuery] = useState("");
const [category, setCategory] = useState<string | null>(null);
// The comments calls answer from the fixtures; the preview never reaches the network.
useEffect(() => {
const real = window.fetch;
const lists: Record<string, unknown[]> = { ...FIXTURE_COMMENTS };
window.fetch = async (input, init) => {
const m = /\/api\/posts\/([^/]+)\/comments/.exec(String(input));
if (!m) return real(input, init);
const id = decodeURIComponent(m[1]);
if (init?.method === "POST") {
const { content } = JSON.parse(String(init.body)) as { content: string };
const made = { id: `local-${Date.now()}`, content, createdAt: new Date(FIXTURE_NOW).toISOString(), author: { displayName: FIXTURE_PERSON.name } };
lists[id] = [...(lists[id] ?? []), made];
return Response.json({ comment: toComments([made])[0] });
}
return Response.json({ comments: toComments(lists[id] ?? []) });
};
return () => { window.fetch = real; };
}, []);
// With no query or category, show every fixture result so the list scrolls.
const results = useMemo(() => {
const q = query.trim().toLowerCase();
return q ? FIXTURE_SEARCH_RESULTS.filter((e) => e.displayName.toLowerCase().includes(q)) : FIXTURE_SEARCH_RESULTS;
}, [query]);
const toggleLike = (post: CardPost) =>
setPosts((cur) =>
cur.map((p) =>
p.key === post.key ? { ...p, isLiked: !p.isLiked, likesCount: p.likesCount + (p.isLiked ? -1 : 1) } : p,
),
);
return (
<AppShell
person={FIXTURE_PERSON}
search={
<ExpertSearchView
query={query}
onQuery={setQuery}
categories={FIXTURE_CATEGORIES}
activeCategory={category}
onCategory={setCategory}
status="ready"
results={results}
/>
}
feed={
<FeedView
posts={posts}
status="ready"
loadingMore={false}
loadMoreFailed={false}
newCount={0}
onRetry={() => {}}
onShowNew={() => {}}
onToggleLike={toggleLike}
feedUserId={FIXTURE_PERSON.feedUserId}
onDelete={(post) => setPosts((cur) => cur.filter((p) => p.key !== post.key))}
composer={{ value: draft, busy: false, error: false, onChange: setDraft, onSubmit: () => setDraft("") }}
now={FIXTURE_NOW}
/>
}
discover={<DiscoverRailsView data={FIXTURE_DISCOVER} />}
/>
);
}