159 lines
4.4 KiB
TypeScript
159 lines
4.4 KiB
TypeScript
import React from 'react';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
|
|
import { getEvents, type TenantEvent } from '../api';
|
|
import { useAuth } from '../auth/context';
|
|
|
|
const STORAGE_KEY = 'tenant-admin.active-event';
|
|
|
|
export interface EventContextValue {
|
|
events: TenantEvent[];
|
|
isLoading: boolean;
|
|
isError: boolean;
|
|
activeEvent: TenantEvent | null;
|
|
activeSlug: string | null;
|
|
hasEvents: boolean;
|
|
hasMultipleEvents: boolean;
|
|
selectEvent: (slug: string | null) => void;
|
|
refetch: () => void;
|
|
}
|
|
|
|
const EventContext = React.createContext<EventContextValue | undefined>(undefined);
|
|
|
|
export function EventProvider({ children }: { children: React.ReactNode }) {
|
|
const { status } = useAuth();
|
|
const [manualEvents, setManualEvents] = React.useState<TenantEvent[]>([]);
|
|
const [manualAttempted, setManualAttempted] = React.useState(false);
|
|
const [storedSlug, setStoredSlug] = React.useState<string | null>(() => {
|
|
if (typeof window === 'undefined') {
|
|
return null;
|
|
}
|
|
return window.localStorage.getItem(STORAGE_KEY);
|
|
});
|
|
|
|
const authReady = status === 'authenticated';
|
|
|
|
const {
|
|
data: fetchedEvents = [],
|
|
isLoading: queryLoading,
|
|
isError,
|
|
refetch,
|
|
} = useQuery<TenantEvent[]>({
|
|
queryKey: ['tenant-events'],
|
|
queryFn: async () => {
|
|
try {
|
|
return await getEvents({ force: true });
|
|
} catch (error) {
|
|
console.warn('[EventContext] Failed to fetch events', error);
|
|
throw error;
|
|
}
|
|
},
|
|
staleTime: 60 * 1000,
|
|
enabled: authReady,
|
|
initialData: [],
|
|
});
|
|
|
|
const events = React.useMemo(
|
|
() => (authReady ? (manualEvents.length ? manualEvents : fetchedEvents) : []),
|
|
[authReady, fetchedEvents, manualEvents]
|
|
);
|
|
const isLoading = authReady ? queryLoading || (!manualAttempted && manualEvents.length === 0 && fetchedEvents.length === 0) : status === 'loading';
|
|
|
|
React.useEffect(() => {
|
|
if (!authReady || manualAttempted || queryLoading) {
|
|
return;
|
|
}
|
|
if (fetchedEvents.length > 0 && !isError) {
|
|
return;
|
|
}
|
|
setManualAttempted(true);
|
|
getEvents({ force: true })
|
|
.then((list) => {
|
|
setManualEvents(list ?? []);
|
|
})
|
|
.catch(() => {
|
|
setManualEvents([]);
|
|
});
|
|
}, [authReady, fetchedEvents.length, isError, manualAttempted, queryLoading]);
|
|
|
|
React.useEffect(() => {
|
|
if (!events.length || typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
const hasStored = Boolean(storedSlug);
|
|
const slugExists = hasStored && events.some((event) => event.slug === storedSlug);
|
|
|
|
if (!slugExists) {
|
|
const shouldAutoselect = events.length === 1;
|
|
const fallbackSlug = shouldAutoselect ? events[0]?.slug : null;
|
|
setStoredSlug(fallbackSlug);
|
|
if (fallbackSlug) {
|
|
window.localStorage.setItem(STORAGE_KEY, fallbackSlug);
|
|
} else {
|
|
window.localStorage.removeItem(STORAGE_KEY);
|
|
}
|
|
}
|
|
}, [events, storedSlug]);
|
|
|
|
const activeEvent = React.useMemo(() => {
|
|
if (!events.length) {
|
|
return null;
|
|
}
|
|
|
|
const matched = events.find((event) => event.slug && event.slug === storedSlug);
|
|
if (matched) {
|
|
return matched;
|
|
}
|
|
|
|
// Only auto-select the single available event. When multiple events exist and
|
|
// no stored slug is present we intentionally return null to let the UI prompt
|
|
// for a selection.
|
|
if (events.length === 1) {
|
|
return events[0];
|
|
}
|
|
|
|
return null;
|
|
}, [events, storedSlug]);
|
|
|
|
const hasEvents = events.length > 0;
|
|
const hasMultipleEvents = events.length > 1;
|
|
const activeSlug = activeEvent?.slug ?? null;
|
|
|
|
const selectEvent = React.useCallback((slug: string | null) => {
|
|
setStoredSlug(slug);
|
|
if (typeof window !== 'undefined') {
|
|
if (slug) {
|
|
window.localStorage.setItem(STORAGE_KEY, slug);
|
|
} else {
|
|
window.localStorage.removeItem(STORAGE_KEY);
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
const value = React.useMemo<EventContextValue>(
|
|
() => ({
|
|
events,
|
|
isLoading,
|
|
isError,
|
|
activeEvent,
|
|
activeSlug,
|
|
hasEvents,
|
|
hasMultipleEvents,
|
|
selectEvent,
|
|
refetch,
|
|
}),
|
|
[events, isLoading, isError, activeEvent, activeSlug, hasEvents, hasMultipleEvents, selectEvent, refetch]
|
|
);
|
|
|
|
return <EventContext.Provider value={value}>{children}</EventContext.Provider>;
|
|
}
|
|
|
|
export function useEventContext(): EventContextValue {
|
|
const ctx = React.useContext(EventContext);
|
|
if (!ctx) {
|
|
throw new Error('useEventContext must be used within an EventProvider');
|
|
}
|
|
return ctx;
|
|
}
|