1460 lines
82 KiB
TypeScript
1460 lines
82 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import Link from "next/link";
|
|
import { Input, Checkbox, Button, Select, SearchableSelect } from "@/components/ui";
|
|
import toast, { Toaster } from 'react-hot-toast';
|
|
import DynamicBranding from "@/components/cadastro/DynamicBranding";
|
|
import DashboardPreview from "@/components/cadastro/DashboardPreview";
|
|
import { saveAuth } from '@/lib/auth';
|
|
import { API_ENDPOINTS, apiRequest } from '@/lib/api';
|
|
import dynamic from 'next/dynamic';
|
|
|
|
const ThemeToggle = dynamic(() => import('@/components/ThemeToggle'), { ssr: false });
|
|
|
|
interface ContactField {
|
|
id: number;
|
|
whatsapp: string;
|
|
}
|
|
|
|
export default function CadastroPage() {
|
|
const [currentStep, setCurrentStep] = useState(1);
|
|
const [completedSteps, setCompletedSteps] = useState<number[]>([]);
|
|
const [formData, setFormData] = useState<Record<string, any>>({});
|
|
const [contacts, setContacts] = useState<ContactField[]>([{ id: 1, whatsapp: "" }]);
|
|
const [password, setPassword] = useState("");
|
|
const [passwordStrength, setPasswordStrength] = useState(0);
|
|
const [cnpjData, setCnpjData] = useState({ razaoSocial: "", endereco: "" });
|
|
const [cepData, setCepData] = useState({ state: "", city: "", neighborhood: "", street: "" });
|
|
const [loadingCnpj, setLoadingCnpj] = useState(false);
|
|
const [loadingCep, setLoadingCep] = useState(false);
|
|
const [subdomain, setSubdomain] = useState("");
|
|
const [domainAvailable, setDomainAvailable] = useState<boolean | null>(null);
|
|
const [checkingDomain, setCheckingDomain] = useState(false);
|
|
const [primaryColor, setPrimaryColor] = useState("#FF3A05");
|
|
const [secondaryColor, setSecondaryColor] = useState("#FF0080");
|
|
const [logoUrl, setLogoUrl] = useState<string>("");
|
|
const [showPreviewMobile, setShowPreviewMobile] = useState(false);
|
|
|
|
// Carregar dados do localStorage ao montar
|
|
useEffect(() => {
|
|
const saved = localStorage.getItem('cadastroFormData');
|
|
if (saved) {
|
|
try {
|
|
const data = JSON.parse(saved);
|
|
setCurrentStep(data.currentStep || 1);
|
|
setCompletedSteps(data.completedSteps || []);
|
|
setFormData(data.formData || {});
|
|
setContacts(data.contacts || [{ id: 1, whatsapp: "" }]);
|
|
setPassword(data.password || "");
|
|
setPasswordStrength(data.passwordStrength || 0);
|
|
setCnpjData(data.cnpjData || { razaoSocial: "", endereco: "" });
|
|
setCepData(data.cepData || { state: "", city: "", neighborhood: "", street: "" });
|
|
setSubdomain(data.subdomain || "");
|
|
setDomainAvailable(data.domainAvailable ?? null);
|
|
setPrimaryColor(data.primaryColor || "#FF3A05");
|
|
setSecondaryColor(data.secondaryColor || "#FF0080");
|
|
setLogoUrl(data.logoUrl || "");
|
|
} catch (error) {
|
|
console.error('Erro ao carregar dados:', error);
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
// Salvar no localStorage sempre que houver mudanças
|
|
useEffect(() => {
|
|
const dataToSave = {
|
|
currentStep,
|
|
completedSteps,
|
|
formData,
|
|
contacts,
|
|
password,
|
|
passwordStrength,
|
|
cnpjData,
|
|
cepData,
|
|
subdomain,
|
|
domainAvailable,
|
|
primaryColor,
|
|
secondaryColor,
|
|
logoUrl
|
|
};
|
|
localStorage.setItem('cadastroFormData', JSON.stringify(dataToSave));
|
|
}, [currentStep, completedSteps, formData, contacts, password, passwordStrength, cnpjData, cepData, subdomain, domainAvailable, primaryColor, secondaryColor, logoUrl]);
|
|
|
|
// Função para atualizar formData
|
|
const updateFormData = (name: string, value: any) => {
|
|
setFormData(prev => ({ ...prev, [name]: value }));
|
|
};
|
|
|
|
const steps = [
|
|
{
|
|
number: 1,
|
|
title: "Dados Pessoais",
|
|
heading: "Seus Dados Pessoais",
|
|
description: "Informe seus dados para criar sua conta de administrador."
|
|
},
|
|
{
|
|
number: 2,
|
|
title: "Empresa",
|
|
heading: "Dados da Empresa",
|
|
description: "Cadastre as informações básicas da sua empresa."
|
|
},
|
|
{
|
|
number: 3,
|
|
title: "Localização e Contato",
|
|
heading: "Endereço e Contato",
|
|
description: "Informe a localização da sua empresa e os contatos para comunicação."
|
|
},
|
|
{
|
|
number: 4,
|
|
title: "Domínio",
|
|
heading: "Escolha seu Domínio",
|
|
description: "Defina o endereço único para acessar o painel da sua empresa."
|
|
},
|
|
{
|
|
number: 5,
|
|
title: "Personalização",
|
|
heading: "Personalize seu Painel",
|
|
description: "Configure as cores e identidade visual da sua empresa."
|
|
},
|
|
];
|
|
|
|
const currentStepData = steps.find(s => s.number === currentStep);
|
|
|
|
const validateCurrentStep = () => {
|
|
const form = document.querySelector('form') || document;
|
|
const inputs = form.querySelectorAll('input[required], select[required], textarea[required]');
|
|
|
|
// Mapeamento de nomes de campos para labels amigáveis
|
|
const fieldLabels: Record<string, string> = {
|
|
fullName: 'Nome Completo',
|
|
email: 'E-mail',
|
|
password: 'Senha',
|
|
confirmPassword: 'Confirmar Senha',
|
|
terms: 'Termos de Uso',
|
|
companyName: 'Nome da Empresa',
|
|
cnpj: 'CNPJ',
|
|
description: 'Descrição',
|
|
industry: 'Segmento',
|
|
teamSize: 'Tamanho da Equipe',
|
|
cep: 'CEP',
|
|
state: 'Estado',
|
|
city: 'Cidade',
|
|
neighborhood: 'Bairro',
|
|
street: 'Rua',
|
|
number: 'Número',
|
|
domain: 'Domínio',
|
|
};
|
|
|
|
for (const input of inputs) {
|
|
const element = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
if (!element.value || element.value.trim() === '') {
|
|
const fieldName = element.getAttribute('name') || '';
|
|
|
|
// Tratamento especial para campos de WhatsApp
|
|
let fieldLabel = fieldLabels[fieldName];
|
|
if (fieldName.startsWith('whatsapp-')) {
|
|
fieldLabel = 'WhatsApp';
|
|
} else {
|
|
fieldLabel = fieldLabel || 'Este campo';
|
|
}
|
|
|
|
toast.error(`O campo "${fieldLabel}" é obrigatório e precisa ser preenchido.`);
|
|
element.focus();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Validações específicas por etapa
|
|
if (currentStep === 1) {
|
|
if (!formData.fullName || formData.fullName.trim().length < 3) {
|
|
toast.error('Por favor, insira seu nome completo (mínimo 3 caracteres).');
|
|
return false;
|
|
}
|
|
|
|
if (!formData.email || !formData.email.includes('@')) {
|
|
toast.error('Por favor, insira um email válido. Exemplo: seu@email.com');
|
|
return false;
|
|
}
|
|
|
|
if (password.length < 8) {
|
|
toast.error('A senha deve ter no mínimo 8 caracteres para maior segurança.');
|
|
return false;
|
|
}
|
|
|
|
if (passwordStrength < 2) {
|
|
toast.error('Sua senha está muito fraca. Use maiúsculas, minúsculas, números e símbolos.');
|
|
return false;
|
|
}
|
|
|
|
if (password !== formData.confirmPassword) {
|
|
toast.error('As senhas não coincidem. Por favor, digite a mesma senha nos dois campos.');
|
|
return false;
|
|
}
|
|
|
|
if (!formData.terms) {
|
|
toast.error('Você precisa aceitar os Termos de Uso para continuar o cadastro.');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (currentStep === 2) {
|
|
if (!formData.companyName || formData.companyName.trim().length < 3) {
|
|
toast.error('O nome da empresa deve ter pelo menos 3 caracteres.');
|
|
return false;
|
|
}
|
|
|
|
const cnpjNumbers = formData.cnpj?.replace(/\D/g, '') || '';
|
|
if (cnpjNumbers.length !== 14) {
|
|
toast.error(`CNPJ incompleto. Digite os 14 dígitos (você digitou ${cnpjNumbers.length}).`);
|
|
return false;
|
|
}
|
|
|
|
if (!formData.description || formData.description.trim().length < 10) {
|
|
toast.error('A descrição da empresa deve ter pelo menos 10 caracteres.');
|
|
return false;
|
|
}
|
|
|
|
if (!formData.industry) {
|
|
toast.error('Por favor, selecione o segmento/indústria da sua empresa.');
|
|
return false;
|
|
}
|
|
|
|
if (!formData.teamSize) {
|
|
toast.error('Por favor, selecione o tamanho da equipe da sua empresa.');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (currentStep === 3) {
|
|
const cepNumbers = formData.cep?.replace(/\D/g, '') || '';
|
|
if (cepNumbers.length !== 8) {
|
|
toast.error(`CEP incompleto. Digite os 8 dígitos (você digitou ${cepNumbers.length}).`);
|
|
return false;
|
|
}
|
|
|
|
if (!cepData.state || cepData.state.length !== 2) {
|
|
toast.error('Digite a sigla do estado (UF) com 2 letras. Exemplo: SP, RJ, MG');
|
|
return false;
|
|
}
|
|
|
|
if (!cepData.city || cepData.city.trim().length < 2) {
|
|
toast.error('Digite o nome da cidade.');
|
|
return false;
|
|
}
|
|
|
|
if (!cepData.neighborhood || cepData.neighborhood.trim().length < 2) {
|
|
toast.error('Digite o nome do bairro.');
|
|
return false;
|
|
}
|
|
|
|
if (!cepData.street || cepData.street.trim().length < 3) {
|
|
toast.error('Digite o nome da rua/avenida.');
|
|
return false;
|
|
}
|
|
|
|
if (!formData.number || formData.number.trim().length < 1) {
|
|
toast.error('Número do endereço não preenchido.');
|
|
return false;
|
|
}
|
|
|
|
// Validar contatos da empresa
|
|
for (let i = 0; i < contacts.length; i++) {
|
|
if (!contacts[i].whatsapp || contacts[i].whatsapp.replace(/\D/g, '').length < 10) {
|
|
toast.error(`WhatsApp não preenchido ou incompleto. Digite um número válido com DDD.`);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (currentStep === 4) {
|
|
if (!subdomain || subdomain.trim().length < 3) {
|
|
toast.error('O subdomínio deve ter pelo menos 3 caracteres. Exemplo: minhaempresa');
|
|
return false;
|
|
}
|
|
|
|
if (!/^[a-z0-9-]+$/.test(subdomain)) {
|
|
toast.error('O subdomínio deve conter apenas letras minúsculas, números e hífens.');
|
|
return false;
|
|
}
|
|
|
|
if (domainAvailable === false) {
|
|
toast.error('Este subdomínio já está em uso. Escolha outro.');
|
|
return false;
|
|
}
|
|
|
|
if (domainAvailable === null) {
|
|
toast.error('Aguarde a verificação de disponibilidade do domínio.');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
|
|
const handleSubmitRegistration = async () => {
|
|
try {
|
|
const payload = {
|
|
// Dados da agência
|
|
agencyName: formData.companyName,
|
|
subdomain: subdomain,
|
|
cnpj: formData.cnpj,
|
|
razaoSocial: formData.razaoSocial,
|
|
description: formData.description,
|
|
website: formData.website,
|
|
industry: formData.industry,
|
|
|
|
// Endereço
|
|
cep: formData.cep,
|
|
state: formData.state,
|
|
city: formData.city,
|
|
neighborhood: formData.neighborhood,
|
|
street: formData.street,
|
|
number: formData.number,
|
|
complement: formData.complement,
|
|
|
|
// Admin
|
|
adminEmail: formData.email,
|
|
adminPassword: password,
|
|
adminName: formData.fullName,
|
|
};
|
|
|
|
console.log('📤 Enviando cadastro completo:', payload);
|
|
toast.loading('Criando sua conta...', { id: 'register' });
|
|
|
|
const response = await fetch('/api/admin/agencies', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.message || 'Erro ao criar conta');
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
console.log('📥 Resposta data:', data);
|
|
|
|
// Salvar autenticação
|
|
if (data.token) {
|
|
saveAuth(data.token, {
|
|
id: data.id,
|
|
email: data.email,
|
|
name: data.name,
|
|
role: data.role || 'ADMIN_AGENCIA',
|
|
tenantId: data.tenantId,
|
|
company: data.company,
|
|
subdomain: data.subdomain
|
|
});
|
|
}
|
|
|
|
// Sucesso - limpar localStorage do form
|
|
localStorage.removeItem('cadastroFormData');
|
|
|
|
toast.success('Conta criada com sucesso! Redirecionando para seu painel...', {
|
|
id: 'register',
|
|
duration: 2000,
|
|
style: {
|
|
background: '#10B981',
|
|
color: '#fff',
|
|
},
|
|
});
|
|
|
|
// Redirecionar para o painel da agência no subdomínio
|
|
setTimeout(() => {
|
|
const agencyUrl = `http://${data.subdomain}.localhost/login`;
|
|
window.location.href = agencyUrl;
|
|
}, 2000);
|
|
|
|
} catch (error: any) {
|
|
console.error('❌ Erro no cadastro:', error);
|
|
toast.error(error.message || 'Erro ao criar conta. Tente novamente.', {
|
|
id: 'register',
|
|
});
|
|
}
|
|
};
|
|
|
|
// MODO TESTE - Preencher dados automaticamente
|
|
const fillTestData = () => {
|
|
const testData = {
|
|
fullName: "Teste Usuario",
|
|
email: "teste@idealpages.com",
|
|
confirmPassword: "senha12345",
|
|
terms: true,
|
|
newsletter: false,
|
|
companyName: "IdealPages",
|
|
cnpj: "12.345.678/0001-90",
|
|
description: "Agência de desenvolvimento web e aplicativos mobile especializada em soluções digitais",
|
|
website: "https://idealpages.com",
|
|
industry: "agencia-digital",
|
|
teamSize: "1-10",
|
|
cep: "01310-100",
|
|
number: "123",
|
|
complement: "Sala 101",
|
|
};
|
|
|
|
setFormData(testData);
|
|
setPassword("senha12345");
|
|
setPasswordStrength(4);
|
|
setCnpjData({ razaoSocial: "IdealPages LTDA", endereco: "Av Paulista, 1000" });
|
|
setCepData({ state: "SP", city: "São Paulo", neighborhood: "Bela Vista", street: "Av Paulista" });
|
|
setContacts([{ id: 1, whatsapp: "(11) 98765-4321" }]);
|
|
setSubdomain("idealpages");
|
|
setDomainAvailable(true);
|
|
setPrimaryColor("#FF3A05");
|
|
setSecondaryColor("#FF0080");
|
|
|
|
// Marcar todos os steps como completos e ir pro step 5
|
|
setCompletedSteps([1, 2, 3, 4]);
|
|
setCurrentStep(5);
|
|
|
|
toast.success('Dados de teste preenchidos! Clique em Finalizar.', {
|
|
duration: 3000,
|
|
});
|
|
};
|
|
|
|
const handleNext = (e?: React.FormEvent) => {
|
|
if (e) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
if (!validateCurrentStep()) {
|
|
return;
|
|
}
|
|
|
|
if (currentStep < 5) {
|
|
setCompletedSteps([...completedSteps, currentStep]);
|
|
setCurrentStep(currentStep + 1);
|
|
} else {
|
|
// Última etapa - enviar dados para o backend
|
|
handleSubmitRegistration();
|
|
}
|
|
};
|
|
|
|
const addContact = () => {
|
|
const newId = contacts.length > 0 ? Math.max(...contacts.map(c => c.id)) + 1 : 1;
|
|
setContacts([...contacts, { id: newId, whatsapp: "" }]);
|
|
};
|
|
|
|
const removeContact = (id: number) => {
|
|
if (contacts.length > 1) {
|
|
setContacts(contacts.filter(c => c.id !== id));
|
|
}
|
|
};
|
|
|
|
const formatPhone = (value: string) => {
|
|
const numbers = value.replace(/\D/g, "");
|
|
if (numbers.length <= 10) {
|
|
return numbers.replace(/(\d{2})(\d{4})(\d{0,4})/, "($1) $2-$3").replace(/-$/, "");
|
|
}
|
|
return numbers.replace(/(\d{2})(\d{5})(\d{0,4})/, "($1) $2-$3").replace(/-$/, "");
|
|
};
|
|
|
|
const calculatePasswordStrength = (pwd: string): number => {
|
|
let strength = 0;
|
|
if (pwd.length >= 8) strength++;
|
|
if (pwd.length >= 12) strength++;
|
|
if (/[a-z]/.test(pwd) && /[A-Z]/.test(pwd)) strength++;
|
|
if (/\d/.test(pwd)) strength++;
|
|
if (/[^a-zA-Z0-9]/.test(pwd)) strength++;
|
|
return strength;
|
|
};
|
|
|
|
const checkDomainAvailability = async (domain: string) => {
|
|
if (!domain || domain.length < 3) {
|
|
setDomainAvailable(null);
|
|
return;
|
|
}
|
|
|
|
if (!/^[a-z0-9-]+$/.test(domain)) {
|
|
setDomainAvailable(null);
|
|
toast.error('Domínio inválido. Use apenas letras minúsculas, números e hífens.');
|
|
return;
|
|
}
|
|
|
|
setCheckingDomain(true);
|
|
setDomainAvailable(null);
|
|
|
|
try {
|
|
// Simulação de verificação - substituir pela API real
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
// Lista de domínios já ocupados (exemplo)
|
|
const unavailableDomains = ['teste', 'demo', 'admin', 'api', 'app', 'www'];
|
|
const isAvailable = !unavailableDomains.includes(domain.toLowerCase());
|
|
|
|
setDomainAvailable(isAvailable);
|
|
|
|
if (isAvailable) {
|
|
toast.success(`✓ ${domain}.aggios.app está disponível!`, {
|
|
duration: 3000,
|
|
style: {
|
|
background: '#10B981',
|
|
color: '#fff',
|
|
},
|
|
});
|
|
} else {
|
|
toast.error(`✗ ${domain}.aggios.app já está em uso. Tente outro.`, {
|
|
duration: 3000,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
toast.error('Erro ao verificar disponibilidade. Tente novamente.');
|
|
setDomainAvailable(null);
|
|
} finally {
|
|
setCheckingDomain(false);
|
|
}
|
|
};
|
|
|
|
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const newPassword = e.target.value;
|
|
setPassword(newPassword);
|
|
setPasswordStrength(calculatePasswordStrength(newPassword));
|
|
};
|
|
|
|
const getPasswordStrengthLabel = () => {
|
|
if (password.length === 0) return "";
|
|
if (passwordStrength <= 1) return "Muito fraca";
|
|
if (passwordStrength === 2) return "Fraca";
|
|
if (passwordStrength === 3) return "Média";
|
|
if (passwordStrength === 4) return "Forte";
|
|
return "Muito forte";
|
|
};
|
|
|
|
const getPasswordStrengthColor = () => {
|
|
if (passwordStrength <= 1) return "#EF4444";
|
|
if (passwordStrength === 2) return "#F59E0B";
|
|
if (passwordStrength === 3) return "#3B82F6";
|
|
if (passwordStrength === 4) return "#10B981";
|
|
return "#059669";
|
|
};
|
|
|
|
const fetchCnpjData = async (cnpj: string) => {
|
|
const numbers = cnpj.replace(/\D/g, "");
|
|
if (numbers.length !== 14) return;
|
|
|
|
setLoadingCnpj(true);
|
|
try {
|
|
const response = await fetch(`https://brasilapi.com.br/api/cnpj/v1/${numbers}`);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
setCnpjData({
|
|
razaoSocial: data.razao_social || "",
|
|
endereco: `${data.logradouro}, ${data.numero} - ${data.bairro}, ${data.municipio}/${data.uf}`
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao buscar CNPJ:", error);
|
|
} finally {
|
|
setLoadingCnpj(false);
|
|
}
|
|
};
|
|
|
|
const fetchCepData = async (cep: string) => {
|
|
const numbers = cep.replace(/\D/g, "");
|
|
if (numbers.length !== 8) return;
|
|
|
|
setLoadingCep(true);
|
|
try {
|
|
const response = await fetch(`https://viacep.com.br/ws/${numbers}/json/`);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
if (!data.erro) {
|
|
setCepData({
|
|
state: data.uf || "",
|
|
city: data.localidade || "",
|
|
neighborhood: data.bairro || "",
|
|
street: data.logradouro || ""
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao buscar CEP:", error);
|
|
} finally {
|
|
setLoadingCep(false);
|
|
}
|
|
};
|
|
|
|
const formatCnpj = (value: string) => {
|
|
const numbers = value.replace(/\D/g, "");
|
|
return numbers.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, "$1.$2.$3/$4-$5").substring(0, 18);
|
|
};
|
|
|
|
const formatCep = (value: string) => {
|
|
const numbers = value.replace(/\D/g, "");
|
|
return numbers.replace(/(\d{5})(\d{3})/, "$1-$2").substring(0, 9);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Toaster
|
|
position="top-center"
|
|
toastOptions={{
|
|
duration: 5000,
|
|
style: {
|
|
background: '#FFFFFF',
|
|
color: '#000000',
|
|
padding: '16px',
|
|
borderRadius: '8px',
|
|
border: '1px solid #E5E5E5',
|
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
|
},
|
|
error: {
|
|
icon: '⚠️',
|
|
style: {
|
|
background: '#ff3a05',
|
|
color: '#FFFFFF',
|
|
border: 'none',
|
|
},
|
|
},
|
|
success: {
|
|
icon: '✓',
|
|
style: {
|
|
background: '#10B981',
|
|
color: '#FFFFFF',
|
|
border: 'none',
|
|
},
|
|
},
|
|
}}
|
|
/>
|
|
<div className="flex min-h-screen">
|
|
{/* Lado Esquerdo - Formulário */}
|
|
<div className="w-full lg:w-[50%] h-screen flex flex-col">
|
|
|
|
{/* Título e texto */}
|
|
<div className="px-6 sm:px-12 py-6 bg-white dark:bg-gray-800 border-b border-[#E5E5E5] dark:border-gray-700">
|
|
<div className="max-w-2xl mx-auto flex items-center gap-6">
|
|
{/* Theme Toggle */}
|
|
<div className="ml-auto">
|
|
<ThemeToggle />
|
|
</div>
|
|
</div>
|
|
<div className="max-w-2xl mx-auto flex items-center gap-6 mt-4">
|
|
{/* Progresso Circular */}
|
|
<div className="relative flex items-center justify-center w-16 h-16 shrink-0">
|
|
<svg className="w-16 h-16 transform -rotate-90">
|
|
<circle
|
|
cx="32"
|
|
cy="32"
|
|
r="28"
|
|
stroke="#E5E5E5"
|
|
strokeWidth="4"
|
|
fill="none"
|
|
/>
|
|
<circle
|
|
cx="32"
|
|
cy="32"
|
|
r="28"
|
|
stroke="url(#gradient)"
|
|
strokeWidth="4"
|
|
fill="none"
|
|
strokeDasharray={`${2 * Math.PI * 28}`}
|
|
strokeDashoffset={`${2 * Math.PI * 28 * (1 - (currentStep / 5))}`}
|
|
strokeLinecap="round"
|
|
className="transition-all duration-300"
|
|
/>
|
|
<defs>
|
|
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
<stop offset="0%" stopColor="#FF3A05" />
|
|
<stop offset="100%" stopColor="#FF0080" />
|
|
</linearGradient>
|
|
</defs>
|
|
</svg>
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<span className="text-sm font-bold text-[#000000]">{Math.round((currentStep / 5) * 100)}%</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Título e Descrição */}
|
|
<div className="flex-1">
|
|
<h1 className="text-[28px] font-bold text-[#000000] mb-1">{currentStepData?.heading}</h1>
|
|
<p className="text-[14px] text-[#7D7D7D]">
|
|
{currentStepData?.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Formulário */}
|
|
<div className="flex-1 overflow-y-auto bg-[#FDFDFC] px-6 sm:px-12 py-6">
|
|
<div className="max-w-2xl mx-auto">
|
|
<form onSubmit={(e) => { e.preventDefault(); handleNext(e); }} className="space-y-6">
|
|
{currentStep === 1 && (
|
|
<div className="space-y-5">
|
|
<Input
|
|
name="fullName"
|
|
label="Nome Completo"
|
|
placeholder="Seu nome completo"
|
|
leftIcon="ri-user-line"
|
|
value={formData.fullName || ''}
|
|
onChange={(e) => updateFormData('fullName', e.target.value)}
|
|
required
|
|
/>
|
|
<Input
|
|
name="email"
|
|
label="Email"
|
|
type="email"
|
|
placeholder="seu@email.com"
|
|
leftIcon="ri-mail-line"
|
|
helperText="Será usado para seu login"
|
|
value={formData.email || ''}
|
|
onChange={(e) => updateFormData('email', e.target.value)}
|
|
required
|
|
/>
|
|
|
|
{/* Separador de seção */}
|
|
<div className="pt-4 border-t border-[#E5E5E5]">
|
|
<h3 className="text-sm font-semibold text-[#000000] mb-4">Crie sua senha de acesso</h3>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<Input
|
|
name="password"
|
|
label="Senha"
|
|
type="password"
|
|
placeholder="Mínimo 8 caracteres"
|
|
leftIcon="ri-lock-line"
|
|
helperText="Use maiúsculas, minúsculas, números e símbolos"
|
|
value={password}
|
|
onChange={handlePasswordChange}
|
|
required
|
|
/>
|
|
{password.length > 0 && (
|
|
<div className="mt-2">
|
|
<div className="flex items-center justify-between mb-1">
|
|
<span className="text-xs text-[#7D7D7D]">Força da senha:</span>
|
|
<span className="text-xs font-semibold" style={{ color: getPasswordStrengthColor() }}>
|
|
{getPasswordStrengthLabel()}
|
|
</span>
|
|
</div>
|
|
<div className="h-1.5 w-full bg-[#E5E5E5] rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full transition-all duration-300 rounded-full"
|
|
style={{
|
|
width: `${(passwordStrength / 5) * 100}%`,
|
|
backgroundColor: getPasswordStrengthColor()
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<Input
|
|
name="confirmPassword"
|
|
label="Confirmar Senha"
|
|
type="password"
|
|
placeholder="Repita a senha"
|
|
leftIcon="ri-lock-password-line"
|
|
value={formData.confirmPassword || ''}
|
|
onChange={(e) => updateFormData('confirmPassword', e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<Checkbox
|
|
name="terms"
|
|
checked={formData.terms || false}
|
|
onChange={(e) => updateFormData('terms', e.target.checked)}
|
|
label={
|
|
<span>
|
|
Concordo com os{" "}
|
|
<Link href="/termos" className="bg-linear-to-r from-[#FF3A05] to-[#FF0080] bg-clip-text text-transparent hover:underline cursor-pointer font-medium">
|
|
Termos de Uso
|
|
</Link>
|
|
</span>
|
|
}
|
|
/>
|
|
<Checkbox
|
|
name="newsletter"
|
|
label="Desejo receber newsletters e novidades"
|
|
checked={formData.newsletter || false}
|
|
onChange={(e) => updateFormData('newsletter', e.target.checked)}
|
|
/>
|
|
|
|
{/* Link para login */}
|
|
<p className="text-center mt-6 text-[14px] text-[#7D7D7D]">
|
|
Já possui uma conta?{" "}
|
|
<Link href="/login" className="bg-linear-to-r from-[#FF3A05] to-[#FF0080] bg-clip-text text-transparent font-medium hover:underline cursor-pointer">
|
|
Fazer login
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
)} {currentStep === 2 && (
|
|
<div className="space-y-5">
|
|
<Input
|
|
name="companyName"
|
|
label="Nome da Empresa"
|
|
placeholder="Ex: IdeaPages, DevStudio"
|
|
leftIcon="ri-building-line"
|
|
value={formData.companyName || ''}
|
|
onChange={(e) => updateFormData('companyName', e.target.value)}
|
|
required
|
|
/>
|
|
<Input
|
|
name="cnpj"
|
|
label="CNPJ"
|
|
placeholder="00.000.000/0000-00"
|
|
leftIcon="ri-file-text-line"
|
|
helperText="Preencheremos automaticamente razão social e endereço"
|
|
maxLength={18}
|
|
value={formData.cnpj || ''}
|
|
onChange={(e) => {
|
|
const formatted = formatCnpj(e.target.value);
|
|
updateFormData('cnpj', formatted);
|
|
if (formatted.replace(/\D/g, "").length === 14) {
|
|
fetchCnpjData(formatted);
|
|
}
|
|
}}
|
|
required
|
|
/>
|
|
<Input
|
|
name="razaoSocial"
|
|
label="Razão Social"
|
|
placeholder="Será preenchido automaticamente"
|
|
leftIcon="ri-building-4-line"
|
|
value={cnpjData.razaoSocial}
|
|
disabled
|
|
/>
|
|
<Input
|
|
name="cnpjAddress"
|
|
label="Endereço (CNPJ)"
|
|
placeholder="Será preenchido automaticamente"
|
|
leftIcon="ri-map-pin-2-line"
|
|
value={cnpjData.endereco}
|
|
disabled
|
|
/>
|
|
<div>
|
|
<label className="block text-[13px] font-semibold text-[#000000] mb-2">
|
|
Descrição Breve<span className="text-[#FF3A05] ml-1">*</span>
|
|
</label>
|
|
<textarea
|
|
name="description"
|
|
placeholder="Apresente sua empresa em poucas palavras (máx 300 caracteres)"
|
|
className="w-full px-3.5 py-3 text-[14px] font-normal border rounded-md bg-white placeholder:text-[#7D7D7D] border-[#E5E5E5] focus:border-[#FF3A05] outline-none ring-0 focus:ring-0 shadow-none focus:shadow-none resize-none"
|
|
rows={4}
|
|
maxLength={300}
|
|
value={formData.description || ''}
|
|
onChange={(e) => updateFormData('description', e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
<Input
|
|
name="website"
|
|
label="Website/Portfolio (opcional)"
|
|
placeholder="https://suaagencia.com.br"
|
|
leftIcon="ri-link-m"
|
|
value={formData.website || ''}
|
|
onChange={(e) => updateFormData('website', e.target.value)}
|
|
/>
|
|
<SearchableSelect
|
|
name="industry"
|
|
label="Segmento/Indústria"
|
|
leftIcon="ri-briefcase-line"
|
|
placeholder="Selecione o segmento"
|
|
value={formData.industry || ''}
|
|
onChange={(value) => updateFormData('industry', value)}
|
|
options={[
|
|
{ value: "agencia-digital", label: "Agência Digital" },
|
|
{ value: "agencia-publicidade", label: "Agência de Publicidade" },
|
|
{ value: "agencia-marketing", label: "Agência de Marketing" },
|
|
{ value: "desenvolvimento-software", label: "Desenvolvimento de Software" },
|
|
{ value: "desenvolvimento-web", label: "Desenvolvimento Web" },
|
|
{ value: "desenvolvimento-mobile", label: "Desenvolvimento Mobile" },
|
|
{ value: "saas", label: "SaaS (Software as a Service)" },
|
|
{ value: "consultoria-ti", label: "Consultoria em TI" },
|
|
{ value: "consultoria-negocios", label: "Consultoria de Negócios" },
|
|
{ value: "marketing-digital", label: "Marketing Digital" },
|
|
{ value: "design-grafico", label: "Design Gráfico" },
|
|
{ value: "design-ui-ux", label: "Design UI/UX" },
|
|
{ value: "tecnologia", label: "Tecnologia" },
|
|
{ value: "ecommerce", label: "E-commerce" },
|
|
{ value: "educacao", label: "Educação" },
|
|
{ value: "educacao-online", label: "Educação Online" },
|
|
{ value: "saude", label: "Saúde" },
|
|
{ value: "financas", label: "Finanças" },
|
|
{ value: "fintech", label: "Fintech" },
|
|
{ value: "imobiliario", label: "Imobiliário" },
|
|
{ value: "varejo", label: "Varejo" },
|
|
{ value: "logistica", label: "Logística" },
|
|
{ value: "turismo", label: "Turismo" },
|
|
{ value: "alimentacao", label: "Alimentação" },
|
|
{ value: "industria", label: "Indústria" },
|
|
{ value: "servicos", label: "Serviços" },
|
|
{ value: "outros", label: "Outros" }
|
|
]}
|
|
required
|
|
/>
|
|
<SearchableSelect
|
|
name="teamSize"
|
|
label="Tamanho da Equipe"
|
|
leftIcon="ri-group-line"
|
|
placeholder="Selecione o tamanho"
|
|
value={formData.teamSize || ''}
|
|
onChange={(value) => updateFormData('teamSize', value)}
|
|
options={[
|
|
{ value: "1-10", label: "1-10 pessoas" },
|
|
{ value: "11-50", label: "11-50 pessoas" },
|
|
{ value: "51-100", label: "51-100 pessoas" },
|
|
{ value: "101-250", label: "101-250 pessoas" },
|
|
{ value: "251-500", label: "251-500 pessoas" },
|
|
{ value: "500+", label: "Mais de 500 pessoas" }
|
|
]}
|
|
required
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{currentStep === 3 && (
|
|
<div className="space-y-4">
|
|
<Input
|
|
name="cep"
|
|
label="CEP"
|
|
placeholder="00000-000"
|
|
leftIcon="ri-map-pin-line"
|
|
maxLength={9}
|
|
value={formData.cep || ''}
|
|
onChange={(e) => {
|
|
const formatted = formatCep(e.target.value);
|
|
updateFormData('cep', formatted);
|
|
const numbers = formatted.replace(/\D/g, "");
|
|
|
|
// Se campo vazio, limpar dados
|
|
if (numbers.length === 0) {
|
|
setCepData({ state: "", city: "", neighborhood: "", street: "" });
|
|
}
|
|
// Se CEP completo, buscar dados
|
|
else if (numbers.length === 8) {
|
|
fetchCepData(formatted);
|
|
}
|
|
}}
|
|
required
|
|
/>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<Input
|
|
name="state"
|
|
label="Estado"
|
|
placeholder="UF"
|
|
leftIcon="ri-map-2-line"
|
|
value={cepData.state}
|
|
disabled
|
|
required
|
|
/>
|
|
<Input
|
|
name="city"
|
|
label="Cidade"
|
|
placeholder="Nome da cidade"
|
|
leftIcon="ri-building-2-line"
|
|
value={cepData.city}
|
|
disabled
|
|
required
|
|
/>
|
|
</div>
|
|
<Input
|
|
name="neighborhood"
|
|
label="Bairro"
|
|
placeholder="Centro"
|
|
value={cepData.neighborhood}
|
|
disabled
|
|
required
|
|
/>
|
|
<Input
|
|
name="street"
|
|
label="Rua/Avenida"
|
|
placeholder="Rua das Flores"
|
|
value={cepData.street}
|
|
disabled
|
|
required
|
|
/>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<Input
|
|
name="number"
|
|
label="Número"
|
|
placeholder="123"
|
|
value={formData.number || ''}
|
|
onChange={(e) => updateFormData('number', e.target.value)}
|
|
required
|
|
/>
|
|
<Input
|
|
name="complement"
|
|
label="Complemento (opcional)"
|
|
placeholder="Apto, Sala..."
|
|
value={formData.complement || ''}
|
|
onChange={(e) => updateFormData('complement', e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{/* Contatos da Empresa */}
|
|
<div className="pt-4 border-t border-[#E5E5E5]">
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-sm font-semibold text-[#000000]">Contatos da Empresa</h3>
|
|
</div>
|
|
{contacts.map((contact, index) => (
|
|
<div key={contact.id} className="space-y-4 p-4 border border-[#E5E5E5] rounded-md bg-white">
|
|
{contacts.length > 1 && (
|
|
<div className="flex items-center justify-end -mt-2 -mr-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => removeContact(contact.id)}
|
|
className="text-[#7D7D7D] hover:text-[#FF3A05] transition-colors"
|
|
>
|
|
<i className="ri-close-line text-[18px]" />
|
|
</button>
|
|
</div>
|
|
)}
|
|
<Input
|
|
name={`whatsapp-${contact.id}`}
|
|
label="WhatsApp"
|
|
placeholder="(00) 00000-0000"
|
|
leftIcon="ri-whatsapp-line"
|
|
maxLength={15}
|
|
value={contact.whatsapp}
|
|
onChange={(e) => {
|
|
const formatted = formatPhone(e.target.value);
|
|
setContacts(contacts.map(c =>
|
|
c.id === contact.id
|
|
? { ...c, whatsapp: formatted }
|
|
: c
|
|
));
|
|
}}
|
|
required
|
|
/>
|
|
</div>
|
|
))}
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={addContact}
|
|
leftIcon="ri-add-line"
|
|
className="w-full"
|
|
>
|
|
Adicionar mais contato
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{currentStep === 4 && (
|
|
<div className="space-y-6">
|
|
{/* Subdomínio Aggios */}
|
|
<div className="space-y-2">
|
|
<label className="block text-sm font-medium text-[#000000]">
|
|
Subdomínio Aggios <span className="text-[#FF3A05]">*</span>
|
|
</label>
|
|
<div className="relative">
|
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
|
<i className="ri-global-line text-[#7D7D7D] text-[18px]" />
|
|
</div>
|
|
<input
|
|
type="text"
|
|
value={subdomain}
|
|
onChange={(e) => {
|
|
const value = e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
setSubdomain(value);
|
|
setDomainAvailable(null);
|
|
}}
|
|
onBlur={() => subdomain && checkDomainAvailability(subdomain)}
|
|
placeholder="minhaempresa"
|
|
className="w-full pl-10 pr-4 py-2 text-sm border border-[#E5E5E5] rounded-md focus:border-[#FF3A05] transition-colors"
|
|
required
|
|
/>
|
|
{checkingDomain && (
|
|
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
|
<div className="w-4 h-4 border-2 border-[#FF3A05] border-t-transparent rounded-full animate-spin" />
|
|
</div>
|
|
)}
|
|
{!checkingDomain && domainAvailable === true && (
|
|
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
|
<i className="ri-checkbox-circle-fill text-[#10B981] text-[20px]" />
|
|
</div>
|
|
)}
|
|
{!checkingDomain && domainAvailable === false && (
|
|
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
|
<i className="ri-close-circle-fill text-[#FF3A05] text-[20px]" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
<p className="text-xs text-[#7D7D7D] flex items-center gap-1">
|
|
<i className="ri-information-line" />
|
|
Seu painel ficará em: <span className="font-medium text-[#000000]">{subdomain || 'seu-dominio'}.aggios.app</span>
|
|
</p>
|
|
{domainAvailable === true && (
|
|
<p className="text-xs text-[#10B981] flex items-center gap-1">
|
|
<i className="ri-checkbox-circle-line" />
|
|
Disponível! Este subdomínio pode ser usado.
|
|
</p>
|
|
)}
|
|
{domainAvailable === false && (
|
|
<p className="text-xs text-[#FF3A05] flex items-center gap-1">
|
|
<i className="ri-error-warning-line" />
|
|
Indisponível. Este subdomínio já está em uso.
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Informações Adicionais */}
|
|
<div className="p-6 bg-[#F5F5F5] rounded-md space-y-3">
|
|
<h4 className="text-sm font-semibold text-[#000000] flex items-center gap-2">
|
|
<i className="ri-lightbulb-line text-[#FF3A05]" />
|
|
Dicas para escolher seu domínio
|
|
</h4>
|
|
<ul className="text-xs text-[#7D7D7D] space-y-1 ml-6">
|
|
<li className="list-disc">Use o nome da sua empresa</li>
|
|
<li className="list-disc">Evite números e hífens quando possível</li>
|
|
<li className="list-disc">Escolha algo fácil de lembrar e digitar</li>
|
|
<li className="list-disc">Mínimo de 3 caracteres</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{currentStep === 5 && (
|
|
<div className="space-y-6">
|
|
{/* Botão Toggle Preview (Mobile Only) */}
|
|
<div className="lg:hidden">
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPreviewMobile(!showPreviewMobile)}
|
|
className="w-full flex items-center justify-center gap-2 px-4 py-3 rounded-lg border-2 border-[#FF3A05] text-[#FF3A05] font-medium hover:bg-[#FF3A05]/5 transition-colors"
|
|
>
|
|
<i className={`${showPreviewMobile ? 'ri-edit-line' : 'ri-eye-line'} text-xl`} />
|
|
{showPreviewMobile ? 'Voltar ao Formulário' : 'Ver Preview do Painel'}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Preview Mobile */}
|
|
{showPreviewMobile && (
|
|
<div className="lg:hidden">
|
|
<DashboardPreview
|
|
companyName={formData.companyName || 'Sua Empresa'}
|
|
subdomain={subdomain}
|
|
primaryColor={primaryColor}
|
|
secondaryColor={secondaryColor}
|
|
logoUrl={logoUrl}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Formulário (oculto quando preview ativo no mobile) */}
|
|
<div className={showPreviewMobile ? 'hidden lg:block space-y-4' : 'block space-y-4'}>
|
|
{/* Upload de Logo */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-[#000000] mb-3">
|
|
Logo da Empresa <span className="text-[#7D7D7D]">(opcional)</span>
|
|
</label>
|
|
<div className="flex items-center gap-6">
|
|
{/* Preview do Logo */}
|
|
<div className="w-20 h-20 rounded-lg border-2 border-dashed border-[#E5E5E5] flex items-center justify-center overflow-hidden bg-[#F5F5F5]">
|
|
{logoUrl ? (
|
|
<img src={logoUrl} alt="Logo preview" className="w-full h-full object-cover" />
|
|
) : (
|
|
<i className="ri-image-line text-3xl text-[#7D7D7D]" />
|
|
)}
|
|
</div>
|
|
{/* Input de Upload */}
|
|
<div className="flex-1">
|
|
<input
|
|
type="file"
|
|
accept="image/*"
|
|
onChange={(e) => {
|
|
const file = e.target.files?.[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onloadend = () => {
|
|
setLogoUrl(reader.result as string);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
}}
|
|
className="hidden"
|
|
id="logo-upload"
|
|
/>
|
|
<label
|
|
htmlFor="logo-upload"
|
|
className="inline-flex items-center gap-2 px-4 py-2 border border-[#E5E5E5] rounded-md text-sm font-medium text-[#000000] hover:bg-[#F5F5F5] transition-colors cursor-pointer"
|
|
>
|
|
<i className="ri-upload-2-line" />
|
|
Escolher arquivo
|
|
</label>
|
|
{logoUrl && (
|
|
<button
|
|
type="button"
|
|
onClick={() => setLogoUrl('')}
|
|
className="ml-2 text-sm bg-linear-to-r from-[#FF3A05] to-[#FF0080] bg-clip-text text-transparent hover:underline font-medium"
|
|
>
|
|
Remover
|
|
</button>
|
|
)}
|
|
<p className="text-xs text-[#7D7D7D] mt-2">
|
|
PNG, JPG ou SVG. Tamanho recomendado: 200x200px
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Cores do Painel */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{/* Cor Primária */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-[#000000] mb-3">
|
|
Cor Primária <span className="text-[#FF3A05]">*</span>
|
|
</label>
|
|
<div className="flex gap-3">
|
|
<div className="relative flex-1">
|
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
|
<i className="ri-palette-line text-[#7D7D7D] text-[18px]" />
|
|
</div>
|
|
<input
|
|
type="text"
|
|
value={primaryColor}
|
|
onChange={(e) => {
|
|
const value = e.target.value;
|
|
if (/^#[0-9A-Fa-f]{0,6}$/.test(value)) {
|
|
setPrimaryColor(value);
|
|
}
|
|
}}
|
|
placeholder="#FF3A05"
|
|
className="w-full pl-10 pr-4 py-2 text-sm border border-[#E5E5E5] rounded-md focus:border-[#FF3A05] transition-colors font-mono"
|
|
/>
|
|
</div>
|
|
<input
|
|
type="color"
|
|
value={primaryColor}
|
|
onChange={(e) => setPrimaryColor(e.target.value)}
|
|
className="w-14 h-10 border-2 border-[#E5E5E5] rounded-md cursor-pointer"
|
|
/>
|
|
</div>
|
|
<p className="text-xs text-[#7D7D7D] mt-1 flex items-center gap-1">
|
|
<i className="ri-information-line" />
|
|
Usada em menus, botões e destaques
|
|
</p>
|
|
</div>
|
|
|
|
{/* Cor Secundária */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-[#000000] mb-3">
|
|
Cor Secundária <span className="text-[#7D7D7D]">(opcional)</span>
|
|
</label>
|
|
<div className="flex gap-3">
|
|
<div className="relative flex-1">
|
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
|
<i className="ri-brush-line text-[#7D7D7D] text-[18px]" />
|
|
</div>
|
|
<input
|
|
type="text"
|
|
value={secondaryColor}
|
|
onChange={(e) => {
|
|
const value = e.target.value;
|
|
if (/^#[0-9A-Fa-f]{0,6}$/.test(value)) {
|
|
setSecondaryColor(value);
|
|
}
|
|
}}
|
|
placeholder="#FF0080"
|
|
className="w-full pl-10 pr-4 py-2 text-sm border border-[#E5E5E5] rounded-md focus:border-[#FF3A05] transition-colors font-mono"
|
|
/>
|
|
</div>
|
|
<input
|
|
type="color"
|
|
value={secondaryColor}
|
|
onChange={(e) => setSecondaryColor(e.target.value)}
|
|
className="w-14 h-10 border-2 border-[#E5E5E5] rounded-md cursor-pointer"
|
|
/>
|
|
</div>
|
|
<p className="text-xs text-[#7D7D7D] mt-1 flex items-center gap-1">
|
|
<i className="ri-information-line" />
|
|
Usada em cards e elementos secundários
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Paletas Sugeridas */}
|
|
<div>
|
|
<h4 className="text-sm font-semibold text-[#000000] mb-4">Paletas Sugeridas</h4>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
{[
|
|
{ name: 'Fogo', primary: '#FF3A05', secondary: '#FF0080' },
|
|
{ name: 'Oceano', primary: '#0EA5E9', secondary: '#3B82F6' },
|
|
{ name: 'Natureza', primary: '#10B981', secondary: '#059669' },
|
|
{ name: 'Elegante', primary: '#8B5CF6', secondary: '#A78BFA' },
|
|
{ name: 'Solar', primary: '#F59E0B', secondary: '#FBBF24' },
|
|
{ name: 'Noturno', primary: '#1E293B', secondary: '#475569' },
|
|
{ name: 'Rosa', primary: '#EC4899', secondary: '#F472B6' },
|
|
{ name: 'Ciano', primary: '#06B6D4', secondary: '#22D3EE' },
|
|
].map((palette) => (
|
|
<button
|
|
key={palette.name}
|
|
type="button"
|
|
onClick={() => {
|
|
setPrimaryColor(palette.primary);
|
|
setSecondaryColor(palette.secondary);
|
|
}}
|
|
className="flex items-center gap-2 p-2 rounded-md border border-[#E5E5E5] hover:border-[#FF3A05] transition-colors group cursor-pointer"
|
|
>
|
|
<div className="flex gap-1">
|
|
<div
|
|
className="w-6 h-6 rounded"
|
|
style={{ backgroundColor: palette.primary }}
|
|
/>
|
|
<div
|
|
className="w-6 h-6 rounded"
|
|
style={{ backgroundColor: palette.secondary }}
|
|
/>
|
|
</div>
|
|
<span className="text-xs font-medium text-[#7D7D7D] group-hover:text-[#000000]">
|
|
{palette.name}
|
|
</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Informações */}
|
|
<div className="p-6 bg-[#F0F9FF] border border-[#BAE6FD] rounded-md">
|
|
<div className="flex gap-4">
|
|
<i className="ri-information-line text-[#0EA5E9] text-xl mt-0.5" />
|
|
<div>
|
|
<h4 className="text-sm font-semibold text-[#000000] mb-1">
|
|
Você pode alterar depois
|
|
</h4>
|
|
<p className="text-xs text-[#7D7D7D]">
|
|
As cores do seu painel podem ser ajustadas a qualquer momento nas configurações.
|
|
Experimente diferentes combinações até encontrar a ideal!
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Rodapé - botão voltar à esquerda, etapas e botão ação à direita */}
|
|
<div className="border-t border-[#E5E5E5] bg-white px-4 sm:px-12 py-4">
|
|
{/* Desktop: Linha única com tudo */}
|
|
<div className="hidden md:flex items-center justify-between">
|
|
{/* Botão voltar à esquerda */}
|
|
<div>
|
|
{currentStep > 1 && (
|
|
<Button variant="outline" onClick={() => setCurrentStep(currentStep - 1)} leftIcon="ri-arrow-left-line">
|
|
Voltar
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Etapas centralizadas e enumeradas */}
|
|
<div className="flex items-center justify-center gap-3">
|
|
{steps.map((step, index) => (
|
|
<div key={step.number} className="flex items-center gap-3">
|
|
<button
|
|
type="button"
|
|
onClick={() => setCurrentStep(step.number)}
|
|
className="flex flex-col items-center gap-1.5 group cursor-pointer transition-all hover:scale-105"
|
|
>
|
|
<div
|
|
className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-semibold transition-all ${completedSteps.includes(step.number)
|
|
? "bg-[#10B981] text-white"
|
|
: currentStep === step.number
|
|
? "text-white"
|
|
: "bg-[#E5E5E5] text-[#7D7D7D] group-hover:bg-[#D5D5D5]"
|
|
}`}
|
|
style={currentStep === step.number ? { background: 'linear-gradient(90deg, #FF3A05, #FF0080)' } : undefined}
|
|
>
|
|
{step.number}
|
|
</div>
|
|
<span className={`text-xs transition-colors ${currentStep === step.number
|
|
? "text-[#000000] font-semibold"
|
|
: "text-[#7D7D7D] group-hover:text-[#000000]"
|
|
}`}>
|
|
{step.title}
|
|
</span>
|
|
</button>
|
|
{index < steps.length - 1 && (
|
|
<div className="w-12 h-0.5 bg-[#E5E5E5] mb-5" />
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Botão de ação */}
|
|
<Button
|
|
variant="primary"
|
|
type="button"
|
|
onClick={handleNext}
|
|
rightIcon={currentStep === 5 ? "ri-check-line" : "ri-arrow-right-line"}
|
|
>
|
|
{currentStep === 5 ? "Finalizar" : "Continuar"}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Mobile: Layout empilhado */}
|
|
<div className="flex md:hidden flex-col gap-4">
|
|
{/* Etapas simplificadas - apenas bolinhas */}
|
|
<div className="flex items-center justify-center gap-2">
|
|
{steps.map((step) => (
|
|
<button
|
|
key={step.number}
|
|
type="button"
|
|
onClick={() => setCurrentStep(step.number)}
|
|
className={`h-2 rounded-full transition-all ${completedSteps.includes(step.number)
|
|
? "w-2 bg-[#10B981]"
|
|
: currentStep === step.number
|
|
? "w-8"
|
|
: "w-2 bg-[#E5E5E5] hover:bg-[#D5D5D5]"
|
|
}`}
|
|
style={currentStep === step.number ? { background: 'linear-gradient(90deg, #FF3A05, #FF0080)' } : undefined}
|
|
aria-label={`Ir para ${step.title}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{/* Botões */}
|
|
<div className="flex gap-2">
|
|
{currentStep > 1 && (
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setCurrentStep(currentStep - 1)}
|
|
leftIcon="ri-arrow-left-line"
|
|
className="flex-1"
|
|
>
|
|
Voltar
|
|
</Button>
|
|
)}
|
|
<Button
|
|
variant="primary"
|
|
type="button"
|
|
onClick={handleNext}
|
|
rightIcon={currentStep === 5 ? "ri-check-line" : "ri-arrow-right-line"}
|
|
className="flex-1"
|
|
>
|
|
{currentStep === 5 ? "Finalizar" : "Continuar"}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Lado Direito - Branding Dinâmico */}
|
|
<div className="hidden lg:flex lg:w-[50%] relative overflow-hidden" style={{ background: 'linear-gradient(90deg, #FF3A05, #FF0080)' }}>
|
|
<DynamicBranding
|
|
currentStep={currentStep}
|
|
companyName={formData.companyName}
|
|
subdomain={subdomain}
|
|
primaryColor={primaryColor}
|
|
secondaryColor={secondaryColor}
|
|
logoUrl={logoUrl}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|