chore: correção geral e ajustes na API

Copiar código
This commit is contained in:
Jonas Francisco 2025-10-14 18:27:07 -03:00
parent 481a951428
commit 1617dc09de
4 changed files with 761 additions and 532 deletions

View File

@ -232,149 +232,175 @@ export function PatientRegistrationForm({
};
}
async function handleSubmit(ev: React.FormEvent) {
ev.preventDefault();
if (!validateLocal()) return;
async function handleSubmit(ev: React.FormEvent) {
ev.preventDefault();
if (!validateLocal()) return;
try {
if (!validarCPFLocal(form.cpf)) {
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." });
try {
if (!validarCPFLocal(form.cpf)) {
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;
}
setSubmitting(true);
try {
if (mode === "edit") {
if (patientId == null) throw new Error("Paciente inexistente para edição");
const payload = toPayload();
const saved = await atualizarPaciente(String(patientId), payload);
onSaved?.(saved);
alert("Paciente atualizado com sucesso!");
setSubmitting(true);
try {
if (mode === "edit") {
if (patientId == null) throw new Error("Paciente inexistente para edição");
const payload = toPayload();
const saved = await atualizarPaciente(String(patientId), payload);
onSaved?.(saved);
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);
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 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);
// Exibe mensagem amigável ao usuário
const userMessage = err?.message?.includes("toPayload") || err?.message?.includes("is not defined")
}
} catch (err: any) {
console.error("❌ Erro no handleSubmit:", err);
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."
: err?.message || "Erro ao salvar paciente. Por favor, tente novamente.";
setErrors({ submit: userMessage });
} finally {
setSubmitting(false);
}
setErrors({ submit: userMessage });
} finally {
setSubmitting(false);
}
}
function handlePhoto(e: React.ChangeEvent<HTMLInputElement>) {
const f = e.target.files?.[0];

View File

@ -1,339 +1,408 @@
'use client'
import { createContext, useContext, useEffect, useState, ReactNode, useCallback, useMemo, useRef } 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,
"use client";
import {
createContext,
useContext,
useEffect,
useState,
ReactNode,
useCallback,
useMemo,
useRef,
} 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,
UserType
} from '@/types/auth'
import { AUTH_STORAGE_KEYS, LOGIN_ROUTES } from '@/types/auth'
UserType,
} 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 }) {
const [authStatus, setAuthStatus] = useState<AuthStatus>('loading')
const [user, setUser] = useState<UserData | null>(null)
const [token, setToken] = useState<string | null>(null)
const router = useRouter()
const hasInitialized = useRef(false)
const [authStatus, setAuthStatus] = useState<AuthStatus>("loading");
const [user, setUser] = useState<UserData | null>(null);
const [token, setToken] = useState<string | null>(null);
const router = useRouter();
const hasInitialized = useRef(false);
// Utilitários de armazenamento memorizados
const clearAuthData = useCallback(() => {
if (typeof window !== 'undefined') {
localStorage.removeItem(AUTH_STORAGE_KEYS.TOKEN)
localStorage.removeItem(AUTH_STORAGE_KEYS.USER)
localStorage.removeItem(AUTH_STORAGE_KEYS.REFRESH_TOKEN)
if (typeof window !== "undefined") {
localStorage.removeItem(AUTH_STORAGE_KEYS.TOKEN);
localStorage.removeItem(AUTH_STORAGE_KEYS.USER);
localStorage.removeItem(AUTH_STORAGE_KEYS.REFRESH_TOKEN);
// Manter USER_TYPE para redirecionamento correto
}
setUser(null)
setToken(null)
setAuthStatus('unauthenticated')
console.log('[AUTH] Dados de autenticação limpos - logout realizado')
}, [])
setUser(null);
setToken(null);
setAuthStatus("unauthenticated");
console.log("[AUTH] Dados de autenticação limpos - logout realizado");
}, []);
const saveAuthData = useCallback((
accessToken: string,
userData: UserData,
refreshToken?: string
) => {
try {
if (typeof window !== 'undefined') {
// Persistir dados de forma atômica
localStorage.setItem(AUTH_STORAGE_KEYS.TOKEN, accessToken)
localStorage.setItem(AUTH_STORAGE_KEYS.USER, JSON.stringify(userData))
localStorage.setItem(AUTH_STORAGE_KEYS.USER_TYPE, userData.userType)
if (refreshToken) {
localStorage.setItem(AUTH_STORAGE_KEYS.REFRESH_TOKEN, refreshToken)
const saveAuthData = useCallback(
(accessToken: string, userData: UserData, refreshToken?: string) => {
try {
if (typeof window !== "undefined") {
// Persistir dados de forma atômica
localStorage.setItem(AUTH_STORAGE_KEYS.TOKEN, accessToken);
localStorage.setItem(
AUTH_STORAGE_KEYS.USER,
JSON.stringify(userData)
);
localStorage.setItem(AUTH_STORAGE_KEYS.USER_TYPE, userData.userType);
if (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)
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])
},
[clearAuthData]
);
// Verificação inicial de autenticação
const checkAuth = useCallback(async (): Promise<void> => {
if (typeof window === 'undefined') {
setAuthStatus('unauthenticated')
return
if (typeof window === "undefined") {
setAuthStatus("unauthenticated");
return;
}
try {
const storedToken = localStorage.getItem(AUTH_STORAGE_KEYS.TOKEN)
const storedUser = localStorage.getItem(AUTH_STORAGE_KEYS.USER)
const storedToken = localStorage.getItem(AUTH_STORAGE_KEYS.TOKEN);
const storedUser = localStorage.getItem(AUTH_STORAGE_KEYS.USER);
console.log('[AUTH] Verificando sessão...', {
console.log("[AUTH] Verificando sessão...", {
hasToken: !!storedToken,
hasUser: !!storedUser,
timestamp: new Date().toLocaleTimeString()
})
timestamp: new Date().toLocaleTimeString(),
});
// Pequeno delay para visualizar logs
await new Promise(resolve => setTimeout(resolve, 800))
await new Promise((resolve) => setTimeout(resolve, 800));
if (!storedToken || !storedUser) {
console.log('[AUTH] Dados ausentes - sessão inválida')
await new Promise(resolve => setTimeout(resolve, 500))
clearAuthData()
return
console.log("[AUTH] Dados ausentes - sessão inválida");
await new Promise((resolve) => setTimeout(resolve, 500));
clearAuthData();
return;
}
// Verificar se token está expirado
if (isExpired(storedToken)) {
console.log('[AUTH] Token expirado - tentando renovar...')
await new Promise(resolve => setTimeout(resolve, 1000))
const refreshToken = localStorage.getItem(AUTH_STORAGE_KEYS.REFRESH_TOKEN)
console.log("[AUTH] Token expirado - tentando renovar...");
await new Promise((resolve) => setTimeout(resolve, 1000));
const refreshToken = localStorage.getItem(
AUTH_STORAGE_KEYS.REFRESH_TOKEN
);
if (refreshToken && !isExpired(refreshToken)) {
// Tentar renovar via HTTP client (que já tem a lógica)
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
const newToken = localStorage.getItem(AUTH_STORAGE_KEYS.TOKEN)
const userData = JSON.parse(storedUser) as UserData
const newToken = localStorage.getItem(AUTH_STORAGE_KEYS.TOKEN);
const userData = JSON.parse(storedUser) as UserData;
if (newToken && newToken !== storedToken) {
setToken(newToken)
setUser(userData)
setAuthStatus('authenticated')
console.log('[AUTH] Token RENOVADO automaticamente!')
await new Promise(resolve => setTimeout(resolve, 800))
return
setToken(newToken);
setUser(userData);
setAuthStatus("authenticated");
console.log("[AUTH] Token RENOVADO automaticamente!");
await new Promise((resolve) => setTimeout(resolve, 800));
return;
}
} catch (refreshError) {
console.log(' [AUTH] Falha no refresh automático')
await new Promise(resolve => setTimeout(resolve, 400))
console.log(" [AUTH] Falha no refresh automático");
await new Promise((resolve) => setTimeout(resolve, 400));
}
}
clearAuthData()
return
clearAuthData();
return;
}
// Restaurar sessão válida
const userData = JSON.parse(storedUser) as UserData
setToken(storedToken)
const userData = JSON.parse(storedUser) as UserData;
setToken(storedToken);
// Tentar buscar profile consolidado (user-info) e mesclar
try {
const info = await getUserInfo()
const info = await getUserInfo();
if (info?.profile) {
const mapped = {
cpf: (info.profile as any).cpf ?? userData.profile?.cpf,
crm: (info.profile as any).crm ?? userData.profile?.crm,
telefone: info.profile.phone ?? userData.profile?.telefone,
foto_url: info.profile.avatar_url ?? userData.profile?.foto_url,
}
};
if (userData.profile) {
userData.profile = { ...userData.profile, ...mapped }
userData.profile = { ...userData.profile, ...mapped };
} else {
userData.profile = mapped
userData.profile = mapped;
}
// Persistir o usuário atualizado no localStorage para evitar
// que 'auth_user.profile' fique vazio após um reload completo
try {
if (typeof window !== 'undefined') {
localStorage.setItem(AUTH_STORAGE_KEYS.USER, JSON.stringify(userData))
if (typeof window !== "undefined") {
localStorage.setItem(
AUTH_STORAGE_KEYS.USER,
JSON.stringify(userData)
);
}
} 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) {
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)
setAuthStatus('authenticated')
setUser(userData);
setAuthStatus("authenticated");
console.log('[AUTH] Sessão RESTAURADA com sucesso!', {
console.log("[AUTH] Sessão RESTAURADA com sucesso!", {
userId: userData.id,
userType: userData.userType,
email: userData.email,
timestamp: new Date().toLocaleTimeString()
})
await new Promise(resolve => setTimeout(resolve, 1000))
timestamp: new Date().toLocaleTimeString(),
});
await new Promise((resolve) => setTimeout(resolve, 1000));
} catch (error) {
console.error('[AUTH] Erro na verificação:', error)
clearAuthData()
console.error("[AUTH] Erro na verificação:", error);
clearAuthData();
}
}, [clearAuthData])
}, [clearAuthData]);
// Login memoizado
const login = useCallback(async (
email: string,
password: string,
userType: UserType
): Promise<boolean> => {
try {
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
const login = useCallback(
async (
email: string,
password: string,
userType: UserType
): Promise<boolean> => {
try {
const infoRes = await fetch(`${ENV_CONFIG.SUPABASE_URL}/functions/v1/user-info`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${response.access_token}`,
'apikey': ENV_CONFIG.SUPABASE_ANON_KEY,
}
})
console.log("[AUTH] Iniciando login:", { email, userType });
if (infoRes.ok) {
const info = await infoRes.json().catch(() => null)
const roles: string[] = Array.isArray(info?.roles) ? info.roles : (info?.roles ? [info.roles] : [])
const response = await loginUser(email, password, userType);
// Derivar tipo de usuário a partir dos roles
let derived: UserType = 'paciente'
if (roles.includes('admin') || roles.includes('gestor') || roles.includes('secretaria')) {
derived = 'administrador'
} else if (roles.includes('medico') || roles.includes('enfermeiro')) {
derived = 'profissional'
}
// Após receber token, buscar roles/permissions reais e reconciliar userType
try {
const infoRes = await fetch(
`${ENV_CONFIG.SUPABASE_URL}/functions/v1/user-info`,
{
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 (response.user && response.user.userType !== derived) {
response.user.userType = derived
console.log('[AUTH] userType reconciled from roles ->', derived)
}
} else {
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)
}
if (infoRes.ok) {
const info = await infoRes.json().catch(() => null);
const roles: string[] = Array.isArray(info?.roles)
? info.roles
: info?.roles
? [info.roles]
: [];
// 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 }
// Derivar tipo de usuário a partir dos roles
// Derivar tipo de usuário a partir dos roles retornados pela API
let derived: UserType = userType; // 🔹 começa respeitando o tipo da tela de login
if (
roles.includes("admin") ||
roles.includes("gestor") ||
roles.includes("secretaria")
) {
derived = "administrador";
} else if (
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 {
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(
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]
);
// Logout memoizado
const logout = useCallback(async (): Promise<void> => {
console.log('[AUTH] Iniciando logout')
const currentUserType = user?.userType ||
(typeof window !== 'undefined' ? localStorage.getItem(AUTH_STORAGE_KEYS.USER_TYPE) : null) ||
'profissional'
console.log("[AUTH] Iniciando logout");
const currentUserType =
user?.userType ||
(typeof window !== "undefined"
? localStorage.getItem(AUTH_STORAGE_KEYS.USER_TYPE)
: null) ||
"profissional";
try {
if (token) {
await logoutUser(token)
console.log('[AUTH] Logout realizado na API')
await logoutUser(token);
console.log("[AUTH] Logout realizado na API");
}
} 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
const loginRoute = LOGIN_ROUTES[currentUserType as UserType] || '/login'
console.log('[AUTH] Redirecionando para:', loginRoute)
if (typeof window !== 'undefined') {
window.location.href = loginRoute
const loginRoute = LOGIN_ROUTES[currentUserType as UserType] || "/login";
console.log("[AUTH] Redirecionando para:", loginRoute);
if (typeof window !== "undefined") {
window.location.href = loginRoute;
}
}, [user?.userType, token, clearAuthData])
}, [user?.userType, token, clearAuthData]);
// Refresh token memoizado (usado pelo HTTP client)
const refreshToken = useCallback(async (): Promise<boolean> => {
// Esta função é principalmente para compatibilidade
// O refresh real é feito pelo HTTP client
return false
}, [])
return false;
}, []);
// Getters memorizados
const contextValue = useMemo(() => ({
authStatus,
user,
token,
login,
logout,
refreshToken
}), [authStatus, user, token, login, logout, refreshToken])
const contextValue = useMemo(
() => ({
authStatus,
user,
token,
login,
logout,
refreshToken,
}),
[authStatus, user, token, login, logout, refreshToken]
);
// Inicialização única
useEffect(() => {
if (!hasInitialized.current && typeof window !== 'undefined') {
hasInitialized.current = true
checkAuth()
if (!hasInitialized.current && typeof window !== "undefined") {
hasInitialized.current = true;
checkAuth();
}
}, [checkAuth])
}, [checkAuth]);
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
)
<AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
);
}
export const useAuth = () => {
const context = useContext(AuthContext)
const context = useContext(AuthContext);
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;
};

View File

@ -220,82 +220,115 @@ async function fetchWithFallback<T = any>(url: string, headers: Record<string, s
// Parse genérico
async function parse<T>(res: Response): Promise<T> {
let json: any = null;
let rawText: string | null = null;
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) {
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) {
// Tenta também ler o body como texto cru para obter mensagens detalhadas
let rawText = '';
try {
rawText = await res.clone().text();
} catch (tErr) {
// ignore
// 🔁 Clona a resposta para tentar ler novamente caso o body já tenha sido consumido
let rawCloneText = rawText;
if (!rawCloneText) {
try {
rawCloneText = await res.clone().text();
} 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 msg = (json && (json.error?.message || json.message || json.error)) ?? res.statusText;
// Mensagens amigáveis para erros comuns
const msg =
(json && (json.error?.message || json.message || json.error)) ??
res.statusText;
// 🧠 Mensagens amigáveis e específicas
let friendlyMessage = msg;
// Erros de criação de usuário
if (res.url?.includes('create-user')) {
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.';
} else if (msg?.includes('already registered')) {
friendlyMessage = 'Este email já está cadastrado no sistema.';
} else if (msg?.includes('Invalid role')) {
friendlyMessage = 'Tipo de acesso inválido.';
} else if (msg?.includes('Missing required fields')) {
friendlyMessage = 'Campos obrigatórios não preenchidos.';
// 🔹 Erros de criação de usuário
if (res.url?.includes("create-user")) {
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.";
} else if (msg?.includes("already registered")) {
friendlyMessage = "Este email já está cadastrado no sistema.";
} else if (msg?.includes("Invalid role")) {
friendlyMessage = "Tipo de acesso inválido.";
} else if (msg?.includes("Missing required fields")) {
friendlyMessage = "Campos obrigatórios não preenchidos.";
} 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) {
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) {
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')) {
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.';
// 🔹 Erro de CPF duplicado
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')) {
friendlyMessage = 'Já existe um paciente cadastrado com este email. Por favor, use um email diferente.';
// 🔹 Erro de email duplicado (paciente)
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')) {
friendlyMessage = 'Já existe um médico cadastrado com este CRM. Por favor, verifique se o médico já está registrado no sistema.';
// 🔹 Erro de CRM duplicado (médico)
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')) {
friendlyMessage = 'Já existe um médico cadastrado com este email. Por favor, use um email diferente.';
// 🔹 Erro de email duplicado (médico)
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') {
friendlyMessage = 'Registro duplicado: já existe um cadastro com essas informações no sistema.';
// 🔹 Outros erros de constraint UNIQUE
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') {
// Mensagem específica para pacientes com relatórios vinculados
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.';
// 🔹 Foreign key violation (registro vinculado)
else if (code === "23503") {
if (msg && msg.toString().toLowerCase().includes("reports")) {
friendlyMessage =
"Não é possível excluir este paciente porque existem relatórios vinculados.";
} 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);
}
// ✅ Retorno padronizado (json.data ou json)
return (json?.data ?? json) as T;
}
// Helper de paginação (Range/Range-Unit)
function rangeHeaders(page?: number, limit?: number): Record<string, string> {
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> {
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, {
method: "POST",
headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"),
body: JSON.stringify(input),
headers: withPrefer(
{ ...baseHeaders(), "Content-Type": "application/json" },
"return=representation"
),
body: JSON.stringify(safeInput),
});
const arr = await parse<Paciente[] | Paciente>(res);
return Array.isArray(arr) ? arr[0] : (arr as 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 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.
* 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 payload = { user_id: String(userId) };
const payload = { email };
const res = await fetch(url, {
method: 'PATCH',
headers: withPrefer({ ...baseHeaders(), 'Content-Type': 'application/json' }, 'return=representation'),
method: "PATCH",
headers: withPrefer(
{ ...baseHeaders(), "Content-Type": "application/json" },
"return=representation"
),
body: JSON.stringify(payload),
});
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> {
console.log(`Tentando atualizar médico ID: ${id}`);
console.log(`Payload original:`, input);
@ -1399,30 +1454,27 @@ export async function criarUsuarioPaciente(paciente: {
full_name: string;
phone_mobile: string;
}): Promise<CreateUserWithPasswordResponse> {
const senha = gerarSenhaAleatoria();
console.log('[CRIAR PACIENTE] Iniciando criação no Supabase Auth...');
console.log('Email:', paciente.email);
console.log('Nome:', paciente.full_name);
console.log('Telefone:', paciente.phone_mobile);
console.log('Senha gerada:', senha);
// Endpoint do Supabase Auth (mesmo que auth.ts usa)
console.log("🩺 [CRIAR PACIENTE] Iniciando criação no Supabase Auth...");
console.log("Email:", paciente.email);
console.log("Nome:", paciente.full_name);
console.log("Telefone:", paciente.phone_mobile);
console.log("Senha gerada:", senha);
const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`;
const payload = {
email: paciente.email,
email: paciente.email.trim(),
password: senha,
data: {
userType: 'paciente', // Para login em /login-paciente -> /paciente
full_name: paciente.full_name,
phone: paciente.phone_mobile,
}
userType: "paciente",
full_name: paciente.full_name.trim(),
phone: paciente.phone_mobile?.trim() || "",
},
};
console.log('[CRIAR PACIENTE] Enviando para:', signupUrl);
try {
const response = await fetch(signupUrl, {
method: "POST",
@ -1433,125 +1485,76 @@ export async function criarUsuarioPaciente(paciente: {
},
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) {
const errorText = await response.text();
console.error('[CRIAR PACIENTE] Erro na resposta:', errorText);
// Tenta parsear o erro para pegar mensagem específica
console.error("[CRIAR PACIENTE] Erro na resposta:", errorText);
let errorMsg = `Erro ao criar usuário (${response.status})`;
try {
const errorData = JSON.parse(errorText);
errorMsg = errorData.msg || errorData.message || errorData.error_description || errorMsg;
// Mensagens amigáveis para erros comuns
if (errorMsg.includes('already registered') || errorMsg.includes('already exists')) {
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';
const errData = JSON.parse(errorText);
errorMsg =
errData.msg ||
errData.message ||
errData.error_description ||
errData.error ||
errorMsg;
if (errorMsg.includes("already registered") || errorMsg.includes("already exists")) {
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) {
// Se não conseguir parsear, usa mensagem genérica
} catch {
// ignora erro de parse
}
throw new Error(errorMsg);
}
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;
// 🔧 AUTO-CONFIRMAR EMAIL: Fazer login automático logo após criar usuário
// Isso força o Supabase a confirmar o email automaticamente
if (responseData.user?.email_confirmed_at === null || !responseData.user?.email_confirmed_at) {
console.warn('[CRIAR PACIENTE] Email NÃO confirmado - tentando auto-confirmar via login...');
try {
const loginUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/token?grant_type=password`;
console.log('[AUTO-CONFIRMAR] Fazendo login automático para confirmar email...');
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!');
console.log("✅ [CRIAR PACIENTE] Usuário criado com sucesso!");
console.log("User ID:", userId);
console.log("Resposta completa:", JSON.stringify(responseData, null, 2));
// 🧩 Corrige: evita invalid_credentials
// O Supabase pode demorar alguns segundos para permitir login (até confirmar email)
if (!responseData.user?.email_confirmed_at) {
console.warn(
"[CRIAR PACIENTE] Email ainda não confirmado — aguardando confirmação automática do Supabase."
);
}
// Log bem visível com as credenciais para teste
console.log('========================================');
console.log('CREDENCIAIS DO PACIENTE CRIADO:');
console.log('Email:', paciente.email);
console.log('Senha:', senha);
console.log('UserType:', 'paciente');
console.log('Pode fazer login?', responseData.user?.email_confirmed_at ? 'SIM' : 'NÃO (precisa confirmar email)');
console.log('========================================');
console.log("========================================");
console.log("CREDENCIAIS DO PACIENTE CRIADO:");
console.log("Email:", paciente.email);
console.log("Senha:", senha);
console.log("UserType:", "paciente");
console.log(
"Pode fazer login?",
responseData.user?.email_confirmed_at ? "SIM" : "NÃO (aguardando confirmação)"
);
console.log("========================================");
return {
success: true,
user: responseData.user || responseData,
email: paciente.email,
password: senha,
};
} 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;
}
}
// ===== CEP (usado nos formulários) =====
export async function buscarCepAPI(cep: string): Promise<{
logradouro?: string;

View File

@ -41,6 +41,7 @@
"@radix-ui/react-toggle": "latest",
"@radix-ui/react-toggle-group": "latest",
"@radix-ui/react-tooltip": "latest",
"@supabase/supabase-js": "^2.75.0",
"@vercel/analytics": "1.3.1",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
@ -2627,6 +2628,80 @@
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
"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": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@ -3250,7 +3325,6 @@
"version": "22.18.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz",
"integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@ -3262,6 +3336,12 @@
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
"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": {
"version": "15.7.15",
"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==",
"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": {
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz",
@ -9144,6 +9233,12 @@
"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": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/trim-canvas/-/trim-canvas-0.1.2.tgz",
@ -9344,7 +9439,6 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/unrs-resolver": {
@ -9519,6 +9613,22 @@
"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": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -9634,6 +9744,27 @@
"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": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",