This reverts commit d3b326e.
The deployment caused reports of blank screens and 400 errors. Reverting to restore stable state while investigating root cause.
115 lines
4.4 KiB
TypeScript
115 lines
4.4 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
import { useAuthStore } from '@/stores/auth-store';
|
|
import { switchTenant } from '@/lib/api/auth';
|
|
import { Building2, ChevronDown, Check, Loader2, Crown } from 'lucide-react';
|
|
import { cn } from '@horux/shared-ui';
|
|
import { isGlobalAdminRfc } from '@horux/shared';
|
|
|
|
/**
|
|
* Switcher para users con múltiples memberships (owner o contador con varias
|
|
* empresas). Distinto del TenantSelector de admin global:
|
|
* - Admin global: impersonación via X-View-Tenant (no cambia el JWT)
|
|
* - Membership switcher: cambia de tenant *real* con nuevo JWT
|
|
*
|
|
* Se oculta si:
|
|
* - El user tiene ≤1 membership
|
|
* - El user es admin global (ya tiene su propio TenantSelector, sería redundante)
|
|
*/
|
|
export function MembershipSwitcher() {
|
|
const [open, setOpen] = useState(false);
|
|
const [switching, setSwitching] = useState(false);
|
|
const { user, setUser, setTokens } = useAuthStore();
|
|
const queryClient = useQueryClient();
|
|
|
|
const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, user?.role, user?.platformRoles);
|
|
const tenants = user?.tenants || [];
|
|
const showSwitcher = !isGlobalAdmin && tenants.length > 1;
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (e: MouseEvent) => {
|
|
const target = e.target as HTMLElement;
|
|
if (!target.closest('.membership-switcher')) setOpen(false);
|
|
};
|
|
document.addEventListener('click', handleClickOutside);
|
|
return () => document.removeEventListener('click', handleClickOutside);
|
|
}, []);
|
|
|
|
if (!showSwitcher) return null;
|
|
|
|
const activeTenant = tenants.find(t => t.id === user?.tenantId);
|
|
|
|
const handleSwitch = async (tenantId: string) => {
|
|
if (tenantId === user?.tenantId) { setOpen(false); return; }
|
|
setSwitching(true);
|
|
try {
|
|
const res = await switchTenant(tenantId);
|
|
setTokens(res.accessToken, res.refreshToken);
|
|
setUser(res.user);
|
|
// Refresca todo el cache — las queries dependen del tenant activo
|
|
queryClient.clear();
|
|
setOpen(false);
|
|
// Reload para que React Query re-fetche desde cero con el nuevo JWT
|
|
window.location.reload();
|
|
} catch (err: any) {
|
|
alert(err?.response?.data?.message || 'Error al cambiar de empresa');
|
|
} finally {
|
|
setSwitching(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="membership-switcher relative">
|
|
<button
|
|
onClick={() => setOpen(!open)}
|
|
disabled={switching}
|
|
className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium hover:bg-accent transition-colors disabled:opacity-50"
|
|
>
|
|
<Building2 className="h-4 w-4" />
|
|
<span className="max-w-[180px] truncate">
|
|
{activeTenant?.nombre || user?.tenantName}
|
|
</span>
|
|
{switching
|
|
? <Loader2 className="h-4 w-4 animate-spin" />
|
|
: <ChevronDown className={cn('h-4 w-4 transition-transform', open && 'rotate-180')} />
|
|
}
|
|
</button>
|
|
|
|
{open && (
|
|
<div className="absolute top-full right-0 mt-2 w-72 rounded-lg border bg-card shadow-lg z-50">
|
|
<div className="p-2 border-b">
|
|
<p className="text-xs text-muted-foreground px-2">Mis empresas</p>
|
|
</div>
|
|
<div className="max-h-80 overflow-y-auto p-1">
|
|
{tenants.map(t => (
|
|
<button
|
|
key={t.id}
|
|
onClick={() => handleSwitch(t.id)}
|
|
disabled={switching}
|
|
className={cn(
|
|
'flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm hover:bg-accent transition-colors text-left',
|
|
t.id === user?.tenantId && 'bg-primary/10'
|
|
)}
|
|
>
|
|
<div className="h-8 w-8 rounded bg-muted flex items-center justify-center text-xs font-medium">
|
|
{t.nombre.substring(0, 2).toUpperCase()}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-1.5">
|
|
<p className="font-medium truncate">{t.nombre}</p>
|
|
{t.isOwner && <Crown className="h-3 w-3 text-amber-500 flex-shrink-0" />}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground truncate">{t.rfc} · {t.role}</p>
|
|
</div>
|
|
{t.id === user?.tenantId && <Check className="h-4 w-4 text-primary flex-shrink-0" />}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|