diff --git a/susconecta/app/api/create-user/route.ts b/susconecta/app/api/create-user/route.ts new file mode 100644 index 0000000..35f12dc --- /dev/null +++ b/susconecta/app/api/create-user/route.ts @@ -0,0 +1,42 @@ +import { NextRequest, NextResponse } from 'next/server' +import { ENV_CONFIG } from '@/lib/env-config' + +export async function POST(req: NextRequest) { + try { + const body = await req.json().catch(() => ({})) + const target = `${ENV_CONFIG.SUPABASE_URL}/functions/v1/create-user` + const headers: Record = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'apikey': ENV_CONFIG.SUPABASE_ANON_KEY, + } + const auth = req.headers.get('authorization') + if (auth) headers.Authorization = auth + + const r = await fetch(target, { method: 'POST', headers, body: JSON.stringify(body) }) + if (r.status === 404 || r.status >= 500) { + // fallback to signup + const email = body.email + let password = body.password + const full_name = body.full_name + const phone = body.phone + const role = body.role || (Array.isArray(body.roles) ? body.roles[0] : undefined) + if (!password) password = `senha${Math.floor(Math.random()*900)+100}!` + const userType = (role && String(role).toLowerCase() === 'paciente') ? 'paciente' : 'profissional' + const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup` + const signupRes = await fetch(signupUrl, { + method: 'POST', + headers: { 'Content-Type':'application/json', 'Accept':'application/json', 'apikey': ENV_CONFIG.SUPABASE_ANON_KEY }, + body: JSON.stringify({ email, password, data: { userType, full_name, phone } }) + }) + const text = await signupRes.text() + try { return NextResponse.json({ fallback: true, from: 'signup', result: JSON.parse(text) }, { status: signupRes.status }) } catch { return new NextResponse(text, { status: signupRes.status }) } + } + + const text = await r.text() + try { return NextResponse.json(JSON.parse(text), { status: r.status }) } catch { return new NextResponse(text, { status: r.status }) } + } catch (err:any) { + console.error('[app/api/create-user] error', err) + return NextResponse.json({ error: 'Bad gateway', details: String(err) }, { status: 502 }) + } +} diff --git a/susconecta/components/admin/AssignmentForm.tsx b/susconecta/components/admin/AssignmentForm.tsx index 2c90560..a775b34 100644 --- a/susconecta/components/admin/AssignmentForm.tsx +++ b/susconecta/components/admin/AssignmentForm.tsx @@ -50,6 +50,15 @@ export default function AssignmentForm({ patientId, open, onClose, onSaved }: Pr if (open) load(); }, [open, patientId]); + // Resolve a display name for a professional id (user_id or id) + function resolveProfessionalName(userId: string | number | null | undefined) { + if (!userId) return String(userId ?? '') + const uid = String(userId) + const found = professionals.find(p => String(p.user_id ?? p.id) === uid) + if (found) return found.full_name || found.name || found.email || String(found.user_id ?? found.id) + return uid + } + async function handleSave() { if (!selectedProfessional) return toast({ title: 'Selecione um profissional', variant: 'default' }); setLoading(true); @@ -96,7 +105,7 @@ export default function AssignmentForm({ patientId, open, onClose, onSaved }: Pr diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index a2a2b53..ff40e79 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -1618,6 +1618,19 @@ export function gerarSenhaAleatoria(): string { } export async function criarUsuario(input: CreateUserInput): Promise { + // When running in the browser, call our Next.js proxy to avoid CORS/preflight + // issues that some Edge Functions may have. On server-side, call the function + // directly. + if (typeof window !== 'undefined') { + const proxyUrl = '/api/create-user' + const res = await fetch(proxyUrl, { + method: 'POST', + headers: { ...baseHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify(input), + }) + return await parse(res as Response) + } + const url = `${API_BASE}/functions/v1/create-user`; const res = await fetch(url, { method: "POST", @@ -1709,298 +1722,94 @@ export async function criarUsuarioDirectAuth(input: { // ============================================ // 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 { - +export async function criarUsuarioMedico(medico: { email: string; full_name: string; phone_mobile: string; }): Promise { + // Prefer server-side creation (new OpenAPI create-user) so roles are assigned + // correctly (and magic link is sent). Fallback to direct Supabase signup if + // the server function is unavailable. + try { + const res = await criarUsuario({ email: medico.email, password: '', full_name: medico.full_name, phone: medico.phone_mobile, role: 'medico' as any }); + return res; + } catch (err) { + console.warn('[CRIAR MÉDICO] Falha no endpoint server-side create-user, tentando fallback direto no Supabase Auth:', err); + // Fallback: create directly in Supabase Auth (old behavior) + } + + // --- Fallback to previous direct signup --- 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 + userType: '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": ENV_CONFIG.SUPABASE_ANON_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; + + const response = await fetch(signupUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "apikey": ENV_CONFIG.SUPABASE_ANON_KEY, + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + const errorText = await response.text(); + let errorMsg = `Erro ao criar usuário (${response.status})`; + try { const errorData = JSON.parse(errorText); errorMsg = errorData.msg || errorData.message || errorData.error_description || errorMsg; } catch {} + throw new Error(errorMsg); } + + const responseData = await response.json(); + return { success: true, user: responseData.user || responseData, email: medico.email, password: senha }; } // 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 { - +export async function criarUsuarioPaciente(paciente: { email: string; full_name: string; phone_mobile: string; }): Promise { + // Prefer server-side creation (OpenAPI create-user) to assign role 'paciente'. + try { + const res = await criarUsuario({ email: paciente.email, password: '', full_name: paciente.full_name, phone: paciente.phone_mobile, role: 'paciente' as any }); + return res; + } catch (err) { + console.warn('[CRIAR PACIENTE] Falha no endpoint server-side create-user, tentando fallback direto no Supabase Auth:', err); + } + + // Fallback to previous direct signup behavior 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 + userType: '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": ENV_CONFIG.SUPABASE_ANON_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; + + const response = await fetch(signupUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "apikey": ENV_CONFIG.SUPABASE_ANON_KEY, + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + const errorText = await response.text(); + let errorMsg = `Erro ao criar usuário (${response.status})`; + try { const errorData = JSON.parse(errorText); errorMsg = errorData.msg || errorData.message || errorData.error_description || errorMsg; } catch {} + throw new Error(errorMsg); } + + const responseData = await response.json(); + return { success: true, user: responseData.user || responseData, email: paciente.email, password: senha }; } // ===== CEP (usado nos formulários) ===== diff --git a/susconecta/pages/api/create-user.ts b/susconecta/pages/api/create-user.ts new file mode 100644 index 0000000..515a20d --- /dev/null +++ b/susconecta/pages/api/create-user.ts @@ -0,0 +1,85 @@ +import type { NextApiRequest, NextApiResponse } from 'next' +import { ENV_CONFIG } from '@/lib/env-config' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' }) + + try { + const target = `${ENV_CONFIG.SUPABASE_URL}/functions/v1/create-user` + const headers: Record = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'apikey': ENV_CONFIG.SUPABASE_ANON_KEY, + } + + // forward authorization header if present (keeps the caller's identity) + if (req.headers.authorization) headers.Authorization = String(req.headers.authorization) + + const r = await fetch(target, { + method: 'POST', + headers, + body: JSON.stringify(req.body), + }) + // If the function is not available (404) or returns server error (5xx), + // perform a fallback: create the user via Supabase Auth signup so the + // client doesn't experience a hard 404. + if (r.status === 404 || r.status >= 500) { + console.warn('[proxy/create-user] function returned', r.status, 'falling back to signup') + // attempt signup + try { + const body = req.body || {} + const email = body.email + let password = body.password + const full_name = body.full_name + const phone = body.phone + const role = body.role || (Array.isArray(body.roles) ? body.roles[0] : undefined) + + // generate a password if none provided + if (!password) { + const rand = Math.floor(Math.random() * 900) + 100 + password = `senha${rand}!` + } + + const userType = (role && String(role).toLowerCase() === 'paciente') ? 'paciente' : 'profissional' + + const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup` + const signupRes = await fetch(signupUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'apikey': ENV_CONFIG.SUPABASE_ANON_KEY, + }, + body: JSON.stringify({ + email, + password, + data: { userType, full_name, phone } + }), + }) + + const signupText = await signupRes.text() + try { + const signupJson = JSON.parse(signupText) + return res.status(signupRes.status).json({ fallback: true, from: 'signup', result: signupJson }) + } catch { + return res.status(signupRes.status).send(signupText) + } + } catch (err2: any) { + console.error('[proxy/create-user] fallback signup failed', err2) + return res.status(502).json({ error: 'Bad gateway (fallback failed)', details: String(err2) }) + } + } + + const text = await r.text() + try { + const json = JSON.parse(text) + return res.status(r.status).json(json) + } catch { + // not JSON, return raw text + res.status(r.status).send(text) + } + } catch (err: any) { + console.error('[proxy/create-user] error', err) + return res.status(502).json({ error: 'Bad gateway', details: String(err) }) + } +}