fix: stabilize auth-to-tenant flow and correct tenant header mapping

Resolve post-login routing and tenant context issues by proxying frontend API
calls, redirecting authenticated users away from /login, and hardening club
loading with retries/loading guards.

Align tenant identity end-to-end by returning tenantId in /api/clubs/me and
sending X-Tenant-Id from cookie-backed tenantId instead of local clubId,
restoring authorized tasks/shifts data access after club selection.
This commit is contained in:
WorkClub Automation
2026-03-06 08:01:09 +01:00
parent dbc8964f07
commit 9950185213
7 changed files with 82 additions and 27 deletions

View File

@@ -8,6 +8,7 @@ type Club = {
id: string;
name: string;
sportType: string;
tenantId: string;
};
type TenantContextType = {
@@ -15,6 +16,8 @@ type TenantContextType = {
setActiveClub: (clubId: string) => void;
userRole: string | null;
clubs: Club[];
clubsLoading: boolean;
clubsError: Error | null;
};
const TenantContext = createContext<TenantContextType | undefined>(undefined);
@@ -24,14 +27,20 @@ export function TenantProvider({ children }: { children: ReactNode }) {
const [activeClubId, setActiveClubId] = useState<string | null>(null);
const queryClient = useQueryClient();
const { data: clubs = [] } = useQuery<Club[]>({
queryKey: ['my-clubs'],
const { data: clubs = [], isLoading: clubsLoading, error: clubsError } = useQuery<Club[]>({
queryKey: ['my-clubs', session?.accessToken],
queryFn: async () => {
const res = await fetch('/api/clubs/me');
if (!res.ok) return [];
const res = await fetch('/api/clubs/me', {
headers: {
Authorization: `Bearer ${session?.accessToken}`,
},
});
if (!res.ok) throw new Error(`Failed to fetch clubs: ${res.statusText}`);
return res.json();
},
enabled: status === 'authenticated',
enabled: status === 'authenticated' && !!session?.accessToken,
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * Math.pow(2, attemptIndex), 10000),
});
useEffect(() => {
@@ -47,21 +56,27 @@ export function TenantProvider({ children }: { children: ReactNode }) {
useEffect(() => {
if (activeClubId) {
document.cookie = `X-Tenant-Id=${activeClubId}; path=/; max-age=86400`;
const selectedClub = clubs.find(c => c.id === activeClubId);
if (selectedClub) {
document.cookie = `X-Tenant-Id=${selectedClub.tenantId}; path=/; max-age=86400`;
}
}
}, [activeClubId]);
}, [activeClubId, clubs]);
const handleSetActiveClub = (clubId: string) => {
setActiveClubId(clubId);
localStorage.setItem('activeClubId', clubId);
document.cookie = `X-Tenant-Id=${clubId}; path=/; max-age=86400`;
const selectedClub = clubs.find(c => c.id === clubId);
if (selectedClub) {
document.cookie = `X-Tenant-Id=${selectedClub.tenantId}; path=/; max-age=86400`;
}
queryClient.invalidateQueries();
};
const userRole = activeClubId && session?.user?.clubs ? session.user.clubs[activeClubId] || null : null;
return (
<TenantContext.Provider value={{ activeClubId, setActiveClub: handleSetActiveClub, userRole, clubs }}>
<TenantContext.Provider value={{ activeClubId, setActiveClub: handleSetActiveClub, userRole, clubs, clubsLoading, clubsError: clubsError || null }}>
{children}
</TenantContext.Provider>
);