chore(ops): migrate clube67 to newwhats.clube67.com directory
continuous-integration/webhook Falha no deploy de clube67_newwhats.local (VPS 4)
continuous-integration/webhook Falha no deploy de clube67_newwhats.local (VPS 4)
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Database, Lock, User, Stethoscope, Phone, ShieldCheck, Mail, ArrowRight, Loader2 } from 'lucide-react';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
|
||||
export const DentistRegisterView: React.FC = () => {
|
||||
const [step, setStep] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const toast = useToast();
|
||||
|
||||
// Get data from URL
|
||||
const query = new URLSearchParams(window.location.hash.split('?')[1]);
|
||||
const clinicaId = query.get('clinica');
|
||||
const tempName = query.get('nome');
|
||||
const token = query.get('token');
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
nome: tempName || '',
|
||||
email: '',
|
||||
senha: '',
|
||||
cro: '',
|
||||
cro_uf: 'MS',
|
||||
celular: '',
|
||||
especialidade: ''
|
||||
});
|
||||
|
||||
if (!token || !clinicaId) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="w-20 h-20 bg-red-50 text-red-500 rounded-full flex items-center justify-center mx-auto shadow-sm">
|
||||
<Lock size={40} />
|
||||
</div>
|
||||
<h2 className="text-2xl font-black text-gray-900 uppercase">LINK INVÁLIDO OU EXPIRADO</h2>
|
||||
<p className="text-gray-500 max-w-sm mx-auto font-medium">Por favor, solicite um novo convite ao administrador da clínica.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch('http://localhost:3005/api/dentistas/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
...formData,
|
||||
id: 'd_' + Math.random().toString(36).substring(2, 9),
|
||||
clinicaId
|
||||
})
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
toast.success("CADASTRO REALIZADO COM SUCESSO!");
|
||||
setStep(3);
|
||||
} else {
|
||||
toast.error("ERRO AO REALIZAR CADASTRO.");
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error("FALHA DE CONEXÃO.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#F8FAFC] flex items-center justify-center p-4">
|
||||
<div className="max-w-xl w-full">
|
||||
{/* Progress Header */}
|
||||
<div className="mb-10 text-center space-y-2">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-blue-50 text-blue-600 rounded-full text-[10px] font-black uppercase tracking-widest border border-blue-100 mb-4">
|
||||
<ShieldCheck size={14} /> Convite de Profissional
|
||||
</div>
|
||||
<h1 className="text-4xl font-black text-gray-900 tracking-tighter uppercase leading-none">Complete seu Perfil</h1>
|
||||
<p className="text-gray-500 font-medium">Bem-vindo ao SCOREODONTO. Você está sendo convidado para a unidade: <span className="text-blue-600 font-bold uppercase">{clinicaId}</span></p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-[2.5rem] shadow-[0_50px_100px_-20px_rgba(0,0,0,0.08)] border border-gray-100 overflow-hidden relative">
|
||||
<div className="h-2 bg-gray-100 w-full">
|
||||
<div
|
||||
className="h-full bg-blue-600 transition-all duration-700"
|
||||
style={{ width: `${(step / 3) * 100}%` }}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{step === 1 && (
|
||||
<div className="p-10 animate-in fade-in slide-in-from-right-10 duration-500">
|
||||
<form className="space-y-6" onSubmit={(e) => { e.preventDefault(); setStep(2); }}>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1 mb-2 block">Seu Nome Profissional</label>
|
||||
<div className="relative group">
|
||||
<User className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-blue-500 transition-colors" size={20} />
|
||||
<input
|
||||
required
|
||||
value={formData.nome}
|
||||
onChange={e => setFormData({ ...formData, nome: e.target.value.toUpperCase() })}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-blue-500 focus:bg-white px-12 py-4 rounded-2xl outline-none transition-all font-bold text-gray-800 uppercase"
|
||||
placeholder="NOME COMPLETO"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1 mb-2 block">Email de Acesso</label>
|
||||
<div className="relative group">
|
||||
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" size={20} />
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={e => setFormData({ ...formData, email: e.target.value })}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-blue-500 focus:bg-white px-12 py-4 rounded-2xl outline-none transition-all font-bold text-gray-800"
|
||||
placeholder="seu@email.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1 mb-2 block">Senha de Acesso</label>
|
||||
<div className="relative group">
|
||||
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" size={20} />
|
||||
<input
|
||||
type="password"
|
||||
required
|
||||
value={formData.senha}
|
||||
onChange={e => setFormData({ ...formData, senha: e.target.value })}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-blue-500 focus:bg-white px-12 py-4 rounded-2xl outline-none transition-all font-bold text-gray-800"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-gray-900 text-white py-5 rounded-2xl font-black uppercase tracking-widest hover:bg-blue-600 hover:shadow-2xl hover:shadow-blue-200 transition-all flex items-center justify-center gap-3 active:scale-95"
|
||||
>
|
||||
Próximo Passo <ArrowRight size={20} />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 2 && (
|
||||
<div className="p-10 animate-in fade-in slide-in-from-right-10 duration-500">
|
||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1 mb-2 block">Inscrição CRO</label>
|
||||
<div className="relative group">
|
||||
<Database className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" size={20} />
|
||||
<input
|
||||
required
|
||||
value={formData.cro}
|
||||
onChange={e => setFormData({ ...formData, cro: e.target.value })}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-blue-500 focus:bg-white px-12 py-4 rounded-2xl outline-none transition-all font-bold text-gray-800 uppercase"
|
||||
placeholder="00000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1 mb-2 block">Estado CRO</label>
|
||||
<select
|
||||
value={formData.cro_uf}
|
||||
onChange={e => setFormData({ ...formData, cro_uf: e.target.value })}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-blue-500 focus:bg-white px-6 py-4 rounded-2xl outline-none transition-all font-bold text-gray-800"
|
||||
>
|
||||
<option>MS</option><option>SP</option><option>RJ</option><option>MG</option><option>PR</option><option>SC</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1 mb-2 block">Especialidade Principal</label>
|
||||
<div className="relative group">
|
||||
<Stethoscope className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" size={20} />
|
||||
<input
|
||||
required
|
||||
value={formData.especialidade}
|
||||
onChange={e => setFormData({ ...formData, especialidade: e.target.value.toUpperCase() })}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-blue-500 focus:bg-white px-12 py-4 rounded-2xl outline-none transition-all font-bold text-gray-800 uppercase"
|
||||
placeholder="EX: ORTODONTIA"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1 mb-2 block">WhatsApp de Contato</label>
|
||||
<div className="relative group">
|
||||
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" size={20} />
|
||||
<input
|
||||
required
|
||||
value={formData.celular}
|
||||
onChange={e => setFormData({ ...formData, celular: e.target.value })}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-blue-500 focus:bg-white px-12 py-4 rounded-2xl outline-none transition-all font-bold text-gray-800 uppercase"
|
||||
placeholder="(00) 00000-0000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setStep(1)}
|
||||
className="flex-1 bg-gray-100 text-gray-600 py-5 rounded-2xl font-black uppercase tracking-widest hover:bg-gray-200 transition-all font-bold"
|
||||
>
|
||||
Voltar
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="flex-[2] bg-blue-600 text-white py-5 rounded-2xl font-black uppercase tracking-widest hover:bg-blue-700 shadow-xl shadow-blue-200 transition-all flex items-center justify-center gap-3 disabled:opacity-50"
|
||||
>
|
||||
{loading ? <Loader2 className="animate-spin" /> : <>Finalizar e Entrar <ArrowRight size={20} /></>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 3 && (
|
||||
<div className="p-16 text-center animate-in zoom-in duration-500">
|
||||
<div className="w-24 h-24 bg-green-50 text-green-500 rounded-[2rem] flex items-center justify-center mx-auto mb-8 shadow-inner ring-1 ring-green-100">
|
||||
<ShieldCheck size={48} />
|
||||
</div>
|
||||
<h2 className="text-3xl font-black text-gray-900 uppercase tracking-tight mb-4">Tudo Pronto!</h2>
|
||||
<p className="text-gray-500 font-medium mb-10">Seu perfil foi criado e você já está vinculado à unidade <br /> <span className="text-blue-600 font-bold">{clinicaId}</span>.</p>
|
||||
|
||||
<button
|
||||
onClick={() => window.location.hash = '/login'}
|
||||
className="w-full bg-gray-900 text-white py-5 rounded-2xl font-black uppercase tracking-widest hover:bg-blue-600 shadow-2xl transition-all"
|
||||
>
|
||||
Ir para o Login
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex items-center justify-center gap-2 text-gray-400 text-[10px] font-black uppercase tracking-widest">
|
||||
<ShieldCheck size={14} /> Ambiente Seguro e Auditorado por Administradores
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user