Add query cache utilities for campaign data invalidation

Introduce new functions in query-cache.ts to handle cache invalidation for campaign-related queries. Update CampaignDetailPage, CampaignsPage, and CampaignWizardPage to utilize these functions for improved cache management upon successful mutations, ensuring the UI reflects the latest campaign data.
This commit is contained in:
Jonny Nguyen 2026-06-26 14:02:44 -07:00
parent 9e895659db
commit 5b2eee84be
4 changed files with 36 additions and 16 deletions

View file

@ -0,0 +1,21 @@
import type { QueryClient } from '@tanstack/react-query';
/** Invalidate list + dashboard after any campaign mutation. */
export function invalidateCampaignListCaches(queryClient: QueryClient): Promise<void> {
return Promise.all([
queryClient.invalidateQueries({ queryKey: ['campaigns'] }),
queryClient.invalidateQueries({ queryKey: ['workspace', 'overview'] }),
]).then(() => undefined);
}
/** Invalidate detail, stats, list, and dashboard for one campaign. */
export function invalidateCampaignCaches(
queryClient: QueryClient,
campaignId: string,
): Promise<void> {
return Promise.all([
queryClient.invalidateQueries({ queryKey: ['campaign', campaignId] }),
queryClient.invalidateQueries({ queryKey: ['campaign', campaignId, 'stats'] }),
invalidateCampaignListCaches(queryClient),
]).then(() => undefined);
}

View file

@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { Send, MessageSquare, Shield, Inbox, Pause, Play, Loader2, Trash2 } from 'lucide-react';
import { apiGet, apiPatch, apiPost, apiDelete } from '../lib/api';
import { invalidateCampaignCaches, invalidateCampaignListCaches } from '../lib/query-cache';
import type {
Campaign,
CampaignSatellite,
@ -120,8 +121,8 @@ export function CampaignDetailPage() {
const saveSettingsMutation = useMutation({
mutationFn: (payload: typeof settingsForm) => apiPatch<Campaign>(`/api/campaigns/${id}`, payload),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['campaign', id] });
onSuccess: async () => {
if (id) await invalidateCampaignCaches(queryClient, id);
setSettingsMessage('Campaign settings saved');
setSettingsError('');
},
@ -130,19 +131,15 @@ export function CampaignDetailPage() {
const pauseMutation = useMutation({
mutationFn: () => apiPost<Campaign>(`/api/campaigns/${id}/pause`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['campaign', id] });
queryClient.invalidateQueries({ queryKey: ['campaign', id, 'stats'] });
queryClient.invalidateQueries({ queryKey: ['workspace', 'overview'] });
onSuccess: async () => {
if (id) await invalidateCampaignCaches(queryClient, id);
},
});
const resumeMutation = useMutation({
mutationFn: () => apiPost<Campaign>(`/api/campaigns/${id}/resume`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['campaign', id] });
queryClient.invalidateQueries({ queryKey: ['campaign', id, 'stats'] });
queryClient.invalidateQueries({ queryKey: ['workspace', 'overview'] });
onSuccess: async () => {
if (id) await invalidateCampaignCaches(queryClient, id);
},
});
@ -165,8 +162,8 @@ export function CampaignDetailPage() {
const deleteMutation = useMutation({
mutationFn: () => apiDelete(`/api/campaigns/${id}`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['campaigns'] });
onSuccess: async () => {
await invalidateCampaignListCaches(queryClient);
queryClient.invalidateQueries({ queryKey: ['mailboxes'] });
navigate('/campaigns');
},

View file

@ -5,6 +5,7 @@ import { ChevronLeft, ChevronRight, Loader2 } from 'lucide-react';
import type { CampaignRecipe } from '@warmbox/shared';
import { PROVIDER_LABELS } from '@warmbox/shared';
import { apiDelete, apiGet, apiPost } from '../lib/api';
import { invalidateCampaignListCaches } from '../lib/query-cache';
import type { Campaign, CreateCampaignInput, Mailbox, RecipePreviewDay } from '../lib/types';
import { PageHeader } from '../components/PageHeader';
import { Button } from '../components/ui/Button';
@ -99,8 +100,8 @@ export function CampaignWizardPage() {
}
return campaign;
},
onSuccess: (campaign) => {
queryClient.invalidateQueries({ queryKey: ['campaigns'] });
onSuccess: async (campaign) => {
await invalidateCampaignListCaches(queryClient);
queryClient.invalidateQueries({ queryKey: ['mailboxes'] });
navigate(`/campaigns/${campaign.id}`);
},

View file

@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import { Plus, Megaphone, Trash2 } from 'lucide-react';
import { apiDelete, apiGet } from '../lib/api';
import { invalidateCampaignListCaches } from '../lib/query-cache';
import type { Campaign } from '../lib/types';
import { PageHeader } from '../components/PageHeader';
import { Button } from '../components/ui/Button';
@ -22,8 +23,8 @@ export function CampaignsPage() {
const deleteMutation = useMutation({
mutationFn: (id: string) => apiDelete(`/api/campaigns/${id}`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['campaigns'] });
onSuccess: async () => {
await invalidateCampaignListCaches(queryClient);
queryClient.invalidateQueries({ queryKey: ['mailboxes'] });
},
});