v3: fixed inconsistent page titles
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s
This commit is contained in:
1
deno.lock
generated
1
deno.lock
generated
@@ -35,6 +35,7 @@
|
|||||||
"npm:@vitejs/plugin-react-swc@^4.3.1": "4.3.1_vite@8.1.0__@types+node@26.0.1__jiti@2.7.0_@types+node@26.0.1_jiti@2.7.0",
|
"npm:@vitejs/plugin-react-swc@^4.3.1": "4.3.1_vite@8.1.0__@types+node@26.0.1__jiti@2.7.0_@types+node@26.0.1_jiti@2.7.0",
|
||||||
"npm:eslint-plugin-react-hooks@^7.1.1": "7.1.1_eslint@10.6.0__jiti@2.7.0_jiti@2.7.0",
|
"npm:eslint-plugin-react-hooks@^7.1.1": "7.1.1_eslint@10.6.0__jiti@2.7.0_jiti@2.7.0",
|
||||||
"npm:eslint-plugin-react-refresh@~0.5.3": "0.5.3_eslint@10.6.0__jiti@2.7.0_jiti@2.7.0",
|
"npm:eslint-plugin-react-refresh@~0.5.3": "0.5.3_eslint@10.6.0__jiti@2.7.0_jiti@2.7.0",
|
||||||
|
"npm:eslint@*": "10.6.0_jiti@2.7.0",
|
||||||
"npm:eslint@^10.6.0": "10.6.0_jiti@2.7.0",
|
"npm:eslint@^10.6.0": "10.6.0_jiti@2.7.0",
|
||||||
"npm:frimousse@0.3": "0.3.0_react@19.2.7_typescript@6.0.3",
|
"npm:frimousse@0.3": "0.3.0_react@19.2.7_typescript@6.0.3",
|
||||||
"npm:globals@^17.7.0": "17.7.0",
|
"npm:globals@^17.7.0": "17.7.0",
|
||||||
|
|||||||
31
src/hooks/useDocumentTitle.ts
Normal file
31
src/hooks/useDocumentTitle.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
// The server injects the configured site name into both <title> and this meta
|
||||||
|
// tag (see api/lib/static.ts / api/middleware/og.ts). Read it once at startup.
|
||||||
|
// In the Vite dev server the placeholder isn't substituted, so fall back.
|
||||||
|
function readSiteName(): string {
|
||||||
|
const meta = document.querySelector('meta[name="site-name"]')
|
||||||
|
?.getAttribute("content")?.trim();
|
||||||
|
if (meta && meta !== "__SITE_NAME__") return meta;
|
||||||
|
return "gerbeur";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SITE_NAME = readSiteName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps document.title in sync with the current page during client-side
|
||||||
|
* navigation. Without this the tab title only ever reflects the server-rendered
|
||||||
|
* <title> from the initial page load and goes stale on every SPA navigation.
|
||||||
|
*
|
||||||
|
* - Pass a non-empty string to show `<title> · <site name>`.
|
||||||
|
* - Pass `null` to show just the site name (home / generic pages).
|
||||||
|
* - Pass `undefined` while the page-specific title is still loading: the title
|
||||||
|
* is left untouched, so a server-rendered OG title survives until data
|
||||||
|
* arrives (avoids a flash to the bare site name on direct loads).
|
||||||
|
*/
|
||||||
|
export function useDocumentTitle(title: string | null | undefined): void {
|
||||||
|
useEffect(() => {
|
||||||
|
if (title === undefined) return;
|
||||||
|
document.title = title ? `${title} · ${SITE_NAME}` : SITE_NAME;
|
||||||
|
}, [title]);
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@ import {
|
|||||||
} from "../model.ts";
|
} from "../model.ts";
|
||||||
|
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { relativeTime } from "../utils/relativeTime.ts";
|
import { relativeTime } from "../utils/relativeTime.ts";
|
||||||
import { useWS } from "../hooks/useWS.ts";
|
import { useWS } from "../hooks/useWS.ts";
|
||||||
import { Avatar } from "../components/Avatar.tsx";
|
import { Avatar } from "../components/Avatar.tsx";
|
||||||
@@ -55,6 +56,14 @@ export function Dump() {
|
|||||||
const [op, setOp] = useState<PublicUser | null>(null);
|
const [op, setOp] = useState<PublicUser | null>(null);
|
||||||
const [playlistModalOpen, setPlaylistModalOpen] = useState(false);
|
const [playlistModalOpen, setPlaylistModalOpen] = useState(false);
|
||||||
|
|
||||||
|
useDocumentTitle(
|
||||||
|
dumpState.status === "loaded"
|
||||||
|
? dumpState.dump.title
|
||||||
|
: dumpState.status === "error"
|
||||||
|
? null
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
|
||||||
const [comments, setComments] = useState<Comment[]>([]);
|
const [comments, setComments] = useState<Comment[]>([]);
|
||||||
const [relatedDumps, setRelatedDumps] = useState<Dump[]>([]);
|
const [relatedDumps, setRelatedDumps] = useState<Dump[]>([]);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { API_URL, VALIDATION } from "../config/api.ts";
|
|||||||
import type { Dump, RawDump, UpdateDumpRequest } from "../model.ts";
|
import type { Dump, RawDump, UpdateDumpRequest } from "../model.ts";
|
||||||
import { deserializeDump, parseAPIResponse } from "../model.ts";
|
import { deserializeDump, parseAPIResponse } from "../model.ts";
|
||||||
import { useRequiredAuth } from "../hooks/useAuth.ts";
|
import { useRequiredAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { formatBytes } from "../utils/format.ts";
|
import { formatBytes } from "../utils/format.ts";
|
||||||
import { dumpFileUrl, dumpThumbnailUrl, dumpUrl } from "../utils/urls.ts";
|
import { dumpFileUrl, dumpThumbnailUrl, dumpUrl } from "../utils/urls.ts";
|
||||||
import { PageShell } from "../components/PageShell.tsx";
|
import { PageShell } from "../components/PageShell.tsx";
|
||||||
@@ -41,6 +42,15 @@ export function DumpEdit() {
|
|||||||
const { authFetch, token } = useRequiredAuth();
|
const { authFetch, token } = useRequiredAuth();
|
||||||
|
|
||||||
const [state, setState] = useState<DumpEditState>({ status: "loading" });
|
const [state, setState] = useState<DumpEditState>({ status: "loading" });
|
||||||
|
|
||||||
|
useDocumentTitle(
|
||||||
|
state.status === "loaded"
|
||||||
|
? t`Editing ${state.dump.title}`
|
||||||
|
: state.status === "error"
|
||||||
|
? null
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
|
||||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
const [thumbUploading, setThumbUploading] = useState(false);
|
const [thumbUploading, setThumbUploading] = useState(false);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
import { friendlyFetchError } from "../utils/apiError.ts";
|
import { friendlyFetchError } from "../utils/apiError.ts";
|
||||||
import { useFeedCache } from "../hooks/useFeedCache.ts";
|
import { useFeedCache } from "../hooks/useFeedCache.ts";
|
||||||
import { useScrollSave } from "../hooks/useScrollSave.ts";
|
import { useScrollSave } from "../hooks/useScrollSave.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
import { useWS } from "../hooks/useWS.ts";
|
import { useWS } from "../hooks/useWS.ts";
|
||||||
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
|
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
|
||||||
@@ -55,6 +56,8 @@ export function Index() {
|
|||||||
const justDeletedId = (location.state as { deletedDumpId?: string } | null)
|
const justDeletedId = (location.state as { deletedDumpId?: string } | null)
|
||||||
?.deletedDumpId;
|
?.deletedDumpId;
|
||||||
|
|
||||||
|
useDocumentTitle(null);
|
||||||
|
|
||||||
const { user, token } = useAuth();
|
const { user, token } = useAuth();
|
||||||
const {
|
const {
|
||||||
voteCounts,
|
voteCounts,
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
|
import { t } from "@lingui/core/macro";
|
||||||
import { Trans } from "@lingui/react/macro";
|
import { Trans } from "@lingui/react/macro";
|
||||||
import { PageShell } from "../components/PageShell.tsx";
|
import { PageShell } from "../components/PageShell.tsx";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
|
|
||||||
export function NotFound() {
|
export function NotFound() {
|
||||||
|
useDocumentTitle(t`Page not found`);
|
||||||
return (
|
return (
|
||||||
<PageShell centered>
|
<PageShell centered>
|
||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { relativeTime } from "../utils/relativeTime.ts";
|
|||||||
|
|
||||||
import { API_URL, NOTIFICATIONS_PAGE_SIZE } from "../config/api.ts";
|
import { API_URL, NOTIFICATIONS_PAGE_SIZE } from "../config/api.ts";
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { ErrorCard } from "../components/ErrorCard.tsx";
|
import { ErrorCard } from "../components/ErrorCard.tsx";
|
||||||
import { Tooltip } from "../components/Tooltip.tsx";
|
import { Tooltip } from "../components/Tooltip.tsx";
|
||||||
import { useWS } from "../hooks/useWS.ts";
|
import { useWS } from "../hooks/useWS.ts";
|
||||||
@@ -256,6 +257,7 @@ function groupByDate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Notifications() {
|
export function Notifications() {
|
||||||
|
useDocumentTitle(t`Notifications`);
|
||||||
const { authFetch } = useAuth();
|
const { authFetch } = useAuth();
|
||||||
const { clearUnreadNotifications, lastNotification } = useWS();
|
const { clearUnreadNotifications, lastNotification } = useWS();
|
||||||
const [state, setState] = useState<State>({ status: "loading" });
|
const [state, setState] = useState<State>({ status: "loading" });
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
import { playlistUrl } from "../utils/urls.ts";
|
import { playlistUrl } from "../utils/urls.ts";
|
||||||
import { can } from "../utils/permissions.ts";
|
import { can } from "../utils/permissions.ts";
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { useWS } from "../hooks/useWS.ts";
|
import { useWS } from "../hooks/useWS.ts";
|
||||||
import { relativeTime } from "../utils/relativeTime.ts";
|
import { relativeTime } from "../utils/relativeTime.ts";
|
||||||
import { DumpCard } from "../components/DumpCard.tsx";
|
import { DumpCard } from "../components/DumpCard.tsx";
|
||||||
@@ -71,6 +72,14 @@ export function PlaylistDetail() {
|
|||||||
// Stable UUID for WS comparisons — avoids re-running effects on every state change
|
// Stable UUID for WS comparisons — avoids re-running effects on every state change
|
||||||
const playlistUUID = state.status === "loaded" ? state.playlist.id : null;
|
const playlistUUID = state.status === "loaded" ? state.playlist.id : null;
|
||||||
|
|
||||||
|
useDocumentTitle(
|
||||||
|
state.status === "loaded"
|
||||||
|
? state.playlist.title
|
||||||
|
: state.status === "error"
|
||||||
|
? null
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
|
||||||
// activeDumpIds: which dumps are currently in the playlist (the canonical set)
|
// activeDumpIds: which dumps are currently in the playlist (the canonical set)
|
||||||
const [activeDumpIds, setActiveDumpIds] = useState<Set<string>>(new Set());
|
const [activeDumpIds, setActiveDumpIds] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { t } from "@lingui/core/macro";
|
|||||||
import { Trans } from "@lingui/react/macro";
|
import { Trans } from "@lingui/react/macro";
|
||||||
|
|
||||||
import { API_URL, VALIDATION } from "../config/api.ts";
|
import { API_URL, VALIDATION } from "../config/api.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { PageShell } from "../components/PageShell.tsx";
|
import { PageShell } from "../components/PageShell.tsx";
|
||||||
import {
|
import {
|
||||||
expectOk,
|
expectOk,
|
||||||
@@ -20,6 +21,7 @@ interface Values {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ResetPassword() {
|
export function ResetPassword() {
|
||||||
|
useDocumentTitle(t`Reset password`);
|
||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const token = params.get("token") ?? "";
|
const token = params.get("token") ?? "";
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { DumpCard } from "../components/DumpCard.tsx";
|
|||||||
import { PlaylistCard } from "../components/PlaylistCard.tsx";
|
import { PlaylistCard } from "../components/PlaylistCard.tsx";
|
||||||
import { ErrorCard } from "../components/ErrorCard.tsx";
|
import { ErrorCard } from "../components/ErrorCard.tsx";
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { useWS } from "../hooks/useWS.ts";
|
import { useWS } from "../hooks/useWS.ts";
|
||||||
import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts";
|
import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts";
|
||||||
import { useTabParam } from "../hooks/useTabParam.ts";
|
import { useTabParam } from "../hooks/useTabParam.ts";
|
||||||
@@ -46,6 +47,7 @@ type SearchState =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function Search() {
|
export function Search() {
|
||||||
|
useDocumentTitle(t`Search`);
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const q = searchParams.get("q") ?? "";
|
const q = searchParams.get("q") ?? "";
|
||||||
const [tab, setTab] = useTabParam<Tab>(SEARCH_TABS, "dumps");
|
const [tab, setTab] = useTabParam<Tab>(SEARCH_TABS, "dumps");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Trans } from "@lingui/react/macro";
|
|||||||
import { Link, useParams } from "react-router";
|
import { Link, useParams } from "react-router";
|
||||||
|
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { useWS } from "../hooks/useWS.ts";
|
import { useWS } from "../hooks/useWS.ts";
|
||||||
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
|
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
|
||||||
import { usePositionAwareSync } from "../hooks/usePositionAwareSync.ts";
|
import { usePositionAwareSync } from "../hooks/usePositionAwareSync.ts";
|
||||||
@@ -19,6 +20,8 @@ export function UserDumps() {
|
|||||||
const { user: me } = useAuth();
|
const { user: me } = useAuth();
|
||||||
const { voteCounts, myVotes, lastDumpEvent, castVote, removeVote } = useWS();
|
const { voteCounts, myVotes, lastDumpEvent, castVote, removeVote } = useWS();
|
||||||
|
|
||||||
|
useDocumentTitle(username ?? null);
|
||||||
|
|
||||||
const { state, setItems, sentinelRef } = useUserDumpFeed(
|
const { state, setItems, sentinelRef } = useUserDumpFeed(
|
||||||
username,
|
username,
|
||||||
"dumps",
|
"dumps",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
type RawAuthResponse,
|
type RawAuthResponse,
|
||||||
} from "../model.ts";
|
} from "../model.ts";
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { PageShell } from "../components/PageShell.tsx";
|
import { PageShell } from "../components/PageShell.tsx";
|
||||||
import {
|
import {
|
||||||
expectOk,
|
expectOk,
|
||||||
@@ -30,6 +31,7 @@ interface ResetValues {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function UserLogin() {
|
export function UserLogin() {
|
||||||
|
useDocumentTitle(t`Log in`);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
const [showReset, setShowReset] = useState(false);
|
const [showReset, setShowReset] = useState(false);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
hydratePlaylist,
|
hydratePlaylist,
|
||||||
} from "../model.ts";
|
} from "../model.ts";
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { useWS } from "../hooks/useWS.ts";
|
import { useWS } from "../hooks/useWS.ts";
|
||||||
import { usePlaylistListSync } from "../hooks/usePlaylistListSync.ts";
|
import { usePlaylistListSync } from "../hooks/usePlaylistListSync.ts";
|
||||||
import { usePositionAwareSync } from "../hooks/usePositionAwareSync.ts";
|
import { usePositionAwareSync } from "../hooks/usePositionAwareSync.ts";
|
||||||
@@ -60,6 +61,8 @@ function initialFeed(items: Playlist[], hasMore: boolean): PlaylistFeed {
|
|||||||
export function UserPlaylists() {
|
export function UserPlaylists() {
|
||||||
const { username } = useParams();
|
const { username } = useParams();
|
||||||
const { user: me, authFetch, token } = useAuth();
|
const { user: me, authFetch, token } = useAuth();
|
||||||
|
|
||||||
|
useDocumentTitle(username ?? null);
|
||||||
const { lastPlaylistEvent } = useWS();
|
const { lastPlaylistEvent } = useWS();
|
||||||
|
|
||||||
const { cached: cachedCreated, saveState: saveCreated } = useFeedCache<
|
const { cached: cachedCreated, saveState: saveCreated } = useFeedCache<
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import { NewPlaylistForm } from "../components/NewPlaylistForm.tsx";
|
|||||||
import { PageShell } from "../components/PageShell.tsx";
|
import { PageShell } from "../components/PageShell.tsx";
|
||||||
import { PageError } from "../components/PageError.tsx";
|
import { PageError } from "../components/PageError.tsx";
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { useTheme } from "../hooks/useTheme.ts";
|
import { useTheme } from "../hooks/useTheme.ts";
|
||||||
import { useDefaultFeedTab } from "../hooks/useDefaultFeedTab.ts";
|
import { useDefaultFeedTab } from "../hooks/useDefaultFeedTab.ts";
|
||||||
import { useWS } from "../hooks/useWS.ts";
|
import { useWS } from "../hooks/useWS.ts";
|
||||||
@@ -211,6 +212,14 @@ export function UserPublicProfile() {
|
|||||||
|
|
||||||
const [state, setState] = useState<ProfileState>({ status: "loading" });
|
const [state, setState] = useState<ProfileState>({ status: "loading" });
|
||||||
|
|
||||||
|
useDocumentTitle(
|
||||||
|
state.status === "loaded"
|
||||||
|
? state.user.username
|
||||||
|
: state.status === "error"
|
||||||
|
? null
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
|
||||||
const profileUserId = state.status === "loaded" ? state.user.id : null;
|
const profileUserId = state.status === "loaded" ? state.user.id : null;
|
||||||
const isOwnProfile = me?.id === profileUserId;
|
const isOwnProfile = me?.id === profileUserId;
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
type RegisterRequest,
|
type RegisterRequest,
|
||||||
} from "../model.ts";
|
} from "../model.ts";
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { PageShell } from "../components/PageShell.tsx";
|
import { PageShell } from "../components/PageShell.tsx";
|
||||||
import { ErrorCard } from "../components/ErrorCard.tsx";
|
import { ErrorCard } from "../components/ErrorCard.tsx";
|
||||||
import {
|
import {
|
||||||
@@ -33,6 +34,7 @@ interface Values {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function UserRegister() {
|
export function UserRegister() {
|
||||||
|
useDocumentTitle(t`Register`);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { API_URL } from "../config/api.ts";
|
|||||||
import type { Dump } from "../model.ts";
|
import type { Dump } from "../model.ts";
|
||||||
import { deserializeDump } from "../model.ts";
|
import { deserializeDump } from "../model.ts";
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { useWS } from "../hooks/useWS.ts";
|
import { useWS } from "../hooks/useWS.ts";
|
||||||
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
|
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
|
||||||
import { useFading } from "../hooks/useFading.ts";
|
import { useFading } from "../hooks/useFading.ts";
|
||||||
@@ -21,6 +22,8 @@ export function UserUpvoted() {
|
|||||||
const { user: me } = useAuth();
|
const { user: me } = useAuth();
|
||||||
const { voteCounts, myVotes, lastVoteEvent, castVote, removeVote } = useWS();
|
const { voteCounts, myVotes, lastVoteEvent, castVote, removeVote } = useWS();
|
||||||
|
|
||||||
|
useDocumentTitle(username ?? null);
|
||||||
|
|
||||||
const [votedIds, setVotedIds] = useState<Set<string>>(new Set());
|
const [votedIds, setVotedIds] = useState<Set<string>>(new Set());
|
||||||
const { fading, startFading, cancelFading, cancelAll } = useFading();
|
const { fading, startFading, cancelFading, cancelAll } = useFading();
|
||||||
const prevMyVotesRef = useRef<Set<string> | null>(null);
|
const prevMyVotesRef = useRef<Set<string> | null>(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user