photum/frontend/pages/Register.tsx

436 lines
16 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Button } from "../components/Button";
import { Input } from "../components/Input";
import { useAuth } from "../contexts/AuthContext";
import { getCompanies } from "../services/apiService";
import { formatPhone, formatCPFCNPJ, formatCEP } from "../utils/masks";
interface RegisterProps {
onNavigate: (page: string) => void;
}
export const Register: React.FC<RegisterProps> = ({ onNavigate }) => {
const navigate = useNavigate();
const { register } = useAuth();
const [companies, setCompanies] = useState<
Array<{ id: string; nome: string }>
>([]);
const [isLoadingCompanies, setIsLoadingCompanies] = useState(true);
const [formData, setFormData] = useState({
name: "",
email: "",
phone: "",
password: "",
confirmPassword: "",
empresaId: "",
cpfCnpj: "",
cep: "",
endereco: "",
numero: "",
complemento: "",
bairro: "",
cidade: "",
estado: "",
});
const [agreedToTerms, setAgreedToTerms] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
const loadCompanies = async () => {
setIsLoadingCompanies(true);
const result = await getCompanies();
if (result.data) {
setCompanies(result.data);
}
setIsLoadingCompanies(false);
};
loadCompanies();
}, []);
// Verifica se tem empresa pré-selecionada via URL (código de acesso) ou sessão
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const empresaIdFromUrl = urlParams.get('empresa_id');
const empresaNomeFromUrl = urlParams.get('empresa_nome');
// Tenta pegar da URL primeiro, se não tiver, tenta da sessão persistida
let targetEmpresaId = empresaIdFromUrl;
if (!targetEmpresaId) {
const sessionDataStr = sessionStorage.getItem('accessCodeData');
if (sessionDataStr) {
try {
const sessionData = JSON.parse(sessionDataStr);
if (sessionData && sessionData.empresa_id) {
targetEmpresaId = sessionData.empresa_id;
}
} catch (e) {
console.error("Erro ao parsear accessCodeData da sessão", e);
}
}
}
if (targetEmpresaId) {
setFormData(prev => ({ ...prev, empresaId: targetEmpresaId }));
// Limpa os parâmetros da URL após usar (para não ficar feio na barra)
if (window.location.search) {
window.history.replaceState({}, document.title, window.location.pathname);
}
}
}, [companies]);
const handleChange = (field: string, value: string | boolean) => {
setFormData((prev) => ({ ...prev, [field]: value }));
setError("");
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setError("");
// Validação do checkbox de termos
if (!agreedToTerms) {
setError("Você precisa concordar com os termos de uso para continuar");
setIsLoading(false);
return;
}
// Validações
if (formData.password !== formData.confirmPassword) {
setError("As senhas não coincidem");
setIsLoading(false);
return;
}
if (formData.password.length < 6) {
setError("A senha deve ter no mínimo 6 caracteres");
setIsLoading(false);
return;
}
try {
await register({
name: formData.name,
email: formData.email,
password: formData.password,
phone: formData.phone,
role: "EVENT_OWNER", // Client Role
company_id: formData.empresaId,
cpf_cnpj: formData.cpfCnpj,
cep: formData.cep,
endereco: formData.endereco,
numero: formData.numero,
complemento: formData.complemento,
bairro: formData.bairro,
cidade: formData.cidade,
estado: formData.estado,
}, false, true); // autoLogin=false, skipLoading=true
// Keep loading true while redirecting
navigate("/cadastro-sucesso", { replace: true });
} catch (err: any) {
setIsLoading(false);
setError(err.message || "Erro ao realizar cadastro");
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-white p-4 py-8 pt-24">
<div className="w-full max-w-md fade-in relative z-10">
<div className="bg-white rounded-2xl shadow-xl border border-gray-100 p-6 sm:p-8">
{/* Logo dentro do card */}
<div className="flex justify-center mb-4">
<img
src="/logo.png"
alt="Photum Formaturas"
className="h-18 sm:h-30 w-auto max-w-[200px] object-contain"
/>
</div>
<div className="text-center">
<span
className="font-bold tracking-widest uppercase text-[10px] sm:text-xs"
style={{ color: "#B9CF33" }}
>
Comece agora
</span>
<h2 className="mt-1.5 sm:mt-2 text-xl sm:text-2xl md:text-3xl font-serif font-bold text-gray-900">
Crie sua conta
</h2>
<p className="mt-1.5 sm:mt-2 text-xs sm:text-sm text-gray-600">
tem uma conta?{" "}
<button
onClick={() => onNavigate("entrar")}
className="font-medium hover:opacity-80 transition-opacity"
style={{ color: "#B9CF33" }}
>
Faça login
</button>
</p>
</div>
<form
className="mt-5 sm:mt-6 space-y-3 sm:space-y-4"
onSubmit={handleSubmit}
>
<div className="flex items-start bg-blue-50 border border-blue-200 rounded-lg p-2.5 sm:p-3 md:p-4 mb-3 sm:mb-4">
<div className="flex-1">
<p className="text-xs sm:text-sm text-gray-700">
<span className="font-medium text-xs sm:text-sm">
Você é um profissional?
</span>
</p>
<button
type="button"
onClick={() => onNavigate("cadastro-profissional")}
className="text-xs sm:text-sm mt-1 hover:opacity-80 transition-opacity underline font-medium"
style={{ color: "#B9CF33" }}
>
Clique aqui para se cadastrar como profissional
</button>
</div>
</div>
<div className="space-y-3">
<Input
label="Nome Completo"
type="text"
required
placeholder="João Silva"
value={formData.name}
onChange={(e) => handleChange("name", e.target.value)}
/>
<Input
label="E-mail"
type="email"
required
placeholder="nome@exemplo.com"
value={formData.email}
onChange={(e) => handleChange("email", e.target.value)}
/>
<Input
label="Telefone"
type="tel"
required
placeholder="(00) 00000-0000"
value={formData.phone}
onChange={(e) => handleChange("phone", formatPhone(e.target.value))}
mask="phone"
/>
<Input
label="CPF/CNPJ"
value={formData.cpfCnpj}
onChange={(e) => handleChange("cpfCnpj", formatCPFCNPJ(e.target.value))}
placeholder="000.000.000-00"
maxLength={18}
/>
<div className="grid grid-cols-2 gap-3">
<Input
label="CEP"
value={formData.cep}
onChange={(e) => handleChange("cep", formatCEP(e.target.value))}
placeholder="00000-000"
onBlur={(e) => {
const cep = e.target.value.replace(/\D/g, '');
if (cep.length === 8) {
fetch(`https://viacep.com.br/ws/${cep}/json/`)
.then(res => res.json())
.then(data => {
if (!data.erro) {
setFormData(prev => ({
...prev,
endereco: data.logradouro,
bairro: data.bairro,
cidade: data.localidade,
estado: data.uf
}));
}
});
}
}}
/>
<Input
label="Número"
value={formData.numero}
onChange={(e) => handleChange("numero", e.target.value)}
/>
</div>
<Input
label="Endereço"
value={formData.endereco}
onChange={(e) => handleChange("endereco", e.target.value)}
/>
<div className="grid grid-cols-2 gap-3">
<Input
label="Complemento"
value={formData.complemento}
onChange={(e) => handleChange("complemento", e.target.value)}
/>
<Input
label="Bairro"
value={formData.bairro}
onChange={(e) => handleChange("bairro", e.target.value)}
/>
</div>
<div className="grid grid-cols-[2fr_1fr] gap-3">
<Input
label="Cidade"
value={formData.cidade}
onChange={(e) => handleChange("cidade", e.target.value)}
/>
<Input
label="UF"
value={formData.estado}
onChange={(e) => handleChange("estado", e.target.value)}
maxLength={2}
/>
</div>
<div>
<label className="block text-[10px] sm:text-xs md:text-sm font-medium text-gray-700 mb-1">
Empresa *
</label>
<p className="text-[10px] text-gray-500 mb-2 leading-tight">
Se a sua empresa não estiver listada, entre em contato com a
administração e selecione <strong>"Não Cadastrado"</strong>{" "}
abaixo.
</p>
{formData.empresaId && companies.find(c => c.id === formData.empresaId) && (
<p className="text-[10px] text-green-600 mb-2 leading-tight">
Empresa pré-selecionada baseada no seu código de acesso
</p>
)}
{isLoadingCompanies ? (
<p className="text-xs sm:text-sm text-gray-500">
Carregando empresas...
</p>
) : (
<select
required
value={formData.empresaId}
onChange={(e) => handleChange("empresaId", e.target.value)}
className="w-full px-2.5 sm:px-3 md:px-4 py-1.5 sm:py-2 text-xs sm:text-sm border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent"
style={{ focusRing: "2px solid #B9CF33" }}
disabled={!!formData.empresaId && !!companies.find(c => c.id === formData.empresaId)}
>
<option value="">
{formData.empresaId ? "Empresa selecionada" : "Selecione uma empresa"}
</option>
{companies
.sort((a, b) => {
const nameA = a.nome.toLowerCase();
const nameB = b.nome.toLowerCase();
if (nameA.includes("não cadastrado") || nameA.includes("nao cadastrado")) return -1;
if (nameB.includes("não cadastrado") || nameB.includes("nao cadastrado")) return 1;
return nameA.localeCompare(nameB);
})
.map((company) => (
<option key={company.id} value={company.id}>
{company.nome}
</option>
))}
</select>
)}
</div>
<Input
label="Senha"
type="password"
required
placeholder="••••••••"
value={formData.password}
onChange={(e) => handleChange("password", e.target.value)}
/>
<Input
label="Confirmar Senha"
type="password"
required
placeholder="••••••••"
value={formData.confirmPassword}
onChange={(e) =>
handleChange("confirmPassword", e.target.value)
}
error={
error &&
(error.includes("senha") || error.includes("coincidem"))
? error
: undefined
}
/>
</div>
<div>
<div className="flex items-start">
<input
type="checkbox"
checked={agreedToTerms}
onChange={(e) => setAgreedToTerms(e.target.checked)}
className="mt-0.5 sm:mt-1 h-4 w-4 flex-shrink-0 border-gray-300 rounded focus:ring-2"
style={{ accentColor: "#B9CF33" }}
/>
<label className="ml-2 text-xs sm:text-sm text-gray-600">
Concordo com os{" "}
<button
type="button"
onClick={() => onNavigate("termos")}
className="hover:opacity-80 transition-opacity underline"
style={{ color: "#B9CF33" }}
>
termos de uso
</button>{" "}
e{" "}
<button
type="button"
onClick={() => onNavigate("privacidade")}
className="hover:opacity-80 transition-opacity underline"
style={{ color: "#B9CF33" }}
>
política de privacidade
</button>
</label>
</div>
{error && error.includes("termos") && (
<span className="text-xs text-red-500 mt-1 block ml-6">
{error}
</span>
)}
</div>
{error &&
!error.includes("termos") &&
!error.includes("senha") &&
!error.includes("coincidem") && (
<div className="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg text-sm mb-4">
{error}
</div>
)}
<Button
type="submit"
className="w-full"
size="lg"
isLoading={isLoading}
>
Criar Conta
</Button>
</form>
</div>
</div>
</div>
);
};