Migracao Logto + Supabase - backend e frontend atualizados para nova autenticação

This commit is contained in:
2026-03-30 20:50:10 +00:00
parent 9d3958b82b
commit f89d5571f4
22 changed files with 1266 additions and 1047 deletions

View File

@@ -1,129 +1,83 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useUser, useOrganization } from '@clerk/clerk-react';
import React, { useState, useEffect, useCallback } from 'react';
import type { AppUser } from '../types';
import { AuthContext } from './AuthContextType';
import { setApiClerkUserId, setApiOrganizationId, getBaseUrl } from '../services/api';
import { getToken, getUser, setUser, login as logtoLogin } from '../main';
const API_URL = getBaseUrl();
const API_URL = import.meta.env.VITE_API_URL || '/api';
interface AuthProviderProps {
children: React.ReactNode;
}
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const { user, isLoaded } = useUser();
const { organization, membership } = useOrganization();
const [appUser, setAppUser] = useState<AppUser | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const lastContextRef = useRef<{ clerkId?: string, orgId?: string | null }>({});
const [isSignedIn, setIsSignedIn] = useState(false);
// Set the clerk user ID and organization ID for the API interceptor
useEffect(() => {
setApiClerkUserId(user?.id || null);
setApiOrganizationId(organization?.id || null);
}, [user?.id, organization?.id]);
const token = getToken();
const user = getUser();
if (token && user) {
setAppUser(user as AppUser);
setIsSignedIn(true);
}
setIsLoading(false);
}, []);
const syncUser = useCallback(async () => {
if (!user) {
const token = getToken();
if (!token) {
setAppUser(null);
setIsSignedIn(false);
setIsLoading(false);
return;
}
try {
// Only set loading if the context has changed (new user or new organization)
// This prevents unmounting/remounting components on window focus revalidations
const isSameContext =
lastContextRef.current.clerkId === user.id &&
lastContextRef.current.orgId === (organization?.id || null);
if (!isSameContext) {
setIsLoading(true);
}
setIsLoading(true);
setError(null);
// Sync user with backend, including organization context
const response = await fetch(`${API_URL}/users/sync`, {
method: 'POST',
const response = await fetch(`${API_URL}/users/me`, {
headers: {
'Content-Type': 'application/json',
'x-clerk-user-id': user.id,
...(organization?.id && { 'x-organization-id': organization.id }),
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
clerkId: user.id,
email: user.primaryEmailAddress?.emailAddress || '',
name: user.fullName || user.firstName || 'Usuário',
organizationId: organization?.id || null,
clerkRole: membership?.role || null, // org:admin, org:member, etc.
}),
});
if (!response.ok) {
const data = await response.json();
if (response.status === 403 && data.error?.includes('bloqueada')) {
setError('Sua conta foi bloqueada. Entre em contato com o administrador.');
setAppUser(null);
return;
}
throw new Error('Falha ao sincronizar usuário');
throw new Error('Falha ao carregar usuário');
}
const syncedUser = await response.json();
// Use organizationRole if available (per-org role), otherwise fall back to global role
const effectiveRole = syncedUser.organizationRole || syncedUser.role || 'guest';
setAppUser({
...syncedUser,
id: syncedUser._id || syncedUser.id,
role: effectiveRole, // Override with organization-specific role
});
// Update last context ref
lastContextRef.current = { clerkId: user.id, orgId: organization?.id || null };
const userData = await response.json();
const effectiveRole = userData.role || 'guest';
const user = {
...userData,
id: userData._id || userData.id,
role: effectiveRole,
};
setUser(token, user);
setAppUser(user);
setIsSignedIn(true);
} catch (err) {
console.error('Error syncing user:', err);
console.error('Error loading user:', err);
setError('Erro ao carregar dados do usuário');
setAppUser(null);
setIsSignedIn(false);
} finally {
setIsLoading(false);
}
}, [user, organization?.id, membership?.role]);
}, []);
const refetchUser = useCallback(async () => {
if (!user) return;
try {
const response = await fetch(`${API_URL}/users/me`, {
headers: {
'x-clerk-user-id': user.id,
...(organization?.id && { 'x-organization-id': organization.id }),
},
});
if (response.ok) {
const userData = await response.json();
const effectiveRole = userData.organizationRole || userData.role || 'guest';
setAppUser({
...userData,
id: userData._id || userData.id,
role: effectiveRole,
});
}
} catch (err) {
console.error('Error refetching user:', err);
}
}, [user, organization?.id]);
// Re-sync when organization changes
useEffect(() => {
if (isLoaded && user) {
syncUser();
}
}, [isLoaded, user, organization?.id, syncUser]);
await syncUser();
}, [syncUser]);
const isDeveloper = useCallback(() => {
return user?.primaryEmailAddress?.emailAddress === 'admtracksteel@gmail.com';
}, [user]);
return appUser?.email === 'admtracksteel@gmail.com';
}, [appUser]);
const isAdmin = useCallback(() => appUser?.role === 'admin' || isDeveloper(), [appUser, isDeveloper]);
const isUser = useCallback(() => appUser?.role === 'user' || isAdmin(), [appUser, isAdmin]);
@@ -135,7 +89,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
value={{
appUser,
isLoading,
isSignedIn: !!user,
isSignedIn,
error,
isAdmin,
isUser,