develop #83

Merged
M-Gabrielly merged 426 commits from develop into main 2025-12-04 04:13:15 +00:00
10 changed files with 1071 additions and 94 deletions
Showing only changes of commit 8955446bc7 - Show all commits

View File

@ -33,7 +33,16 @@ export default function LoginPacientePage() {
console.error('[LOGIN-PACIENTE] Erro no login:', err)
if (err instanceof AuthenticationError) {
// 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.')
}

View File

@ -35,7 +35,16 @@ export default function LoginPage() {
console.error('[LOGIN-PROFISSIONAL] Erro no login:', err)
if (err instanceof AuthenticationError) {
// 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.')
}

View File

@ -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 (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" />
{userType === "médico" ? "Médico" : "Paciente"} Cadastrado com Sucesso!
</DialogTitle>
<DialogDescription>
O {userType} <strong>{userName}</strong> foi cadastrado e pode fazer login com as credenciais abaixo.
</DialogDescription>
</DialogHeader>
<Alert className="bg-amber-50 border-amber-200">
<AlertDescription className="text-amber-900">
<strong>Importante:</strong> Anote ou copie estas credenciais agora. Por segurança, essa senha não será exibida novamente.
</AlertDescription>
</Alert>
<Alert className="bg-blue-50 border-blue-200">
<AlertDescription className="text-blue-900">
<strong>📧 Confirme o email:</strong> Um email de confirmação foi enviado para <strong>{email}</strong>.
O {userType} deve clicar no link de confirmação antes de fazer o primeiro login.
</AlertDescription>
</Alert>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="email">Email de Acesso</Label>
<div className="flex gap-2">
<Input
id="email"
value={email}
readOnly
className="bg-muted"
/>
<Button
type="button"
variant="outline"
size="icon"
onClick={handleCopyEmail}
title="Copiar email"
>
{copiedEmail ? <CheckCircle2 className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password">Senha Temporária</Label>
<div className="flex gap-2">
<div className="relative flex-1">
<Input
id="password"
type={showPassword ? "text" : "password"}
value={password}
readOnly
className="bg-muted pr-10"
/>
<Button
type="button"
variant="ghost"
size="icon"
className="absolute right-0 top-0 h-full"
onClick={() => setShowPassword(!showPassword)}
title={showPassword ? "Ocultar senha" : "Mostrar senha"}
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
<Button
type="button"
variant="outline"
size="icon"
onClick={handleCopyPassword}
title="Copiar senha"
>
{copiedPassword ? <CheckCircle2 className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
</div>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-md p-3 text-sm text-blue-900">
<strong>Próximos passos:</strong>
<ol className="list-decimal list-inside mt-2 space-y-1">
<li>Compartilhe estas credenciais com o {userType}</li>
<li>
<strong className="text-blue-700">O {userType} deve confirmar o email</strong> clicando no link enviado para{" "}
<strong>{email}</strong> (verifique também a pasta de spam)
</li>
<li>
Após confirmar o email, o {userType} deve acessar:{" "}
<code className="bg-blue-100 px-1 py-0.5 rounded text-xs font-mono">
{userType === "médico" ? "/login" : "/login-paciente"}
</code>
</li>
<li>
Após o login, terá acesso à área:{" "}
<code className="bg-blue-100 px-1 py-0.5 rounded text-xs font-mono">
{userType === "médico" ? "/profissional" : "/paciente"}
</code>
</li>
<li>Recomende trocar a senha no primeiro acesso</li>
</ol>
</div>
<DialogFooter className="flex-col sm:flex-row gap-2">
<Button
type="button"
variant="outline"
onClick={handleCopyBoth}
className="w-full sm:w-auto"
>
<Copy className="mr-2 h-4 w-4" />
Copiar Tudo
</Button>
<Button
type="button"
onClick={() => onOpenChange(false)}
className="w-full sm:w-auto"
>
Fechar
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@ -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 { CredentialsDialog } from "@/components/credentials-dialog";
type FormacaoAcademica = {
instituicao: string;
@ -151,6 +154,11 @@ export function DoctorRegistrationForm({
const [photoPreview, setPhotoPreview] = useState<string | null>(null);
const [serverAnexos, setServerAnexos] = useState<any[]>([]);
// Estados para o dialog de credenciais
const [showCredentials, setShowCredentials] = useState(false);
const [credentials, setCredentials] = useState<CreateUserWithPasswordResponse | null>(null);
const [savedDoctor, setSavedDoctor] = useState<Medico | null>(null);
const title = useMemo(() => (mode === "create" ? "Cadastro de Médico" : "Editar Médico"), [mode]);
useEffect(() => {
@ -391,8 +399,69 @@ if (missingFields.length > 0) {
console.log("✅ Médico salvo com sucesso:", saved);
onSaved?.(saved);
// 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:", {
@ -935,9 +1004,48 @@ if (missingFields.length > 0) {
</>
);
if (inline) return <div className="space-y-6">{content}</div>;
if (inline) {
return (
<>
<div className="space-y-6">{content}</div>
{/* Dialog de credenciais */}
{credentials && (
<CredentialsDialog
open={showCredentials}
onOpenChange={(open) => {
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 (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
@ -948,5 +1056,38 @@ if (missingFields.length > 0) {
{content}
</DialogContent>
</Dialog>
{/* Dialog de credenciais */}
{credentials && (
<CredentialsDialog
open={showCredentials}
onOpenChange={(open) => {
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"
/>
)}
</>
);
}

View File

@ -25,10 +25,13 @@ import {
listarAnexos,
removerAnexo,
buscarPacientePorId,
criarUsuarioPaciente,
CreateUserWithPasswordResponse,
} from "@/lib/api";
import { validarCPFLocal } from "@/lib/utils";
import { verificarCpfDuplicado } from "@/lib/api";
import { CredentialsDialog } from "@/components/credentials-dialog";
@ -105,6 +108,11 @@ export function PatientRegistrationForm({
const [photoPreview, setPhotoPreview] = useState<string | null>(null);
const [serverAnexos, setServerAnexos] = useState<any[]>([]);
// Estados para o dialog de credenciais
const [showCredentials, setShowCredentials] = useState(false);
const [credentials, setCredentials] = useState<CreateUserWithPasswordResponse | null>(null);
const [savedPatient, setSavedPatient] = useState<Paciente | null>(null);
const title = useMemo(() => (mode === "create" ? "Cadastro de Paciente" : "Editar Paciente"), [mode]);
@ -264,7 +272,77 @@ export function PatientRegistrationForm({
}
}
onSaved?.(saved);
// 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("<22> 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([]);
@ -273,6 +351,9 @@ export function PatientRegistrationForm({
else onOpenChange?.(false);
alert(mode === "create" ? "Paciente cadastrado!" : "Paciente atualizado!");
}
onSaved?.(saved);
} catch (err: any) {
setErrors({ submit: err?.message || "Erro ao salvar paciente." });
} finally {
@ -611,9 +692,53 @@ export function PatientRegistrationForm({
</>
);
if (inline) return <div className="space-y-6">{content}</div>;
if (inline) {
return (
<>
<div className="space-y-6">{content}</div>
{/* Debug */}
{console.log("🎨 RENDER inline - credentials:", credentials, "showCredentials:", showCredentials)}
{/* Dialog de credenciais */}
{credentials && (
<CredentialsDialog
open={showCredentials}
onOpenChange={(open) => {
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 (
<>
{console.log("🎨 RENDER dialog - credentials:", credentials, "showCredentials:", showCredentials)}
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
@ -624,5 +749,28 @@ export function PatientRegistrationForm({
{content}
</DialogContent>
</Dialog>
{/* Dialog de credenciais */}
{credentials && (
<CredentialsDialog
open={showCredentials}
onOpenChange={(open) => {
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"
/>
)}
</>
);
}

View File

@ -5,31 +5,84 @@ import eslint from "@eslint/js";
import nextPlugin from "@next/eslint-plugin-next";
import unicornPlugin from "eslint-plugin-unicorn";
import prettierConfig from "eslint-config-prettier";
import { FlatCompat } from "@eslint/eslintrc";
import { fileURLToPath } from "url";
import { dirname } from "path";
export default [
eslint.configs.recommended,
...tseslint.configs.recommended,
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname
});
const eslintConfig = [
{
ignores: ["node_modules/**", ".next/**", "out/**", "build/**", "next-env.d.ts"],
},
// Base JS/TS config
{
files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
plugins: {
"@next/next": nextPlugin,
"unicorn": unicornPlugin,
},
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
plugins: {
"@next/next": nextPlugin,
},
rules: {
...nextPlugin.configs.recommended.rules,
...nextPlugin.configs["core-web-vitals"].rules,
}
},
// TypeScript specific config
{
files: ["**/*.{ts,tsx}"],
plugins: {
"unicorn": unicornPlugin,
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: "./tsconfig.json",
},
},
rules: {
...nextPlugin.configs.recommended.rules,
...nextPlugin.configs["core-web-vitals"].rules,
...tseslint.configs.recommended.rules,
...unicornPlugin.configs.recommended.rules,
// Disable noisy unicorn rules
"unicorn/prevent-abbreviations": "off",
"unicorn/filename-case": "off",
"unicorn/no-null": "off",
"unicorn/consistent-function-scoping": "off",
"unicorn/no-array-for-each": "off",
"unicorn/catch-error-name": "off",
"unicorn/explicit-length-check": "off",
"unicorn/no-array-reduce": "off",
"unicorn/prefer-spread": "off",
"unicorn/no-document-cookie": "off",
"unicorn/prefer-query-selector": "off",
"unicorn/prefer-add-event-listener": "off",
"unicorn/prefer-string-slice": "off",
"unicorn/prefer-string-replace-all": "off",
"unicorn/prefer-number-properties": "off",
"unicorn/consistent-existence-index-check": "off",
"unicorn/no-negated-condition": "off",
"unicorn/switch-case-braces": "off",
"unicorn/prefer-global-this": "off",
"unicorn/no-useless-undefined": "off",
"unicorn/no-array-callback-reference": "off",
"unicorn/no-array-sort": "off",
"unicorn/numeric-separators-style": "off",
"unicorn/prefer-optional-catch-binding": "off",
"unicorn/prefer-ternary": "off",
"unicorn/prefer-code-point": "off",
"unicorn/prefer-single-call": "off",
}
},
prettierConfig,
...compat.extends("next/core-web-vitals"),
];
export default eslintConfig;

View File

@ -1,5 +1,8 @@
// lib/api.ts
import { ENV_CONFIG } from '@/lib/env-config';
import { API_KEY } from '@/lib/config';
export type ApiOk<T = any> = {
success?: boolean;
data: T;
@ -194,7 +197,32 @@ async function parse<T>(res: Response): Promise<T> {
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,432 @@ export async function excluirMedico(id: string | number): Promise<void> {
await parse<any>(res);
}
// ===== USUÁRIOS =====
export type UserRole = {
id: string;
user_id: string;
role: string;
created_at: string;
};
export async function listarUserRoles(): Promise<UserRole[]> {
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<UserRole[]>(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 ProfileInput = Partial<Omit<Profile, 'id' | 'created_at' | 'updated_at'>>;
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<CurrentUser> {
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<CurrentUser>(res);
}
export async function getUserInfo(): Promise<UserInfo> {
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<UserInfo>(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<CreateUserResponse> {
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<CreateUserResponse>(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<CreateUserWithPasswordResponse> {
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<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)
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;
@ -588,3 +1042,14 @@ export async function adicionarAnexoMedico(_id: string | number, _file: File): P
export async function removerAnexoMedico(_id: string | number, _anexoId: string | number): Promise<void> {}
export async function uploadFotoMedico(_id: string | number, _file: File): Promise<{ foto_url?: string; thumbnail_url?: string }> { return {}; }
export async function removerFotoMedico(_id: string | number): Promise<void> {}
// ===== PERFIS DE USUÁRIOS =====
export async function listarPerfis(): Promise<Profile[]> {
const url = `https://mock.apidog.com/m1/1053378-0-default/rest/v1/profiles`;
const res = await fetch(url, {
method: "GET",
headers: baseHeaders(),
});
return await parse<Profile[]>(res);
}

View File

@ -104,6 +104,11 @@ export async function loginUser(
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();

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "next build",
"dev": "next dev",
"lint": "next lint",
"lint": "eslint .",
"start": "next start"
},
"dependencies": {
@ -86,6 +86,7 @@
"tailwindcss": "^4.1.9",
"tw-animate-css": "1.3.3",
"typescript": "^5",
"typescript-eslint": "^8.45.0"
"typescript-eslint": "^8.45.0",
"@eslint/eslintrc": "^3"
}
}

View File

@ -183,6 +183,9 @@ importers:
specifier: 3.25.67
version: 3.25.67
devDependencies:
'@eslint/eslintrc':
specifier: ^3
version: 3.3.1
'@eslint/js':
specifier: ^9.36.0
version: 9.36.0