185 lines
9.2 KiB
TypeScript
185 lines
9.2 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { useUser, useOrganizationList, useOrganization } from '@clerk/clerk-react';
|
|
import { Building2, Users, RefreshCw, Mail } from 'lucide-react';
|
|
|
|
export const OrganizationSelector: React.FC = () => {
|
|
const { user } = useUser();
|
|
const { setActive, userMemberships, userInvitations } = useOrganizationList({
|
|
userMemberships: {
|
|
infinite: true,
|
|
},
|
|
userInvitations: {
|
|
infinite: true,
|
|
}
|
|
});
|
|
const { organization } = useOrganization();
|
|
const [isAcceptingInvites, setIsAcceptingInvites] = useState(false);
|
|
|
|
console.log('OrganizationSelector rendered');
|
|
console.log('Current organization:', organization);
|
|
console.log('User memberships:', userMemberships);
|
|
console.log('User memberships data:', userMemberships.data);
|
|
console.log('User invitations:', userInvitations);
|
|
console.log('User invitations data:', userInvitations.data);
|
|
|
|
// Auto-accept pending invitations
|
|
useEffect(() => {
|
|
const acceptPendingInvitations = async () => {
|
|
if (userInvitations.data && userInvitations.data.length > 0 && !isAcceptingInvites) {
|
|
console.log('Found pending invitations, auto-accepting...');
|
|
setIsAcceptingInvites(true);
|
|
|
|
for (const invitation of userInvitations.data) {
|
|
try {
|
|
console.log('Accepting invitation:', invitation);
|
|
await invitation.accept();
|
|
console.log('Invitation accepted successfully');
|
|
} catch (error) {
|
|
console.error('Error accepting invitation:', error);
|
|
}
|
|
}
|
|
|
|
// Reload memberships after accepting invitations
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 1000);
|
|
}
|
|
};
|
|
|
|
acceptPendingInvitations();
|
|
}, [userInvitations.data, isAcceptingInvites]);
|
|
|
|
// Auto-select if user has only one organization
|
|
useEffect(() => {
|
|
console.log('Auto-select effect running...');
|
|
if (!organization && userMemberships.data && userMemberships.data.length === 1) {
|
|
console.log('Auto-selecting single organization...');
|
|
const membership = userMemberships.data[0];
|
|
if (setActive) {
|
|
setActive({ organization: membership.organization });
|
|
}
|
|
}
|
|
}, [organization, userMemberships.data, setActive]);
|
|
|
|
const handleSelectOrganization = async (orgId: string) => {
|
|
console.log('Selecting organization:', orgId);
|
|
if (setActive) {
|
|
await setActive({ organization: orgId });
|
|
}
|
|
// The auth context will automatically sync after organization changes
|
|
};
|
|
|
|
// Loading state - check if data exists or accepting invites
|
|
if (!userMemberships.data || isAcceptingInvites) {
|
|
console.log('Loading state - no data yet or accepting invites');
|
|
return (
|
|
<div className="min-h-screen bg-background flex items-center justify-center">
|
|
<div className="text-center">
|
|
{isAcceptingInvites ? (
|
|
<>
|
|
<Mail className="w-12 h-12 text-primary animate-bounce mx-auto mb-4" />
|
|
<p className="text-text-main font-bold mb-2">Aceitando convites pendentes...</p>
|
|
<p className="text-text-muted text-sm">Por favor aguarde</p>
|
|
</>
|
|
) : (
|
|
<>
|
|
<RefreshCw className="w-12 h-12 text-primary animate-spin mx-auto mb-4" />
|
|
<p className="text-text-muted">Carregando organizações...</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (userMemberships.data?.length === 0) {
|
|
return (
|
|
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
|
<div className="max-w-md w-full bg-surface rounded-2xl border border-border/40 p-8 text-center">
|
|
<div className="w-16 h-16 rounded-2xl bg-amber-500/20 flex items-center justify-center mx-auto mb-4">
|
|
<Building2 className="w-8 h-8 text-amber-500" />
|
|
</div>
|
|
<h1 className="text-2xl font-bold text-text-main mb-2">
|
|
Nenhuma Organização
|
|
</h1>
|
|
<p className="text-text-muted mb-6">
|
|
Você ainda não faz parte de nenhuma organização. Entre em contato com o administrador para receber um convite.
|
|
</p>
|
|
<div className="text-sm text-text-muted bg-surface-soft rounded-lg p-4">
|
|
<p className="font-semibold mb-1">Conectado como:</p>
|
|
<p>{user?.primaryEmailAddress?.emailAddress}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
|
<div className="max-w-2xl w-full">
|
|
<div className="text-center mb-8">
|
|
<div className="w-16 h-16 rounded-2xl bg-primary/20 flex items-center justify-center mx-auto mb-4">
|
|
<Building2 className="w-8 h-8 text-primary" />
|
|
</div>
|
|
<h1 className="text-3xl font-bold text-text-main mb-2">
|
|
Selecione uma Organização
|
|
</h1>
|
|
<p className="text-text-muted">
|
|
Escolha qual organização você deseja acessar
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid gap-4">
|
|
{userMemberships.data?.map((membership) => (
|
|
<button
|
|
key={membership.organization.id}
|
|
onClick={() => handleSelectOrganization(membership.organization.id)}
|
|
className="w-full bg-surface hover:bg-surface-hover border border-border/40 rounded-2xl p-6 text-left transition-all hover:border-primary/50 hover:shadow-lg hover:shadow-primary/10 group"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-14 h-14 rounded-xl bg-primary/20 flex items-center justify-center flex-shrink-0 group-hover:bg-primary/30 transition-colors">
|
|
{membership.organization.imageUrl ? (
|
|
<img
|
|
src={membership.organization.imageUrl}
|
|
alt={membership.organization.name}
|
|
className="w-12 h-12 rounded-lg object-contain"
|
|
/>
|
|
) : (
|
|
<Building2 className="w-7 h-7 text-primary" />
|
|
)}
|
|
</div>
|
|
<div className="flex-1">
|
|
<h3 className="text-lg font-bold text-text-main group-hover:text-primary transition-colors">
|
|
{membership.organization.name}
|
|
</h3>
|
|
<div className="flex items-center gap-2 mt-1">
|
|
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-primary/20 text-primary text-xs font-semibold">
|
|
{membership.role === 'org:admin' ? 'Administrador' :
|
|
membership.role === 'org:member' ? 'Membro' : 'Convidado'}
|
|
</span>
|
|
<span className="flex items-center gap-1 text-xs text-text-muted">
|
|
<Users className="w-3 h-3" />
|
|
{membership.organization.membersCount || 0} membros
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="text-primary opacity-0 group-hover:opacity-100 transition-opacity">
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-6 text-center">
|
|
<p className="text-sm text-text-muted">
|
|
Conectado como <span className="font-semibold">{user?.primaryEmailAddress?.emailAddress}</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|