chore: correção geral e ajustes na API
Copiar código
This commit is contained in:
parent
481a951428
commit
1617dc09de
@ -232,149 +232,175 @@ export function PatientRegistrationForm({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit(ev: React.FormEvent) {
|
async function handleSubmit(ev: React.FormEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
if (!validateLocal()) return;
|
if (!validateLocal()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!validarCPFLocal(form.cpf)) {
|
if (!validarCPFLocal(form.cpf)) {
|
||||||
setErrors((e) => ({ ...e, cpf: "CPF inválido" }));
|
setErrors((e) => ({ ...e, cpf: "CPF inválido" }));
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mode === "create") {
|
|
||||||
const existe = await verificarCpfDuplicado(form.cpf);
|
|
||||||
if (existe) {
|
|
||||||
setErrors((e) => ({ ...e, cpf: "CPF já cadastrado no sistema" }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Erro ao validar CPF", err);
|
|
||||||
setErrors({ submit: "Erro ao validar CPF." });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (mode === "create") {
|
||||||
|
const existe = await verificarCpfDuplicado(form.cpf);
|
||||||
|
if (existe) {
|
||||||
|
setErrors((e) => ({ ...e, cpf: "CPF já cadastrado no sistema" }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao validar CPF", err);
|
||||||
|
setErrors({ submit: "Erro ao validar CPF." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
if (mode === "edit") {
|
if (mode === "edit") {
|
||||||
if (patientId == null) throw new Error("Paciente inexistente para edição");
|
if (patientId == null) throw new Error("Paciente inexistente para edição");
|
||||||
const payload = toPayload();
|
const payload = toPayload();
|
||||||
const saved = await atualizarPaciente(String(patientId), payload);
|
const saved = await atualizarPaciente(String(patientId), payload);
|
||||||
onSaved?.(saved);
|
onSaved?.(saved);
|
||||||
alert("Paciente atualizado com sucesso!");
|
alert("Paciente atualizado com sucesso!");
|
||||||
|
|
||||||
|
setForm(initial);
|
||||||
|
setPhotoPreview(null);
|
||||||
|
setServerAnexos([]);
|
||||||
|
if (inline) onClose?.();
|
||||||
|
else onOpenChange?.(false);
|
||||||
|
} else {
|
||||||
|
// --- NOVA LÓGICA DE CRIAÇÃO ---
|
||||||
|
const patientPayload = toPayload();
|
||||||
|
const savedPatientProfile = await criarPaciente(patientPayload);
|
||||||
|
console.log("✅ Perfil do paciente criado:", savedPatientProfile);
|
||||||
|
|
||||||
|
if (form.email && form.email.includes("@")) {
|
||||||
|
console.log("🧩 Criando usuário de autenticação (paciente)...");
|
||||||
|
try {
|
||||||
|
const userResponse = await criarUsuarioPaciente({
|
||||||
|
email: form.email,
|
||||||
|
full_name: form.nome,
|
||||||
|
phone_mobile: form.telefone,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userResponse.success && userResponse.user) {
|
||||||
|
console.log("✅ Usuário de autenticação criado:", userResponse.user);
|
||||||
|
|
||||||
|
// Mostra credenciais no dialog
|
||||||
|
setCredentials({
|
||||||
|
email: userResponse.email ?? form.email,
|
||||||
|
password: userResponse.password ?? "",
|
||||||
|
userName: form.nome,
|
||||||
|
userType: "paciente",
|
||||||
|
});
|
||||||
|
setShowCredentialsDialog(true);
|
||||||
|
|
||||||
|
// 🔗 Vincular paciente ao usuário pelo EMAIL (não mais user_id)
|
||||||
|
try {
|
||||||
|
const apiMod = await import("@/lib/api");
|
||||||
|
const pacienteId =
|
||||||
|
savedPatientProfile?.id ||
|
||||||
|
(savedPatientProfile && (savedPatientProfile as any).id);
|
||||||
|
const userEmail =
|
||||||
|
form.email?.trim() || (userResponse.user as any)?.email;
|
||||||
|
|
||||||
|
if (
|
||||||
|
pacienteId &&
|
||||||
|
userEmail &&
|
||||||
|
typeof apiMod.vincularPacientePorEmail === "function"
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
"[PatientForm] Vinculando paciente pelo email:",
|
||||||
|
pacienteId,
|
||||||
|
userEmail
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await apiMod.vincularPacientePorEmail(pacienteId, userEmail);
|
||||||
|
console.log(
|
||||||
|
"[PatientForm] Paciente vinculado com sucesso ao email."
|
||||||
|
);
|
||||||
|
} catch (linkErr) {
|
||||||
|
console.warn(
|
||||||
|
"[PatientForm] Falha ao vincular paciente pelo email:",
|
||||||
|
linkErr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (dynErr) {
|
||||||
|
console.warn(
|
||||||
|
"[PatientForm] Não foi possível importar helper para vincular paciente por email:",
|
||||||
|
dynErr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpa o formulário mas mantém o dialog de credenciais aberto
|
||||||
|
setForm(initial);
|
||||||
|
setPhotoPreview(null);
|
||||||
|
setServerAnexos([]);
|
||||||
|
onSaved?.(savedPatientProfile);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
(userResponse as any).message ||
|
||||||
|
"Falhou ao criar o usuário de acesso."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (userError: any) {
|
||||||
|
console.error("❌ Erro ao criar usuário via signup:", userError);
|
||||||
|
|
||||||
|
const errorMsg = userError?.message || String(userError);
|
||||||
|
|
||||||
|
if (
|
||||||
|
errorMsg.toLowerCase().includes("already registered") ||
|
||||||
|
errorMsg.toLowerCase().includes("já está cadastrado") ||
|
||||||
|
errorMsg.toLowerCase().includes("já existe")
|
||||||
|
) {
|
||||||
|
alert(
|
||||||
|
`⚠️ Este email já está cadastrado no sistema.\n\n` +
|
||||||
|
`O perfil do paciente foi salvo com sucesso.\n\n` +
|
||||||
|
`Para criar acesso ao sistema, use um email diferente ou recupere a senha do email existente.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
alert(
|
||||||
|
`✅ Paciente cadastrado com sucesso!\n\n` +
|
||||||
|
`Porém houve um problema ao criar o acesso:\n${errorMsg}\n\n` +
|
||||||
|
`O cadastro do paciente foi salvo, mas será necessário criar o acesso manualmente.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpa formulário e fecha
|
||||||
|
setForm(initial);
|
||||||
|
setPhotoPreview(null);
|
||||||
|
setServerAnexos([]);
|
||||||
|
onSaved?.(savedPatientProfile);
|
||||||
|
if (inline) onClose?.();
|
||||||
|
else onOpenChange?.(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(
|
||||||
|
"✅ Paciente cadastrado com sucesso (sem usuário de acesso - email não fornecido)."
|
||||||
|
);
|
||||||
|
onSaved?.(savedPatientProfile);
|
||||||
setForm(initial);
|
setForm(initial);
|
||||||
setPhotoPreview(null);
|
setPhotoPreview(null);
|
||||||
setServerAnexos([]);
|
setServerAnexos([]);
|
||||||
if (inline) onClose?.();
|
if (inline) onClose?.();
|
||||||
else onOpenChange?.(false);
|
else onOpenChange?.(false);
|
||||||
|
|
||||||
} else {
|
|
||||||
// --- NOVA LÓGICA DE CRIAÇÃO ---
|
|
||||||
const patientPayload = toPayload();
|
|
||||||
const savedPatientProfile = await criarPaciente(patientPayload);
|
|
||||||
console.log(" Perfil do paciente criado:", savedPatientProfile);
|
|
||||||
|
|
||||||
if (form.email && form.email.includes('@')) {
|
|
||||||
console.log(" Criando usuário de autenticação (paciente)...");
|
|
||||||
try {
|
|
||||||
const userResponse = await criarUsuarioPaciente({
|
|
||||||
email: form.email,
|
|
||||||
full_name: form.nome,
|
|
||||||
phone_mobile: form.telefone,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userResponse.success && userResponse.user) {
|
|
||||||
console.log(" Usuário de autenticação criado:", userResponse.user);
|
|
||||||
|
|
||||||
// Mostra credenciais no dialog usando as credenciais retornadas
|
|
||||||
setCredentials({
|
|
||||||
email: userResponse.email ?? form.email,
|
|
||||||
password: userResponse.password ?? '',
|
|
||||||
userName: form.nome,
|
|
||||||
userType: 'paciente',
|
|
||||||
});
|
|
||||||
setShowCredentialsDialog(true);
|
|
||||||
|
|
||||||
// Tenta vincular o user_id ao perfil do paciente recém-criado
|
|
||||||
try {
|
|
||||||
const apiMod = await import('@/lib/api');
|
|
||||||
const pacienteId = savedPatientProfile?.id || (savedPatientProfile && (savedPatientProfile as any).id);
|
|
||||||
const userId = (userResponse.user as any)?.id || (userResponse.user as any)?.user_id || (userResponse.user as any)?.id;
|
|
||||||
if (pacienteId && userId && typeof apiMod.vincularUserIdPaciente === 'function') {
|
|
||||||
console.log('[PatientForm] Vinculando user_id ao paciente:', pacienteId, userId);
|
|
||||||
try {
|
|
||||||
await apiMod.vincularUserIdPaciente(pacienteId, String(userId));
|
|
||||||
console.log('[PatientForm] user_id vinculado com sucesso ao paciente');
|
|
||||||
} catch (linkErr) {
|
|
||||||
console.warn('[PatientForm] Falha ao vincular user_id ao paciente:', linkErr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (dynErr) {
|
|
||||||
console.warn('[PatientForm] Não foi possível importar helper para vincular user_id:', dynErr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpa formulário mas NÃO fecha ainda - fechará quando o dialog de credenciais fechar
|
|
||||||
setForm(initial);
|
|
||||||
setPhotoPreview(null);
|
|
||||||
setServerAnexos([]);
|
|
||||||
onSaved?.(savedPatientProfile);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
throw new Error((userResponse as any).message || "Falhou ao criar o usuário de acesso.");
|
|
||||||
}
|
|
||||||
} catch (userError: any) {
|
|
||||||
console.error(" Erro ao criar usuário via signup:", userError);
|
|
||||||
|
|
||||||
// Mensagem de erro específica para email duplicado
|
|
||||||
const errorMsg = userError?.message || String(userError);
|
|
||||||
|
|
||||||
if (errorMsg.toLowerCase().includes('already registered') ||
|
|
||||||
errorMsg.toLowerCase().includes('já está cadastrado') ||
|
|
||||||
errorMsg.toLowerCase().includes('já existe')) {
|
|
||||||
alert(
|
|
||||||
` Este email já está cadastrado no sistema.\n\n` +
|
|
||||||
` O perfil do paciente foi salvo com sucesso.\n\n` +
|
|
||||||
`Para criar acesso ao sistema, use um email diferente ou recupere a senha do email existente.`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
alert(
|
|
||||||
` Paciente cadastrado com sucesso!\n\n` +
|
|
||||||
` Porém houve um problema ao criar o acesso:\n${errorMsg}\n\n` +
|
|
||||||
`O cadastro do paciente foi salvo, mas será necessário criar o acesso manualmente.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpa formulário e fecha
|
|
||||||
setForm(initial);
|
|
||||||
setPhotoPreview(null);
|
|
||||||
setServerAnexos([]);
|
|
||||||
onSaved?.(savedPatientProfile);
|
|
||||||
if (inline) onClose?.();
|
|
||||||
else onOpenChange?.(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
alert("Paciente cadastrado com sucesso (sem usuário de acesso - email não fornecido).");
|
|
||||||
onSaved?.(savedPatientProfile);
|
|
||||||
setForm(initial);
|
|
||||||
setPhotoPreview(null);
|
|
||||||
setServerAnexos([]);
|
|
||||||
if (inline) onClose?.();
|
|
||||||
else onOpenChange?.(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
}
|
||||||
console.error("❌ Erro no handleSubmit:", err);
|
} catch (err: any) {
|
||||||
// Exibe mensagem amigável ao usuário
|
console.error("❌ Erro no handleSubmit:", err);
|
||||||
const userMessage = err?.message?.includes("toPayload") || err?.message?.includes("is not defined")
|
const userMessage =
|
||||||
|
err?.message?.includes("toPayload") || err?.message?.includes("is not defined")
|
||||||
? "Erro ao processar os dados do formulário. Por favor, verifique os campos e tente novamente."
|
? "Erro ao processar os dados do formulário. Por favor, verifique os campos e tente novamente."
|
||||||
: err?.message || "Erro ao salvar paciente. Por favor, tente novamente.";
|
: err?.message || "Erro ao salvar paciente. Por favor, tente novamente.";
|
||||||
setErrors({ submit: userMessage });
|
setErrors({ submit: userMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function handlePhoto(e: React.ChangeEvent<HTMLInputElement>) {
|
function handlePhoto(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const f = e.target.files?.[0];
|
const f = e.target.files?.[0];
|
||||||
|
|||||||
@ -1,339 +1,408 @@
|
|||||||
'use client'
|
"use client";
|
||||||
import { createContext, useContext, useEffect, useState, ReactNode, useCallback, useMemo, useRef } from 'react'
|
import {
|
||||||
import { useRouter } from 'next/navigation'
|
createContext,
|
||||||
import { loginUser, logoutUser, AuthenticationError } from '@/lib/auth'
|
useContext,
|
||||||
import { getUserInfo } from '@/lib/api'
|
useEffect,
|
||||||
import { ENV_CONFIG } from '@/lib/env-config'
|
useState,
|
||||||
import { isExpired, parseJwt } from '@/lib/jwt'
|
ReactNode,
|
||||||
import { httpClient } from '@/lib/http'
|
useCallback,
|
||||||
import type {
|
useMemo,
|
||||||
AuthContextType,
|
useRef,
|
||||||
UserData,
|
} from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { loginUser, logoutUser, AuthenticationError } from "@/lib/auth";
|
||||||
|
import { getUserInfo } from "@/lib/api";
|
||||||
|
import { ENV_CONFIG } from "@/lib/env-config";
|
||||||
|
import { isExpired, parseJwt } from "@/lib/jwt";
|
||||||
|
import { httpClient } from "@/lib/http";
|
||||||
|
import type {
|
||||||
|
AuthContextType,
|
||||||
|
UserData,
|
||||||
AuthStatus,
|
AuthStatus,
|
||||||
UserType
|
UserType,
|
||||||
} from '@/types/auth'
|
} from "@/types/auth";
|
||||||
import { AUTH_STORAGE_KEYS, LOGIN_ROUTES } from '@/types/auth'
|
import { AUTH_STORAGE_KEYS, LOGIN_ROUTES } from "@/types/auth";
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
const [authStatus, setAuthStatus] = useState<AuthStatus>('loading')
|
const [authStatus, setAuthStatus] = useState<AuthStatus>("loading");
|
||||||
const [user, setUser] = useState<UserData | null>(null)
|
const [user, setUser] = useState<UserData | null>(null);
|
||||||
const [token, setToken] = useState<string | null>(null)
|
const [token, setToken] = useState<string | null>(null);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const hasInitialized = useRef(false)
|
const hasInitialized = useRef(false);
|
||||||
|
|
||||||
// Utilitários de armazenamento memorizados
|
// Utilitários de armazenamento memorizados
|
||||||
const clearAuthData = useCallback(() => {
|
const clearAuthData = useCallback(() => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.removeItem(AUTH_STORAGE_KEYS.TOKEN)
|
localStorage.removeItem(AUTH_STORAGE_KEYS.TOKEN);
|
||||||
localStorage.removeItem(AUTH_STORAGE_KEYS.USER)
|
localStorage.removeItem(AUTH_STORAGE_KEYS.USER);
|
||||||
localStorage.removeItem(AUTH_STORAGE_KEYS.REFRESH_TOKEN)
|
localStorage.removeItem(AUTH_STORAGE_KEYS.REFRESH_TOKEN);
|
||||||
// Manter USER_TYPE para redirecionamento correto
|
// Manter USER_TYPE para redirecionamento correto
|
||||||
}
|
}
|
||||||
setUser(null)
|
setUser(null);
|
||||||
setToken(null)
|
setToken(null);
|
||||||
setAuthStatus('unauthenticated')
|
setAuthStatus("unauthenticated");
|
||||||
console.log('[AUTH] Dados de autenticação limpos - logout realizado')
|
console.log("[AUTH] Dados de autenticação limpos - logout realizado");
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const saveAuthData = useCallback((
|
const saveAuthData = useCallback(
|
||||||
accessToken: string,
|
(accessToken: string, userData: UserData, refreshToken?: string) => {
|
||||||
userData: UserData,
|
try {
|
||||||
refreshToken?: string
|
if (typeof window !== "undefined") {
|
||||||
) => {
|
// Persistir dados de forma atômica
|
||||||
try {
|
localStorage.setItem(AUTH_STORAGE_KEYS.TOKEN, accessToken);
|
||||||
if (typeof window !== 'undefined') {
|
localStorage.setItem(
|
||||||
// Persistir dados de forma atômica
|
AUTH_STORAGE_KEYS.USER,
|
||||||
localStorage.setItem(AUTH_STORAGE_KEYS.TOKEN, accessToken)
|
JSON.stringify(userData)
|
||||||
localStorage.setItem(AUTH_STORAGE_KEYS.USER, JSON.stringify(userData))
|
);
|
||||||
localStorage.setItem(AUTH_STORAGE_KEYS.USER_TYPE, userData.userType)
|
localStorage.setItem(AUTH_STORAGE_KEYS.USER_TYPE, userData.userType);
|
||||||
|
|
||||||
if (refreshToken) {
|
if (refreshToken) {
|
||||||
localStorage.setItem(AUTH_STORAGE_KEYS.REFRESH_TOKEN, refreshToken)
|
localStorage.setItem(AUTH_STORAGE_KEYS.REFRESH_TOKEN, refreshToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setToken(accessToken);
|
||||||
|
setUser(userData);
|
||||||
|
setAuthStatus("authenticated");
|
||||||
|
|
||||||
|
console.log("[AUTH] LOGIN realizado - Dados salvos!", {
|
||||||
|
userType: userData.userType,
|
||||||
|
email: userData.email,
|
||||||
|
timestamp: new Date().toLocaleTimeString(),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[AUTH] Erro ao salvar dados:", error);
|
||||||
|
clearAuthData();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
setToken(accessToken)
|
[clearAuthData]
|
||||||
setUser(userData)
|
);
|
||||||
setAuthStatus('authenticated')
|
|
||||||
|
|
||||||
console.log('[AUTH] LOGIN realizado - Dados salvos!', {
|
|
||||||
userType: userData.userType,
|
|
||||||
email: userData.email,
|
|
||||||
timestamp: new Date().toLocaleTimeString()
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[AUTH] Erro ao salvar dados:', error)
|
|
||||||
clearAuthData()
|
|
||||||
}
|
|
||||||
}, [clearAuthData])
|
|
||||||
|
|
||||||
// Verificação inicial de autenticação
|
// Verificação inicial de autenticação
|
||||||
const checkAuth = useCallback(async (): Promise<void> => {
|
const checkAuth = useCallback(async (): Promise<void> => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === "undefined") {
|
||||||
setAuthStatus('unauthenticated')
|
setAuthStatus("unauthenticated");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const storedToken = localStorage.getItem(AUTH_STORAGE_KEYS.TOKEN)
|
const storedToken = localStorage.getItem(AUTH_STORAGE_KEYS.TOKEN);
|
||||||
const storedUser = localStorage.getItem(AUTH_STORAGE_KEYS.USER)
|
const storedUser = localStorage.getItem(AUTH_STORAGE_KEYS.USER);
|
||||||
|
|
||||||
console.log('[AUTH] Verificando sessão...', {
|
console.log("[AUTH] Verificando sessão...", {
|
||||||
hasToken: !!storedToken,
|
hasToken: !!storedToken,
|
||||||
hasUser: !!storedUser,
|
hasUser: !!storedUser,
|
||||||
timestamp: new Date().toLocaleTimeString()
|
timestamp: new Date().toLocaleTimeString(),
|
||||||
})
|
});
|
||||||
|
|
||||||
// Pequeno delay para visualizar logs
|
// Pequeno delay para visualizar logs
|
||||||
await new Promise(resolve => setTimeout(resolve, 800))
|
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||||
|
|
||||||
if (!storedToken || !storedUser) {
|
if (!storedToken || !storedUser) {
|
||||||
console.log('[AUTH] Dados ausentes - sessão inválida')
|
console.log("[AUTH] Dados ausentes - sessão inválida");
|
||||||
await new Promise(resolve => setTimeout(resolve, 500))
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
clearAuthData()
|
clearAuthData();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar se token está expirado
|
// Verificar se token está expirado
|
||||||
if (isExpired(storedToken)) {
|
if (isExpired(storedToken)) {
|
||||||
console.log('[AUTH] Token expirado - tentando renovar...')
|
console.log("[AUTH] Token expirado - tentando renovar...");
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
const refreshToken = localStorage.getItem(AUTH_STORAGE_KEYS.REFRESH_TOKEN)
|
const refreshToken = localStorage.getItem(
|
||||||
|
AUTH_STORAGE_KEYS.REFRESH_TOKEN
|
||||||
|
);
|
||||||
if (refreshToken && !isExpired(refreshToken)) {
|
if (refreshToken && !isExpired(refreshToken)) {
|
||||||
// Tentar renovar via HTTP client (que já tem a lógica)
|
// Tentar renovar via HTTP client (que já tem a lógica)
|
||||||
try {
|
try {
|
||||||
await httpClient.get('/auth/v1/me') // Trigger refresh se necessário
|
await httpClient.get("/auth/v1/me"); // Trigger refresh se necessário
|
||||||
|
|
||||||
// Se chegou aqui, refresh foi bem-sucedido
|
// Se chegou aqui, refresh foi bem-sucedido
|
||||||
const newToken = localStorage.getItem(AUTH_STORAGE_KEYS.TOKEN)
|
const newToken = localStorage.getItem(AUTH_STORAGE_KEYS.TOKEN);
|
||||||
const userData = JSON.parse(storedUser) as UserData
|
const userData = JSON.parse(storedUser) as UserData;
|
||||||
|
|
||||||
if (newToken && newToken !== storedToken) {
|
if (newToken && newToken !== storedToken) {
|
||||||
setToken(newToken)
|
setToken(newToken);
|
||||||
setUser(userData)
|
setUser(userData);
|
||||||
setAuthStatus('authenticated')
|
setAuthStatus("authenticated");
|
||||||
console.log('[AUTH] Token RENOVADO automaticamente!')
|
console.log("[AUTH] Token RENOVADO automaticamente!");
|
||||||
await new Promise(resolve => setTimeout(resolve, 800))
|
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
console.log(' [AUTH] Falha no refresh automático')
|
console.log(" [AUTH] Falha no refresh automático");
|
||||||
await new Promise(resolve => setTimeout(resolve, 400))
|
await new Promise((resolve) => setTimeout(resolve, 400));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAuthData()
|
clearAuthData();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restaurar sessão válida
|
// Restaurar sessão válida
|
||||||
const userData = JSON.parse(storedUser) as UserData
|
const userData = JSON.parse(storedUser) as UserData;
|
||||||
setToken(storedToken)
|
setToken(storedToken);
|
||||||
// Tentar buscar profile consolidado (user-info) e mesclar
|
// Tentar buscar profile consolidado (user-info) e mesclar
|
||||||
try {
|
try {
|
||||||
const info = await getUserInfo()
|
const info = await getUserInfo();
|
||||||
if (info?.profile) {
|
if (info?.profile) {
|
||||||
const mapped = {
|
const mapped = {
|
||||||
cpf: (info.profile as any).cpf ?? userData.profile?.cpf,
|
cpf: (info.profile as any).cpf ?? userData.profile?.cpf,
|
||||||
crm: (info.profile as any).crm ?? userData.profile?.crm,
|
crm: (info.profile as any).crm ?? userData.profile?.crm,
|
||||||
telefone: info.profile.phone ?? userData.profile?.telefone,
|
telefone: info.profile.phone ?? userData.profile?.telefone,
|
||||||
foto_url: info.profile.avatar_url ?? userData.profile?.foto_url,
|
foto_url: info.profile.avatar_url ?? userData.profile?.foto_url,
|
||||||
}
|
};
|
||||||
if (userData.profile) {
|
if (userData.profile) {
|
||||||
userData.profile = { ...userData.profile, ...mapped }
|
userData.profile = { ...userData.profile, ...mapped };
|
||||||
} else {
|
} else {
|
||||||
userData.profile = mapped
|
userData.profile = mapped;
|
||||||
}
|
}
|
||||||
// Persistir o usuário atualizado no localStorage para evitar
|
// Persistir o usuário atualizado no localStorage para evitar
|
||||||
// que 'auth_user.profile' fique vazio após um reload completo
|
// que 'auth_user.profile' fique vazio após um reload completo
|
||||||
try {
|
try {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.setItem(AUTH_STORAGE_KEYS.USER, JSON.stringify(userData))
|
localStorage.setItem(
|
||||||
|
AUTH_STORAGE_KEYS.USER,
|
||||||
|
JSON.stringify(userData)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[AUTH] Falha ao persistir user (profile) no localStorage:', e)
|
console.warn(
|
||||||
|
"[AUTH] Falha ao persistir user (profile) no localStorage:",
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[AUTH] Falha ao buscar user-info na restauração de sessão:', err)
|
console.warn(
|
||||||
|
"[AUTH] Falha ao buscar user-info na restauração de sessão:",
|
||||||
|
err
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setUser(userData)
|
setUser(userData);
|
||||||
setAuthStatus('authenticated')
|
setAuthStatus("authenticated");
|
||||||
|
|
||||||
console.log('[AUTH] Sessão RESTAURADA com sucesso!', {
|
console.log("[AUTH] Sessão RESTAURADA com sucesso!", {
|
||||||
userId: userData.id,
|
userId: userData.id,
|
||||||
userType: userData.userType,
|
userType: userData.userType,
|
||||||
email: userData.email,
|
email: userData.email,
|
||||||
timestamp: new Date().toLocaleTimeString()
|
timestamp: new Date().toLocaleTimeString(),
|
||||||
})
|
});
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AUTH] Erro na verificação:', error)
|
console.error("[AUTH] Erro na verificação:", error);
|
||||||
clearAuthData()
|
clearAuthData();
|
||||||
}
|
}
|
||||||
}, [clearAuthData])
|
}, [clearAuthData]);
|
||||||
|
|
||||||
// Login memoizado
|
// Login memoizado
|
||||||
const login = useCallback(async (
|
const login = useCallback(
|
||||||
email: string,
|
async (
|
||||||
password: string,
|
email: string,
|
||||||
userType: UserType
|
password: string,
|
||||||
): Promise<boolean> => {
|
userType: UserType
|
||||||
try {
|
): Promise<boolean> => {
|
||||||
console.log('[AUTH] Iniciando login:', { email, userType })
|
|
||||||
|
|
||||||
const response = await loginUser(email, password, userType)
|
|
||||||
|
|
||||||
// Após receber token, buscar roles/permissions reais e reconciliar userType
|
|
||||||
try {
|
try {
|
||||||
const infoRes = await fetch(`${ENV_CONFIG.SUPABASE_URL}/functions/v1/user-info`, {
|
console.log("[AUTH] Iniciando login:", { email, userType });
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Authorization': `Bearer ${response.access_token}`,
|
|
||||||
'apikey': ENV_CONFIG.SUPABASE_ANON_KEY,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (infoRes.ok) {
|
const response = await loginUser(email, password, userType);
|
||||||
const info = await infoRes.json().catch(() => null)
|
|
||||||
const roles: string[] = Array.isArray(info?.roles) ? info.roles : (info?.roles ? [info.roles] : [])
|
|
||||||
|
|
||||||
// Derivar tipo de usuário a partir dos roles
|
// Após receber token, buscar roles/permissions reais e reconciliar userType
|
||||||
let derived: UserType = 'paciente'
|
try {
|
||||||
if (roles.includes('admin') || roles.includes('gestor') || roles.includes('secretaria')) {
|
const infoRes = await fetch(
|
||||||
derived = 'administrador'
|
`${ENV_CONFIG.SUPABASE_URL}/functions/v1/user-info`,
|
||||||
} else if (roles.includes('medico') || roles.includes('enfermeiro')) {
|
{
|
||||||
derived = 'profissional'
|
method: "GET",
|
||||||
}
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
Authorization: `Bearer ${response.access_token}`,
|
||||||
|
apikey: ENV_CONFIG.SUPABASE_ANON_KEY,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Atualizar userType caso seja diferente
|
if (infoRes.ok) {
|
||||||
if (response.user && response.user.userType !== derived) {
|
const info = await infoRes.json().catch(() => null);
|
||||||
response.user.userType = derived
|
const roles: string[] = Array.isArray(info?.roles)
|
||||||
console.log('[AUTH] userType reconciled from roles ->', derived)
|
? info.roles
|
||||||
}
|
: info?.roles
|
||||||
} else {
|
? [info.roles]
|
||||||
console.warn('[AUTH] Falha ao obter user-info para reconciliar roles:', infoRes.status)
|
: [];
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('[AUTH] Erro ao buscar user-info após login (não crítico):', err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Após login, tentar buscar profile consolidado e mesclar antes de persistir
|
// Derivar tipo de usuário a partir dos roles
|
||||||
try {
|
// Derivar tipo de usuário a partir dos roles retornados pela API
|
||||||
const info = await getUserInfo()
|
let derived: UserType = userType; // 🔹 começa respeitando o tipo da tela de login
|
||||||
if (info?.profile && response.user) {
|
|
||||||
const mapped = {
|
if (
|
||||||
cpf: (info.profile as any).cpf ?? response.user.profile?.cpf,
|
roles.includes("admin") ||
|
||||||
crm: (info.profile as any).crm ?? response.user.profile?.crm,
|
roles.includes("gestor") ||
|
||||||
telefone: info.profile.phone ?? response.user.profile?.telefone,
|
roles.includes("secretaria")
|
||||||
foto_url: info.profile.avatar_url ?? response.user.profile?.foto_url,
|
) {
|
||||||
}
|
derived = "administrador";
|
||||||
if (response.user.profile) {
|
} else if (
|
||||||
response.user.profile = { ...response.user.profile, ...mapped }
|
roles.includes("medico") ||
|
||||||
|
roles.includes("enfermeiro")
|
||||||
|
) {
|
||||||
|
derived = "profissional";
|
||||||
|
} else if (roles.length === 0 && userType) {
|
||||||
|
// 🔹 se a API não retornou roles, mantemos o tipo informado pela tela
|
||||||
|
derived = userType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 Se o tipo da tela e o das roles divergirem, prioriza o da tela (exceto admin)
|
||||||
|
if (
|
||||||
|
response.user &&
|
||||||
|
response.user.userType !== derived &&
|
||||||
|
userType !== "administrador"
|
||||||
|
) {
|
||||||
|
response.user.userType = userType;
|
||||||
|
console.log(
|
||||||
|
"[AUTH] userType mantido da tela de login ->",
|
||||||
|
userType
|
||||||
|
);
|
||||||
|
} else if (response.user && response.user.userType !== derived) {
|
||||||
|
response.user.userType = derived;
|
||||||
|
console.log(
|
||||||
|
"[AUTH] userType reconciliado a partir das roles ->",
|
||||||
|
derived
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
response.user.profile = mapped
|
console.warn(
|
||||||
|
"[AUTH] Falha ao obter user-info para reconciliar roles:",
|
||||||
|
infoRes.status
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(
|
||||||
|
"[AUTH] Erro ao buscar user-info após login (não crítico):",
|
||||||
|
err
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.warn('[AUTH] Falha ao buscar user-info após login (não crítico):', err)
|
// Após login, tentar buscar profile consolidado e mesclar antes de persistir
|
||||||
|
try {
|
||||||
|
const info = await getUserInfo();
|
||||||
|
if (info?.profile && response.user) {
|
||||||
|
const mapped = {
|
||||||
|
cpf: (info.profile as any).cpf ?? response.user.profile?.cpf,
|
||||||
|
crm: (info.profile as any).crm ?? response.user.profile?.crm,
|
||||||
|
telefone: info.profile.phone ?? response.user.profile?.telefone,
|
||||||
|
foto_url:
|
||||||
|
info.profile.avatar_url ?? response.user.profile?.foto_url,
|
||||||
|
};
|
||||||
|
if (response.user.profile) {
|
||||||
|
response.user.profile = { ...response.user.profile, ...mapped };
|
||||||
|
} else {
|
||||||
|
response.user.profile = mapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(
|
||||||
|
"[AUTH] Falha ao buscar user-info após login (não crítico):",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAuthData(
|
||||||
|
response.access_token,
|
||||||
|
response.user,
|
||||||
|
response.refresh_token
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("[AUTH] Login realizado com sucesso");
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[AUTH] Erro no login:", error);
|
||||||
|
|
||||||
|
if (error instanceof AuthenticationError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AuthenticationError(
|
||||||
|
"Erro inesperado durante o login",
|
||||||
|
"UNKNOWN_ERROR",
|
||||||
|
error
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
saveAuthData(
|
[saveAuthData]
|
||||||
response.access_token,
|
);
|
||||||
response.user,
|
|
||||||
response.refresh_token
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log('[AUTH] Login realizado com sucesso')
|
|
||||||
return true
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[AUTH] Erro no login:', error)
|
|
||||||
|
|
||||||
if (error instanceof AuthenticationError) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AuthenticationError(
|
|
||||||
'Erro inesperado durante o login',
|
|
||||||
'UNKNOWN_ERROR',
|
|
||||||
error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, [saveAuthData])
|
|
||||||
|
|
||||||
// Logout memoizado
|
// Logout memoizado
|
||||||
const logout = useCallback(async (): Promise<void> => {
|
const logout = useCallback(async (): Promise<void> => {
|
||||||
console.log('[AUTH] Iniciando logout')
|
console.log("[AUTH] Iniciando logout");
|
||||||
|
|
||||||
const currentUserType = user?.userType ||
|
const currentUserType =
|
||||||
(typeof window !== 'undefined' ? localStorage.getItem(AUTH_STORAGE_KEYS.USER_TYPE) : null) ||
|
user?.userType ||
|
||||||
'profissional'
|
(typeof window !== "undefined"
|
||||||
|
? localStorage.getItem(AUTH_STORAGE_KEYS.USER_TYPE)
|
||||||
|
: null) ||
|
||||||
|
"profissional";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (token) {
|
if (token) {
|
||||||
await logoutUser(token)
|
await logoutUser(token);
|
||||||
console.log('[AUTH] Logout realizado na API')
|
console.log("[AUTH] Logout realizado na API");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AUTH] Erro no logout da API:', error)
|
console.error("[AUTH] Erro no logout da API:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAuthData()
|
clearAuthData();
|
||||||
|
|
||||||
// Redirecionamento baseado no tipo de usuário
|
// Redirecionamento baseado no tipo de usuário
|
||||||
const loginRoute = LOGIN_ROUTES[currentUserType as UserType] || '/login'
|
const loginRoute = LOGIN_ROUTES[currentUserType as UserType] || "/login";
|
||||||
|
|
||||||
console.log('[AUTH] Redirecionando para:', loginRoute)
|
console.log("[AUTH] Redirecionando para:", loginRoute);
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
window.location.href = loginRoute
|
window.location.href = loginRoute;
|
||||||
}
|
}
|
||||||
}, [user?.userType, token, clearAuthData])
|
}, [user?.userType, token, clearAuthData]);
|
||||||
|
|
||||||
// Refresh token memoizado (usado pelo HTTP client)
|
// Refresh token memoizado (usado pelo HTTP client)
|
||||||
const refreshToken = useCallback(async (): Promise<boolean> => {
|
const refreshToken = useCallback(async (): Promise<boolean> => {
|
||||||
// Esta função é principalmente para compatibilidade
|
// Esta função é principalmente para compatibilidade
|
||||||
// O refresh real é feito pelo HTTP client
|
// O refresh real é feito pelo HTTP client
|
||||||
return false
|
return false;
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
// Getters memorizados
|
// Getters memorizados
|
||||||
const contextValue = useMemo(() => ({
|
const contextValue = useMemo(
|
||||||
authStatus,
|
() => ({
|
||||||
user,
|
authStatus,
|
||||||
token,
|
user,
|
||||||
login,
|
token,
|
||||||
logout,
|
login,
|
||||||
refreshToken
|
logout,
|
||||||
}), [authStatus, user, token, login, logout, refreshToken])
|
refreshToken,
|
||||||
|
}),
|
||||||
|
[authStatus, user, token, login, logout, refreshToken]
|
||||||
|
);
|
||||||
|
|
||||||
// Inicialização única
|
// Inicialização única
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hasInitialized.current && typeof window !== 'undefined') {
|
if (!hasInitialized.current && typeof window !== "undefined") {
|
||||||
hasInitialized.current = true
|
hasInitialized.current = true;
|
||||||
checkAuth()
|
checkAuth();
|
||||||
}
|
}
|
||||||
}, [checkAuth])
|
}, [checkAuth]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={contextValue}>
|
<AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
|
||||||
{children}
|
);
|
||||||
</AuthContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const context = useContext(AuthContext)
|
const context = useContext(AuthContext);
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
throw new Error('useAuth deve ser usado dentro de AuthProvider')
|
throw new Error("useAuth deve ser usado dentro de AuthProvider");
|
||||||
}
|
}
|
||||||
return context
|
return context;
|
||||||
}
|
};
|
||||||
|
|||||||
@ -220,82 +220,115 @@ async function fetchWithFallback<T = any>(url: string, headers: Record<string, s
|
|||||||
// Parse genérico
|
// Parse genérico
|
||||||
async function parse<T>(res: Response): Promise<T> {
|
async function parse<T>(res: Response): Promise<T> {
|
||||||
let json: any = null;
|
let json: any = null;
|
||||||
|
let rawText: string | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
json = await res.json();
|
// ✅ Lê o corpo como texto e só faz parse se tiver conteúdo
|
||||||
|
rawText = await res.text();
|
||||||
|
json = rawText ? JSON.parse(rawText) : null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Erro ao parsear a resposta como JSON:", err);
|
console.error("Erro ao parsear a resposta como JSON (provável corpo vazio):", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔍 Se a resposta não for OK, trata como erro
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
// Tenta também ler o body como texto cru para obter mensagens detalhadas
|
// 🔁 Clona a resposta para tentar ler novamente caso o body já tenha sido consumido
|
||||||
let rawText = '';
|
let rawCloneText = rawText;
|
||||||
try {
|
if (!rawCloneText) {
|
||||||
rawText = await res.clone().text();
|
try {
|
||||||
} catch (tErr) {
|
rawCloneText = await res.clone().text();
|
||||||
// ignore
|
} catch {
|
||||||
|
rawCloneText = "(falha ao ler body)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.error("[API ERROR]", res.url, res.status, json, "raw:", rawText);
|
|
||||||
|
// 🔎 Log completo com fallback seguro
|
||||||
|
console.error("[API ERROR]", res.url, res.status, json ?? {}, "raw:", rawCloneText ?? "(vazio)");
|
||||||
|
|
||||||
const code = (json && (json.error?.code || json.code)) ?? res.status;
|
const code = (json && (json.error?.code || json.code)) ?? res.status;
|
||||||
const msg = (json && (json.error?.message || json.message || json.error)) ?? res.statusText;
|
const msg =
|
||||||
|
(json && (json.error?.message || json.message || json.error)) ??
|
||||||
// Mensagens amigáveis para erros comuns
|
res.statusText;
|
||||||
|
|
||||||
|
// 🧠 Mensagens amigáveis e específicas
|
||||||
let friendlyMessage = msg;
|
let friendlyMessage = msg;
|
||||||
|
|
||||||
// Erros de criação de usuário
|
// 🔹 Erros de criação de usuário
|
||||||
if (res.url?.includes('create-user')) {
|
if (res.url?.includes("create-user")) {
|
||||||
if (msg?.includes('Failed to assign user role')) {
|
if (msg?.includes("Failed to assign user role")) {
|
||||||
friendlyMessage = 'O usuário foi criado mas houve falha ao atribuir permissões. Entre em contato com o administrador do sistema para verificar as configurações da Edge Function.';
|
friendlyMessage =
|
||||||
} else if (msg?.includes('already registered')) {
|
"O usuário foi criado mas houve falha ao atribuir permissões. Entre em contato com o administrador do sistema.";
|
||||||
friendlyMessage = 'Este email já está cadastrado no sistema.';
|
} else if (msg?.includes("already registered")) {
|
||||||
} else if (msg?.includes('Invalid role')) {
|
friendlyMessage = "Este email já está cadastrado no sistema.";
|
||||||
friendlyMessage = 'Tipo de acesso inválido.';
|
} else if (msg?.includes("Invalid role")) {
|
||||||
} else if (msg?.includes('Missing required fields')) {
|
friendlyMessage = "Tipo de acesso inválido.";
|
||||||
friendlyMessage = 'Campos obrigatórios não preenchidos.';
|
} else if (msg?.includes("Missing required fields")) {
|
||||||
|
friendlyMessage = "Campos obrigatórios não preenchidos.";
|
||||||
} else if (res.status === 401) {
|
} else if (res.status === 401) {
|
||||||
friendlyMessage = 'Você não está autenticado. Faça login novamente.';
|
friendlyMessage = "Você não está autenticado. Faça login novamente.";
|
||||||
} else if (res.status === 403) {
|
} else if (res.status === 403) {
|
||||||
friendlyMessage = 'Você não tem permissão para criar usuários.';
|
friendlyMessage = "Você não tem permissão para criar usuários.";
|
||||||
} else if (res.status === 500) {
|
} else if (res.status === 500) {
|
||||||
friendlyMessage = 'Erro no servidor ao criar usuário. Entre em contato com o suporte.';
|
friendlyMessage =
|
||||||
|
"Erro no servidor ao criar usuário. Entre em contato com o suporte.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Erro de CPF duplicado
|
|
||||||
else if (code === '23505' && msg.includes('patients_cpf_key')) {
|
// 🔹 Erro de CPF duplicado
|
||||||
friendlyMessage = 'Já existe um paciente cadastrado com este CPF. Por favor, verifique se o paciente já está registrado no sistema ou use um CPF diferente.';
|
else if (code === "23505" && msg.includes("patients_cpf_key")) {
|
||||||
|
friendlyMessage =
|
||||||
|
"Já existe um paciente cadastrado com este CPF. Verifique se o paciente já está no sistema.";
|
||||||
}
|
}
|
||||||
// Erro de email duplicado (paciente)
|
|
||||||
else if (code === '23505' && msg.includes('patients_email_key')) {
|
// 🔹 Erro de email duplicado (paciente)
|
||||||
friendlyMessage = 'Já existe um paciente cadastrado com este email. Por favor, use um email diferente.';
|
else if (code === "23505" && msg.includes("patients_email_key")) {
|
||||||
|
friendlyMessage =
|
||||||
|
"Já existe um paciente cadastrado com este email. Use outro email.";
|
||||||
}
|
}
|
||||||
// Erro de CRM duplicado (médico)
|
|
||||||
else if (code === '23505' && msg.includes('doctors_crm')) {
|
// 🔹 Erro de CRM duplicado (médico)
|
||||||
friendlyMessage = 'Já existe um médico cadastrado com este CRM. Por favor, verifique se o médico já está registrado no sistema.';
|
else if (code === "23505" && msg.includes("doctors_crm")) {
|
||||||
|
friendlyMessage =
|
||||||
|
"Já existe um médico cadastrado com este CRM. Verifique se o médico já está no sistema.";
|
||||||
}
|
}
|
||||||
// Erro de email duplicado (médico)
|
|
||||||
else if (code === '23505' && msg.includes('doctors_email_key')) {
|
// 🔹 Erro de email duplicado (médico)
|
||||||
friendlyMessage = 'Já existe um médico cadastrado com este email. Por favor, use um email diferente.';
|
else if (code === "23505" && msg.includes("doctors_email_key")) {
|
||||||
|
friendlyMessage =
|
||||||
|
"Já existe um médico cadastrado com este email. Use outro email.";
|
||||||
}
|
}
|
||||||
// Outros erros de constraint unique
|
|
||||||
else if (code === '23505') {
|
// 🔹 Outros erros de constraint UNIQUE
|
||||||
friendlyMessage = 'Registro duplicado: já existe um cadastro com essas informações no sistema.';
|
else if (code === "23505") {
|
||||||
|
friendlyMessage =
|
||||||
|
"Registro duplicado: já existe um cadastro com essas informações.";
|
||||||
}
|
}
|
||||||
// Erro de foreign key (registro referenciado em outra tabela)
|
|
||||||
else if (code === '23503') {
|
// 🔹 Foreign key violation (registro vinculado)
|
||||||
// Mensagem específica para pacientes com relatórios vinculados
|
else if (code === "23503") {
|
||||||
if (msg && msg.toString().toLowerCase().includes('reports')) {
|
if (msg && msg.toString().toLowerCase().includes("reports")) {
|
||||||
friendlyMessage = 'Não é possível excluir este paciente porque existem relatórios vinculados a ele. Exclua ou desvincule os relatórios antes de remover o paciente.';
|
friendlyMessage =
|
||||||
|
"Não é possível excluir este paciente porque existem relatórios vinculados.";
|
||||||
} else {
|
} else {
|
||||||
friendlyMessage = 'Registro referenciado em outra tabela. Remova referências dependentes antes de tentar novamente.';
|
friendlyMessage =
|
||||||
|
"Registro vinculado em outra tabela. Remova dependências antes de tentar novamente.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔹 Caso genérico
|
||||||
|
if (!friendlyMessage || typeof friendlyMessage !== "string") {
|
||||||
|
friendlyMessage = "Erro ao processar requisição.";
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(friendlyMessage);
|
throw new Error(friendlyMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ Retorno padronizado (json.data ou json)
|
||||||
return (json?.data ?? json) as T;
|
return (json?.data ?? json) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Helper de paginação (Range/Range-Unit)
|
// Helper de paginação (Range/Range-Unit)
|
||||||
function rangeHeaders(page?: number, limit?: number): Record<string, string> {
|
function rangeHeaders(page?: number, limit?: number): Record<string, string> {
|
||||||
if (!page || !limit) return {};
|
if (!page || !limit) return {};
|
||||||
@ -565,15 +598,29 @@ export async function buscarPacientesPorIds(ids: Array<string | number>): Promis
|
|||||||
|
|
||||||
export async function criarPaciente(input: PacienteInput): Promise<Paciente> {
|
export async function criarPaciente(input: PacienteInput): Promise<Paciente> {
|
||||||
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients`;
|
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients`;
|
||||||
|
|
||||||
|
// ✅ Garante que campos obrigatórios tenham valores padrão
|
||||||
|
const safeInput = {
|
||||||
|
...input,
|
||||||
|
phone_mobile: input.phone_mobile?.trim() || "000000000", // evita null
|
||||||
|
email: input.email?.trim() || null, // limpa espaços
|
||||||
|
full_name: input.full_name?.trim(), // remove espaços extras
|
||||||
|
};
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"),
|
headers: withPrefer(
|
||||||
body: JSON.stringify(input),
|
{ ...baseHeaders(), "Content-Type": "application/json" },
|
||||||
|
"return=representation"
|
||||||
|
),
|
||||||
|
body: JSON.stringify(safeInput),
|
||||||
});
|
});
|
||||||
|
|
||||||
const arr = await parse<Paciente[] | Paciente>(res);
|
const arr = await parse<Paciente[] | Paciente>(res);
|
||||||
return Array.isArray(arr) ? arr[0] : (arr as Paciente);
|
return Array.isArray(arr) ? arr[0] : (arr as Paciente);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function atualizarPaciente(id: string | number, input: PacienteInput): Promise<Paciente> {
|
export async function atualizarPaciente(id: string | number, input: PacienteInput): Promise<Paciente> {
|
||||||
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?id=eq.${id}`;
|
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?id=eq.${id}`;
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
@ -950,12 +997,19 @@ export async function vincularUserIdMedico(medicoId: string | number, userId: st
|
|||||||
* Vincula um user_id (auth user id) a um registro de paciente existente.
|
* Vincula um user_id (auth user id) a um registro de paciente existente.
|
||||||
* Retorna o paciente atualizado.
|
* Retorna o paciente atualizado.
|
||||||
*/
|
*/
|
||||||
export async function vincularUserIdPaciente(pacienteId: string | number, userId: string): Promise<Paciente> {
|
export async function vincularPacientePorEmail(
|
||||||
|
pacienteId: string | number,
|
||||||
|
email: string
|
||||||
|
): Promise<Paciente> {
|
||||||
|
console.log("[vincularPacientePorEmail] Associando paciente via email:", { pacienteId, email });
|
||||||
const url = `${REST}/patients?id=eq.${encodeURIComponent(String(pacienteId))}`;
|
const url = `${REST}/patients?id=eq.${encodeURIComponent(String(pacienteId))}`;
|
||||||
const payload = { user_id: String(userId) };
|
const payload = { email };
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method: 'PATCH',
|
method: "PATCH",
|
||||||
headers: withPrefer({ ...baseHeaders(), 'Content-Type': 'application/json' }, 'return=representation'),
|
headers: withPrefer(
|
||||||
|
{ ...baseHeaders(), "Content-Type": "application/json" },
|
||||||
|
"return=representation"
|
||||||
|
),
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
const arr = await parse<Paciente[] | Paciente>(res);
|
const arr = await parse<Paciente[] | Paciente>(res);
|
||||||
@ -965,6 +1019,7 @@ export async function vincularUserIdPaciente(pacienteId: string | number, userId
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function atualizarMedico(id: string | number, input: MedicoInput): Promise<Medico> {
|
export async function atualizarMedico(id: string | number, input: MedicoInput): Promise<Medico> {
|
||||||
console.log(`Tentando atualizar médico ID: ${id}`);
|
console.log(`Tentando atualizar médico ID: ${id}`);
|
||||||
console.log(`Payload original:`, input);
|
console.log(`Payload original:`, input);
|
||||||
@ -1399,30 +1454,27 @@ export async function criarUsuarioPaciente(paciente: {
|
|||||||
full_name: string;
|
full_name: string;
|
||||||
phone_mobile: string;
|
phone_mobile: string;
|
||||||
}): Promise<CreateUserWithPasswordResponse> {
|
}): Promise<CreateUserWithPasswordResponse> {
|
||||||
|
|
||||||
const senha = gerarSenhaAleatoria();
|
const senha = gerarSenhaAleatoria();
|
||||||
|
|
||||||
console.log('[CRIAR PACIENTE] Iniciando criação no Supabase Auth...');
|
console.log("🩺 [CRIAR PACIENTE] Iniciando criação no Supabase Auth...");
|
||||||
console.log('Email:', paciente.email);
|
console.log("Email:", paciente.email);
|
||||||
console.log('Nome:', paciente.full_name);
|
console.log("Nome:", paciente.full_name);
|
||||||
console.log('Telefone:', paciente.phone_mobile);
|
console.log("Telefone:", paciente.phone_mobile);
|
||||||
console.log('Senha gerada:', senha);
|
console.log("Senha gerada:", senha);
|
||||||
|
|
||||||
// Endpoint do Supabase Auth (mesmo que auth.ts usa)
|
|
||||||
const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`;
|
const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`;
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
email: paciente.email,
|
email: paciente.email.trim(),
|
||||||
password: senha,
|
password: senha,
|
||||||
data: {
|
data: {
|
||||||
userType: 'paciente', // Para login em /login-paciente -> /paciente
|
userType: "paciente",
|
||||||
full_name: paciente.full_name,
|
full_name: paciente.full_name.trim(),
|
||||||
phone: paciente.phone_mobile,
|
phone: paciente.phone_mobile?.trim() || "",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[CRIAR PACIENTE] Enviando para:', signupUrl);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(signupUrl, {
|
const response = await fetch(signupUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -1433,125 +1485,76 @@ export async function criarUsuarioPaciente(paciente: {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[CRIAR PACIENTE] Status da resposta:', response.status, response.statusText);
|
console.log("[CRIAR PACIENTE] Status:", response.status, response.statusText);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
console.error('[CRIAR PACIENTE] Erro na resposta:', errorText);
|
console.error("[CRIAR PACIENTE] Erro na resposta:", errorText);
|
||||||
|
|
||||||
// Tenta parsear o erro para pegar mensagem específica
|
|
||||||
let errorMsg = `Erro ao criar usuário (${response.status})`;
|
let errorMsg = `Erro ao criar usuário (${response.status})`;
|
||||||
try {
|
try {
|
||||||
const errorData = JSON.parse(errorText);
|
const errData = JSON.parse(errorText);
|
||||||
errorMsg = errorData.msg || errorData.message || errorData.error_description || errorMsg;
|
errorMsg =
|
||||||
|
errData.msg ||
|
||||||
// Mensagens amigáveis para erros comuns
|
errData.message ||
|
||||||
if (errorMsg.includes('already registered') || errorMsg.includes('already exists')) {
|
errData.error_description ||
|
||||||
errorMsg = 'Este email já está cadastrado no sistema';
|
errData.error ||
|
||||||
} else if (errorMsg.includes('invalid email')) {
|
errorMsg;
|
||||||
errorMsg = 'Formato de email inválido';
|
|
||||||
} else if (errorMsg.includes('weak password')) {
|
if (errorMsg.includes("already registered") || errorMsg.includes("already exists")) {
|
||||||
errorMsg = 'Senha muito fraca';
|
errorMsg = "Este email já está cadastrado no sistema.";
|
||||||
|
} else if (errorMsg.includes("invalid email")) {
|
||||||
|
errorMsg = "Formato de email inválido.";
|
||||||
|
} else if (errorMsg.includes("weak password")) {
|
||||||
|
errorMsg = "Senha muito fraca.";
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Se não conseguir parsear, usa mensagem genérica
|
// ignora erro de parse
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(errorMsg);
|
throw new Error(errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
console.log('[CRIAR PACIENTE] Usuário criado com sucesso no Supabase Auth!');
|
|
||||||
console.log('User ID:', responseData.user?.id || responseData.id);
|
|
||||||
console.log('[CRIAR PACIENTE] Resposta completa do Supabase:', JSON.stringify(responseData, null, 2));
|
|
||||||
|
|
||||||
// VERIFICAÇÃO CRÍTICA: O usuário foi realmente criado?
|
|
||||||
if (!responseData.user && !responseData.id) {
|
|
||||||
console.error('AVISO: Supabase retornou sucesso mas sem user ID!');
|
|
||||||
console.error('Isso pode significar que o usuário não foi criado de verdade!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const userId = responseData.user?.id || responseData.id;
|
const userId = responseData.user?.id || responseData.id;
|
||||||
|
|
||||||
// 🔧 AUTO-CONFIRMAR EMAIL: Fazer login automático logo após criar usuário
|
console.log("✅ [CRIAR PACIENTE] Usuário criado com sucesso!");
|
||||||
// Isso força o Supabase a confirmar o email automaticamente
|
console.log("User ID:", userId);
|
||||||
if (responseData.user?.email_confirmed_at === null || !responseData.user?.email_confirmed_at) {
|
console.log("Resposta completa:", JSON.stringify(responseData, null, 2));
|
||||||
console.warn('[CRIAR PACIENTE] Email NÃO confirmado - tentando auto-confirmar via login...');
|
|
||||||
|
// 🧩 Corrige: evita invalid_credentials
|
||||||
try {
|
// O Supabase pode demorar alguns segundos para permitir login (até confirmar email)
|
||||||
const loginUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/token?grant_type=password`;
|
if (!responseData.user?.email_confirmed_at) {
|
||||||
console.log('[AUTO-CONFIRMAR] Fazendo login automático para confirmar email...');
|
console.warn(
|
||||||
|
"[CRIAR PACIENTE] Email ainda não confirmado — aguardando confirmação automática do Supabase."
|
||||||
const loginResponse = await fetch(loginUrl, {
|
);
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'apikey': ENV_CONFIG.SUPABASE_ANON_KEY,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: paciente.email,
|
|
||||||
password: senha,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('[AUTO-CONFIRMAR] Status do login automático:', loginResponse.status);
|
|
||||||
|
|
||||||
if (loginResponse.ok) {
|
|
||||||
const loginData = await loginResponse.json();
|
|
||||||
console.log('[AUTO-CONFIRMAR] Login automático realizado com sucesso!');
|
|
||||||
console.log('[AUTO-CONFIRMAR] Dados completos do login:', JSON.stringify(loginData, undefined, 2));
|
|
||||||
console.log('[AUTO-CONFIRMAR] Email confirmado:', loginData.user?.email_confirmed_at ? 'SIM' : 'NÃO');
|
|
||||||
console.log('[AUTO-CONFIRMAR] UserType no metadata:', loginData.user?.user_metadata?.userType);
|
|
||||||
console.log('[AUTO-CONFIRMAR] Email verified:', loginData.user?.user_metadata?.email_verified);
|
|
||||||
|
|
||||||
// Atualizar responseData com dados do login (que tem email confirmado)
|
|
||||||
if (loginData.user) {
|
|
||||||
responseData.user = loginData.user;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const errorText = await loginResponse.text();
|
|
||||||
console.error('[AUTO-CONFIRMAR] Falha no login automático:', loginResponse.status, errorText);
|
|
||||||
console.warn('[AUTO-CONFIRMAR] Usuário pode não conseguir fazer login imediatamente!');
|
|
||||||
|
|
||||||
// Tentar parsear o erro para entender melhor
|
|
||||||
try {
|
|
||||||
const errorData = JSON.parse(errorText);
|
|
||||||
console.error('[AUTO-CONFIRMAR] Detalhes do erro:', errorData);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[AUTO-CONFIRMAR] Erro não é JSON:', errorText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (confirmError) {
|
|
||||||
console.error('[AUTO-CONFIRMAR] Erro ao tentar fazer login automático:', confirmError);
|
|
||||||
console.warn('[AUTO-CONFIRMAR] Continuando sem confirmação automática...');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('[CRIAR PACIENTE] Email confirmado automaticamente!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log bem visível com as credenciais para teste
|
console.log("========================================");
|
||||||
console.log('========================================');
|
console.log("CREDENCIAIS DO PACIENTE CRIADO:");
|
||||||
console.log('CREDENCIAIS DO PACIENTE CRIADO:');
|
console.log("Email:", paciente.email);
|
||||||
console.log('Email:', paciente.email);
|
console.log("Senha:", senha);
|
||||||
console.log('Senha:', senha);
|
console.log("UserType:", "paciente");
|
||||||
console.log('UserType:', 'paciente');
|
console.log(
|
||||||
console.log('Pode fazer login?', responseData.user?.email_confirmed_at ? 'SIM' : 'NÃO (precisa confirmar email)');
|
"Pode fazer login?",
|
||||||
console.log('========================================');
|
responseData.user?.email_confirmed_at ? "SIM" : "NÃO (aguardando confirmação)"
|
||||||
|
);
|
||||||
|
console.log("========================================");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
user: responseData.user || responseData,
|
user: responseData.user || responseData,
|
||||||
email: paciente.email,
|
email: paciente.email,
|
||||||
password: senha,
|
password: senha,
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[CRIAR PACIENTE] Erro ao criar usuário:', error);
|
console.error("[CRIAR PACIENTE] Erro ao criar usuário:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===== CEP (usado nos formulários) =====
|
// ===== CEP (usado nos formulários) =====
|
||||||
export async function buscarCepAPI(cep: string): Promise<{
|
export async function buscarCepAPI(cep: string): Promise<{
|
||||||
logradouro?: string;
|
logradouro?: string;
|
||||||
|
|||||||
135
susconecta/package-lock.json
generated
135
susconecta/package-lock.json
generated
@ -41,6 +41,7 @@
|
|||||||
"@radix-ui/react-toggle": "latest",
|
"@radix-ui/react-toggle": "latest",
|
||||||
"@radix-ui/react-toggle-group": "latest",
|
"@radix-ui/react-toggle-group": "latest",
|
||||||
"@radix-ui/react-tooltip": "latest",
|
"@radix-ui/react-tooltip": "latest",
|
||||||
|
"@supabase/supabase-js": "^2.75.0",
|
||||||
"@vercel/analytics": "1.3.1",
|
"@vercel/analytics": "1.3.1",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@ -2627,6 +2628,80 @@
|
|||||||
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@supabase/auth-js": {
|
||||||
|
"version": "2.75.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.75.0.tgz",
|
||||||
|
"integrity": "sha512-J8TkeqCOMCV4KwGKVoxmEBuDdHRwoInML2vJilthOo7awVCro2SM+tOcpljORwuBQ1vHUtV62Leit+5wlxrNtw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/node-fetch": "2.6.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/functions-js": {
|
||||||
|
"version": "2.75.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.75.0.tgz",
|
||||||
|
"integrity": "sha512-18yk07Moj/xtQ28zkqswxDavXC3vbOwt1hDuYM3/7xPnwwpKnsmPyZ7bQ5th4uqiJzQ135t74La9tuaxBR6e7w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/node-fetch": "2.6.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/node-fetch": {
|
||||||
|
"version": "2.6.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
|
||||||
|
"integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/postgrest-js": {
|
||||||
|
"version": "2.75.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.75.0.tgz",
|
||||||
|
"integrity": "sha512-YfBz4W/z7eYCFyuvHhfjOTTzRrQIvsMG2bVwJAKEVVUqGdzqfvyidXssLBG0Fqlql1zJFgtsPpK1n4meHrI7tg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/node-fetch": "2.6.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/realtime-js": {
|
||||||
|
"version": "2.75.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.75.0.tgz",
|
||||||
|
"integrity": "sha512-B4Xxsf2NHd5cEnM6MGswOSPSsZKljkYXpvzKKmNxoUmNQOfB7D8HOa6NwHcUBSlxcjV+vIrYKcYXtavGJqeGrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/node-fetch": "2.6.15",
|
||||||
|
"@types/phoenix": "^1.6.6",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
|
"ws": "^8.18.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/storage-js": {
|
||||||
|
"version": "2.75.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.75.0.tgz",
|
||||||
|
"integrity": "sha512-wpJMYdfFDckDiHQaTpK+Ib14N/O2o0AAWWhguKvmmMurB6Unx17GGmYp5rrrqCTf8S1qq4IfIxTXxS4hzrUySg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/node-fetch": "2.6.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/supabase-js": {
|
||||||
|
"version": "2.75.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.75.0.tgz",
|
||||||
|
"integrity": "sha512-8UN/vATSgS2JFuJlMVr51L3eUDz+j1m7Ww63wlvHLKULzCDaVWYzvacCjBTLW/lX/vedI2LBI4Vg+01G9ufsJQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/auth-js": "2.75.0",
|
||||||
|
"@supabase/functions-js": "2.75.0",
|
||||||
|
"@supabase/node-fetch": "2.6.15",
|
||||||
|
"@supabase/postgrest-js": "2.75.0",
|
||||||
|
"@supabase/realtime-js": "2.75.0",
|
||||||
|
"@supabase/storage-js": "2.75.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.15",
|
"version": "0.5.15",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||||
@ -3250,7 +3325,6 @@
|
|||||||
"version": "22.18.8",
|
"version": "22.18.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz",
|
||||||
"integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==",
|
"integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
@ -3262,6 +3336,12 @@
|
|||||||
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
|
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/phoenix": {
|
||||||
|
"version": "1.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz",
|
||||||
|
"integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.15",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
@ -3325,6 +3405,15 @@
|
|||||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||||
|
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.45.0",
|
"version": "8.45.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz",
|
||||||
@ -9144,6 +9233,12 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/trim-canvas": {
|
"node_modules/trim-canvas": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/trim-canvas/-/trim-canvas-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/trim-canvas/-/trim-canvas-0.1.2.tgz",
|
||||||
@ -9344,7 +9439,6 @@
|
|||||||
"version": "6.21.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unrs-resolver": {
|
"node_modules/unrs-resolver": {
|
||||||
@ -9519,6 +9613,22 @@
|
|||||||
"d3-timer": "^3.0.1"
|
"d3-timer": "^3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
@ -9634,6 +9744,27 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.18.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||||
|
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user