From 20f7d79474d3d209578e2cbaa53fe16481f30bfa Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Fri, 3 Oct 2025 04:42:24 -0300 Subject: [PATCH] feat: add email confirmation on user registration Implements automatic creation in Supabase Auth with mandatory email confirmation. Adds credentials popup and clear messages about the confirmation process. BREAKING CHANGE: Users must confirm email before login --- susconecta/app/login-paciente/page.tsx | 11 +- susconecta/app/login/page.tsx | 11 +- susconecta/components/credentials-dialog.tsx | 177 +++++++ .../forms/doctor-registration-form.tsx | 169 ++++++- .../forms/patient-registration-form.tsx | 188 +++++++- susconecta/lib/api.ts | 454 +++++++++++++++++- susconecta/lib/auth.ts | 43 +- susconecta/next-env.d.ts | 3 +- 8 files changed, 982 insertions(+), 74 deletions(-) create mode 100644 susconecta/components/credentials-dialog.tsx diff --git a/susconecta/app/login-paciente/page.tsx b/susconecta/app/login-paciente/page.tsx index 6468241..7ae6e81 100644 --- a/susconecta/app/login-paciente/page.tsx +++ b/susconecta/app/login-paciente/page.tsx @@ -33,7 +33,16 @@ export default function LoginPacientePage() { console.error('[LOGIN-PACIENTE] Erro no login:', err) if (err instanceof AuthenticationError) { - setError(err.message) + // Verificar se é erro de credenciais inválidas (pode ser email não confirmado) + if (err.code === '400' || err.details?.error_code === 'invalid_credentials') { + setError( + '⚠️ Email ou senha incorretos. Se você acabou de se cadastrar, ' + + 'verifique sua caixa de entrada e clique no link de confirmação ' + + 'que foi enviado para ' + credentials.email + ) + } else { + setError(err.message) + } } else { setError('Erro inesperado. Tente novamente.') } diff --git a/susconecta/app/login/page.tsx b/susconecta/app/login/page.tsx index bbf1826..88755ff 100644 --- a/susconecta/app/login/page.tsx +++ b/susconecta/app/login/page.tsx @@ -35,7 +35,16 @@ export default function LoginPage() { console.error('[LOGIN-PROFISSIONAL] Erro no login:', err) if (err instanceof AuthenticationError) { - setError(err.message) + // Verificar se é erro de credenciais inválidas (pode ser email não confirmado) + if (err.code === '400' || err.details?.error_code === 'invalid_credentials') { + setError( + '⚠️ Email ou senha incorretos. Se você acabou de se cadastrar, ' + + 'verifique sua caixa de entrada e clique no link de confirmação ' + + 'que foi enviado para ' + credentials.email + ) + } else { + setError(err.message) + } } else { setError('Erro inesperado. Tente novamente.') } diff --git a/susconecta/components/credentials-dialog.tsx b/susconecta/components/credentials-dialog.tsx new file mode 100644 index 0000000..8a1cec8 --- /dev/null +++ b/susconecta/components/credentials-dialog.tsx @@ -0,0 +1,177 @@ +"use client"; + +import { useState } from "react"; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { CheckCircle2, Copy, Eye, EyeOff } from "lucide-react"; +import { Alert, AlertDescription } from "@/components/ui/alert"; + +export interface CredentialsDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + email: string; + password: string; + userName: string; + userType: "médico" | "paciente"; +} + +export function CredentialsDialog({ + open, + onOpenChange, + email, + password, + userName, + userType, +}: CredentialsDialogProps) { + const [showPassword, setShowPassword] = useState(false); + const [copiedEmail, setCopiedEmail] = useState(false); + const [copiedPassword, setCopiedPassword] = useState(false); + + function handleCopyEmail() { + navigator.clipboard.writeText(email); + setCopiedEmail(true); + setTimeout(() => setCopiedEmail(false), 2000); + } + + function handleCopyPassword() { + navigator.clipboard.writeText(password); + setCopiedPassword(true); + setTimeout(() => setCopiedPassword(false), 2000); + } + + function handleCopyBoth() { + const text = `Email: ${email}\nSenha: ${password}`; + navigator.clipboard.writeText(text); + } + + return ( + + + + + + {userType === "médico" ? "Médico" : "Paciente"} Cadastrado com Sucesso! + + + O {userType} {userName} foi cadastrado e pode fazer login com as credenciais abaixo. + + + + + + Importante: Anote ou copie estas credenciais agora. Por segurança, essa senha não será exibida novamente. + + + + + + 📧 Confirme o email: Um email de confirmação foi enviado para {email}. + O {userType} deve clicar no link de confirmação antes de fazer o primeiro login. + + + +
+
+ +
+ + +
+
+ +
+ +
+
+ + +
+ +
+
+
+ +
+ Próximos passos: +
    +
  1. Compartilhe estas credenciais com o {userType}
  2. +
  3. + O {userType} deve confirmar o email clicando no link enviado para{" "} + {email} (verifique também a pasta de spam) +
  4. +
  5. + Após confirmar o email, o {userType} deve acessar:{" "} + + {userType === "médico" ? "/login" : "/login-paciente"} + +
  6. +
  7. + Após o login, terá acesso à área:{" "} + + {userType === "médico" ? "/profissional" : "/paciente"} + +
  8. +
  9. Recomende trocar a senha no primeiro acesso
  10. +
+
+ + + + + +
+
+ ); +} diff --git a/susconecta/components/forms/doctor-registration-form.tsx b/susconecta/components/forms/doctor-registration-form.tsx index b50ef60..1db55f8 100644 --- a/susconecta/components/forms/doctor-registration-form.tsx +++ b/susconecta/components/forms/doctor-registration-form.tsx @@ -24,10 +24,13 @@ import { removerAnexoMedico, MedicoInput, // 👈 importado do lib/api Medico, // 👈 adicionado import do tipo Medico + criarUsuarioMedico, + CreateUserWithPasswordResponse, } from "@/lib/api"; ; -import { buscarCepAPI } from "@/lib/api"; +import { buscarCepAPI } from "@/lib/api"; +import { CredentialsDialog } from "@/components/credentials-dialog"; type FormacaoAcademica = { instituicao: string; @@ -148,6 +151,11 @@ export function DoctorRegistrationForm({ const [isSearchingCEP, setSearchingCEP] = useState(false); const [photoPreview, setPhotoPreview] = useState(null); const [serverAnexos, setServerAnexos] = useState([]); + + // Estados para o dialog de credenciais + const [showCredentials, setShowCredentials] = useState(false); + const [credentials, setCredentials] = useState(null); + const [savedDoctor, setSavedDoctor] = useState(null); const title = useMemo(() => (mode === "create" ? "Cadastro de Médico" : "Editar Médico"), [mode]); @@ -389,8 +397,69 @@ if (missingFields.length > 0) { console.log("✅ Médico salvo com sucesso:", saved); - onSaved?.(saved); - setSubmitting(false); + // Se for criação de novo médico e tiver email válido, cria usuário + if (mode === "create" && form.email && form.email.includes('@')) { + console.log("🔐 Iniciando criação de usuário para o médico..."); + console.log("📧 Email:", form.email); + console.log("👤 Nome:", form.full_name); + console.log("📱 Telefone:", form.celular); + + try { + const userCredentials = await criarUsuarioMedico({ + email: form.email, + full_name: form.full_name, + phone_mobile: form.celular, + }); + + console.log("✅ Usuário criado com sucesso!", userCredentials); + console.log("🔑 Senha gerada:", userCredentials.password); + + // Armazena as credenciais e mostra o dialog + setCredentials(userCredentials); + setShowCredentials(true); + setSavedDoctor(saved); // Salva médico para chamar onSaved depois + + console.log("📋 Credenciais definidas, dialog deve aparecer!"); + + // NÃO chama onSaved aqui! Isso fecha o formulário. + // O dialog vai chamar onSaved quando o usuário fechar + setSubmitting(false); + return; // ← IMPORTANTE: Impede que o código abaixo seja executado + + } catch (userError: any) { + console.error("❌ ERRO ao criar usuário:", userError); + console.error("📋 Stack trace:", userError?.stack); + const errorMessage = userError?.message || "Erro desconhecido"; + console.error("💬 Mensagem:", errorMessage); + + // Mostra erro mas fecha o formulário normalmente + alert(`Médico cadastrado com sucesso!\n\n⚠️ Porém, houve erro ao criar usuário de acesso:\n${errorMessage}\n\nVerifique os logs do console (F12) para mais detalhes.`); + + // Fecha o formulário mesmo com erro na criação de usuário + setForm(initial); + setPhotoPreview(null); + setServerAnexos([]); + onSaved?.(saved); + if (inline) onClose?.(); + else onOpenChange?.(false); + setSubmitting(false); + return; + } + } else { + console.log("⚠️ Não criará usuário. Motivo:"); + console.log(" - Mode:", mode); + console.log(" - Email:", form.email); + console.log(" - Tem @:", form.email?.includes('@')); + + // Se não for criar usuário, fecha normalmente + setForm(initial); + setPhotoPreview(null); + setServerAnexos([]); + onSaved?.(saved); + if (inline) onClose?.(); + else onOpenChange?.(false); + setSubmitting(false); + } } catch (err: any) { console.error("❌ Erro ao salvar médico:", err); console.error("❌ Detalhes do erro:", { @@ -943,18 +1012,90 @@ if (missingFields.length > 0) { ); - if (inline) return
{content}
; + if (inline) { + return ( + <> +
{content}
+ + {/* Dialog de credenciais */} + {credentials && ( + { + console.log("🔄 CredentialsDialog (inline) onOpenChange:", open); + setShowCredentials(open); + if (!open) { + console.log("🔄 Dialog fechando - chamando onSaved e limpando formulário"); + + // Chama onSaved com o médico salvo + if (savedDoctor) { + console.log("✅ Chamando onSaved com médico:", savedDoctor.id); + onSaved?.(savedDoctor); + } + + // Limpa o formulário e fecha + setForm(initial); + setPhotoPreview(null); + setServerAnexos([]); + setCredentials(null); + setSavedDoctor(null); + onClose?.(); + } + }} + email={credentials.email} + password={credentials.password} + userName={form.full_name} + userType="médico" + /> + )} + + ); + } return ( - - - - - {title} - - - {content} - - + <> + + + + + {title} + + + {content} + + + + {/* Dialog de credenciais */} + {credentials && ( + { + console.log("🔄 CredentialsDialog (dialog mode) onOpenChange:", open); + setShowCredentials(open); + if (!open) { + console.log("🔄 Dialog fechando - chamando onSaved e fechando modal principal"); + + // Chama onSaved com o médico salvo + if (savedDoctor) { + console.log("✅ Chamando onSaved com médico:", savedDoctor.id); + onSaved?.(savedDoctor); + } + + // Limpa o formulário e fecha o modal principal + setForm(initial); + setPhotoPreview(null); + setServerAnexos([]); + setCredentials(null); + setSavedDoctor(null); + onOpenChange?.(false); + } + }} + email={credentials.email} + password={credentials.password} + userName={form.full_name} + userType="médico" + /> + )} + ); } diff --git a/susconecta/components/forms/patient-registration-form.tsx b/susconecta/components/forms/patient-registration-form.tsx index f2a68b1..b25ea24 100644 --- a/susconecta/components/forms/patient-registration-form.tsx +++ b/susconecta/components/forms/patient-registration-form.tsx @@ -25,10 +25,13 @@ import { listarAnexos, removerAnexo, buscarPacientePorId, + criarUsuarioPaciente, + CreateUserWithPasswordResponse, } from "@/lib/api"; import { validarCPFLocal } from "@/lib/utils"; -import { verificarCpfDuplicado } from "@/lib/api"; +import { verificarCpfDuplicado } from "@/lib/api"; +import { CredentialsDialog } from "@/components/credentials-dialog"; @@ -104,6 +107,11 @@ export function PatientRegistrationForm({ const [isSearchingCEP, setSearchingCEP] = useState(false); const [photoPreview, setPhotoPreview] = useState(null); const [serverAnexos, setServerAnexos] = useState([]); + + // Estados para o dialog de credenciais + const [showCredentials, setShowCredentials] = useState(false); + const [credentials, setCredentials] = useState(null); + const [savedPatient, setSavedPatient] = useState(null); const title = useMemo(() => (mode === "create" ? "Cadastro de Paciente" : "Editar Paciente"), [mode]); @@ -264,15 +272,88 @@ export function PatientRegistrationForm({ } } + // Se for criação de novo paciente e tiver email válido, cria usuário + if (mode === "create" && form.email && form.email.includes('@')) { + console.log("🔐 Iniciando criação de usuário para o paciente..."); + console.log("📧 Email:", form.email); + console.log("👤 Nome:", form.nome); + console.log("📱 Telefone:", form.telefone); + + try { + const userCredentials = await criarUsuarioPaciente({ + email: form.email, + full_name: form.nome, + phone_mobile: form.telefone, + }); + + console.log("✅ Usuário criado com sucesso!", userCredentials); + console.log("🔑 Senha gerada:", userCredentials.password); + + // Armazena as credenciais e mostra o dialog + console.log("📋 Antes de setCredentials - credentials atual:", credentials); + console.log("📋 Antes de setShowCredentials - showCredentials atual:", showCredentials); + + setCredentials(userCredentials); + setShowCredentials(true); + + console.log("📋 Depois de set - credentials:", userCredentials); + console.log("📋 Depois de set - showCredentials: true"); + console.log("📋 Modo inline?", inline); + console.log("📋 userCredentials completo:", JSON.stringify(userCredentials)); + + // Força re-render + setTimeout(() => { + console.log("⏰ Timeout - credentials:", credentials); + console.log("⏰ Timeout - showCredentials:", showCredentials); + }, 100); + + console.log("📋 Credenciais definidas, dialog deve aparecer!"); + + // Salva o paciente para chamar onSaved depois + setSavedPatient(saved); + + // ⚠️ NÃO chama onSaved aqui! O dialog vai chamar quando fechar. + // Se chamar agora, o formulário fecha e o dialog desaparece. + console.log("⚠️ NÃO chamando onSaved ainda - aguardando dialog fechar"); + + // RETORNA AQUI para não executar o código abaixo + return; + + } catch (userError: any) { + console.error("❌ ERRO ao criar usuário:", userError); + console.error("📋 Stack trace:", userError?.stack); + const errorMessage = userError?.message || "Erro desconhecido"; + console.error("� Mensagem:", errorMessage); + + // Mostra erro mas fecha o formulário normalmente + alert(`Paciente cadastrado com sucesso!\n\n⚠️ Porém, houve erro ao criar usuário de acesso:\n${errorMessage}\n\nVerifique os logs do console (F12) para mais detalhes.`); + + // Fecha o formulário mesmo com erro na criação de usuário + setForm(initial); + setPhotoPreview(null); + setServerAnexos([]); + + if (inline) onClose?.(); + else onOpenChange?.(false); + } + } else { + console.log("⚠️ Não criará usuário. Motivo:"); + console.log(" - Mode:", mode); + console.log(" - Email:", form.email); + console.log(" - Tem @:", form.email?.includes('@')); + + // Se não for criar usuário, fecha normalmente + setForm(initial); + setPhotoPreview(null); + setServerAnexos([]); + + if (inline) onClose?.(); + else onOpenChange?.(false); + + alert(mode === "create" ? "Paciente cadastrado!" : "Paciente atualizado!"); + } + onSaved?.(saved); - setForm(initial); - setPhotoPreview(null); - setServerAnexos([]); - - if (inline) onClose?.(); - else onOpenChange?.(false); - - alert(mode === "create" ? "Paciente cadastrado!" : "Paciente atualizado!"); } catch (err: any) { setErrors({ submit: err?.message || "Erro ao salvar paciente." }); } finally { @@ -611,18 +692,85 @@ export function PatientRegistrationForm({ ); - if (inline) return
{content}
; + if (inline) { + return ( + <> +
{content}
+ + {/* Debug */} + {console.log("🎨 RENDER inline - credentials:", credentials, "showCredentials:", showCredentials)} + + {/* Dialog de credenciais */} + {credentials && ( + { + console.log("🔄 CredentialsDialog onOpenChange:", open); + setShowCredentials(open); + if (!open) { + console.log("🔄 Dialog fechando - chamando onSaved e limpando formulário"); + + // Chama onSaved com o paciente salvo + if (savedPatient) { + console.log("✅ Chamando onSaved com paciente:", savedPatient.id); + onSaved?.(savedPatient); + } + + // Limpa o formulário e fecha + setForm(initial); + setPhotoPreview(null); + setServerAnexos([]); + setCredentials(null); + setSavedPatient(null); + onClose?.(); + } + }} + email={credentials.email} + password={credentials.password} + userName={form.nome} + userType="paciente" + /> + )} + + ); + } return ( - - - - - {title} - - - {content} - - + <> + {console.log("🎨 RENDER dialog - credentials:", credentials, "showCredentials:", showCredentials)} + + + + + + {title} + + + {content} + + + + {/* Dialog de credenciais */} + {credentials && ( + { + setShowCredentials(open); + if (!open) { + // Quando fechar o dialog, limpa o formulário e fecha o modal principal + setForm(initial); + setPhotoPreview(null); + setServerAnexos([]); + setCredentials(null); + onOpenChange?.(false); + } + }} + email={credentials.email} + password={credentials.password} + userName={form.nome} + userType="paciente" + /> + )} + ); } diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index c009b0f..8f36b02 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -1,5 +1,8 @@ // lib/api.ts +import { ENV_CONFIG } from '@/lib/env-config'; +import { API_KEY } from '@/lib/config'; + export type ApiOk = { success?: boolean; data: T; @@ -194,7 +197,32 @@ async function parse(res: Response): Promise { console.error("[API ERROR]", res.url, res.status, json); const code = (json && (json.error?.code || json.code)) ?? res.status; const msg = (json && (json.error?.message || json.message)) ?? res.statusText; - throw new Error(`${code}: ${msg}`); + + // Mensagens amigáveis para erros comuns + let friendlyMessage = `${code}: ${msg}`; + + // Erro de CPF duplicado + 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 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 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 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.'; + } + // Outros erros de constraint unique + else if (code === '23505') { + friendlyMessage = 'Registro duplicado: já existe um cadastro com essas informações no sistema.'; + } + + throw new Error(friendlyMessage); } return (json?.data ?? json) as T; @@ -552,6 +580,430 @@ export async function excluirMedico(id: string | number): Promise { await parse(res); } +// ===== USUÁRIOS ===== +export type UserRole = { + id: string; + user_id: string; + role: string; + created_at: string; +}; + +export async function listarUserRoles(): Promise { + const url = `https://mock.apidog.com/m1/1053378-0-default/rest/v1/user_roles`; + const res = await fetch(url, { + method: "GET", + headers: baseHeaders(), + }); + return await parse(res); +} + +export type User = { + id: string; + email: string; + email_confirmed_at: string; + created_at: string; + last_sign_in_at: string; +}; + +export type CurrentUser = { + id: string; + email: string; + email_confirmed_at: string; + created_at: string; + last_sign_in_at: string; +}; + +export type Profile = { + id: string; + full_name: string; + email: string; + phone: string; + avatar_url: string; + disabled: boolean; + created_at: string; + updated_at: string; +}; + +export type Permissions = { + isAdmin: boolean; + isManager: boolean; + isDoctor: boolean; + isSecretary: boolean; + isAdminOrManager: boolean; +}; + +export type UserInfo = { + user: User; + profile: Profile; + roles: string[]; + permissions: Permissions; +}; + +export async function getCurrentUser(): Promise { + const url = `https://mock.apidog.com/m1/1053378-0-default/auth/v1/user`; + const res = await fetch(url, { + method: "GET", + headers: baseHeaders(), + }); + return await parse(res); +} + +export async function getUserInfo(): Promise { + const url = `https://mock.apidog.com/m1/1053378-0-default/functions/v1/user-info`; + const res = await fetch(url, { + method: "GET", + headers: baseHeaders(), + }); + return await parse(res); +} + +export type CreateUserInput = { + email: string; + full_name: string; + phone: string; + role: string; + password?: string; +}; + +export type CreatedUser = { + id: string; + email: string; + full_name: string; + phone: string; + role: string; +}; + +export type CreateUserResponse = { + success: boolean; + user: CreatedUser; + password?: string; +}; + +export type CreateUserWithPasswordResponse = { + success: boolean; + user: CreatedUser; + email: string; + password: string; +}; + +// Função para gerar senha aleatória (formato: senhaXXX!) +export function gerarSenhaAleatoria(): string { + const num1 = Math.floor(Math.random() * 10); + const num2 = Math.floor(Math.random() * 10); + const num3 = Math.floor(Math.random() * 10); + return `senha${num1}${num2}${num3}!`; +} + +export async function criarUsuario(input: CreateUserInput): Promise { + const url = `https://mock.apidog.com/m1/1053378-0-default/functions/v1/create-user`; + const res = await fetch(url, { + method: "POST", + headers: { ...baseHeaders(), "Content-Type": "application/json" }, + body: JSON.stringify(input), + }); + return await parse(res); +} + +// ============================================ +// CRIAÇÃO DE USUÁRIOS NO SUPABASE AUTH +// Vínculo com pacientes/médicos por EMAIL +// ============================================ + +// Criar usuário para MÉDICO no Supabase Auth (sistema de autenticação) +export async function criarUsuarioMedico(medico: { + email: string; + full_name: string; + phone_mobile: string; +}): Promise { + + const senha = gerarSenhaAleatoria(); + + console.log('🏥 [CRIAR MÉDICO] Iniciando criação no Supabase Auth...'); + console.log('📧 Email:', medico.email); + console.log('👤 Nome:', medico.full_name); + console.log('📱 Telefone:', medico.phone_mobile); + console.log('🔑 Senha gerada:', senha); + + // Endpoint do Supabase Auth (mesmo que auth.ts usa) + const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`; + + const payload = { + email: medico.email, + password: senha, + data: { + userType: 'profissional', // Para login em /login -> /profissional + full_name: medico.full_name, + phone: medico.phone_mobile, + } + }; + + console.log('📤 [CRIAR MÉDICO] Enviando para:', signupUrl); + + try { + const response = await fetch(signupUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "apikey": API_KEY, + }, + body: JSON.stringify(payload), + }); + + console.log('📋 [CRIAR MÉDICO] Status da resposta:', response.status, response.statusText); + + if (!response.ok) { + const errorText = await response.text(); + console.error('❌ [CRIAR MÉDICO] Erro na resposta:', errorText); + + // Tenta parsear o erro para pegar mensagem específica + 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'; + } + } catch (e) { + // Se não conseguir parsear, usa mensagem genérica + } + + throw new Error(errorMsg); + } + + const responseData = await response.json(); + console.log('✅ [CRIAR MÉDICO] Usuário criado com sucesso no Supabase Auth!'); + console.log('🆔 User ID:', 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 MÉDICO] 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: medico.email, + password: senha, + }), + }); + + if (loginResponse.ok) { + const loginData = await loginResponse.json(); + console.log('✅ [AUTO-CONFIRMAR] Login automático realizado com sucesso!'); + console.log('📦 [AUTO-CONFIRMAR] Email confirmado:', loginData.user?.email_confirmed_at ? 'SIM ✅' : 'NÃO ❌'); + + // 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!'); + } + } 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 MÉDICO] Email confirmado automaticamente!'); + } + + // Log bem visível com as credenciais para teste + console.log('🔐🔐🔐 ========================================'); + console.log('🔐 CREDENCIAIS DO MÉDICO CRIADO:'); + console.log('🔐 Email:', medico.email); + console.log('🔐 Senha:', senha); + console.log('🔐 Pode fazer login?', responseData.user?.email_confirmed_at ? 'SIM ✅' : 'NÃO ❌ (precisa confirmar email)'); + console.log('🔐 ========================================'); + + return { + success: true, + user: responseData.user || responseData, + email: medico.email, + password: senha, + }; + + } catch (error: any) { + console.error('❌ [CRIAR MÉDICO] Erro ao criar usuário:', error); + throw error; + } +} + +// Criar usuário para PACIENTE no Supabase Auth (sistema de autenticação) +export async function criarUsuarioPaciente(paciente: { + email: string; + full_name: string; + phone_mobile: string; +}): Promise { + + 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) + const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`; + + const payload = { + email: paciente.email, + password: senha, + data: { + userType: 'paciente', // Para login em /login-paciente -> /paciente + full_name: paciente.full_name, + phone: paciente.phone_mobile, + } + }; + + console.log('📤 [CRIAR PACIENTE] Enviando para:', signupUrl); + + try { + const response = await fetch(signupUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "apikey": API_KEY, + }, + body: JSON.stringify(payload), + }); + + console.log('📋 [CRIAR PACIENTE] Status da resposta:', 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 + 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'; + } + } catch (e) { + // Se não conseguir parsear, usa mensagem genérica + } + + 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!'); + } + + // 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('🔐 ========================================'); + + 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); + throw error; + } +} + // ===== CEP (usado nos formulários) ===== export async function buscarCepAPI(cep: string): Promise<{ logradouro?: string; diff --git a/susconecta/lib/auth.ts b/susconecta/lib/auth.ts index 1e0ea62..e87a2ad 100644 --- a/susconecta/lib/auth.ts +++ b/susconecta/lib/auth.ts @@ -103,6 +103,11 @@ export async function loginUser( payload, timestamp: new Date().toLocaleTimeString() }); + + console.log('🔑 [AUTH-API] Credenciais sendo usadas no login:'); + console.log('📧 Email:', email); + console.log('🔐 Senha:', password); + console.log('👤 UserType:', userType); // Delay para visualizar na aba Network await new Promise(resolve => setTimeout(resolve, 50)); @@ -113,53 +118,19 @@ export async function loginUser( // Debug: Log request sem credenciais sensíveis debugRequest('POST', url, getLoginHeaders(), payload); - let response = await fetch(url, { + const response = await fetch(url, { method: 'POST', headers: getLoginHeaders(), body: JSON.stringify(payload), }); - // Se login falhar com 400, tentar criar usuário automaticamente - if (!response.ok && response.status === 400) { - console.log('[AUTH-API] Login falhou (400), tentando criar usuário...'); - - const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`; - const signupPayload = { - email, - password, - data: { - userType: userType, - name: email.split('@')[0], - } - }; - - debugRequest('POST', signupUrl, getLoginHeaders(), signupPayload); - - const signupResponse = await fetch(signupUrl, { - method: 'POST', - headers: getLoginHeaders(), - body: JSON.stringify(signupPayload), - }); - - if (signupResponse.ok) { - console.log('[AUTH-API] Usuário criado, tentando login novamente...'); - await new Promise(resolve => setTimeout(resolve, 100)); - - response = await fetch(url, { - method: 'POST', - headers: getLoginHeaders(), - body: JSON.stringify(payload), - }); - } - } - console.log(`[AUTH-API] Login response: ${response.status} ${response.statusText}`, { url: response.url, status: response.status, timestamp: new Date().toLocaleTimeString() }); - // Se ainda for 400, mostrar detalhes do erro + // Se falhar, mostrar detalhes do erro if (!response.ok) { try { const errorText = await response.text(); diff --git a/susconecta/next-env.d.ts b/susconecta/next-env.d.ts index 40c3d68..830fb59 100644 --- a/susconecta/next-env.d.ts +++ b/susconecta/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.