fix(dashboard): block duplicate quick-watch clicks until refetch and use logical end inset

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Sonnet 5.5 committed 2026-10-05 14:21:47 -04:00
1 parent 04bcb66436
commit e3f7ccca07
2 files changed
+15 -10

No files matched your search

@@ -116,7 +116,7 @@ export function ContinueWatchingCard({ item }: { item: ContinueWatchingItemProps
size="icon" size="icon"
aria-label={quickWatchLabel} aria-label={quickWatchLabel}
disabled={pendingId === nextEpisode.id} disabled={pendingId === nextEpisode.id}
className="absolute top-2 right-2 rounded-full bg-black/50 text-white backdrop-blur-sm hover:bg-black/70" className="absolute end-2 top-2 rounded-full bg-black/50 text-white backdrop-blur-sm hover:bg-black/70"
onClick={() => onClick={() =>
watchEpisode( watchEpisode(
nextEpisode.id, nextEpisode.id,
@@ -1,5 +1,6 @@
import { useLingui } from "@lingui/react/macro"; import { useLingui } from "@lingui/react/macro";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { getErrorMessage } from "@/lib/error-messages"; import { getErrorMessage } from "@/lib/error-messages";
@@ -10,21 +11,25 @@ import { invalidateTrackingQueries } from "@/lib/orpc/invalidate";
export function useQuickWatchEpisode() { export function useQuickWatchEpisode() {
const { t } = useLingui(); const { t } = useLingui();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { mutate, variables, isPending } = useMutation(orpc.tracking.watch.mutationOptions()); const [label, setLabel] = useState("");
// Handlers live on the hook-level options so the mutation stays pending until
function watchEpisode(episodeId: string, label: string) { // the tracking queries have refetched, which blocks duplicate clicks.
mutate( const { mutate, variables, isPending } = useMutation(
{ scope: "episode", ids: [episodeId] }, orpc.tracking.watch.mutationOptions({
{ onSuccess: async () => {
onSuccess: () => {
toast.success(t`Marked ${label} as watched`); toast.success(t`Marked ${label} as watched`);
void invalidateTrackingQueries(queryClient); await invalidateTrackingQueries(queryClient);
}, },
onError: (err) => { onError: (err) => {
toast.error(getErrorMessage(err, t`Failed to mark episode`)); toast.error(getErrorMessage(err, t`Failed to mark episode`));
}, },
}, }),
); );
function watchEpisode(episodeId: string, episodeLabel: string) {
if (isPending) return;
setLabel(episodeLabel);
mutate({ scope: "episode", ids: [episodeId] });
} }
const pendingId = isPending ? (variables?.ids?.[0] ?? null) : null; const pendingId = isPending ? (variables?.ids?.[0] ?? null) : null;