mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-16 01:05:42 -04:00
fix(web): refresh tracking-dependent queries after mutations
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
5 files changed
+49
-7
No files matched your search
@@ -12,7 +12,7 @@ import {
|
||||
IconTrash,
|
||||
IconX,
|
||||
} from "@tabler/icons-react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigate, useRouter } from "@tanstack/react-router";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
@@ -44,6 +44,7 @@ import { authClient, signOut } from "@/lib/auth/client";
|
||||
import { getErrorMessage } from "@/lib/error-messages";
|
||||
import { useAppForm } from "@/lib/form";
|
||||
import { client, orpc } from "@/lib/orpc/client";
|
||||
import { invalidateTrackingQueries } from "@/lib/orpc/invalidate";
|
||||
import type { NormalizedImport } from "@sofa/api/schemas";
|
||||
import { formatDate } from "@sofa/i18n/format";
|
||||
|
||||
@@ -379,6 +380,7 @@ interface ImportResult {
|
||||
|
||||
function SofaImportDialog() {
|
||||
const { t } = useLingui();
|
||||
const queryClient = useQueryClient();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const importAbortRef = useRef<AbortController | null>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -448,6 +450,7 @@ function SofaImportDialog() {
|
||||
warnings: event.job.warnings,
|
||||
});
|
||||
setStep("done");
|
||||
void invalidateTrackingQueries(queryClient);
|
||||
const importedCount = event.job.importedCount;
|
||||
if (importedCount > 0) {
|
||||
toast.success(t`Imported ${importedCount} items from Sofa export`);
|
||||
@@ -481,6 +484,7 @@ function SofaImportDialog() {
|
||||
warnings: finalJob.warnings,
|
||||
});
|
||||
setStep("done");
|
||||
void invalidateTrackingQueries(queryClient);
|
||||
} else {
|
||||
toast.info(t`Import is still running in the background. Check back later.`);
|
||||
handleClose();
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { MessageDescriptor } from "@lingui/core";
|
||||
import { msg } from "@lingui/core/macro";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { IconCloudUpload, IconLink } from "@tabler/icons-react";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Progress } from "@/components/ui/progress";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { getErrorMessage } from "@/lib/error-messages";
|
||||
import { client, orpc } from "@/lib/orpc/client";
|
||||
import { invalidateTrackingQueries } from "@/lib/orpc/invalidate";
|
||||
import type { NormalizedImport } from "@sofa/api/schemas";
|
||||
import { formatList } from "@sofa/i18n/format";
|
||||
|
||||
@@ -192,6 +193,7 @@ export function ImportsSection() {
|
||||
|
||||
function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
const { i18n, t } = useLingui();
|
||||
const queryClient = useQueryClient();
|
||||
const { data: systemStatus } = useQuery(orpc.system.status.queryOptions());
|
||||
const publicApiUrl = systemStatus?.publicApiUrl ?? "https://public-api.sofa.watch";
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -285,6 +287,7 @@ function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
warnings: event.job.warnings,
|
||||
});
|
||||
setStep("done");
|
||||
void invalidateTrackingQueries(queryClient);
|
||||
const importedCount = event.job.importedCount;
|
||||
const sourceLabel = config.label;
|
||||
if (importedCount > 0) {
|
||||
@@ -320,6 +323,7 @@ function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
warnings: finalJob.warnings,
|
||||
});
|
||||
setStep("done");
|
||||
void invalidateTrackingQueries(queryClient);
|
||||
} else {
|
||||
toast.info(t`Import is still running in the background. Check back later.`);
|
||||
setStep("preview");
|
||||
|
||||
@@ -10,15 +10,17 @@ import {
|
||||
IconPlus,
|
||||
IconStarFilled,
|
||||
} from "@tabler/icons-react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { type MotionStyle, type MotionValue, motion } from "motion/react";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useTiltEffect } from "@/hooks/use-tilt-effect";
|
||||
import { orpc } from "@/lib/orpc/client";
|
||||
import { invalidateTrackingQueries } from "@/lib/orpc/invalidate";
|
||||
import { thumbHashToUrl } from "@/lib/thumbhash";
|
||||
|
||||
export function TitleCardSkeleton() {
|
||||
@@ -86,11 +88,14 @@ function useStatusConfig() {
|
||||
function QuickAddButton({ id, userStatus }: { id: string; userStatus?: TitleStatus | null }) {
|
||||
const { t } = useLingui();
|
||||
const statusConfig = useStatusConfig();
|
||||
const queryClient = useQueryClient();
|
||||
const [optimisticStatus, setOptimisticStatus] = useState<TitleStatus | null>(null);
|
||||
|
||||
const addToWatchlistMutation = useMutation(
|
||||
orpc.tracking.updateStatus.mutationOptions({
|
||||
onSuccess: () => setOptimisticStatus("in_watchlist"),
|
||||
onError: () => toast.error(t`Failed to update status`),
|
||||
onSettled: () => void invalidateTrackingQueries(queryClient),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useCallback } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { orpc } from "@/lib/orpc/client";
|
||||
import { invalidateTrackingQueries } from "@/lib/orpc/invalidate";
|
||||
import type { Season } from "@sofa/api/schemas";
|
||||
|
||||
import { useTitleContext } from "./title-context";
|
||||
@@ -40,10 +41,19 @@ export function useTitleActions() {
|
||||
[queryClient, userInfoKey],
|
||||
);
|
||||
|
||||
const { mutateAsync: watch } = useMutation(orpc.tracking.watch.mutationOptions());
|
||||
const { mutateAsync: unwatch } = useMutation(orpc.tracking.unwatch.mutationOptions());
|
||||
const { mutateAsync: updateStatus } = useMutation(orpc.tracking.updateStatus.mutationOptions());
|
||||
const { mutateAsync: updateRating } = useMutation(orpc.tracking.rate.mutationOptions());
|
||||
const invalidateAfterMutation = { onSettled: () => void invalidateTrackingQueries(queryClient) };
|
||||
const { mutateAsync: watch } = useMutation(
|
||||
orpc.tracking.watch.mutationOptions(invalidateAfterMutation),
|
||||
);
|
||||
const { mutateAsync: unwatch } = useMutation(
|
||||
orpc.tracking.unwatch.mutationOptions(invalidateAfterMutation),
|
||||
);
|
||||
const { mutateAsync: updateStatus } = useMutation(
|
||||
orpc.tracking.updateStatus.mutationOptions(invalidateAfterMutation),
|
||||
);
|
||||
const { mutateAsync: updateRating } = useMutation(
|
||||
orpc.tracking.rate.mutationOptions(invalidateAfterMutation),
|
||||
);
|
||||
|
||||
const catchUp = useCallback(
|
||||
async (episodeIds: string[]) => {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { orpc } from "./client";
|
||||
|
||||
/**
|
||||
* Mark every query whose response depends on the user's tracking state
|
||||
* (statuses, watches, ratings, library membership) as stale. Active queries
|
||||
* refetch immediately; inactive ones refetch on next mount. Title details
|
||||
* (`titles.get`) are deliberately excluded — they don't depend on tracking.
|
||||
*/
|
||||
export function invalidateTrackingQueries(queryClient: QueryClient): Promise<unknown> {
|
||||
return Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: orpc.tracking.key() }),
|
||||
queryClient.invalidateQueries({ queryKey: orpc.library.key() }),
|
||||
queryClient.invalidateQueries({ queryKey: orpc.discover.key() }),
|
||||
queryClient.invalidateQueries({ queryKey: orpc.people.key() }),
|
||||
queryClient.invalidateQueries({ queryKey: orpc.titles.similar.key() }),
|
||||
]);
|
||||
}
|
||||
Reference in new issue
Block a user