All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s
131 lines
3.7 KiB
TypeScript
131 lines
3.7 KiB
TypeScript
import { useState } from "react";
|
|
import { t } from "@lingui/core/macro";
|
|
import { Trans } from "@lingui/react/macro";
|
|
import { Link, useParams } from "react-router";
|
|
|
|
import { useAuth } from "../hooks/useAuth.ts";
|
|
import { useWS } from "../hooks/useWS.ts";
|
|
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
|
|
import { usePositionAwareSync } from "../hooks/usePositionAwareSync.ts";
|
|
import { useUserDumpFeed } from "../hooks/useUserDumpFeed.ts";
|
|
import { DumpCard } from "../components/DumpCard.tsx";
|
|
import { DumpCreateModal } from "../components/DumpCreateModal.tsx";
|
|
import { ProfileSubpageHeader } from "../components/ProfileSubpageHeader.tsx";
|
|
import { PageShell } from "../components/PageShell.tsx";
|
|
import { PageError } from "../components/PageError.tsx";
|
|
|
|
export function UserDumps() {
|
|
const { username } = useParams();
|
|
const { user: me } = useAuth();
|
|
const { voteCounts, myVotes, lastDumpEvent, castVote, removeVote } = useWS();
|
|
|
|
const { state, setItems, sentinelRef } = useUserDumpFeed(
|
|
username,
|
|
"dumps",
|
|
`feed:user-dumps-full:${username ?? ""}`,
|
|
);
|
|
|
|
const [createModalOpen, setCreateModalOpen] = useState(false);
|
|
|
|
const profileUserId = state.status === "loaded" ? state.profileUser.id : null;
|
|
const isOwnProfile = me?.id === profileUserId;
|
|
|
|
const dumpItems = state.status === "loaded" ? state.items : [];
|
|
usePositionAwareSync(
|
|
dumpItems,
|
|
setItems,
|
|
lastDumpEvent,
|
|
(d) => d.isPrivate,
|
|
(d) => !d.isPrivate && d.userId === profileUserId,
|
|
);
|
|
useDumpListSync(setItems, {
|
|
ownerId: profileUserId ?? undefined,
|
|
isOwner: isOwnProfile,
|
|
skipReinsert: true,
|
|
});
|
|
|
|
if (state.status === "loading") {
|
|
return (
|
|
<PageShell>
|
|
<p className="page-loading">
|
|
<Trans>Loading…</Trans>
|
|
</p>
|
|
</PageShell>
|
|
);
|
|
}
|
|
|
|
if (state.status === "error") {
|
|
return (
|
|
<PageError
|
|
message={state.error}
|
|
actions={
|
|
<Link to={`/users/${username}`} className="btn-border">
|
|
<Trans>← Back to profile</Trans>
|
|
</Link>
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const { profileUser, items: dumps, hasMore, loadingMore } = state;
|
|
|
|
return (
|
|
<PageShell
|
|
feed={dumps.length > 0 && (
|
|
<>
|
|
<ul className="dump-feed">
|
|
{dumps.map((dump) => (
|
|
<DumpCard
|
|
key={dump.id}
|
|
dump={dump}
|
|
voteCount={voteCounts[dump.id] ?? dump.voteCount}
|
|
voted={myVotes.has(dump.id)}
|
|
canVote={!!me}
|
|
castVote={castVote}
|
|
removeVote={removeVote}
|
|
isOwner={isOwnProfile}
|
|
/>
|
|
))}
|
|
</ul>
|
|
{hasMore && <div ref={sentinelRef} />}
|
|
{loadingMore && (
|
|
<p className="feed-loading-more">
|
|
<Trans>Loading more…</Trans>
|
|
</p>
|
|
)}
|
|
{!hasMore && (
|
|
<p className="feed-end">
|
|
<Trans>You've reached the end.</Trans>
|
|
</p>
|
|
)}
|
|
</>
|
|
)}
|
|
>
|
|
<ProfileSubpageHeader
|
|
username={username!}
|
|
profileUser={profileUser}
|
|
title={t`Dumps`}
|
|
actions={isOwnProfile && (
|
|
<button
|
|
type="button"
|
|
className="new-item-toggle"
|
|
onClick={() => setCreateModalOpen(true)}
|
|
>
|
|
+<span className="btn-new-label">
|
|
<Trans>New dump</Trans>
|
|
</span>
|
|
</button>
|
|
)}
|
|
/>
|
|
{createModalOpen && (
|
|
<DumpCreateModal onClose={() => setCreateModalOpen(false)} />
|
|
)}
|
|
{dumps.length === 0 && (
|
|
<p className="empty-state">
|
|
<Trans>Nothing here yet.</Trans>
|
|
</p>
|
|
)}
|
|
</PageShell>
|
|
);
|
|
}
|