- ajuste na barra de rolagem no modal de novo usuario - ajuste no redirecionamento para rota de perfil em ambiente mobile - ajuste no nome da funçao do profissional na descricao do perfil
377 lines
13 KiB
TypeScript
377 lines
13 KiB
TypeScript
|
|
import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
|
|
import { User, UserRole } from '../types';
|
|
|
|
// Mock Users Database
|
|
const MOCK_USERS: User[] = [
|
|
{
|
|
id: 'superadmin-1',
|
|
name: 'Dev Admin',
|
|
email: 'admin@photum.com',
|
|
role: UserRole.SUPERADMIN,
|
|
avatar: 'https://i.pravatar.cc/150?u=admin'
|
|
},
|
|
{
|
|
id: 'owner-1',
|
|
name: 'PHOTUM CEO',
|
|
email: 'empresa@photum.com',
|
|
role: UserRole.BUSINESS_OWNER,
|
|
avatar: 'https://i.pravatar.cc/150?u=ceo',
|
|
allowedRegions: ['SP', 'MG']
|
|
},
|
|
{
|
|
id: 'owner-sp',
|
|
name: 'ADMIN SP',
|
|
email: 'admin.sp@photum.com',
|
|
role: UserRole.BUSINESS_OWNER,
|
|
avatar: 'https://i.pravatar.cc/150?u=sp',
|
|
allowedRegions: ['SP']
|
|
},
|
|
{
|
|
id: 'owner-mg',
|
|
name: 'ADMIN MG',
|
|
email: 'admin.mg@photum.com',
|
|
role: UserRole.BUSINESS_OWNER,
|
|
avatar: 'https://i.pravatar.cc/150?u=mg',
|
|
allowedRegions: ['MG']
|
|
},
|
|
{
|
|
id: 'photographer-1',
|
|
name: 'COLABORADOR PHOTUM',
|
|
email: 'foto@photum.com',
|
|
role: UserRole.PHOTOGRAPHER,
|
|
avatar: 'https://i.pravatar.cc/150?u=photo'
|
|
},
|
|
{
|
|
id: 'client-1',
|
|
name: 'PARCEIRO PHOTUM',
|
|
email: 'cliente@photum.com',
|
|
role: UserRole.EVENT_OWNER,
|
|
avatar: 'https://i.pravatar.cc/150?u=client'
|
|
},
|
|
{
|
|
id: 'viewer-1',
|
|
name: 'VISUALIZADOR PHOTUM',
|
|
email: 'viewer@photum.com',
|
|
role: UserRole.AGENDA_VIEWER,
|
|
avatar: 'https://i.pravatar.cc/150?u=viewer'
|
|
}
|
|
];
|
|
|
|
interface AuthContextType {
|
|
user: User | null;
|
|
login: (email: string, password?: string) => Promise<boolean>;
|
|
logout: () => void;
|
|
register: (data: { nome: string; email: string; senha: string; telefone: string; role: string; empresaId?: string; tipo_profissional?: string; regiao?: string }) => Promise<{ success: boolean; userId?: string; token?: string }>;
|
|
availableUsers: User[]; // Helper for the login screen demo
|
|
token: string | null;
|
|
isLoading: boolean;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
|
|
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [token, setToken] = useState<string | null>(localStorage.getItem("token"));
|
|
|
|
// Initial loading state depends on whether we have a token to verify
|
|
const [isLoading, setIsLoading] = useState<boolean>(!!localStorage.getItem("token"));
|
|
|
|
useEffect(() => {
|
|
const restoreSession = async () => {
|
|
if (!token) {
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
|
|
try {
|
|
const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8080";
|
|
const response = await fetch(`${API_URL}/api/me`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
const backendUser = data.user;
|
|
console.log("AuthContext: restoreSession raw user:", backendUser);
|
|
const mappedUser: User = {
|
|
id: backendUser.id,
|
|
email: backendUser.email,
|
|
name: data.profissional?.nome || data.empresa?.nome || backendUser.name || backendUser.nome || backendUser.email.split('@')[0],
|
|
phone: backendUser.phone || backendUser.telefone || backendUser.whatsapp, // Map phone
|
|
role: backendUser.role as UserRole,
|
|
ativo: backendUser.ativo,
|
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
|
avatar: data.profissional?.avatar_url || data.empresa?.avatar_url || backendUser.avatar,
|
|
allowedRegions: backendUser.allowed_regions || [], // Map allowed regions
|
|
|
|
// Client specific fields
|
|
cpf_cnpj: backendUser.cpf_cnpj,
|
|
cep: backendUser.cep,
|
|
endereco: backendUser.endereco,
|
|
numero: backendUser.numero,
|
|
complemento: backendUser.complemento,
|
|
bairro: backendUser.bairro,
|
|
cidade: backendUser.cidade,
|
|
estado: backendUser.estado,
|
|
professionalId: data.profissional?.id, // Map professional ID
|
|
functions: data.profissional?.functions || [],
|
|
};
|
|
console.log("AuthContext: restoreSession mapped user:", mappedUser);
|
|
if (!backendUser.ativo) {
|
|
console.warn("User is not active, logging out.");
|
|
logout();
|
|
return;
|
|
}
|
|
setUser(mappedUser);
|
|
} else {
|
|
// Token invalid or expired
|
|
logout();
|
|
}
|
|
} catch (err) {
|
|
console.error("Session restore error:", err);
|
|
logout();
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
if (token) {
|
|
restoreSession();
|
|
} else {
|
|
setIsLoading(false);
|
|
}
|
|
}, [token]); // removed 'user' from dependency to avoid loop if user is set, though !user check handles it. safer to just depend on token mount.
|
|
|
|
const getErrorMessage = (errorKey: string): string => {
|
|
// Map backend error messages to Portuguese
|
|
const errorMap: { [key: string]: string } = {
|
|
"email already registered": "Este e-mail já está cadastrado.",
|
|
"invalid credentials": "E-mail ou senha incorretos.",
|
|
"user not found": "Usuário não encontrado.",
|
|
"crypto/bcrypt: hashedPassword is not the hash of the given password": "Senha incorreta.",
|
|
"password is too short": "A senha é muito curta.",
|
|
"cpf already registered": "CPF já cadastrado.",
|
|
};
|
|
|
|
// Check for partial matches or exact matches
|
|
if (errorMap[errorKey]) return errorMap[errorKey];
|
|
|
|
// Fallbacks
|
|
if (errorKey.includes('hashedPassword')) return "Senha incorreta.";
|
|
if (errorKey.includes('email')) return "Erro relacionado ao e-mail.";
|
|
if (errorKey.includes('password')) return "Erro relacionado à senha.";
|
|
|
|
return "Ocorreu um erro inesperado. Tente novamente.";
|
|
};
|
|
|
|
const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8080";
|
|
|
|
const login = async (email: string, password?: string) => {
|
|
// 1. Try Real API first
|
|
try {
|
|
const response = await fetch(`${API_URL}/auth/login`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email, senha: password })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(getErrorMessage(errorData.error || 'Falha no login'));
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
const backendUser = data.user;
|
|
|
|
// Enforce active check
|
|
if (!backendUser.ativo) {
|
|
throw new Error("Cadastro pendente de aprovação.");
|
|
}
|
|
|
|
// Store token (optional, if you need it for other requests outside cookies)
|
|
localStorage.setItem('token', data.access_token);
|
|
setToken(data.access_token);
|
|
|
|
// Map backend user to frontend User type
|
|
const mappedUser: User = {
|
|
id: backendUser.id,
|
|
email: backendUser.email,
|
|
name: data.profissional?.nome || data.empresa?.nome || backendUser.name || backendUser.nome || backendUser.email.split('@')[0],
|
|
phone: backendUser.phone || backendUser.telefone || backendUser.whatsapp, // Map phone
|
|
role: backendUser.role as UserRole,
|
|
ativo: backendUser.ativo,
|
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
|
avatar: data.profissional?.avatar_url || data.empresa?.avatar_url || backendUser.avatar,
|
|
allowedRegions: backendUser.allowed_regions || [],
|
|
|
|
// Client specific fields
|
|
cpf_cnpj: backendUser.cpf_cnpj,
|
|
cep: backendUser.cep,
|
|
endereco: backendUser.endereco,
|
|
numero: backendUser.numero,
|
|
complemento: backendUser.complemento,
|
|
bairro: backendUser.bairro,
|
|
cidade: backendUser.cidade,
|
|
estado: backendUser.estado,
|
|
professionalId: data.profissional?.id, // Map professional ID
|
|
functions: data.profissional?.functions || [],
|
|
};
|
|
|
|
setUser(mappedUser);
|
|
return true;
|
|
} catch (err) {
|
|
console.error('Login error:', err);
|
|
|
|
// 2. Fallback to Demo/Mock users if API fails
|
|
const mockUser = MOCK_USERS.find(u => u.email === email);
|
|
if (mockUser) {
|
|
console.log('Using demo user:', mockUser.email);
|
|
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate delay
|
|
setUser({ ...mockUser, ativo: true });
|
|
return true;
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
const logout = async () => {
|
|
try {
|
|
if (token) {
|
|
await fetch(`${import.meta.env.VITE_API_URL}/auth/logout`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}` // Though refresh token is in cookie
|
|
},
|
|
// body: JSON.stringify({ refresh_token: ... }) // If we had it in client state, but it is in cookie.
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Logout failed", error);
|
|
} finally {
|
|
localStorage.removeItem('token');
|
|
setToken(null);
|
|
setUser(null);
|
|
// Force redirect or let app router handle it safely
|
|
window.location.href = '/';
|
|
}
|
|
};
|
|
|
|
const register = async (data: any, autoLogin: boolean = true, skipLoading: boolean = false) => {
|
|
if (!skipLoading) setIsLoading(true);
|
|
|
|
try {
|
|
const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8080";
|
|
|
|
const payload = {
|
|
email: data.email,
|
|
senha: data.password || data.senha,
|
|
role: data.role,
|
|
nome: data.name || data.nome,
|
|
telefone: data.phone || data.telefone,
|
|
professional_type: data.professional_type || data.tipo_profissional,
|
|
empresa_id: data.company_id || data.empresaId || data.empresa_id,
|
|
regiao: data.regiao,
|
|
cpf_cnpj: data.cpf_cnpj,
|
|
cep: data.cep,
|
|
endereco: data.endereco,
|
|
numero: data.numero,
|
|
complemento: data.complemento,
|
|
bairro: data.bairro,
|
|
cidade: data.cidade,
|
|
estado: data.estado
|
|
};
|
|
|
|
const response = await fetch(`${API_URL}/auth/register`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"x-regiao": data.regiao || 'SP'
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
const rawError = errorData.error || 'Falha no cadastro';
|
|
throw new Error(getErrorMessage(rawError));
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
|
|
if (responseData.user && responseData.user.ativo) {
|
|
|
|
if (autoLogin) {
|
|
if (responseData.access_token) {
|
|
localStorage.setItem('token', responseData.access_token);
|
|
setToken(responseData.access_token);
|
|
}
|
|
}
|
|
|
|
|
|
const backendUser = responseData.user;
|
|
const mappedUser: User = {
|
|
id: backendUser.id,
|
|
email: backendUser.email,
|
|
name: backendUser.nome || backendUser.email.split('@')[0],
|
|
phone: backendUser.phone || backendUser.telefone || backendUser.whatsapp, // Map phone
|
|
role: backendUser.role as UserRole,
|
|
ativo: backendUser.ativo,
|
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
|
avatar: backendUser.avatar,
|
|
allowedRegions: backendUser.allowed_regions || [],
|
|
|
|
// Client specific fields
|
|
cpf_cnpj: backendUser.cpf_cnpj,
|
|
cep: backendUser.cep,
|
|
endereco: backendUser.endereco,
|
|
numero: backendUser.numero,
|
|
complemento: backendUser.complemento,
|
|
bairro: backendUser.bairro,
|
|
cidade: backendUser.cidade,
|
|
estado: backendUser.estado,
|
|
functions: backendUser.functions || [],
|
|
};
|
|
if (autoLogin) {
|
|
setUser(mappedUser);
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
userId: responseData.user?.id || responseData.userId,
|
|
token: responseData.access_token
|
|
};
|
|
} catch (err) {
|
|
console.error('Registration error:', err);
|
|
if (err instanceof Error) {
|
|
return { success: false, error: err.message };
|
|
}
|
|
return { success: false, error: "Erro desconhecido" };
|
|
} finally {
|
|
if (!skipLoading) setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ user, token, login, logout, register, availableUsers: MOCK_USERS, isLoading }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useAuth = () => {
|
|
const context = useContext(AuthContext);
|
|
if (!context) throw new Error('useAuth must be used within an AuthProvider');
|
|
return context;
|
|
};
|