Files
scoreodonto.com/frontend/views/TutorCandidaturaView.tsx
T
VPS 4 Builder ba062e92d0 fix(login): render standalone views edge-to-edge e esconde scrollbar do carrossel de onboarding
- App.tsx: wrappers de padding (p-4/md:p-8) e max-w-7xl/mx-auto agora só se aplicam quando não é tela standalone; login/landing/public renderizam full-bleed
- OnboardingChat.tsx: classe ob-noscroll esconde a barra de rolagem do slide intro (mantém scroll), substituindo a custom-scrollbar indefinida no fluxo de login

Inclui também demais alterações pendentes do working tree (snapshot do estado atual de dev).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 13:23:19 +02:00

622 lines
32 KiB
TypeScript

import React, { useState, useEffect, useCallback } from 'react';
import {
GraduationCap, ChevronRight, ChevronLeft, CheckCircle2,
Clock, XCircle, CreditCard, ExternalLink, Loader2,
Award, Stethoscope, User, DollarSign, AlertCircle, RefreshCw,
Banknote
} from 'lucide-react';
import { HybridBackend } from '../services/backend.ts';
import { useToast } from '../contexts/ToastContext.tsx';
import { PageHeader } from '../components/PageHeader.tsx';
// ─── Dados do formulário ──────────────────────────────────────────
const TITULACOES = ['ESPECIALISTA', 'MESTRE', 'DOUTOR', 'PHD'];
const ESPECIALIDADES = [
'ORTODONTIA', 'ORTOPEDIA FACIAL', 'ALINHADORES INVISÍVEIS',
'ORTO-CIRÚRGICA', 'ORTODONTIA LINGUAL', 'APARELHO AUTOLIGADO',
'CONTENÇÃO E RECIDIVA', 'ORTOPEDIA DOS MAXILARES',
];
// ─── Status display ───────────────────────────────────────────────
function StatusBanner({ candidatura, onPagar }: { candidatura: any; onPagar: () => void }) {
const statusMap: Record<string, { icon: React.ReactNode; cor: string; titulo: string; subtitulo: string }> = {
PENDENTE: {
icon: <Clock size={32} className="text-amber-500" />,
cor: 'bg-amber-50 border-amber-200',
titulo: 'CANDIDATURA EM ANÁLISE',
subtitulo: 'Sua candidatura foi recebida e está sendo analisada pela equipe Scoreodonto. Você receberá uma notificação quando houver atualização.',
},
AGUARDANDO_PAGAMENTO: {
icon: <CreditCard size={32} className="text-blue-500" />,
cor: 'bg-blue-50 border-blue-200',
titulo: 'APROVADO — AGUARDANDO PAGAMENTO DA TAXA',
subtitulo: 'Parabéns! Sua candidatura foi aprovada. Realize o pagamento da taxa de ativação para liberar seu perfil como tutor.',
},
ATIVO: {
icon: <CheckCircle2 size={32} className="text-green-500" />,
cor: 'bg-green-50 border-green-200',
titulo: 'PERFIL ATIVO — VOCÊ É UM TUTOR SCOREODONTO!',
subtitulo: 'Seu perfil está ativo e disponível para todas as clínicas do sistema. Acesse o Painel do Tutor para ver e responder as solicitações.',
},
REJEITADO: {
icon: <XCircle size={32} className="text-red-500" />,
cor: 'bg-red-50 border-red-200',
titulo: 'CANDIDATURA NÃO APROVADA',
subtitulo: candidatura.observacao_admin || 'Sua candidatura não foi aprovada neste momento. Você pode submeter uma nova candidatura após 30 dias.',
},
};
const info = statusMap[candidatura.status];
if (!info) return null;
return (
<div className={`border rounded-2xl p-6 ${info.cor}`}>
<div className="flex items-start gap-4">
{info.icon}
<div className="flex-1">
<h3 className="font-black text-gray-800 uppercase text-sm mb-1">{info.titulo}</h3>
<p className="text-xs text-gray-600 font-medium leading-relaxed">{info.subtitulo}</p>
{candidatura.status === 'AGUARDANDO_PAGAMENTO' && (
<button
onClick={onPagar}
className="mt-4 flex items-center gap-2 px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white rounded-xl text-[11px] font-black uppercase transition-colors shadow-sm"
>
<CreditCard size={14} /> PAGAR TAXA DE ATIVAÇÃO
</button>
)}
{candidatura.status === 'REJEITADO' && (
<p className="mt-2 text-[10px] text-red-600 font-bold uppercase">
Candidatura em {new Date(candidatura.created_at).toLocaleDateString()}
</p>
)}
</div>
</div>
</div>
);
}
// ─── Bloco de resumo do perfil ativo ─────────────────────────────
// ─── Seção de credenciais de pagamento (tutor ativo) ─────────────
function CredenciaisPagamento({ candidatura }: { candidatura: any }) {
const toast = useToast();
const [tutor, setTutor] = useState<any>(null);
const [pixKey, setPixKey] = useState('');
const [mpEmail, setMpEmail] = useState('');
const [aceitouSplit, setAceitouSplit] = useState(false);
const [comissaoPct, setComissaoPct] = useState<number | null>(null);
const [salvando, setSalvando] = useState(false);
useEffect(() => {
const userData = JSON.parse(localStorage.getItem('SCOREODONTO_USER_DATA') || '{}');
HybridBackend.getTutorPorUsuario(userData.id || userData.userId).then(r => {
if (r.success && r.tutor) {
setTutor(r.tutor);
setPixKey(r.tutor.pix_key || '');
setMpEmail(r.tutor.mp_email || '');
setAceitouSplit(!!r.tutor.aceite_split_pct);
}
});
HybridBackend.getAdminTutorConfig().then(r => {
if (r.success) setComissaoPct(parseFloat(r.config?.comissao_pct) || 20);
});
}, []);
const save = async () => {
if (!pixKey.trim()) { toast.error('CHAVE PIX OBRIGATÓRIA.'); return; }
if (!aceitouSplit) { toast.error('VOCÊ DEVE ACEITAR O PERCENTUAL DA PLATAFORMA.'); return; }
if (!tutor?.id) { toast.error('PERFIL DE TUTOR NÃO ENCONTRADO.'); return; }
setSalvando(true);
try {
const res = await HybridBackend.updateCredenciaisPagamentoTutor(tutor.id, { pix_key: pixKey, mp_email: mpEmail, aceitar_split: aceitouSplit });
if (!res.success) { toast.error(res.message || 'ERRO.'); return; }
toast.success('CREDENCIAIS SALVAS E SPLIT ACEITO!');
} catch { toast.error('ERRO AO SALVAR.'); }
finally { setSalvando(false); }
};
return (
<div className="bg-white border border-gray-200 rounded-2xl p-6 space-y-4">
<h4 className="font-black text-gray-700 uppercase text-sm flex items-center gap-2">
<DollarSign size={16} className="text-green-500" /> DADOS PARA RECEBIMENTO
</h4>
{/* Split info */}
{comissaoPct !== null && (
<div className="bg-indigo-50 border border-indigo-100 rounded-xl p-4 grid grid-cols-2 gap-4 text-center">
<div>
<p className="text-[9px] font-black text-gray-400 uppercase">COMISSÃO DA PLATAFORMA</p>
<p className="text-2xl font-black text-indigo-600">{comissaoPct}%</p>
</div>
<div>
<p className="text-[9px] font-black text-gray-400 uppercase">SEU REPASSE POR CONSULTA</p>
<p className="text-2xl font-black text-green-600">{100 - comissaoPct}%</p>
{candidatura.preco_por_consulta && (
<p className="text-[10px] text-green-500 font-bold">
R$ {((1 - comissaoPct / 100) * Number(candidatura.preco_por_consulta)).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}
</p>
)}
</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">CHAVE PIX PARA RECEBIMENTO *</label>
<input type="text" value={pixKey} onChange={e => setPixKey(e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium focus:outline-none focus:border-green-400"
placeholder="CPF, e-mail, celular ou chave aleatória" />
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">E-MAIL MERCADOPAGO (OPCIONAL)</label>
<input type="email" value={mpEmail} onChange={e => setMpEmail(e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium focus:outline-none focus:border-green-400"
placeholder="email@mercadopago.com" />
</div>
</div>
{/* Aceite do split */}
<label className={`flex items-start gap-3 p-4 rounded-xl border-2 cursor-pointer transition-all ${aceitouSplit ? 'border-green-500 bg-green-50' : 'border-gray-200 hover:border-green-300'}`}
onClick={() => setAceitouSplit(!aceitouSplit)}>
<div className={`w-5 h-5 rounded border-2 flex items-center justify-center flex-shrink-0 mt-0.5 transition-all ${aceitouSplit ? 'bg-green-500 border-green-500' : 'border-gray-300'}`}>
{aceitouSplit && <CheckCircle2 size={12} className="text-white" />}
</div>
<p className="text-[11px] font-black text-gray-700 uppercase leading-relaxed">
LI E ACEITO O MODELO DE SPLIT: A PLATAFORMA SCOREODONTO RETERÁ {comissaoPct ?? '—'}% DO VALOR DE CADA CONSULTORIA DE TUTORIA.
MEU REPASSE SERÁ DE {comissaoPct !== null ? 100 - comissaoPct : '—'}% VIA PIX APÓS CONFIRMAÇÃO DO PAGAMENTO PELA CLÍNICA.
</p>
</label>
{tutor?.aceite_split_pct && (
<div className="flex items-center gap-2 text-green-600 text-[10px] font-black uppercase">
<CheckCircle2 size={14} /> SPLIT ACEITO EM {tutor.aceite_split_at ? new Date(tutor.aceite_split_at).toLocaleDateString('pt-BR') : '—'} {tutor.aceite_split_pct}% DE COMISSÃO
</div>
)}
<button onClick={save} disabled={salvando || !pixKey.trim() || !aceitouSplit}
className="w-full py-2.5 bg-green-600 hover:bg-green-700 text-white rounded-xl text-[11px] font-black uppercase transition-colors disabled:opacity-40 flex items-center justify-center gap-2">
{salvando ? <><Loader2 size={13} className="animate-spin" /> SALVANDO...</> : <><CheckCircle2 size={13} /> SALVAR CREDENCIAIS E ACEITAR SPLIT</>}
</button>
</div>
);
}
function PerfilAtivoCard({ candidatura }: { candidatura: any }) {
return (
<div className="bg-white border border-gray-200 rounded-2xl p-6 space-y-4">
<div className="flex items-center gap-4">
<div className="w-14 h-14 bg-indigo-100 rounded-full flex items-center justify-center text-indigo-600 font-black text-lg">
{candidatura.nome?.charAt(0)}
</div>
<div>
<h3 className="font-black text-gray-800 text-lg uppercase">{candidatura.nome}</h3>
<p className="text-[11px] text-gray-500 font-bold uppercase">{candidatura.titulacao} {candidatura.especialidade_principal}</p>
{candidatura.cro && <p className="text-[10px] text-gray-400 font-bold">CRO {candidatura.cro_uf}/{candidatura.cro}</p>}
</div>
<div className="ml-auto text-right">
{candidatura.preco_por_consulta && (
<div>
<p className="text-2xl font-black text-indigo-600">R$ {Number(candidatura.preco_por_consulta).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}</p>
<p className="text-[10px] text-gray-400 font-bold uppercase">POR CONSULTA</p>
</div>
)}
</div>
</div>
{candidatura.bio && (
<p className="text-xs text-gray-600 border-t border-gray-100 pt-3 leading-relaxed">{candidatura.bio}</p>
)}
<div className="grid grid-cols-2 gap-3">
{candidatura.anos_experiencia && (
<div className="bg-gray-50 rounded-xl p-3">
<p className="text-[9px] font-black text-gray-400 uppercase">EXPERIÊNCIA</p>
<p className="font-black text-gray-700 text-sm">{candidatura.anos_experiencia} ANOS</p>
</div>
)}
{candidatura.instituicao && (
<div className="bg-gray-50 rounded-xl p-3">
<p className="text-[9px] font-black text-gray-400 uppercase">INSTITUIÇÃO</p>
<p className="font-black text-gray-700 text-sm truncate">{candidatura.instituicao}</p>
</div>
)}
</div>
</div>
);
}
// ─── Formulário em 3 passos ───────────────────────────────────────
interface FormData {
nome: string; email: string; telefone: string; cro: string; cro_uf: string;
titulacao: string; instituicao: string; ano_conclusao: string;
especialidade_principal: string; especialidades_adicionais: string[];
anos_experiencia: string;
bio: string; preco_por_consulta: string; portfolio_url: string; linkedin_url: string;
}
const EMPTY: FormData = {
nome: '', email: '', telefone: '', cro: '', cro_uf: '',
titulacao: '', instituicao: '', ano_conclusao: '',
especialidade_principal: '', especialidades_adicionais: [],
anos_experiencia: '',
bio: '', preco_por_consulta: '', portfolio_url: '', linkedin_url: '',
};
function CandidaturaForm({ onSubmitted, initialData }: { onSubmitted: () => void; initialData?: Partial<FormData> }) {
const toast = useToast();
const [step, setStep] = useState(0);
const [form, setForm] = useState<FormData>({ ...EMPTY, ...initialData });
const [salvando, setSalvando] = useState(false);
const set = (field: keyof FormData, value: any) => setForm(prev => ({ ...prev, [field]: value }));
const toggleEsp = (esp: string) => {
const cur = form.especialidades_adicionais;
set('especialidades_adicionais', cur.includes(esp) ? cur.filter(e => e !== esp) : [...cur, esp]);
};
const steps = [
{
label: 'DADOS PESSOAIS',
icon: <User size={16} />,
valid: () => form.nome && form.cro && form.cro_uf && form.email,
content: (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="md:col-span-2">
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">NOME COMPLETO *</label>
<input type="text" value={form.nome} onChange={e => set('nome', e.target.value.toUpperCase())}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400 uppercase" placeholder="DR. JOÃO SILVA" />
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">E-MAIL *</label>
<input type="email" value={form.email} onChange={e => set('email', e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium focus:outline-none focus:border-indigo-400" placeholder="dr@email.com" />
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">TELEFONE</label>
<input type="text" value={form.telefone} onChange={e => set('telefone', e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium focus:outline-none focus:border-indigo-400" placeholder="(11) 99999-9999" />
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">CRO *</label>
<input type="text" value={form.cro} onChange={e => set('cro', e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400" placeholder="123456" />
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">CRO UF *</label>
<input type="text" maxLength={2} value={form.cro_uf} onChange={e => set('cro_uf', e.target.value.toUpperCase())}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400 uppercase" placeholder="SP" />
</div>
</div>
),
},
{
label: 'FORMAÇÃO ACADÊMICA',
icon: <Award size={16} />,
valid: () => form.titulacao && form.especialidade_principal && form.anos_experiencia,
content: (
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">TITULAÇÃO *</label>
<select value={form.titulacao} onChange={e => set('titulacao', e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400">
<option value=""> SELECIONE </option>
{TITULACOES.map(t => <option key={t} value={t}>{t}</option>)}
</select>
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">ANOS DE EXPERIÊNCIA *</label>
<input type="number" min={0} max={50} value={form.anos_experiencia} onChange={e => set('anos_experiencia', e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400" placeholder="10" />
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">INSTITUIÇÃO DE FORMAÇÃO</label>
<input type="text" value={form.instituicao} onChange={e => set('instituicao', e.target.value.toUpperCase())}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400 uppercase" placeholder="USP, UNICAMP..." />
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">ANO DE CONCLUSÃO</label>
<input type="number" min={1980} max={2030} value={form.ano_conclusao} onChange={e => set('ano_conclusao', e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400" placeholder="2015" />
</div>
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-2">ESPECIALIDADE PRINCIPAL *</label>
<select value={form.especialidade_principal} onChange={e => set('especialidade_principal', e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400">
<option value=""> SELECIONE </option>
{ESPECIALIDADES.map(e => <option key={e} value={e}>{e}</option>)}
</select>
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-2">ESPECIALIDADES ADICIONAIS (OPCIONAL)</label>
<div className="flex flex-wrap gap-2">
{ESPECIALIDADES.filter(e => e !== form.especialidade_principal).map(esp => (
<button key={esp} type="button" onClick={() => toggleEsp(esp)}
className={`px-3 py-1.5 rounded-full text-[10px] font-black uppercase border transition-all ${
form.especialidades_adicionais.includes(esp)
? 'bg-indigo-600 text-white border-indigo-600'
: 'bg-white text-gray-600 border-gray-200 hover:border-indigo-300'
}`}>
{esp}
</button>
))}
</div>
</div>
</div>
),
},
{
label: 'PERFIL PÚBLICO',
icon: <Stethoscope size={16} />,
valid: () => form.bio && form.preco_por_consulta,
content: (
<div className="space-y-4">
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">BIO / APRESENTAÇÃO * <span className="text-gray-400 font-normal normal-case">(visível para as clínicas)</span></label>
<textarea value={form.bio} onChange={e => set('bio', e.target.value)}
placeholder="Descreva sua experiência, abordagem clínica e diferenciais como tutor..."
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium focus:outline-none focus:border-indigo-400 h-28 resize-none" />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">PREÇO POR CONSULTA DE TUTORIA (R$) *</label>
<input type="number" min={0} step={10} value={form.preco_por_consulta} onChange={e => set('preco_por_consulta', e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400" placeholder="350" />
<p className="text-[10px] text-gray-400 mt-1">VALOR COBRADO DIRETAMENTE DA CLÍNICA QUE CONTRATAR VOCÊ</p>
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">LINKEDIN (OPCIONAL)</label>
<input type="url" value={form.linkedin_url} onChange={e => set('linkedin_url', e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium focus:outline-none focus:border-indigo-400" placeholder="https://linkedin.com/in/..." />
</div>
<div className="md:col-span-2">
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">LINK PARA PORTFÓLIO / CURRÍCULO (OPCIONAL)</label>
<input type="url" value={form.portfolio_url} onChange={e => set('portfolio_url', e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium focus:outline-none focus:border-indigo-400" placeholder="https://..." />
</div>
</div>
</div>
),
},
];
const handleSubmit = async () => {
setSalvando(true);
try {
const userData = JSON.parse(localStorage.getItem('SCOREODONTO_USER_DATA') || '{}');
const res = await HybridBackend.submitCandidatura({
usuario_id: userData.id || userData.userId,
...form,
anos_experiencia: parseInt(form.anos_experiencia) || null,
ano_conclusao: parseInt(form.ano_conclusao) || null,
preco_por_consulta: parseFloat(form.preco_por_consulta) || null,
});
if (!res.success) { toast.error(res.message || 'ERRO AO ENVIAR.'); return; }
toast.success('CANDIDATURA ENVIADA! AGUARDE ANÁLISE DA EQUIPE SCOREODONTO.');
onSubmitted();
} catch { toast.error('ERRO AO ENVIAR CANDIDATURA.'); }
finally { setSalvando(false); }
};
return (
<div className="space-y-6">
{/* Steps indicator */}
<div className="flex items-center gap-2">
{steps.map((s, i) => (
<React.Fragment key={i}>
<div className={`flex items-center gap-2 px-3 py-1.5 rounded-xl text-[10px] font-black uppercase transition-all ${
i === step ? 'bg-indigo-600 text-white' : i < step ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-400'
}`}>
{i < step ? <CheckCircle2 size={12} /> : s.icon}
<span className="hidden sm:inline">{s.label}</span>
<span className="sm:hidden">{i + 1}</span>
</div>
{i < steps.length - 1 && <div className="flex-1 h-px bg-gray-200" />}
</React.Fragment>
))}
</div>
{/* Conteúdo do passo */}
<div className="bg-white border border-gray-200 rounded-2xl p-6">
<h3 className="font-black text-gray-700 uppercase text-sm mb-5 flex items-center gap-2">
{steps[step].icon} {steps[step].label}
</h3>
{steps[step].content}
</div>
{/* Navegação */}
<div className="flex gap-3">
{step > 0 && (
<button onClick={() => setStep(s => s - 1)}
className="flex items-center gap-2 px-4 py-2.5 border border-gray-200 rounded-xl text-[11px] font-black uppercase hover:bg-gray-50 transition-colors">
<ChevronLeft size={14} /> VOLTAR
</button>
)}
{step < steps.length - 1 ? (
<button onClick={() => setStep(s => s + 1)} disabled={!steps[step].valid()}
className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl text-[11px] font-black uppercase transition-colors disabled:opacity-40">
PRÓXIMO <ChevronRight size={14} />
</button>
) : (
<button onClick={handleSubmit} disabled={salvando || !steps[step].valid()}
className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-green-600 hover:bg-green-700 text-white rounded-xl text-[11px] font-black uppercase transition-colors disabled:opacity-40">
{salvando ? <><Loader2 size={13} className="animate-spin" /> ENVIANDO...</> : <><CheckCircle2 size={13} /> ENVIAR CANDIDATURA</>}
</button>
)}
</div>
</div>
);
}
// ─── Modal de pagamento MP ────────────────────────────────────────
function PagamentoModal({ candidaturaId, onClose, onPago }: { candidaturaId: string; onClose: () => void; onPago: () => void }) {
const toast = useToast();
const [loading, setLoading] = useState(false);
const [checkoutData, setCheckoutData] = useState<{ checkout_url: string; taxa: number } | null>(null);
const iniciar = async () => {
setLoading(true);
try {
const res = await HybridBackend.iniciarPagamentoCandidatura(candidaturaId);
if (!res.success) { toast.error(res.message || 'ERRO AO GERAR LINK DE PAGAMENTO.'); return; }
setCheckoutData(res);
} catch { toast.error('ERRO AO GERAR LINK.'); }
finally { setLoading(false); }
};
useEffect(() => { iniciar(); }, []);
return (
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-md">
<div className="p-6 space-y-5">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-100 rounded-xl flex items-center justify-center">
<CreditCard size={18} className="text-blue-600" />
</div>
<div>
<h3 className="font-black text-gray-800 uppercase text-sm">PAGAMENTO DA TAXA DE ATIVAÇÃO</h3>
<p className="text-[10px] text-gray-400 font-bold uppercase">VIA MERCADOPAGO PIX / CARTÃO</p>
</div>
</div>
{loading ? (
<div className="flex items-center justify-center py-8 gap-3 text-gray-400">
<Loader2 size={22} className="animate-spin" />
<span className="text-xs font-bold uppercase">GERANDO LINK DE PAGAMENTO...</span>
</div>
) : checkoutData ? (
<>
<div className="bg-blue-50 border border-blue-100 rounded-xl p-4 text-center">
<p className="text-3xl font-black text-blue-600">R$ {Number(checkoutData.taxa).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}</p>
<p className="text-[10px] text-blue-500 font-bold uppercase mt-1">TAXA DE ATIVAÇÃO PAGAMENTO ÚNICO</p>
</div>
<div className="bg-gray-50 border border-gray-200 rounded-xl p-3">
<p className="text-[10px] font-black text-gray-400 uppercase mb-1">FORMAS DE PAGAMENTO ACEITAS</p>
<p className="text-xs text-gray-600 font-medium">PIX · CARTÃO DE CRÉDITO · BOLETO</p>
</div>
<a href={checkoutData.checkout_url} target="_blank" rel="noopener noreferrer"
className="flex items-center justify-center gap-2 w-full py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-xl font-black text-[11px] uppercase transition-colors shadow-sm">
<ExternalLink size={14} /> PAGAR AGORA VIA MERCADOPAGO
</a>
<p className="text-[10px] text-gray-400 text-center">APÓS O PAGAMENTO APROVADO SEU PERFIL SERÁ ATIVADO AUTOMATICAMENTE</p>
</>
) : (
<div className="flex items-center gap-2 text-red-500 text-xs font-bold uppercase">
<AlertCircle size={16} /> ERRO AO GERAR LINK. TENTE NOVAMENTE.
</div>
)}
<button onClick={onClose} className="w-full py-2.5 border border-gray-200 rounded-xl text-[11px] font-black uppercase hover:bg-gray-50 transition-colors">
FECHAR
</button>
</div>
</div>
</div>
);
}
// ─── View principal ───────────────────────────────────────────────
export const TutorCandidaturaView: React.FC = () => {
const [candidatura, setCandidatura] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [showPagamento, setShowPagamento] = useState(false);
const userData = JSON.parse(localStorage.getItem('SCOREODONTO_USER_DATA') || '{}');
const userId = userData.id || userData.userId;
const load = useCallback(async () => {
setLoading(true);
try {
const res = await HybridBackend.getMinhaCandidatura(userId);
if (res.success) setCandidatura(res.candidatura);
} catch {}
finally { setLoading(false); }
}, [userId]);
useEffect(() => { load(); }, [load]);
// Abre modal de pagamento se voltou do MP com ?payment=success/pending
useEffect(() => {
const params = new URLSearchParams(window.location.search);
if (params.get('payment')) { load(); }
}, []);
return (
<div className="space-y-6">
<PageHeader title="CANDIDATURA PARA TUTOR ORTODÔNTICO">
<button onClick={load} className="p-2 hover:bg-gray-100 rounded-lg transition-colors text-gray-400">
<RefreshCw size={15} />
</button>
</PageHeader>
{loading ? (
<div className="flex items-center justify-center h-40"><Loader2 size={28} className="animate-spin text-gray-300" /></div>
) : !candidatura ? (
// Sem candidatura — mostra intro + formulário
<div className="space-y-6">
<div className="bg-indigo-50 border border-indigo-100 rounded-2xl p-6">
<div className="flex items-start gap-4">
<GraduationCap size={32} className="text-indigo-500 flex-shrink-0" />
<div>
<h3 className="font-black text-gray-800 uppercase text-sm mb-2">TORNE-SE UM TUTOR ORTODÔNTICO SCOREODONTO</h3>
<p className="text-xs text-gray-600 font-medium leading-relaxed mb-3">
Como tutor, você disponibiliza seu conhecimento para clínicas de todo o Brasil através da plataforma.
Receba solicitações de segunda opinião, planejamento e mentoria clínica no seu tempo, no seu preço.
</p>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
{[
{ icon: '🌎', label: 'Visibilidade nacional', desc: 'Seu perfil disponível para todas as clínicas do sistema' },
{ icon: '💰', label: 'Você define o preço', desc: 'Precifique cada consulta de tutoria conforme desejar' },
{ icon: '⏱️', label: 'Flexível', desc: 'Responda no seu próprio tempo, sem compromisso de horário' },
].map(item => (
<div key={item.label} className="bg-white rounded-xl p-3 border border-indigo-100">
<p className="text-lg mb-1">{item.icon}</p>
<p className="text-[10px] font-black text-gray-700 uppercase">{item.label}</p>
<p className="text-[9px] text-gray-400 mt-0.5">{item.desc}</p>
</div>
))}
</div>
</div>
</div>
</div>
<CandidaturaForm onSubmitted={load} />
</div>
) : (
// Tem candidatura — mostra status
<div className="space-y-5">
<StatusBanner candidatura={candidatura} onPagar={() => setShowPagamento(true)} />
{candidatura.status === 'ATIVO' && (
<>
<PerfilAtivoCard candidatura={candidatura} />
<CredenciaisPagamento candidatura={candidatura} />
</>
)}
{candidatura.status === 'REJEITADO' && (
<div className="mt-4">
<h4 className="font-black text-gray-700 uppercase text-sm mb-3">SUBMETER NOVA CANDIDATURA</h4>
<CandidaturaForm onSubmitted={load} initialData={{ nome: candidatura.nome, email: candidatura.email, cro: candidatura.cro, cro_uf: candidatura.cro_uf }} />
</div>
)}
</div>
)}
{showPagamento && candidatura && (
<PagamentoModal
candidaturaId={candidatura.id}
onClose={() => setShowPagamento(false)}
onPago={() => { setShowPagamento(false); load(); }}
/>
)}
</div>
);
};