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:
parent
9e895659db
commit
5b2eee84be
4 changed files with 36 additions and 16 deletions
21
apps/web/src/lib/query-cache.ts
Normal file
21
apps/web/src/lib/query-cache.ts
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Send, MessageSquare, Shield, Inbox, Pause, Play, Loader2, Trash2 } from 'lucide-react';
|
import { Send, MessageSquare, Shield, Inbox, Pause, Play, Loader2, Trash2 } from 'lucide-react';
|
||||||
import { apiGet, apiPatch, apiPost, apiDelete } from '../lib/api';
|
import { apiGet, apiPatch, apiPost, apiDelete } from '../lib/api';
|
||||||
|
import { invalidateCampaignCaches, invalidateCampaignListCaches } from '../lib/query-cache';
|
||||||
import type {
|
import type {
|
||||||
Campaign,
|
Campaign,
|
||||||
CampaignSatellite,
|
CampaignSatellite,
|
||||||
|
|
@ -120,8 +121,8 @@ export function CampaignDetailPage() {
|
||||||
|
|
||||||
const saveSettingsMutation = useMutation({
|
const saveSettingsMutation = useMutation({
|
||||||
mutationFn: (payload: typeof settingsForm) => apiPatch<Campaign>(`/api/campaigns/${id}`, payload),
|
mutationFn: (payload: typeof settingsForm) => apiPatch<Campaign>(`/api/campaigns/${id}`, payload),
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['campaign', id] });
|
if (id) await invalidateCampaignCaches(queryClient, id);
|
||||||
setSettingsMessage('Campaign settings saved');
|
setSettingsMessage('Campaign settings saved');
|
||||||
setSettingsError('');
|
setSettingsError('');
|
||||||
},
|
},
|
||||||
|
|
@ -130,19 +131,15 @@ export function CampaignDetailPage() {
|
||||||
|
|
||||||
const pauseMutation = useMutation({
|
const pauseMutation = useMutation({
|
||||||
mutationFn: () => apiPost<Campaign>(`/api/campaigns/${id}/pause`),
|
mutationFn: () => apiPost<Campaign>(`/api/campaigns/${id}/pause`),
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['campaign', id] });
|
if (id) await invalidateCampaignCaches(queryClient, id);
|
||||||
queryClient.invalidateQueries({ queryKey: ['campaign', id, 'stats'] });
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['workspace', 'overview'] });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const resumeMutation = useMutation({
|
const resumeMutation = useMutation({
|
||||||
mutationFn: () => apiPost<Campaign>(`/api/campaigns/${id}/resume`),
|
mutationFn: () => apiPost<Campaign>(`/api/campaigns/${id}/resume`),
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['campaign', id] });
|
if (id) await invalidateCampaignCaches(queryClient, id);
|
||||||
queryClient.invalidateQueries({ queryKey: ['campaign', id, 'stats'] });
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['workspace', 'overview'] });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -165,8 +162,8 @@ export function CampaignDetailPage() {
|
||||||
|
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: () => apiDelete(`/api/campaigns/${id}`),
|
mutationFn: () => apiDelete(`/api/campaigns/${id}`),
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['campaigns'] });
|
await invalidateCampaignListCaches(queryClient);
|
||||||
queryClient.invalidateQueries({ queryKey: ['mailboxes'] });
|
queryClient.invalidateQueries({ queryKey: ['mailboxes'] });
|
||||||
navigate('/campaigns');
|
navigate('/campaigns');
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { ChevronLeft, ChevronRight, Loader2 } from 'lucide-react';
|
||||||
import type { CampaignRecipe } from '@warmbox/shared';
|
import type { CampaignRecipe } from '@warmbox/shared';
|
||||||
import { PROVIDER_LABELS } from '@warmbox/shared';
|
import { PROVIDER_LABELS } from '@warmbox/shared';
|
||||||
import { apiDelete, apiGet, apiPost } from '../lib/api';
|
import { apiDelete, apiGet, apiPost } from '../lib/api';
|
||||||
|
import { invalidateCampaignListCaches } from '../lib/query-cache';
|
||||||
import type { Campaign, CreateCampaignInput, Mailbox, RecipePreviewDay } from '../lib/types';
|
import type { Campaign, CreateCampaignInput, Mailbox, RecipePreviewDay } from '../lib/types';
|
||||||
import { PageHeader } from '../components/PageHeader';
|
import { PageHeader } from '../components/PageHeader';
|
||||||
import { Button } from '../components/ui/Button';
|
import { Button } from '../components/ui/Button';
|
||||||
|
|
@ -99,8 +100,8 @@ export function CampaignWizardPage() {
|
||||||
}
|
}
|
||||||
return campaign;
|
return campaign;
|
||||||
},
|
},
|
||||||
onSuccess: (campaign) => {
|
onSuccess: async (campaign) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['campaigns'] });
|
await invalidateCampaignListCaches(queryClient);
|
||||||
queryClient.invalidateQueries({ queryKey: ['mailboxes'] });
|
queryClient.invalidateQueries({ queryKey: ['mailboxes'] });
|
||||||
navigate(`/campaigns/${campaign.id}`);
|
navigate(`/campaigns/${campaign.id}`);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Plus, Megaphone, Trash2 } from 'lucide-react';
|
import { Plus, Megaphone, Trash2 } from 'lucide-react';
|
||||||
import { apiDelete, apiGet } from '../lib/api';
|
import { apiDelete, apiGet } from '../lib/api';
|
||||||
|
import { invalidateCampaignListCaches } from '../lib/query-cache';
|
||||||
import type { Campaign } from '../lib/types';
|
import type { Campaign } from '../lib/types';
|
||||||
import { PageHeader } from '../components/PageHeader';
|
import { PageHeader } from '../components/PageHeader';
|
||||||
import { Button } from '../components/ui/Button';
|
import { Button } from '../components/ui/Button';
|
||||||
|
|
@ -22,8 +23,8 @@ export function CampaignsPage() {
|
||||||
|
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: (id: string) => apiDelete(`/api/campaigns/${id}`),
|
mutationFn: (id: string) => apiDelete(`/api/campaigns/${id}`),
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['campaigns'] });
|
await invalidateCampaignListCaches(queryClient);
|
||||||
queryClient.invalidateQueries({ queryKey: ['mailboxes'] });
|
queryClient.invalidateQueries({ queryKey: ['mailboxes'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue