develop #83
@ -33,7 +33,16 @@ export default function LoginPacientePage() {
|
|||||||
console.error('[LOGIN-PACIENTE] Erro no login:', err)
|
console.error('[LOGIN-PACIENTE] Erro no login:', err)
|
||||||
|
|
||||||
if (err instanceof AuthenticationError) {
|
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 {
|
} else {
|
||||||
setError('Erro inesperado. Tente novamente.')
|
setError('Erro inesperado. Tente novamente.')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,16 @@ export default function LoginPage() {
|
|||||||
console.error('[LOGIN-PROFISSIONAL] Erro no login:', err)
|
console.error('[LOGIN-PROFISSIONAL] Erro no login:', err)
|
||||||
|
|
||||||
if (err instanceof AuthenticationError) {
|
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 {
|
} else {
|
||||||
setError('Erro inesperado. Tente novamente.')
|
setError('Erro inesperado. Tente novamente.')
|
||||||
}
|
}
|
||||||
|
|||||||
177
susconecta/components/credentials-dialog.tsx
Normal file
177
susconecta/components/credentials-dialog.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -24,10 +24,13 @@ import {
|
|||||||
removerAnexoMedico,
|
removerAnexoMedico,
|
||||||
MedicoInput, // 👈 importado do lib/api
|
MedicoInput, // 👈 importado do lib/api
|
||||||
Medico, // 👈 adicionado import do tipo Medico
|
Medico, // 👈 adicionado import do tipo Medico
|
||||||
|
criarUsuarioMedico,
|
||||||
|
CreateUserWithPasswordResponse,
|
||||||
} from "@/lib/api";
|
} from "@/lib/api";
|
||||||
;
|
;
|
||||||
|
|
||||||
import { buscarCepAPI } from "@/lib/api";
|
import { buscarCepAPI } from "@/lib/api";
|
||||||
|
import { CredentialsDialog } from "@/components/credentials-dialog";
|
||||||
|
|
||||||
type FormacaoAcademica = {
|
type FormacaoAcademica = {
|
||||||
instituicao: string;
|
instituicao: string;
|
||||||
@ -150,6 +153,11 @@ export function DoctorRegistrationForm({
|
|||||||
const [isSearchingCEP, setSearchingCEP] = useState(false);
|
const [isSearchingCEP, setSearchingCEP] = useState(false);
|
||||||
const [photoPreview, setPhotoPreview] = useState<string | null>(null);
|
const [photoPreview, setPhotoPreview] = useState<string | null>(null);
|
||||||
const [serverAnexos, setServerAnexos] = useState<any[]>([]);
|
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]);
|
const title = useMemo(() => (mode === "create" ? "Cadastro de Médico" : "Editar Médico"), [mode]);
|
||||||
|
|
||||||
@ -391,8 +399,69 @@ if (missingFields.length > 0) {
|
|||||||
|
|
||||||
console.log("✅ Médico salvo com sucesso:", saved);
|
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
|
||||||
setSubmitting(false);
|
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) {
|
} catch (err: any) {
|
||||||
console.error("❌ Erro ao salvar médico:", err);
|
console.error("❌ Erro ao salvar médico:", err);
|
||||||
console.error("❌ Detalhes do erro:", {
|
console.error("❌ Detalhes do erro:", {
|
||||||
@ -935,18 +1004,90 @@ 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 (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<>
|
||||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogHeader>
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogHeader>
|
||||||
<User className="h-5 w-5" /> {title}
|
<DialogTitle className="flex items-center gap-2">
|
||||||
</DialogTitle>
|
<User className="h-5 w-5" /> {title}
|
||||||
</DialogHeader>
|
</DialogTitle>
|
||||||
{content}
|
</DialogHeader>
|
||||||
</DialogContent>
|
{content}
|
||||||
</Dialog>
|
</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"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,10 +25,13 @@ import {
|
|||||||
listarAnexos,
|
listarAnexos,
|
||||||
removerAnexo,
|
removerAnexo,
|
||||||
buscarPacientePorId,
|
buscarPacientePorId,
|
||||||
|
criarUsuarioPaciente,
|
||||||
|
CreateUserWithPasswordResponse,
|
||||||
} from "@/lib/api";
|
} from "@/lib/api";
|
||||||
|
|
||||||
import { validarCPFLocal } from "@/lib/utils";
|
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 [isSearchingCEP, setSearchingCEP] = useState(false);
|
||||||
const [photoPreview, setPhotoPreview] = useState<string | null>(null);
|
const [photoPreview, setPhotoPreview] = useState<string | null>(null);
|
||||||
const [serverAnexos, setServerAnexos] = useState<any[]>([]);
|
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]);
|
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("<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([]);
|
||||||
|
|
||||||
|
if (inline) onClose?.();
|
||||||
|
else onOpenChange?.(false);
|
||||||
|
|
||||||
|
alert(mode === "create" ? "Paciente cadastrado!" : "Paciente atualizado!");
|
||||||
|
}
|
||||||
|
|
||||||
onSaved?.(saved);
|
onSaved?.(saved);
|
||||||
setForm(initial);
|
|
||||||
setPhotoPreview(null);
|
|
||||||
setServerAnexos([]);
|
|
||||||
|
|
||||||
if (inline) onClose?.();
|
|
||||||
else onOpenChange?.(false);
|
|
||||||
|
|
||||||
alert(mode === "create" ? "Paciente cadastrado!" : "Paciente atualizado!");
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setErrors({ submit: err?.message || "Erro ao salvar paciente." });
|
setErrors({ submit: err?.message || "Erro ao salvar paciente." });
|
||||||
} finally {
|
} finally {
|
||||||
@ -611,18 +692,85 @@ 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 (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<>
|
||||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
{console.log("🎨 RENDER dialog - credentials:", credentials, "showCredentials:", showCredentials)}
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<User className="h-5 w-5" /> {title}
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||||
</DialogTitle>
|
<DialogHeader>
|
||||||
</DialogHeader>
|
<DialogTitle className="flex items-center gap-2">
|
||||||
{content}
|
<User className="h-5 w-5" /> {title}
|
||||||
</DialogContent>
|
</DialogTitle>
|
||||||
</Dialog>
|
</DialogHeader>
|
||||||
|
{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"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,31 +5,84 @@ import eslint from "@eslint/js";
|
|||||||
import nextPlugin from "@next/eslint-plugin-next";
|
import nextPlugin from "@next/eslint-plugin-next";
|
||||||
import unicornPlugin from "eslint-plugin-unicorn";
|
import unicornPlugin from "eslint-plugin-unicorn";
|
||||||
import prettierConfig from "eslint-config-prettier";
|
import prettierConfig from "eslint-config-prettier";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { dirname } from "path";
|
||||||
|
|
||||||
export default [
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
eslint.configs.recommended,
|
const __dirname = dirname(__filename);
|
||||||
...tseslint.configs.recommended,
|
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}"],
|
files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
|
||||||
plugins: {
|
|
||||||
"@next/next": nextPlugin,
|
|
||||||
"unicorn": unicornPlugin,
|
|
||||||
},
|
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
...globals.browser,
|
...globals.browser,
|
||||||
...globals.node,
|
...globals.node,
|
||||||
},
|
},
|
||||||
parser: tseslint.parser,
|
},
|
||||||
parserOptions: {
|
plugins: {
|
||||||
project: "./tsconfig.json",
|
"@next/next": nextPlugin,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...nextPlugin.configs.recommended.rules,
|
...nextPlugin.configs.recommended.rules,
|
||||||
...nextPlugin.configs["core-web-vitals"].rules,
|
...nextPlugin.configs["core-web-vitals"].rules,
|
||||||
...unicornPlugin.configs.recommended.rules,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// TypeScript specific config
|
||||||
|
{
|
||||||
|
files: ["**/*.{ts,tsx}"],
|
||||||
|
plugins: {
|
||||||
|
"unicorn": unicornPlugin,
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
parserOptions: {
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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,
|
prettierConfig,
|
||||||
];
|
...compat.extends("next/core-web-vitals"),
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
|
|||||||
@ -103,6 +103,11 @@ export async function loginUser(
|
|||||||
payload,
|
payload,
|
||||||
timestamp: new Date().toLocaleTimeString()
|
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
|
// Delay para visualizar na aba Network
|
||||||
await new Promise(resolve => setTimeout(resolve, 50));
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
@ -113,53 +118,19 @@ export async function loginUser(
|
|||||||
// Debug: Log request sem credenciais sensíveis
|
// Debug: Log request sem credenciais sensíveis
|
||||||
debugRequest('POST', url, getLoginHeaders(), payload);
|
debugRequest('POST', url, getLoginHeaders(), payload);
|
||||||
|
|
||||||
let response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getLoginHeaders(),
|
headers: getLoginHeaders(),
|
||||||
body: JSON.stringify(payload),
|
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}`, {
|
console.log(`[AUTH-API] Login response: ${response.status} ${response.statusText}`, {
|
||||||
url: response.url,
|
url: response.url,
|
||||||
status: response.status,
|
status: response.status,
|
||||||
timestamp: new Date().toLocaleTimeString()
|
timestamp: new Date().toLocaleTimeString()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Se ainda for 400, mostrar detalhes do erro
|
// Se falhar, mostrar detalhes do erro
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
try {
|
try {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"lint": "next lint",
|
"lint": "eslint .",
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -86,6 +86,7 @@
|
|||||||
"tailwindcss": "^4.1.9",
|
"tailwindcss": "^4.1.9",
|
||||||
"tw-animate-css": "1.3.3",
|
"tw-animate-css": "1.3.3",
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
"typescript-eslint": "^8.45.0"
|
"typescript-eslint": "^8.45.0",
|
||||||
|
"@eslint/eslintrc": "^3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
susconecta/pnpm-lock.yaml
generated
3
susconecta/pnpm-lock.yaml
generated
@ -183,6 +183,9 @@ importers:
|
|||||||
specifier: 3.25.67
|
specifier: 3.25.67
|
||||||
version: 3.25.67
|
version: 3.25.67
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@eslint/eslintrc':
|
||||||
|
specifier: ^3
|
||||||
|
version: 3.3.1
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.36.0
|
specifier: ^9.36.0
|
||||||
version: 9.36.0
|
version: 9.36.0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user