Formulários Complexos e Upload
Módulo 4: Eventos e Formulários
Aula 4
1
2
Upload de Arquivos e Campos Dinâmicos
Implemente upload de arquivos e formulários que crescem dinamicamente
Upload de Arquivos e Campos Dinâmicos
Upload de Arquivos
Input de Arquivo Básico
function UploadSimples() {
const [arquivo, setArquivo] = useState(null);
const [preview, setPreview] = useState('');
const handleFileChange = (e) => {
const file = e.target.files[0];
if (file) {
setArquivo(file);
// Preview para imagens
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onloadend = () => {
setPreview(reader.result);
};
reader.readAsDataURL(file);
}
}
};
return (
<div>
<input
type="file"
onChange={handleFileChange}
accept="image/*"
/>
{preview && (
<img
src={preview}
alt="Preview"
style={{ maxWidth: '200px' }}
/>
)}
{arquivo && (
<div>
<p>Nome: {arquivo.name}</p>
<p>Tamanho: {(arquivo.size / 1024).toFixed(2)} KB</p>
<p>Tipo: {arquivo.type}</p>
</div>
)}
</div>
);
}
Upload Múltiplo com Drag & Drop
function UploadMultiplo() {
const [arquivos, setArquivos] = useState([]);
const [arrastando, setArrastando] = useState(false);
const handleDrop = (e) => {
e.preventDefault();
setArrastando(false);
const files = Array.from(e.dataTransfer.files);
processarArquivos(files);
};
const handleFileSelect = (e) => {
const files = Array.from(e.target.files);
processarArquivos(files);
};
const processarArquivos = (files) => {
const novosArquivos = files.map(file => ({
id: Date.now() + Math.random(),
file,
nome: file.name,
tamanho: file.size,
tipo: file.type,
progresso: 0,
status: 'pendente'
}));
setArquivos(prev => [...prev, ...novosArquivos]);
// Simular upload
novosArquivos.forEach(arquivo => {
simularUpload(arquivo.id);
});
};
const simularUpload = (id) => {
let progresso = 0;
const interval = setInterval(() => {
progresso += 10;
setArquivos(prev => prev.map(arquivo => {
if (arquivo.id === id) {
if (progresso >= 100) {
clearInterval(interval);
return { ...arquivo, progresso: 100, status: 'completo' };
}
return { ...arquivo, progresso };
}
return arquivo;
}));
}, 200);
};
const removerArquivo = (id) => {
setArquivos(prev => prev.filter(arquivo => arquivo.id !== id));
};
return (
<div>
<div
onDrop={handleDrop}
onDragOver={(e) => {
e.preventDefault();
setArrastando(true);
}}
onDragLeave={() => setArrastando(false)}
style={{
border: `2px dashed ${arrastando ? '#2196f3' : '#ccc'}`,
borderRadius: '8px',
padding: '40px',
textAlign: 'center',
backgroundColor: arrastando ? '#e3f2fd' : '#f5f5f5',
transition: 'all 0.3s'
}}
>
<p>Arraste arquivos aqui ou</p>
<input
type="file"
multiple
onChange={handleFileSelect}
style={{ display: 'none' }}
id="file-input"
/>
<label htmlFor="file-input" style={{
cursor: 'pointer',
color: '#2196f3',
textDecoration: 'underline'
}}>
clique para selecionar
</label>
</div>
<div style={{ marginTop: '20px' }}>
{arquivos.map(arquivo => (
<div key={arquivo.id} style={{
border: '1px solid #ddd',
padding: '10px',
marginBottom: '10px',
borderRadius: '4px'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{arquivo.nome}</span>
<button onClick={() => removerArquivo(arquivo.id)}>×</button>
</div>
<div style={{
height: '4px',
backgroundColor: '#e0e0e0',
borderRadius: '2px',
marginTop: '5px',
overflow: 'hidden'
}}>
<div style={{
height: '100%',
width: `${arquivo.progresso}%`,
backgroundColor: arquivo.status === 'completo' ? '#4caf50' : '#2196f3',
transition: 'width 0.3s'
}} />
</div>
<small>
{arquivo.status === 'completo' ? 'Concluído' : `${arquivo.progresso}%`}
</small>
</div>
))}
</div>
</div>
);
}
Campos Dinâmicos
Lista de Itens Dinâmica
function ListaDinamica() {
const [itens, setItens] = useState([
{ id: 1, nome: '', quantidade: 1, preco: 0 }
]);
const adicionarItem = () => {
const novoItem = {
id: Date.now(),
nome: '',
quantidade: 1,
preco: 0
};
setItens([...itens, novoItem]);
};
const removerItem = (id) => {
if (itens.length > 1) {
setItens(itens.filter(item => item.id !== id));
}
};
const atualizarItem = (id, campo, valor) => {
setItens(itens.map(item => {
if (item.id === id) {
return { ...item, [campo]: valor };
}
return item;
}));
};
const calcularTotal = () => {
return itens.reduce((total, item) => {
return total + (item.quantidade * item.preco);
}, 0);
};
return (
<div>
<h3>Lista de Compras</h3>
{itens.map((item, index) => (
<div key={item.id} style={{
display: 'grid',
gridTemplateColumns: '3fr 1fr 1fr auto',
gap: '10px',
marginBottom: '10px',
alignItems: 'center'
}}>
<input
type="text"
placeholder="Nome do item"
value={item.nome}
onChange={(e) => atualizarItem(item.id, 'nome', e.target.value)}
/>
<input
type="number"
placeholder="Qtd"
min="1"
value={item.quantidade}
onChange={(e) => atualizarItem(item.id, 'quantidade', parseInt(e.target.value))}
/>
<input
type="number"
placeholder="Preço"
min="0"
step="0.01"
value={item.preco}
onChange={(e) => atualizarItem(item.id, 'preco', parseFloat(e.target.value))}
/>
<button
onClick={() => removerItem(item.id)}
disabled={itens.length === 1}
>
Remover
</button>
</div>
))}
<button onClick={adicionarItem}>
+ Adicionar Item
</button>
<div style={{ marginTop: '20px', fontSize: '18px' }}>
<strong>Total: R$ {calcularTotal().toFixed(2)}</strong>
</div>
</div>
);
}
Formulário de Endereços Múltiplos
function EnderecosMultiplos() {
const [enderecos, setEnderecos] = useState([{
id: 1,
tipo: 'residencial',
cep: '',
rua: '',
numero: '',
complemento: '',
bairro: '',
cidade: '',
estado: '',
principal: true
}]);
const adicionarEndereco = () => {
const novoEndereco = {
id: Date.now(),
tipo: 'residencial',
cep: '',
rua: '',
numero: '',
complemento: '',
bairro: '',
cidade: '',
estado: '',
principal: false
};
setEnderecos([...enderecos, novoEndereco]);
};
const removerEndereco = (id) => {
const enderecosRestantes = enderecos.filter(end => end.id !== id);
// Se removeu o principal, tornar o primeiro como principal
if (enderecos.find(e => e.id === id)?.principal && enderecosRestantes.length > 0) {
enderecosRestantes[0].principal = true;
}
setEnderecos(enderecosRestantes);
};
const atualizarEndereco = (id, campo, valor) => {
setEnderecos(enderecos.map(endereco => {
if (endereco.id === id) {
// Se marcou como principal, desmarcar os outros
if (campo === 'principal' && valor === true) {
enderecos.forEach(e => {
if (e.id !== id) e.principal = false;
});
}
return { ...endereco, [campo]: valor };
}
return endereco;
}));
};
const buscarCEP = async (id, cep) => {
if (cep.length === 8) {
try {
const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`);
const data = await response.json();
if (!data.erro) {
setEnderecos(enderecos.map(endereco => {
if (endereco.id === id) {
return {
...endereco,
rua: data.logradouro,
bairro: data.bairro,
cidade: data.localidade,
estado: data.uf
};
}
return endereco;
}));
}
} catch (error) {
console.error('Erro ao buscar CEP:', error);
}
}
};
return (
<div>
<h3>Endereços</h3>
{enderecos.map((endereco, index) => (
<fieldset key={endereco.id} style={{ marginBottom: '20px' }}>
<legend>
Endereço {index + 1}
{endereco.principal && <span> (Principal)</span>}
</legend>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
<div>
<label>Tipo</label>
<select
value={endereco.tipo}
onChange={(e) => atualizarEndereco(endereco.id, 'tipo', e.target.value)}
>
<option value="residencial">Residencial</option>
<option value="comercial">Comercial</option>
<option value="outro">Outro</option>
</select>
</div>
<div>
<label>CEP</label>
<input
type="text"
value={endereco.cep}
onChange={(e) => {
const valor = e.target.value.replace(/\D/g, '');
atualizarEndereco(endereco.id, 'cep', valor);
if (valor.length === 8) {
buscarCEP(endereco.id, valor);
}
}}
maxLength="8"
placeholder="00000000"
/>
</div>
<div style={{ gridColumn: 'span 2' }}>
<label>Rua</label>
<input
type="text"
value={endereco.rua}
onChange={(e) => atualizarEndereco(endereco.id, 'rua', e.target.value)}
/>
</div>
<div>
<label>Número</label>
<input
type="text"
value={endereco.numero}
onChange={(e) => atualizarEndereco(endereco.id, 'numero', e.target.value)}
/>
</div>
<div>
<label>Complemento</label>
<input
type="text"
value={endereco.complemento}
onChange={(e) => atualizarEndereco(endereco.id, 'complemento', e.target.value)}
/>
</div>
<div>
<label>Bairro</label>
<input
type="text"
value={endereco.bairro}
onChange={(e) => atualizarEndereco(endereco.id, 'bairro', e.target.value)}
/>
</div>
<div>
<label>Cidade</label>
<input
type="text"
value={endereco.cidade}
onChange={(e) => atualizarEndereco(endereco.id, 'cidade', e.target.value)}
/>
</div>
<div>
<label>Estado</label>
<input
type="text"
value={endereco.estado}
onChange={(e) => atualizarEndereco(endereco.id, 'estado', e.target.value)}
maxLength="2"
/>
</div>
</div>
<div style={{ marginTop: '10px', display: 'flex', justifyContent: 'space-between' }}>
<label>
<input
type="checkbox"
checked={endereco.principal}
onChange={(e) => atualizarEndereco(endereco.id, 'principal', e.target.checked)}
/>
Endereço principal
</label>
{enderecos.length > 1 && (
<button
type="button"
onClick={() => removerEndereco(endereco.id)}
style={{ color: 'red' }}
>
Remover endereço
</button>
)}
</div>
</fieldset>
))}
<button type="button" onClick={adicionarEndereco}>
+ Adicionar Endereço
</button>
</div>
);
}
Formulário Multi-Step Completo
function FormularioMultiStep() {
const [etapaAtual, setEtapaAtual] = useState(0);
const [dadosFormulario, setDadosFormulario] = useState({
// Etapa 1 - Dados Pessoais
nome: '',
email: '',
telefone: '',
dataNascimento: '',
// Etapa 2 - Endereço
cep: '',
endereco: '',
numero: '',
cidade: '',
estado: '',
// Etapa 3 - Preferências
interesses: [],
newsletter: false,
notificacoes: 'email',
// Etapa 4 - Documentos
documentos: []
});
const etapas = [
{ titulo: 'Dados Pessoais', componente: EtapaDadosPessoais },
{ titulo: 'Endereço', componente: EtapaEndereco },
{ titulo: 'Preferências', componente: EtapaPreferencias },
{ titulo: 'Documentos', componente: EtapaDocumentos },
{ titulo: 'Confirmação', componente: EtapaConfirmacao }
];
const proximaEtapa = () => {
if (etapaAtual < etapas.length - 1) {
setEtapaAtual(etapaAtual + 1);
}
};
const etapaAnterior = () => {
if (etapaAtual > 0) {
setEtapaAtual(etapaAtual - 1);
}
};
const atualizarDados = (novosDados) => {
setDadosFormulario({ ...dadosFormulario, ...novosDados });
};
const enviarFormulario = () => {
console.log('Formulário enviado:', dadosFormulario);
// Processar envio
};
const EtapaComponente = etapas[etapaAtual].componente;
return (
<div style={{ maxWidth: '600px', margin: '0 auto' }}>
{/* Indicador de Progresso */}
<div style={{ marginBottom: '30px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{etapas.map((etapa, index) => (
<div
key={index}
style={{
flex: 1,
textAlign: 'center',
position: 'relative'
}}
>
<div
style={{
width: '30px',
height: '30px',
borderRadius: '50%',
backgroundColor: index <= etapaAtual ? '#4caf50' : '#e0e0e0',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto',
fontWeight: 'bold'
}}
>
{index < etapaAtual ? '✓' : index + 1}
</div>
<small style={{ marginTop: '5px', display: 'block' }}>
{etapa.titulo}
</small>
{index < etapas.length - 1 && (
<div
style={{
position: 'absolute',
top: '15px',
left: '50%',
width: '100%',
height: '2px',
backgroundColor: index < etapaAtual ? '#4caf50' : '#e0e0e0',
zIndex: -1
}}
/>
)}
</div>
))}
</div>
</div>
{/* Conteúdo da Etapa */}
<div style={{ minHeight: '300px' }}>
<h2>{etapas[etapaAtual].titulo}</h2>
<EtapaComponente
dados={dadosFormulario}
atualizarDados={atualizarDados}
/>
</div>
{/* Botões de Navegação */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
marginTop: '30px'
}}>
<button
onClick={etapaAnterior}
disabled={etapaAtual === 0}
style={{
visibility: etapaAtual === 0 ? 'hidden' : 'visible'
}}
>
Anterior
</button>
{etapaAtual === etapas.length - 1 ? (
<button
onClick={enviarFormulario}
style={{
backgroundColor: '#4caf50',
color: 'white',
padding: '10px 20px',
border: 'none',
borderRadius: '4px'
}}
>
Enviar
</button>
) : (
<button onClick={proximaEtapa}>
Próximo
</button>
)}
</div>
</div>
);
}
// Componentes das Etapas (exemplos simplificados)
function EtapaDadosPessoais({ dados, atualizarDados }) {
return (
<div>
<input
type="text"
placeholder="Nome completo"
value={dados.nome}
onChange={(e) => atualizarDados({ nome: e.target.value })}
/>
{/* Outros campos... */}
</div>
);
}
function EtapaEndereco({ dados, atualizarDados }) {
return (
<div>
<input
type="text"
placeholder="CEP"
value={dados.cep}
onChange={(e) => atualizarDados({ cep: e.target.value })}
/>
{/* Outros campos... */}
</div>
);
}
function EtapaPreferencias({ dados, atualizarDados }) {
return (
<div>
<label>
<input
type="checkbox"
checked={dados.newsletter}
onChange={(e) => atualizarDados({ newsletter: e.target.checked })}
/>
Receber newsletter
</label>
{/* Outros campos... */}
</div>
);
}
function EtapaDocumentos({ dados, atualizarDados }) {
return (
<div>
<p>Upload de documentos...</p>
{/* Implementar upload */}
</div>
);
}
function EtapaConfirmacao({ dados }) {
return (
<div>
<h3>Confirme seus dados:</h3>
<pre>{JSON.stringify(dados, null, 2)}</pre>
</div>
);
}
3
Projeto: Sistema de Inscrição Completo
Crie um sistema de inscrição com todas as técnicas aprendidas
Activity
Projeto: Sistema de Inscrição Completo
Objetivo
Criar um sistema completo de inscrição para um evento/curso com:
- Formulário multi-step
- Upload de documentos
- Campos dinâmicos
- Validação completa
- Integração com APIs
Requisitos do Sistema
Etapa 1: Dados Pessoais
- Nome completo
- CPF
- Data de nascimento
- Telefone
- Foto de perfil (upload)
Etapa 2: Dados Profissionais
- Empresa atual
- Cargo
- Tempo de experiência
- Habilidades (dinâmicas)
- Currículo (upload PDF)
Etapa 3: Formação Acadêmica
- Formações (dinâmicas)
- Instituição
- Curso
- Início/Fim
- Diploma (upload)
Etapa 4: Preferências
- Áreas de interesse
- Disponibilidade de horário
- Modalidade preferida
- Necessidades especiais
Etapa 5: Pagamento
- Tipo de inscrição
- Cupom de desconto
- Dados de pagamento
- Comprovante (upload)
Etapa 6: Confirmação
- Resumo de todos os dados
- Termos e condições
- Enviar inscrição
Template Inicial
import React, { useState, useEffect } from 'react';
function SistemaInscricao() {
const [etapaAtual, setEtapaAtual] = useState(0);
const [dadosInscricao, setDadosInscricao] = useState({
// Dados Pessoais
dadosPessoais: {
nome: '',
email: '',
cpf: '',
dataNascimento: '',
telefone: '',
fotoPerfil: null
},
// Dados Profissionais
dadosProfissionais: {
empresa: '',
cargo: '',
tempoExperiencia: '',
habilidades: [],
linkedin: '',
curriculo: null
},
// Formação
formacoes: [],
// Preferências
preferencias: {
areasInteresse: [],
disponibilidade: [],
modalidade: '',
necessidadesEspeciais: ''
},
// Pagamento
pagamento: {
tipoInscricao: '',
cupom: '',
metodoPagamento: '',
comprovante: null
},
// Controle
aceitouTermos: false
});
// Seu código aqui
return (
<div>
{/* Implemente o sistema aqui */}
</div>
);
}
export default SistemaInscricao;
Funcionalidades Esperadas
-
Navegação entre etapas
- Botões avançar/voltar
- Indicador de progresso visual
- Salvar progresso no localStorage
-
Validação por etapa
- Não permitir avançar com erros
- Mostrar erros específicos
- Validação de CPF real
-
Upload de arquivos
- Preview de imagens
- Limite de tamanho
- Tipos permitidos
- Progress bar
-
Campos dinâmicos
- Adicionar/remover formações
- Adicionar/remover habilidades
- Mínimo e máximo de itens
-
Integrações
- Buscar CEP
- Validar cupom de desconto
- Simular processamento de pagamento
Solução Completa
Clique para ver a solução completa
import React, { useState, useEffect } from 'react';
// Hook customizado para persistência
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('Erro ao salvar no localStorage:', error);
}
}, [key, value]);
return [value, setValue];
}
// Componente Principal
function SistemaInscricao() {
const [etapaAtual, setEtapaAtual] = useState(0);
const [dadosInscricao, setDadosInscricao] = useLocalStorage('inscricao', {
dadosPessoais: {
nome: '',
email: '',
cpf: '',
dataNascimento: '',
telefone: '',
fotoPerfil: null
},
dadosProfissionais: {
empresa: '',
cargo: '',
tempoExperiencia: '',
habilidades: [],
linkedin: '',
curriculo: null
},
formacoes: [],
preferencias: {
areasInteresse: [],
disponibilidade: [],
modalidade: '',
necessidadesEspeciais: ''
},
pagamento: {
tipoInscricao: '',
cupom: '',
cupomValidado: false,
desconto: 0,
metodoPagamento: '',
comprovante: null
},
aceitouTermos: false
});
const [errosEtapa, setErrosEtapa] = useState({});
const [processando, setProcessando] = useState(false);
const etapas = [
{ titulo: 'Dados Pessoais', componente: EtapaDadosPessoais },
{ titulo: 'Dados Profissionais', componente: EtapaDadosProfissionais },
{ titulo: 'Formação', componente: EtapaFormacao },
{ titulo: 'Preferências', componente: EtapaPreferencias },
{ titulo: 'Pagamento', componente: EtapaPagamento },
{ titulo: 'Confirmação', componente: EtapaConfirmacao }
];
const validarEtapa = () => {
const validadores = [
validarDadosPessoais,
validarDadosProfissionais,
validarFormacao,
validarPreferencias,
validarPagamento,
validarConfirmacao
];
const erros = validadores[etapaAtual](dadosInscricao);
setErrosEtapa(erros);
return Object.keys(erros).length === 0;
};
const proximaEtapa = () => {
if (validarEtapa() && etapaAtual < etapas.length - 1) {
setEtapaAtual(etapaAtual + 1);
setErrosEtapa({});
}
};
const etapaAnterior = () => {
if (etapaAtual > 0) {
setEtapaAtual(etapaAtual - 1);
setErrosEtapa({});
}
};
const atualizarDados = (categoria, dados) => {
setDadosInscricao(prev => {
if (categoria) {
return {
...prev,
[categoria]: { ...prev[categoria], ...dados }
};
}
return { ...prev, ...dados };
});
};
const enviarInscricao = async () => {
if (!dadosInscricao.aceitouTermos) {
setErrosEtapa({ termos: 'Você deve aceitar os termos' });
return;
}
setProcessando(true);
// Simular envio
await new Promise(resolve => setTimeout(resolve, 3000));
console.log('Inscrição enviada:', dadosInscricao);
alert('Inscrição realizada com sucesso!');
// Limpar dados
localStorage.removeItem('inscricao');
window.location.reload();
};
const EtapaComponente = etapas[etapaAtual].componente;
return (
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
<h1>Sistema de Inscrição</h1>
{/* Progress Bar */}
<div style={{ marginBottom: '40px' }}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
position: 'relative'
}}>
{etapas.map((etapa, index) => (
<div key={index} style={{
flex: 1,
textAlign: 'center',
position: 'relative',
zIndex: 1
}}>
<div style={{
width: '40px',
height: '40px',
borderRadius: '50%',
backgroundColor: index <= etapaAtual ? '#4caf50' : '#e0e0e0',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto',
fontWeight: 'bold',
cursor: index < etapaAtual ? 'pointer' : 'default'
}}
onClick={() => index < etapaAtual && setEtapaAtual(index)}
>
{index < etapaAtual ? '✓' : index + 1}
</div>
<small style={{ marginTop: '8px', display: 'block' }}>
{etapa.titulo}
</small>
</div>
))}
{/* Linha de progresso */}
<div style={{
position: 'absolute',
top: '20px',
left: '20px',
right: '20px',
height: '2px',
backgroundColor: '#e0e0e0',
zIndex: 0
}}>
<div style={{
width: `${(etapaAtual / (etapas.length - 1)) * 100}%`,
height: '100%',
backgroundColor: '#4caf50',
transition: 'width 0.3s'
}} />
</div>
</div>
</div>
{/* Conteúdo da Etapa */}
<div style={{
minHeight: '400px',
border: '1px solid #ddd',
borderRadius: '8px',
padding: '30px',
marginBottom: '20px'
}}>
<h2>{etapas[etapaAtual].titulo}</h2>
<EtapaComponente
dados={dadosInscricao}
atualizarDados={atualizarDados}
erros={errosEtapa}
/>
</div>
{/* Botões de Navegação */}
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<button
onClick={etapaAnterior}
disabled={etapaAtual === 0}
style={{
padding: '10px 20px',
visibility: etapaAtual === 0 ? 'hidden' : 'visible'
}}
>
← Anterior
</button>
{etapaAtual === etapas.length - 1 ? (
<button
onClick={enviarInscricao}
disabled={processando}
style={{
padding: '10px 30px',
backgroundColor: '#4caf50',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: processando ? 'not-allowed' : 'pointer'
}}
>
{processando ? 'Enviando...' : 'Finalizar Inscrição'}
</button>
) : (
<button
onClick={proximaEtapa}
style={{
padding: '10px 20px',
backgroundColor: '#2196f3',
color: 'white',
border: 'none',
borderRadius: '4px'
}}
>
Próximo →
</button>
)}
</div>
</div>
);
}
// Etapa 1: Dados Pessoais
function EtapaDadosPessoais({ dados, atualizarDados, erros }) {
const [previewFoto, setPreviewFoto] = useState('');
const handleFotoChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setPreviewFoto(reader.result);
atualizarDados('dadosPessoais', {
fotoPerfil: reader.result
});
};
reader.readAsDataURL(file);
}
};
const formatarCPF = (valor) => {
const numero = valor.replace(/\D/g, '');
if (numero.length <= 11) {
return numero.replace(
/(\d{3})(\d{3})(\d{3})(\d{2})/,
'$1.$2.$3-$4'
).substring(0, 14);
}
return valor;
};
const formatarTelefone = (valor) => {
const numero = valor.replace(/\D/g, '');
if (numero.length <= 11) {
return numero.replace(
/(\d{2})(\d{5})(\d{4})/,
'($1) $2-$3'
).substring(0, 15);
}
return valor;
};
return (
<div>
<div style={{ marginBottom: '20px', textAlign: 'center' }}>
<div style={{
width: '150px',
height: '150px',
borderRadius: '50%',
backgroundColor: '#f0f0f0',
margin: '0 auto',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
border: '2px solid #ddd'
}}>
{previewFoto || dados.dadosPessoais.fotoPerfil ? (
<img
src={previewFoto || dados.dadosPessoais.fotoPerfil}
alt="Foto de perfil"
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
) : (
<span style={{ color: '#999' }}>Foto</span>
)}
</div>
<input
type="file"
accept="image/*"
onChange={handleFotoChange}
style={{ marginTop: '10px' }}
/>
{erros.fotoPerfil && <span style={{ color: 'red' }}>{erros.fotoPerfil}</span>}
</div>
<div style={{ display: 'grid', gap: '15px' }}>
<div>
<label>Nome Completo *</label>
<input
type="text"
value={dados.dadosPessoais.nome}
onChange={(e) => atualizarDados('dadosPessoais', { nome: e.target.value })}
style={{ width: '100%', padding: '8px' }}
/>
{erros.nome && <span style={{ color: 'red' }}>{erros.nome}</span>}
</div>
<div>
<label>Email *</label>
<input
type="email"
value={dados.dadosPessoais.email}
onChange={(e) => atualizarDados('dadosPessoais', { email: e.target.value })}
style={{ width: '100%', padding: '8px' }}
/>
{erros.email && <span style={{ color: 'red' }}>{erros.email}</span>}
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px' }}>
<div>
<label>CPF *</label>
<input
type="text"
value={dados.dadosPessoais.cpf}
onChange={(e) => atualizarDados('dadosPessoais', {
cpf: formatarCPF(e.target.value)
})}
maxLength="14"
style={{ width: '100%', padding: '8px' }}
/>
{erros.cpf && <span style={{ color: 'red' }}>{erros.cpf}</span>}
</div>
<div>
<label>Data de Nascimento *</label>
<input
type="date"
value={dados.dadosPessoais.dataNascimento}
onChange={(e) => atualizarDados('dadosPessoais', {
dataNascimento: e.target.value
})}
style={{ width: '100%', padding: '8px' }}
/>
{erros.dataNascimento && <span style={{ color: 'red' }}>{erros.dataNascimento}</span>}
</div>
</div>
<div>
<label>Telefone *</label>
<input
type="text"
value={dados.dadosPessoais.telefone}
onChange={(e) => atualizarDados('dadosPessoais', {
telefone: formatarTelefone(e.target.value)
})}
maxLength="15"
style={{ width: '100%', padding: '8px' }}
/>
{erros.telefone && <span style={{ color: 'red' }}>{erros.telefone}</span>}
</div>
</div>
</div>
);
}
// Etapa 2: Dados Profissionais
function EtapaDadosProfissionais({ dados, atualizarDados, erros }) {
const [novaHabilidade, setNovaHabilidade] = useState('');
const adicionarHabilidade = () => {
if (novaHabilidade.trim()) {
atualizarDados('dadosProfissionais', {
habilidades: [...dados.dadosProfissionais.habilidades, novaHabilidade.trim()]
});
setNovaHabilidade('');
}
};
const removerHabilidade = (index) => {
const novasHabilidades = dados.dadosProfissionais.habilidades.filter(
(_, i) => i !== index
);
atualizarDados('dadosProfissionais', { habilidades: novasHabilidades });
};
return (
<div>
<div style={{ display: 'grid', gap: '15px' }}>
<div>
<label>Empresa Atual *</label>
<input
type="text"
value={dados.dadosProfissionais.empresa}
onChange={(e) => atualizarDados('dadosProfissionais', {
empresa: e.target.value
})}
style={{ width: '100%', padding: '8px' }}
/>
{erros.empresa && <span style={{ color: 'red' }}>{erros.empresa}</span>}
</div>
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '15px' }}>
<div>
<label>Cargo *</label>
<input
type="text"
value={dados.dadosProfissionais.cargo}
onChange={(e) => atualizarDados('dadosProfissionais', {
cargo: e.target.value
})}
style={{ width: '100%', padding: '8px' }}
/>
{erros.cargo && <span style={{ color: 'red' }}>{erros.cargo}</span>}
</div>
<div>
<label>Tempo de Experiência *</label>
<select
value={dados.dadosProfissionais.tempoExperiencia}
onChange={(e) => atualizarDados('dadosProfissionais', {
tempoExperiencia: e.target.value
})}
style={{ width: '100%', padding: '8px' }}
>
<option value="">Selecione</option>
<option value="0-1">Menos de 1 ano</option>
<option value="1-3">1 a 3 anos</option>
<option value="3-5">3 a 5 anos</option>
<option value="5-10">5 a 10 anos</option>
<option value="10+">Mais de 10 anos</option>
</select>
{erros.tempoExperiencia && <span style={{ color: 'red' }}>{erros.tempoExperiencia}</span>}
</div>
</div>
<div>
<label>LinkedIn</label>
<input
type="url"
value={dados.dadosProfissionais.linkedin}
onChange={(e) => atualizarDados('dadosProfissionais', {
linkedin: e.target.value
})}
placeholder="https://linkedin.com/in/seu-perfil"
style={{ width: '100%', padding: '8px' }}
/>
</div>
<div>
<label>Habilidades *</label>
<div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}>
<input
type="text"
value={novaHabilidade}
onChange={(e) => setNovaHabilidade(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && adicionarHabilidade()}
placeholder="Digite uma habilidade"
style={{ flex: 1, padding: '8px' }}
/>
<button type="button" onClick={adicionarHabilidade}>
Adicionar
</button>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{dados.dadosProfissionais.habilidades.map((habilidade, index) => (
<span key={index} style={{
backgroundColor: '#e3f2fd',
padding: '5px 10px',
borderRadius: '16px',
display: 'flex',
alignItems: 'center',
gap: '5px'
}}>
{habilidade}
<button
type="button"
onClick={() => removerHabilidade(index)}
style={{
background: 'none',
border: 'none',
color: '#666',
cursor: 'pointer',
padding: '0 5px'
}}
>
×
</button>
</span>
))}
</div>
{erros.habilidades && <span style={{ color: 'red' }}>{erros.habilidades}</span>}
</div>
<div>
<label>Currículo (PDF) *</label>
<input
type="file"
accept=".pdf"
onChange={(e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
atualizarDados('dadosProfissionais', {
curriculo: {
nome: file.name,
tamanho: file.size,
dados: reader.result
}
});
};
reader.readAsDataURL(file);
}
}}
/>
{dados.dadosProfissionais.curriculo && (
<small>
Arquivo: {dados.dadosProfissionais.curriculo.nome}
({(dados.dadosProfissionais.curriculo.tamanho / 1024).toFixed(2)} KB)
</small>
)}
{erros.curriculo && <span style={{ color: 'red' }}>{erros.curriculo}</span>}
</div>
</div>
</div>
);
}
// Etapa 3: Formação Acadêmica
function EtapaFormacao({ dados, atualizarDados, erros }) {
const adicionarFormacao = () => {
const novaFormacao = {
id: Date.now(),
instituicao: '',
curso: '',
nivel: '',
dataInicio: '',
dataFim: '',
emAndamento: false,
diploma: null
};
atualizarDados(null, {
formacoes: [...dados.formacoes, novaFormacao]
});
};
const atualizarFormacao = (id, campo, valor) => {
const formacoesAtualizadas = dados.formacoes.map(formacao => {
if (formacao.id === id) {
return { ...formacao, [campo]: valor };
}
return formacao;
});
atualizarDados(null, { formacoes: formacoesAtualizadas });
};
const removerFormacao = (id) => {
const formacoesAtualizadas = dados.formacoes.filter(
formacao => formacao.id !== id
);
atualizarDados(null, { formacoes: formacoesAtualizadas });
};
return (
<div>
{dados.formacoes.length === 0 && (
<p style={{ textAlign: 'center', color: '#666' }}>
Nenhuma formação adicionada ainda.
</p>
)}
{dados.formacoes.map((formacao, index) => (
<fieldset key={formacao.id} style={{
marginBottom: '20px',
padding: '15px',
border: '1px solid #ddd',
borderRadius: '4px'
}}>
<legend>Formação {index + 1}</legend>
<div style={{ display: 'grid', gap: '15px' }}>
<div>
<label>Instituição *</label>
<input
type="text"
value={formacao.instituicao}
onChange={(e) => atualizarFormacao(formacao.id, 'instituicao', e.target.value)}
style={{ width: '100%', padding: '8px' }}
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '15px' }}>
<div>
<label>Curso *</label>
<input
type="text"
value={formacao.curso}
onChange={(e) => atualizarFormacao(formacao.id, 'curso', e.target.value)}
style={{ width: '100%', padding: '8px' }}
/>
</div>
<div>
<label>Nível *</label>
<select
value={formacao.nivel}
onChange={(e) => atualizarFormacao(formacao.id, 'nivel', e.target.value)}
style={{ width: '100%', padding: '8px' }}
>
<option value="">Selecione</option>
<option value="tecnico">Técnico</option>
<option value="graduacao">Graduação</option>
<option value="pos-graduacao">Pós-graduação</option>
<option value="mestrado">Mestrado</option>
<option value="doutorado">Doutorado</option>
</select>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px' }}>
<div>
<label>Data de Início *</label>
<input
type="date"
value={formacao.dataInicio}
onChange={(e) => atualizarFormacao(formacao.id, 'dataInicio', e.target.value)}
style={{ width: '100%', padding: '8px' }}
/>
</div>
<div>
<label>Data de Conclusão</label>
<input
type="date"
value={formacao.dataFim}
onChange={(e) => atualizarFormacao(formacao.id, 'dataFim', e.target.value)}
disabled={formacao.emAndamento}
style={{ width: '100%', padding: '8px' }}
/>
</div>
</div>
<label>
<input
type="checkbox"
checked={formacao.emAndamento}
onChange={(e) => atualizarFormacao(formacao.id, 'emAndamento', e.target.checked)}
/>
Cursando atualmente
</label>
<div>
<label>Diploma/Certificado</label>
<input
type="file"
accept=".pdf,.jpg,.jpeg,.png"
onChange={(e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
atualizarFormacao(formacao.id, 'diploma', {
nome: file.name,
dados: reader.result
});
};
reader.readAsDataURL(file);
}
}}
/>
{formacao.diploma && <small>Arquivo: {formacao.diploma.nome}</small>}
</div>
<button
type="button"
onClick={() => removerFormacao(formacao.id)}
style={{ color: 'red' }}
>
Remover esta formação
</button>
</div>
</fieldset>
))}
<button type="button" onClick={adicionarFormacao}>
+ Adicionar Formação
</button>
{erros.formacoes && <span style={{ color: 'red' }}>{erros.formacoes}</span>}
</div>
);
}
// Etapa 4: Preferências
function EtapaPreferencias({ dados, atualizarDados, erros }) {
const areasDisponiveis = [
'Desenvolvimento Web',
'Mobile',
'Data Science',
'DevOps',
'UX/UI Design',
'Inteligência Artificial',
'Segurança',
'Cloud Computing'
];
const horariosDisponiveis = [
'Manhã (8h-12h)',
'Tarde (13h-17h)',
'Noite (18h-22h)',
'Fins de semana'
];
const toggleArea = (area) => {
const areas = dados.preferencias.areasInteresse;
if (areas.includes(area)) {
atualizarDados('preferencias', {
areasInteresse: areas.filter(a => a !== area)
});
} else {
atualizarDados('preferencias', {
areasInteresse: [...areas, area]
});
}
};
const toggleHorario = (horario) => {
const horarios = dados.preferencias.disponibilidade;
if (horarios.includes(horario)) {
atualizarDados('preferencias', {
disponibilidade: horarios.filter(h => h !== horario)
});
} else {
atualizarDados('preferencias', {
disponibilidade: [...horarios, horario]
});
}
};
return (
<div>
<div style={{ marginBottom: '20px' }}>
<h3>Áreas de Interesse *</h3>
<p style={{ color: '#666', fontSize: '14px' }}>
Selecione pelo menos 2 áreas de interesse
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px' }}>
{areasDisponiveis.map(area => (
<label key={area} style={{ display: 'flex', alignItems: 'center' }}>
<input
type="checkbox"
checked={dados.preferencias.areasInteresse.includes(area)}
onChange={() => toggleArea(area)}
style={{ marginRight: '8px' }}
/>
{area}
</label>
))}
</div>
{erros.areasInteresse && <span style={{ color: 'red' }}>{erros.areasInteresse}</span>}
</div>
<div style={{ marginBottom: '20px' }}>
<h3>Disponibilidade de Horário *</h3>
<p style={{ color: '#666', fontSize: '14px' }}>
Selecione os horários em que você pode participar
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px' }}>
{horariosDisponiveis.map(horario => (
<label key={horario} style={{ display: 'flex', alignItems: 'center' }}>
<input
type="checkbox"
checked={dados.preferencias.disponibilidade.includes(horario)}
onChange={() => toggleHorario(horario)}
style={{ marginRight: '8px' }}
/>
{horario}
</label>
))}
</div>
{erros.disponibilidade && <span style={{ color: 'red' }}>{erros.disponibilidade}</span>}
</div>
<div style={{ marginBottom: '20px' }}>
<label>Modalidade Preferida *</label>
<select
value={dados.preferencias.modalidade}
onChange={(e) => atualizarDados('preferencias', {
modalidade: e.target.value
})}
style={{ width: '100%', padding: '8px' }}
>
<option value="">Selecione</option>
<option value="presencial">Presencial</option>
<option value="online">Online</option>
<option value="hibrido">Híbrido</option>
</select>
{erros.modalidade && <span style={{ color: 'red' }}>{erros.modalidade}</span>}
</div>
<div>
<label>Necessidades Especiais</label>
<textarea
value={dados.preferencias.necessidadesEspeciais}
onChange={(e) => atualizarDados('preferencias', {
necessidadesEspeciais: e.target.value
})}
placeholder="Descreva se você tem alguma necessidade especial de acessibilidade..."
rows="4"
style={{ width: '100%', padding: '8px' }}
/>
</div>
</div>
);
}
// Etapa 5: Pagamento
function EtapaPagamento({ dados, atualizarDados, erros }) {
const [validandoCupom, setValidandoCupom] = useState(false);
const tiposInscricao = [
{ id: 'estudante', nome: 'Estudante', valor: 97.00 },
{ id: 'profissional', nome: 'Profissional', valor: 197.00 },
{ id: 'empresa', nome: 'Empresa', valor: 297.00 }
];
const validarCupom = async () => {
if (!dados.pagamento.cupom) return;
setValidandoCupom(true);
// Simular validação de cupom
await new Promise(resolve => setTimeout(resolve, 1000));
// Cupons de exemplo
const cuponsValidos = {
'DESCONTO10': 10,
'PRIMEIRA20': 20,
'ESPECIAL50': 50
};
const desconto = cuponsValidos[dados.pagamento.cupom.toUpperCase()];
if (desconto) {
atualizarDados('pagamento', {
cupomValidado: true,
desconto: desconto
});
} else {
atualizarDados('pagamento', {
cupomValidado: false,
desconto: 0
});
alert('Cupom inválido!');
}
setValidandoCupom(false);
};
const calcularValorFinal = () => {
const tipo = tiposInscricao.find(t => t.id === dados.pagamento.tipoInscricao);
if (!tipo) return 0;
const valor = tipo.valor;
const desconto = dados.pagamento.desconto || 0;
return valor - (valor * desconto / 100);
};
return (
<div>
<div style={{ marginBottom: '20px' }}>
<h3>Tipo de Inscrição *</h3>
<div style={{ display: 'grid', gap: '10px' }}>
{tiposInscricao.map(tipo => (
<label
key={tipo.id}
style={{
display: 'flex',
alignItems: 'center',
padding: '15px',
border: '2px solid',
borderColor: dados.pagamento.tipoInscricao === tipo.id ? '#2196f3' : '#ddd',
borderRadius: '4px',
cursor: 'pointer'
}}
>
<input
type="radio"
name="tipoInscricao"
value={tipo.id}
checked={dados.pagamento.tipoInscricao === tipo.id}
onChange={(e) => atualizarDados('pagamento', {
tipoInscricao: e.target.value
})}
style={{ marginRight: '10px' }}
/>
<div>
<strong>{tipo.nome}</strong>
<span style={{ marginLeft: '10px', color: '#666' }}>
R$ {tipo.valor.toFixed(2)}
</span>
</div>
</label>
))}
</div>
{erros.tipoInscricao && <span style={{ color: 'red' }}>{erros.tipoInscricao}</span>}
</div>
<div style={{ marginBottom: '20px' }}>
<label>Cupom de Desconto</label>
<div style={{ display: 'flex', gap: '10px' }}>
<input
type="text"
value={dados.pagamento.cupom}
onChange={(e) => atualizarDados('pagamento', {
cupom: e.target.value.toUpperCase()
})}
placeholder="Digite o cupom"
style={{ flex: 1, padding: '8px' }}
/>
<button
type="button"
onClick={validarCupom}
disabled={validandoCupom || !dados.pagamento.cupom}
>
{validandoCupom ? 'Validando...' : 'Aplicar'}
</button>
</div>
{dados.pagamento.cupomValidado && (
<span style={{ color: 'green' }}>
Cupom aplicado! {dados.pagamento.desconto}% de desconto
</span>
)}
</div>
<div style={{ marginBottom: '20px' }}>
<h3>Método de Pagamento *</h3>
<div style={{ display: 'grid', gap: '10px' }}>
<label>
<input
type="radio"
name="metodoPagamento"
value="boleto"
checked={dados.pagamento.metodoPagamento === 'boleto'}
onChange={(e) => atualizarDados('pagamento', {
metodoPagamento: e.target.value
})}
/>
Boleto Bancário
</label>
<label>
<input
type="radio"
name="metodoPagamento"
value="cartao"
checked={dados.pagamento.metodoPagamento === 'cartao'}
onChange={(e) => atualizarDados('pagamento', {
metodoPagamento: e.target.value
})}
/>
Cartão de Crédito
</label>
<label>
<input
type="radio"
name="metodoPagamento"
value="pix"
checked={dados.pagamento.metodoPagamento === 'pix'}
onChange={(e) => atualizarDados('pagamento', {
metodoPagamento: e.target.value
})}
/>
PIX
</label>
</div>
{erros.metodoPagamento && <span style={{ color: 'red' }}>{erros.metodoPagamento}</span>}
</div>
<div style={{ marginBottom: '20px' }}>
<label>Comprovante de Pagamento</label>
<input
type="file"
accept=".pdf,.jpg,.jpeg,.png"
onChange={(e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
atualizarDados('pagamento', {
comprovante: {
nome: file.name,
dados: reader.result
}
});
};
reader.readAsDataURL(file);
}
}}
/>
{dados.pagamento.comprovante && (
<small>Arquivo: {dados.pagamento.comprovante.nome}</small>
)}
</div>
<div style={{
padding: '20px',
backgroundColor: '#f5f5f5',
borderRadius: '4px',
marginTop: '20px'
}}>
<h3>Resumo do Pagamento</h3>
{dados.pagamento.tipoInscricao && (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Inscrição:</span>
<span>
{tiposInscricao.find(t => t.id === dados.pagamento.tipoInscricao)?.nome}
</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Valor original:</span>
<span>
R$ {tiposInscricao.find(t => t.id === dados.pagamento.tipoInscricao)?.valor.toFixed(2)}
</span>
</div>
{dados.pagamento.desconto > 0 && (
<div style={{ display: 'flex', justifyContent: 'space-between', color: 'green' }}>
<span>Desconto ({dados.pagamento.desconto}%):</span>
<span>
-R$ {(tiposInscricao.find(t => t.id === dados.pagamento.tipoInscricao)?.valor * dados.pagamento.desconto / 100).toFixed(2)}
</span>
</div>
)}
<hr />
<div style={{ display: 'flex', justifyContent: 'space-between', fontWeight: 'bold' }}>
<span>Total:</span>
<span>R$ {calcularValorFinal().toFixed(2)}</span>
</div>
</div>
)}
</div>
</div>
);
}
// Etapa 6: Confirmação
function EtapaConfirmacao({ dados, atualizarDados, erros }) {
return (
<div>
<h3>Revise seus dados antes de finalizar:</h3>
<div style={{ marginBottom: '20px' }}>
<h4>Dados Pessoais</h4>
<div style={{ backgroundColor: '#f5f5f5', padding: '15px', borderRadius: '4px' }}>
<p><strong>Nome:</strong> {dados.dadosPessoais.nome}</p>
<p><strong>Email:</strong> {dados.dadosPessoais.email}</p>
<p><strong>CPF:</strong> {dados.dadosPessoais.cpf}</p>
<p><strong>Telefone:</strong> {dados.dadosPessoais.telefone}</p>
<p><strong>Data de Nascimento:</strong> {dados.dadosPessoais.dataNascimento}</p>
</div>
</div>
<div style={{ marginBottom: '20px' }}>
<h4>Dados Profissionais</h4>
<div style={{ backgroundColor: '#f5f5f5', padding: '15px', borderRadius: '4px' }}>
<p><strong>Empresa:</strong> {dados.dadosProfissionais.empresa}</p>
<p><strong>Cargo:</strong> {dados.dadosProfissionais.cargo}</p>
<p><strong>Experiência:</strong> {dados.dadosProfissionais.tempoExperiencia}</p>
<p><strong>Habilidades:</strong> {dados.dadosProfissionais.habilidades.join(', ')}</p>
</div>
</div>
<div style={{ marginBottom: '20px' }}>
<h4>Formação Acadêmica</h4>
<div style={{ backgroundColor: '#f5f5f5', padding: '15px', borderRadius: '4px' }}>
{dados.formacoes.map((formacao, index) => (
<div key={formacao.id} style={{ marginBottom: '10px' }}>
<p><strong>Formação {index + 1}:</strong></p>
<p>{formacao.curso} - {formacao.instituicao}</p>
<p>{formacao.nivel} ({formacao.emAndamento ? 'Em andamento' : `Concluído em ${formacao.dataFim}`})</p>
</div>
))}
</div>
</div>
<div style={{ marginBottom: '20px' }}>
<h4>Preferências</h4>
<div style={{ backgroundColor: '#f5f5f5', padding: '15px', borderRadius: '4px' }}>
<p><strong>Áreas de Interesse:</strong> {dados.preferencias.areasInteresse.join(', ')}</p>
<p><strong>Disponibilidade:</strong> {dados.preferencias.disponibilidade.join(', ')}</p>
<p><strong>Modalidade:</strong> {dados.preferencias.modalidade}</p>
</div>
</div>
<div style={{ marginBottom: '20px' }}>
<h4>Pagamento</h4>
<div style={{ backgroundColor: '#f5f5f5', padding: '15px', borderRadius: '4px' }}>
<p><strong>Tipo de Inscrição:</strong> {dados.pagamento.tipoInscricao}</p>
<p><strong>Método de Pagamento:</strong> {dados.pagamento.metodoPagamento}</p>
{dados.pagamento.cupom && <p><strong>Cupom:</strong> {dados.pagamento.cupom} ({dados.pagamento.desconto}% desconto)</p>}
</div>
</div>
<div style={{
marginTop: '30px',
padding: '20px',
backgroundColor: '#fff3cd',
borderRadius: '4px'
}}>
<label>
<input
type="checkbox"
checked={dados.aceitouTermos}
onChange={(e) => atualizarDados(null, { aceitouTermos: e.target.checked })}
/>
Li e aceito os termos e condições de inscrição
</label>
{erros.termos && <span style={{ color: 'red', display: 'block' }}>{erros.termos}</span>}
</div>
</div>
);
}
// Funções de Validação
function validarCPF(cpf) {
const cpfLimpo = cpf.replace(/\D/g, '');
if (cpfLimpo.length !== 11) return false;
if (/^(\d)\1{10}$/.test(cpfLimpo)) return false;
let soma = 0;
for (let i = 0; i < 9; i++) {
soma += parseInt(cpfLimpo.charAt(i)) * (10 - i);
}
let resto = 11 - (soma % 11);
if (resto === 10 || resto === 11) resto = 0;
if (resto !== parseInt(cpfLimpo.charAt(9))) return false;
soma = 0;
for (let i = 0; i < 10; i++) {
soma += parseInt(cpfLimpo.charAt(i)) * (11 - i);
}
resto = 11 - (soma % 11);
if (resto === 10 || resto === 11) resto = 0;
if (resto !== parseInt(cpfLimpo.charAt(10))) return false;
return true;
}
function validarDadosPessoais(dados) {
const erros = {};
if (!dados.dadosPessoais.nome) {
erros.nome = 'Nome obrigatório';
}
if (!dados.dadosPessoais.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(dados.dadosPessoais.email)) {
erros.email = 'Email inválido';
}
if (!dados.dadosPessoais.cpf || !validarCPF(dados.dadosPessoais.cpf)) {
erros.cpf = 'CPF inválido';
}
if (!dados.dadosPessoais.dataNascimento) {
erros.dataNascimento = 'Data de nascimento obrigatória';
}
if (!dados.dadosPessoais.telefone) {
erros.telefone = 'Telefone obrigatório';
}
if (!dados.dadosPessoais.fotoPerfil) {
erros.fotoPerfil = 'Foto obrigatória';
}
return erros;
}
function validarDadosProfissionais(dados) {
const erros = {};
if (!dados.dadosProfissionais.empresa) {
erros.empresa = 'Empresa obrigatória';
}
if (!dados.dadosProfissionais.cargo) {
erros.cargo = 'Cargo obrigatório';
}
if (!dados.dadosProfissionais.tempoExperiencia) {
erros.tempoExperiencia = 'Tempo de experiência obrigatório';
}
if (dados.dadosProfissionais.habilidades.length < 3) {
erros.habilidades = 'Adicione pelo menos 3 habilidades';
}
if (!dados.dadosProfissionais.curriculo) {
erros.curriculo = 'Currículo obrigatório';
}
return erros;
}
function validarFormacao(dados) {
const erros = {};
if (dados.formacoes.length === 0) {
erros.formacoes = 'Adicione pelo menos uma formação';
}
return erros;
}
function validarPreferencias(dados) {
const erros = {};
if (dados.preferencias.areasInteresse.length < 2) {
erros.areasInteresse = 'Selecione pelo menos 2 áreas';
}
if (dados.preferencias.disponibilidade.length === 0) {
erros.disponibilidade = 'Selecione pelo menos um horário';
}
if (!dados.preferencias.modalidade) {
erros.modalidade = 'Selecione uma modalidade';
}
return erros;
}
function validarPagamento(dados) {
const erros = {};
if (!dados.pagamento.tipoInscricao) {
erros.tipoInscricao = 'Selecione um tipo de inscrição';
}
if (!dados.pagamento.metodoPagamento) {
erros.metodoPagamento = 'Selecione um método de pagamento';
}
return erros;
}
function validarConfirmacao(dados) {
const erros = {};
if (!dados.aceitouTermos) {
erros.termos = 'Você deve aceitar os termos';
}
return erros;
}
export default SistemaInscricao;
Desafios Extras
-
Integração Real com APIs
- Busca de CEP real
- Upload para servidor
- Validação de cupom via API
-
Melhorias de UX
- Animações entre etapas
- Preview de PDF
- Crop de imagem de perfil
-
Segurança
- Criptografia de dados sensíveis
- Validação no servidor
- Rate limiting
-
Acessibilidade
- Navegação por teclado
- Screen reader friendly
- Alto contraste
-
Mobile
- Design responsivo
- Touch friendly
- Câmera para uploads
3 content items