Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/frontend/src/components/atoms/ExpiryWarningBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
sumExpiringUploadBytes,
} from '../../utils/credits';
import { BannerShell } from '../organisms/BannerNotifications/BannerShell';
import { useUserStore } from '../../globalStates/user';

/**
* Displays a warning banner when the user has purchased credits that will
Expand All @@ -20,13 +21,16 @@ import { BannerShell } from '../organisms/BannerNotifications/BannerShell';
export const ExpiryWarningBanner = () => {
const { api } = useNetwork();
const session = useContext(SessionContext);
const features = useUserStore((m) => m.features);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale persisted features can still cause 404 requests

Low Severity

ExpiryWarningBanner reads features from the Zustand store, which is persisted to localStorage and never cleared on logout (clearUser omits features). If buyCredits was previously true and the backend later disables the flag, the stale persisted value causes the query to fire on initial page load, producing the exact 404 this PR aims to prevent. SessionEnsurer avoids this by using the fresh useQuery result (which starts as undefined). The two components use inconsistent data sources for the same gating check.

Additional Locations (1)
Fix in Cursor Fix in Web


const { data: expiringBatches } = useQuery<ExpiringCreditBatch[]>({
queryKey: ['expiringCreditBatches'],
queryFn: () => api.getExpiringCreditBatches(),
// Refresh every 5 minutes – expiry warnings don't need to be real-time
refetchInterval: 5 * 60 * 1000,
enabled: !!session?.data,
// Only fetch when the buyCredits feature flag is enabled – the backend
// returns 404 for these endpoints when the flag is off.
enabled: !!session?.data && !!features?.buyCredits,
});

if (!expiringBatches || expiringBatches.length === 0) return null;
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend/src/components/atoms/SessionEnsurer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ export const SessionEnsurer = ({ children }: { children: React.ReactNode }) => {
queryFn: () => api.getCreditSummary(),
// Refresh every 30 s so the cap / balance stays reasonably fresh
refetchInterval: 30_000,
enabled: !!session?.data,
// Only fetch when the buyCredits feature flag is enabled – the backend
// returns 404 for these endpoints when the flag is off.
enabled: !!session?.data && !!features?.buyCredits,
});

const { data: touStatusData, isLoading: touStatusLoading } = useQuery({
Expand Down