upgrade to tamagui v2 and guest pwa overhaul

This commit is contained in:
Codex Agent
2026-02-02 13:01:20 +01:00
parent 2e78f3ab8d
commit 7c6e14ffe2
168 changed files with 47462 additions and 8914 deletions

View File

@@ -0,0 +1,103 @@
import React from 'react';
import { fetchEvent, type EventData, FetchEventError } from '../services/eventApi';
import { isTaskModeEnabled } from '@/guest/lib/engagement';
type EventDataStatus = 'idle' | 'loading' | 'ready' | 'error';
type EventDataContextValue = {
event: EventData | null;
status: EventDataStatus;
error: string | null;
token: string | null;
tasksEnabled: boolean;
};
const EventDataContext = React.createContext<EventDataContextValue>({
event: null,
status: 'idle',
error: null,
token: null,
tasksEnabled: true,
});
type EventDataProviderProps = {
token?: string | null;
tasksEnabledFallback?: boolean;
children: React.ReactNode;
};
export function EventDataProvider({
token,
tasksEnabledFallback = true,
children,
}: EventDataProviderProps) {
const [event, setEvent] = React.useState<EventData | null>(null);
const [status, setStatus] = React.useState<EventDataStatus>(token ? 'loading' : 'idle');
const [error, setError] = React.useState<string | null>(null);
React.useEffect(() => {
if (!token) {
setEvent(null);
setStatus('idle');
setError(null);
return;
}
let cancelled = false;
const loadEvent = async () => {
setStatus('loading');
setError(null);
try {
const eventData = await fetchEvent(token);
if (cancelled) {
return;
}
setEvent(eventData);
setStatus('ready');
} catch (err) {
if (cancelled) {
return;
}
setEvent(null);
setStatus('error');
if (err instanceof FetchEventError) {
setError(err.message);
} else if (err instanceof Error) {
setError(err.message || 'Event could not be loaded.');
} else {
setError('Event could not be loaded.');
}
}
};
loadEvent();
return () => {
cancelled = true;
};
}, [token]);
const tasksEnabled = event ? isTaskModeEnabled(event) : tasksEnabledFallback;
return (
<EventDataContext.Provider
value={{
event,
status,
error,
token: token ?? null,
tasksEnabled,
}}
>
{children}
</EventDataContext.Provider>
);
}
export function useEventData() {
return React.useContext(EventDataContext);
}

View File

@@ -0,0 +1,111 @@
import React from 'react';
type GuestIdentityContextValue = {
eventKey: string;
slug: string;
name: string;
hydrated: boolean;
setName: (nextName: string) => void;
clearName: () => void;
reload: () => void;
};
const GuestIdentityContext = React.createContext<GuestIdentityContextValue | undefined>(undefined);
function storageKey(eventKey: string) {
return `guestName_${eventKey}`;
}
export function readGuestName(eventKey: string) {
if (!eventKey || typeof window === 'undefined') {
return '';
}
try {
return window.localStorage.getItem(storageKey(eventKey)) ?? '';
} catch (error) {
console.warn('Failed to read guest name', error);
return '';
}
}
export function GuestIdentityProvider({ eventKey, children }: { eventKey: string; children: React.ReactNode }) {
const [name, setNameState] = React.useState('');
const [hydrated, setHydrated] = React.useState(false);
const loadFromStorage = React.useCallback(() => {
if (!eventKey) {
setHydrated(true);
setNameState('');
return;
}
try {
const stored = window.localStorage.getItem(storageKey(eventKey));
setNameState(stored ?? '');
} catch (error) {
console.warn('Failed to read guest name from storage', error);
setNameState('');
} finally {
setHydrated(true);
}
}, [eventKey]);
React.useEffect(() => {
setHydrated(false);
loadFromStorage();
}, [loadFromStorage]);
const persistName = React.useCallback(
(nextName: string) => {
const trimmed = nextName.trim();
setNameState(trimmed);
try {
if (trimmed) {
window.localStorage.setItem(storageKey(eventKey), trimmed);
} else {
window.localStorage.removeItem(storageKey(eventKey));
}
} catch (error) {
console.warn('Failed to persist guest name', error);
}
},
[eventKey]
);
const clearName = React.useCallback(() => {
setNameState('');
try {
window.localStorage.removeItem(storageKey(eventKey));
} catch (error) {
console.warn('Failed to clear guest name', error);
}
}, [eventKey]);
const value = React.useMemo<GuestIdentityContextValue>(
() => ({
eventKey,
slug: eventKey,
name,
hydrated,
setName: persistName,
clearName,
reload: loadFromStorage,
}),
[eventKey, name, hydrated, persistName, clearName, loadFromStorage]
);
return <GuestIdentityContext.Provider value={value}>{children}</GuestIdentityContext.Provider>;
}
export function useGuestIdentity() {
const ctx = React.useContext(GuestIdentityContext);
if (!ctx) {
throw new Error('useGuestIdentity must be used within a GuestIdentityProvider');
}
return ctx;
}
export function useOptionalGuestIdentity() {
return React.useContext(GuestIdentityContext);
}