Skip to content

Commit 2862bee

Browse files
committed
feat: add polling to campaign query and improve cache invalidation across crowdfunding hooks
1 parent ec2cbcb commit 2862bee

4 files changed

Lines changed: 34 additions & 13 deletions

File tree

app/me/crowdfunding/[slug]/milestones/[milestoneIndex]/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { use } from 'react';
4+
import { useRouter } from 'next/navigation';
45
import { uploadMilestoneDocuments } from '@/lib/api/upload';
56
import {
67
useCampaign,
@@ -26,6 +27,7 @@ interface PageProps {
2627
export default function MilestoneDetailPage({ params }: PageProps) {
2728
const { slug, milestoneIndex: milestoneParam } = use(params);
2829

30+
const router = useRouter();
2931
const { data: campaign, isLoading, error } = useCampaign(slug);
3032

3133
const milestones = campaign?.milestones ?? [];
@@ -77,6 +79,7 @@ export default function MilestoneDetailPage({ params }: PageProps) {
7779
toast.success(
7880
'Evidence submitted. The team will review your submission.'
7981
);
82+
router.push(`/me/crowdfunding/${campaign!.slug}/milestones`);
8083
},
8184
onError: err => {
8285
toast.error(

features/crowdfunding/api/keys.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ export const crowdfundingKeys = {
1313
mine: (page: number, limit: number) =>
1414
[...crowdfundingKeys.all, 'mine', { page, limit }] as const,
1515

16-
// Single campaign (by ID or slug - same cache slot since slug is unique)
16+
// Covers ALL single-campaign entries regardless of whether they were keyed by
17+
// UUID or slug. Use this for invalidation when you only have one form of the key.
18+
campaignPrefix: () => [...crowdfundingKeys.all, 'campaign'] as const,
19+
20+
// Single campaign (by ID or slug — separate cache entries, same data)
1721
campaign: (idOrSlug: string) =>
1822
[...crowdfundingKeys.all, 'campaign', idOrSlug] as const,
1923

features/crowdfunding/api/use-campaign.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ export function useCampaign(idOrSlug: string | null | undefined) {
7171
queryFn: () =>
7272
isId ? fetchCampaignById(idOrSlug!) : fetchCampaignBySlug(idOrSlug!),
7373
staleTime: 30_000,
74+
// Poll during transient states so the UI self-heals without a manual refresh:
75+
// PUBLISHING — fast poll until the on-chain tx confirms and status flips to FUNDING.
76+
// VOTING / FUNDING — slower poll to pick up vote counts and contributions from other users.
77+
refetchInterval: query => {
78+
const status = (query.state.data as CrowdfundingCampaign | undefined)
79+
?.v2Status;
80+
if (status === 'PUBLISHING') return 5_000;
81+
if (status === 'VOTING' || status === 'FUNDING') return 30_000;
82+
return false;
83+
},
7484
});
7585
}
7686

@@ -106,10 +116,8 @@ export function useReviseAndResubmit() {
106116
const queryClient = useQueryClient();
107117
return useMutation({
108118
mutationFn: (id: string) => reviseAndResubmit(id),
109-
onSuccess: (_data, id) => {
110-
queryClient.invalidateQueries({
111-
queryKey: crowdfundingKeys.campaign(id),
112-
});
119+
onSuccess: () => {
120+
queryClient.invalidateQueries({ queryKey: crowdfundingKeys.all });
113121
},
114122
});
115123
}
@@ -155,9 +163,11 @@ export function useContributeV2() {
155163
return useMutation({
156164
mutationFn: ({ id, body }: { id: string; body: ContributeV2Body }) =>
157165
contributeV2(id, body),
158-
onSuccess: (_data, { id }) => {
166+
// Invalidate all campaign entries (UUID-keyed AND slug-keyed) so both the
167+
// builder page and the public page update without a manual refresh.
168+
onSuccess: () => {
159169
queryClient.invalidateQueries({
160-
queryKey: crowdfundingKeys.campaign(id),
170+
queryKey: crowdfundingKeys.campaignPrefix(),
161171
});
162172
},
163173
});
@@ -168,12 +178,11 @@ export function useCastVote() {
168178
return useMutation({
169179
mutationFn: ({ id, choice }: { id: string; choice: 'UP' | 'DOWN' }) =>
170180
castVote(id, choice),
171-
onSuccess: (_data, { id }) => {
172-
queryClient.invalidateQueries({
173-
queryKey: crowdfundingKeys.campaign(id),
174-
});
181+
// campaignPrefix() covers ['crowdfunding','campaign'] which matches every
182+
// per-campaign entry — slug-keyed, UUID-keyed, and my-vote sub-keys alike.
183+
onSuccess: () => {
175184
queryClient.invalidateQueries({
176-
queryKey: [...crowdfundingKeys.campaign(id), 'my-vote'],
185+
queryKey: crowdfundingKeys.campaignPrefix(),
177186
});
178187
},
179188
});

features/crowdfunding/api/use-milestone.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,16 @@ export function useSubmitMilestoneEvidence(campaignId: string) {
7575
});
7676
},
7777
onSuccess: (_data, { milestoneId }) => {
78+
// Bust the single-milestone entry and ALL campaign entries regardless of
79+
// whether they were cached by UUID or by slug (public page uses slug).
7880
queryClient.invalidateQueries({
7981
queryKey: crowdfundingKeys.milestone(campaignId, milestoneId),
8082
});
8183
queryClient.invalidateQueries({
82-
queryKey: crowdfundingKeys.campaign(campaignId),
84+
queryKey: crowdfundingKeys.milestones(campaignId),
85+
});
86+
queryClient.invalidateQueries({
87+
queryKey: crowdfundingKeys.campaignPrefix(),
8388
});
8489
},
8590
});

0 commit comments

Comments
 (0)