94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
import type { Ref } from "react";
|
|
import { canDelete, type CardPost } from "@/lib/feed-model";
|
|
import { Composer, type ComposerProps } from "./Composer";
|
|
import { PostCard } from "./PostCard";
|
|
|
|
export type FeedStatus = "loading" | "ready" | "error";
|
|
|
|
export interface FeedViewProps {
|
|
posts: CardPost[];
|
|
status: FeedStatus;
|
|
loadingMore: boolean;
|
|
loadMoreFailed: boolean;
|
|
newCount: number;
|
|
onRetry: () => void;
|
|
onShowNew: () => void;
|
|
onToggleLike?: (post: CardPost) => void;
|
|
likeBusy?: ReadonlySet<string>;
|
|
likeErrors?: ReadonlySet<string>;
|
|
onSignedOut?: () => void;
|
|
/** The signed-in person's feed user id; delete is offered only on their own posts. */
|
|
feedUserId?: string | null;
|
|
onDelete?: (post: CardPost) => void;
|
|
deleteBusy?: ReadonlySet<string>;
|
|
deleteErrors?: ReadonlySet<string>;
|
|
composer?: ComposerProps;
|
|
sentinelRef?: Ref<HTMLDivElement>;
|
|
now?: number;
|
|
}
|
|
|
|
function Spinner() {
|
|
return <span className="spinner" role="status" aria-label="Loading" />;
|
|
}
|
|
|
|
export function FeedView(p: FeedViewProps) {
|
|
if (p.status === "loading") {
|
|
return (
|
|
<section className="feed">
|
|
<div className="feed-center">
|
|
<Spinner />
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
if (p.status === "error") {
|
|
return (
|
|
<section className="feed">
|
|
<div className="feed-message" role="alert">
|
|
<p>The feed did not load.</p>
|
|
<button type="button" onClick={p.onRetry}>Try again</button>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
return (
|
|
<section className="feed">
|
|
{p.composer ? <Composer {...p.composer} /> : null}
|
|
{p.newCount > 0 ? (
|
|
<button type="button" className="new-posts" onClick={p.onShowNew}>
|
|
{p.newCount === 1 ? "1 new post" : `${p.newCount} new posts`}
|
|
</button>
|
|
) : null}
|
|
{p.posts.length === 0 ? (
|
|
<p className="feed-message">No posts yet.</p>
|
|
) : (
|
|
p.posts.map((post) => <PostCard
|
|
key={post.key}
|
|
post={post}
|
|
now={p.now}
|
|
onToggleLike={p.onToggleLike}
|
|
likeBusy={p.likeBusy?.has(post.key)}
|
|
likeError={p.likeErrors?.has(post.key)}
|
|
onSignedOut={p.onSignedOut}
|
|
canDelete={canDelete(post, p.feedUserId)}
|
|
onDelete={p.onDelete}
|
|
deleteBusy={p.deleteBusy?.has(post.key)}
|
|
deleteError={p.deleteErrors?.has(post.key)}
|
|
/>)
|
|
)}
|
|
{p.loadMoreFailed ? (
|
|
<div className="feed-message" role="alert">
|
|
<p>The feed did not load.</p>
|
|
<button type="button" onClick={p.onRetry}>Try again</button>
|
|
</div>
|
|
) : null}
|
|
{p.loadingMore ? (
|
|
<div className="feed-center">
|
|
<Spinner />
|
|
</div>
|
|
) : null}
|
|
<div ref={p.sentinelRef} className="sentinel" aria-hidden="true" />
|
|
</section>
|
|
);
|
|
}
|