Validação de Formulários

Módulo 4: Eventos e Formulários

Aula 3
1

Estratégias de Validação

18:00

Aprenda diferentes formas de validar formulários em React

2

Implementando Validações

Exemplos práticos de diferentes tipos de validação

Implementando Validações

Validações Básicas

Campo Obrigatório

function validarCampoObrigatorio(valor, nomeCampo) {
  if (!valor || valor.trim() === '') {
    return `${nomeCampo} é obrigatório`;
  }
  return '';
}

Email

function validarEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  
  if (!email) {
    return 'Email é obrigatório';
  }
  
  if (!regex.test(email)) {
    return 'Email inválido';
  }
  
  return '';
}

Telefone

function validarTelefone(telefone) {
  const regex = /^\(?[1-9]{2}\)? ?(?:[2-8]|9[1-9])[0-9]{3}\-?[0-9]{4}$/;
  
  if (!telefone) {
    return ''; // Campo opcional
  }
  
  const numeroLimpo = telefone.replace(/\D/g, '');
  
  if (!regex.test(numeroLimpo)) {
    return 'Telefone inválido';
  }
  
  return '';
}

CPF

function validarCPF(cpf) {
  const cpfLimpo = cpf.replace(/\D/g, '');
  
  if (cpfLimpo.length !== 11) {
    return 'CPF deve ter 11 dígitos';
  }
  
  // Validação de CPF (algoritmo completo)
  if (/^(\d)\1{10}$/.test(cpfLimpo)) {
    return 'CPF inválido';
  }
  
  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 'CPF inválido';
  }
  
  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 'CPF inválido';
  }
  
  return '';
}

Sistema de Validação Completo

function FormularioValidado() {
  const [formData, setFormData] = useState({
    nome: '',
    email: '',
    cpf: '',
    telefone: '',
    senha: '',
    confirmarSenha: ''
  });

  const [erros, setErros] = useState({});
  const [tocados, setTocados] = useState({});

  // Regras de validação
  const validacoes = {
    nome: (valor) => {
      if (!valor) return 'Nome é obrigatório';
      if (valor.length < 3) return 'Nome deve ter pelo menos 3 caracteres';
      if (!/^[a-zA-Zà-ú\s]+$/.test(valor)) return 'Nome deve conter apenas letras';
      return '';
    },
    
    email: (valor) => {
      if (!valor) return 'Email é obrigatório';
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(valor)) return 'Email inválido';
      return '';
    },
    
    cpf: (valor) => validarCPF(valor),
    
    telefone: (valor) => validarTelefone(valor),
    
    senha: (valor) => {
      if (!valor) return 'Senha é obrigatória';
      if (valor.length < 8) return 'Senha deve ter pelo menos 8 caracteres';
      if (!/(?=.*[a-z])/.test(valor)) return 'Senha deve ter pelo menos uma letra minúscula';
      if (!/(?=.*[A-Z])/.test(valor)) return 'Senha deve ter pelo menos uma letra maiúscula';
      if (!/(?=.*\d)/.test(valor)) return 'Senha deve ter pelo menos um número';
      if (!/(?=.*[@$!%*?&])/.test(valor)) return 'Senha deve ter pelo menos um caractere especial';
      return '';
    },
    
    confirmarSenha: (valor, todosValores) => {
      if (!valor) return 'Confirmação de senha é obrigatória';
      if (valor !== todosValores.senha) return 'Senhas não coincidem';
      return '';
    }
  };

  // Validar campo individual
  const validarCampo = (nome, valor) => {
    const validacao = validacoes[nome];
    if (validacao) {
      return validacao(valor, formData);
    }
    return '';
  };

  // Validar todos os campos
  const validarFormulario = () => {
    const novosErros = {};
    
    Object.keys(formData).forEach(campo => {
      const erro = validarCampo(campo, formData[campo]);
      if (erro) {
        novosErros[campo] = erro;
      }
    });
    
    setErros(novosErros);
    return Object.keys(novosErros).length === 0;
  };

  // Handler de mudança com validação
  const handleChange = (e) => {
    const { name, value } = e.target;
    
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));

    // Validação em tempo real apenas se o campo já foi tocado
    if (tocados[name]) {
      const erro = validarCampo(name, value);
      setErros(prev => ({
        ...prev,
        [name]: erro
      }));
    }
  };

  // Handler de blur
  const handleBlur = (e) => {
    const { name, value } = e.target;
    
    setTocados(prev => ({
      ...prev,
      [name]: true
    }));

    const erro = validarCampo(name, value);
    setErros(prev => ({
      ...prev,
      [name]: erro
    }));
  };

  // Submit do formulário
  const handleSubmit = (e) => {
    e.preventDefault();
    
    // Marca todos os campos como tocados
    const todosTocados = {};
    Object.keys(formData).forEach(campo => {
      todosTocados[campo] = true;
    });
    setTocados(todosTocados);
    
    if (validarFormulario()) {
      console.log('Formulário válido:', formData);
      // Enviar dados
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          type="text"
          name="nome"
          value={formData.nome}
          onChange={handleChange}
          onBlur={handleBlur}
          placeholder="Nome completo"
          className={erros.nome && tocados.nome ? 'erro' : ''}
        />
        {erros.nome && tocados.nome && (
          <span className="mensagem-erro">{erros.nome}</span>
        )}
      </div>

      <div>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          onBlur={handleBlur}
          placeholder="Email"
          className={erros.email && tocados.email ? 'erro' : ''}
        />
        {erros.email && tocados.email && (
          <span className="mensagem-erro">{erros.email}</span>
        )}
      </div>

      <div>
        <input
          type="text"
          name="cpf"
          value={formData.cpf}
          onChange={handleChange}
          onBlur={handleBlur}
          placeholder="CPF"
          className={erros.cpf && tocados.cpf ? 'erro' : ''}
        />
        {erros.cpf && tocados.cpf && (
          <span className="mensagem-erro">{erros.cpf}</span>
        )}
      </div>

      <div>
        <input
          type="password"
          name="senha"
          value={formData.senha}
          onChange={handleChange}
          onBlur={handleBlur}
          placeholder="Senha"
          className={erros.senha && tocados.senha ? 'erro' : ''}
        />
        {erros.senha && tocados.senha && (
          <span className="mensagem-erro">{erros.senha}</span>
        )}
      </div>

      <button type="submit">Enviar</button>
    </form>
  );
}

Hook Customizado para Validação

function useFormValidation(valoresIniciais, validacoes) {
  const [valores, setValores] = useState(valoresIniciais);
  const [erros, setErros] = useState({});
  const [tocados, setTocados] = useState({});
  const [enviando, setEnviando] = useState(false);

  const validarCampo = (nome, valor) => {
    const validacao = validacoes[nome];
    if (validacao) {
      return validacao(valor, valores);
    }
    return '';
  };

  const handleChange = (e) => {
    const { name, value } = e.target;
    
    setValores(prev => ({
      ...prev,
      [name]: value
    }));

    if (tocados[name]) {
      const erro = validarCampo(name, value);
      setErros(prev => ({
        ...prev,
        [name]: erro
      }));
    }
  };

  const handleBlur = (e) => {
    const { name } = e.target;
    
    setTocados(prev => ({
      ...prev,
      [name]: true
    }));

    const erro = validarCampo(name, valores[name]);
    setErros(prev => ({
      ...prev,
      [name]: erro
    }));
  };

  const validarTudo = () => {
    const novosErros = {};
    
    Object.keys(valores).forEach(campo => {
      const erro = validarCampo(campo, valores[campo]);
      if (erro) {
        novosErros[campo] = erro;
      }
    });
    
    setErros(novosErros);
    return Object.keys(novosErros).length === 0;
  };

  const handleSubmit = (callback) => (e) => {
    e.preventDefault();
    
    const todosTocados = {};
    Object.keys(valores).forEach(campo => {
      todosTocados[campo] = true;
    });
    setTocados(todosTocados);
    
    if (validarTudo()) {
      setEnviando(true);
      callback(valores);
    }
  };

  return {
    valores,
    erros,
    tocados,
    enviando,
    handleChange,
    handleBlur,
    handleSubmit,
    setEnviando
  };
}

// Usando o hook
function MeuFormulario() {
  const validacoes = {
    email: (valor) => {
      if (!valor) return 'Email obrigatório';
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(valor)) return 'Email inválido';
      return '';
    },
    senha: (valor) => {
      if (!valor) return 'Senha obrigatória';
      if (valor.length < 6) return 'Senha muito curta';
      return '';
    }
  };

  const {
    valores,
    erros,
    tocados,
    enviando,
    handleChange,
    handleBlur,
    handleSubmit
  } = useFormValidation(
    { email: '', senha: '' },
    validacoes
  );

  const enviarFormulario = async (dados) => {
    // Simular envio
    await new Promise(resolve => setTimeout(resolve, 2000));
    console.log('Dados enviados:', dados);
  };

  return (
    <form onSubmit={handleSubmit(enviarFormulario)}>
      <input
        type="email"
        name="email"
        value={valores.email}
        onChange={handleChange}
        onBlur={handleBlur}
      />
      {erros.email && tocados.email && <span>{erros.email}</span>}
      
      <input
        type="password"
        name="senha"
        value={valores.senha}
        onChange={handleChange}
        onBlur={handleBlur}
      />
      {erros.senha && tocados.senha && <span>{erros.senha}</span>}
      
      <button type="submit" disabled={enviando}>
        {enviando ? 'Enviando...' : 'Enviar'}
      </button>
    </form>
  );
}

Indicadores Visuais de Validação

function CampoComFeedback({ tipo, nome, valor, erro, tocado, ...props }) {
  const getStatusClass = () => {
    if (!tocado) return '';
    return erro ? 'campo-erro' : 'campo-valido';
  };

  const getStatusIcon = () => {
    if (!tocado) return null;
    return erro ? '❌' : '✅';
  };

  return (
    <div className="campo-wrapper">
      <div className="campo-input">
        <input
          type={tipo}
          name={nome}
          value={valor}
          className={getStatusClass()}
          {...props}
        />
        <span className="status-icon">{getStatusIcon()}</span>
      </div>
      {erro && tocado && (
        <span className="mensagem-erro">{erro}</span>
      )}
    </div>
  );
}

CSS para Feedback Visual

.campo-erro {
  border: 2px solid #f44336;
  background-color: #ffebee;
}

.campo-valido {
  border: 2px solid #4caf50;
  background-color: #e8f5e9;
}

.mensagem-erro {
  color: #f44336;
  font-size: 12px;
  margin-top: 4px;
  display: block;
}

.status-icon {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
}

.campo-wrapper {
  margin-bottom: 16px;
}

.campo-input {
  position: relative;
}
3

Exercício: Sistema de Validação Completo

Crie um formulário com validação avançada e feedback visual

Activity

Exercício: Sistema de Validação Completo

Objetivo

Criar um formulário de pagamento com validação completa, incluindo validação de cartão de crédito e feedback visual.

Requisitos

  1. Campos do Formulário

    • Nome do titular (texto)
    • Número do cartão (formatado)
    • Data de validade (MM/AA)
    • CVV (3 ou 4 dígitos)
    • CEP (busca automática de endereço)
    • Endereço completo
    • Valor do pagamento
  2. Validações

    • Nome: mínimo 3 caracteres, apenas letras
    • Cartão: algoritmo de Luhn
    • Data: não expirada
    • CVV: 3 dígitos (4 para Amex)
    • CEP: formato válido e busca API
    • Valor: maior que zero
  3. Recursos Visuais

    • Identificação da bandeira do cartão
    • Máscara de formatação em tempo real
    • Indicadores de campo válido/inválido
    • Animações de transição
    • Resumo do pagamento

Template Inicial

import React, { useState, useEffect } from 'react';

function FormularioPagamento() {
  const [formData, setFormData] = useState({
    nomeCartao: '',
    numeroCartao: '',
    dataValidade: '',
    cvv: '',
    cep: '',
    endereco: '',
    numero: '',
    complemento: '',
    cidade: '',
    estado: '',
    valor: ''
  });

  const [erros, setErros] = useState({});
  const [tocados, setTocados] = useState({});
  const [bandeira, setBandeira] = useState('');
  const [processando, setProcessando] = useState(false);

  // Seu código aqui

  return (
    <div>
      {/* Implemente o formulário aqui */}
    </div>
  );
}

export default FormularioPagamento;

Funções Úteis

Algoritmo de Luhn (Validação de Cartão)

function validarLuhn(numero) {
  const digitos = numero.replace(/\D/g, '');
  let soma = 0;
  let isSegundo = false;
  
  for (let i = digitos.length - 1; i >= 0; i--) {
    let digito = parseInt(digitos[i]);
    
    if (isSegundo) {
      digito *= 2;
      if (digito > 9) {
        digito -= 9;
      }
    }
    
    soma += digito;
    isSegundo = !isSegundo;
  }
  
  return soma % 10 === 0;
}

Identificar Bandeira

function identificarBandeira(numero) {
  const padroes = {
    visa: /^4/,
    mastercard: /^5[1-5]/,
    amex: /^3[47]/,
    discover: /^6(?:011|5)/,
    diners: /^3(?:0[0-5]|[68])/,
    jcb: /^(?:2131|1800|35)/
  };
  
  for (const [bandeira, padrao] of Object.entries(padroes)) {
    if (padrao.test(numero)) {
      return bandeira;
    }
  }
  
  return '';
}

Solução

Clique para ver a solução completa
import React, { useState, useEffect } from 'react';

function FormularioPagamento() {
  const [formData, setFormData] = useState({
    nomeCartao: '',
    numeroCartao: '',
    dataValidade: '',
    cvv: '',
    cep: '',
    endereco: '',
    numero: '',
    complemento: '',
    cidade: '',
    estado: '',
    valor: ''
  });

  const [erros, setErros] = useState({});
  const [tocados, setTocados] = useState({});
  const [bandeira, setBandeira] = useState('');
  const [processando, setProcessando] = useState(false);
  const [buscandoCep, setBuscandoCep] = useState(false);
  const [pagamentoSucesso, setPagamentoSucesso] = useState(false);

  // Validação Luhn
  const validarLuhn = (numero) => {
    const digitos = numero.replace(/\D/g, '');
    let soma = 0;
    let isSegundo = false;
    
    for (let i = digitos.length - 1; i >= 0; i--) {
      let digito = parseInt(digitos[i]);
      
      if (isSegundo) {
        digito *= 2;
        if (digito > 9) {
          digito -= 9;
        }
      }
      
      soma += digito;
      isSegundo = !isSegundo;
    }
    
    return soma % 10 === 0;
  };

  // Identificar bandeira
  const identificarBandeira = (numero) => {
    const padroes = {
      visa: /^4/,
      mastercard: /^5[1-5]/,
      amex: /^3[47]/,
      discover: /^6(?:011|5)/,
      diners: /^3(?:0[0-5]|[68])/,
      jcb: /^(?:2131|1800|35)/
    };
    
    for (const [bandeira, padrao] of Object.entries(padroes)) {
      if (padrao.test(numero)) {
        return bandeira;
      }
    }
    
    return '';
  };

  // Formatar número do cartão
  const formatarNumeroCartao = (valor) => {
    const numero = valor.replace(/\D/g, '');
    const partes = [];
    
    if (bandeira === 'amex') {
      // Amex: 4-6-5
      if (numero.length > 0) partes.push(numero.substring(0, 4));
      if (numero.length > 4) partes.push(numero.substring(4, 10));
      if (numero.length > 10) partes.push(numero.substring(10, 15));
    } else {
      // Outros: 4-4-4-4
      for (let i = 0; i < numero.length; i += 4) {
        partes.push(numero.substring(i, i + 4));
      }
    }
    
    return partes.join(' ').substring(0, 23);
  };

  // Formatar data de validade
  const formatarDataValidade = (valor) => {
    const numero = valor.replace(/\D/g, '');
    if (numero.length >= 2) {
      return numero.substring(0, 2) + '/' + numero.substring(2, 4);
    }
    return numero;
  };

  // Formatar CEP
  const formatarCEP = (valor) => {
    const numero = valor.replace(/\D/g, '');
    if (numero.length > 5) {
      return numero.substring(0, 5) + '-' + numero.substring(5, 8);
    }
    return numero;
  };

  // Formatar valor
  const formatarValor = (valor) => {
    const numero = valor.replace(/\D/g, '');
    const valorFormatado = (parseInt(numero) / 100).toFixed(2);
    return `R$ ${valorFormatado.replace('.', ',')}`;
  };

  // Buscar CEP
  const buscarCEP = async (cep) => {
    const cepLimpo = cep.replace(/\D/g, '');
    if (cepLimpo.length === 8) {
      setBuscandoCep(true);
      try {
        const response = await fetch(`https://viacep.com.br/ws/${cepLimpo}/json/`);
        const data = await response.json();
        
        if (!data.erro) {
          setFormData(prev => ({
            ...prev,
            endereco: data.logradouro,
            cidade: data.localidade,
            estado: data.uf
          }));
        } else {
          setErros(prev => ({ ...prev, cep: 'CEP não encontrado' }));
        }
      } catch (error) {
        setErros(prev => ({ ...prev, cep: 'Erro ao buscar CEP' }));
      }
      setBuscandoCep(false);
    }
  };

  // Validações
  const validacoes = {
    nomeCartao: (valor) => {
      if (!valor) return 'Nome do titular obrigatório';
      if (valor.length < 3) return 'Nome muito curto';
      if (!/^[a-zA-Zà-ú\s]+$/.test(valor)) return 'Nome inválido';
      return '';
    },
    
    numeroCartao: (valor) => {
      const numero = valor.replace(/\D/g, '');
      if (!numero) return 'Número do cartão obrigatório';
      if (numero.length < 13) return 'Número incompleto';
      if (!validarLuhn(numero)) return 'Número de cartão inválido';
      return '';
    },
    
    dataValidade: (valor) => {
      if (!valor) return 'Data de validade obrigatória';
      if (valor.length !== 5) return 'Data incompleta';
      
      const [mes, ano] = valor.split('/');
      const mesNum = parseInt(mes);
      const anoNum = parseInt('20' + ano);
      const hoje = new Date();
      const dataCartao = new Date(anoNum, mesNum - 1);
      
      if (mesNum < 1 || mesNum > 12) return 'Mês inválido';
      if (dataCartao < hoje) return 'Cartão expirado';
      return '';
    },
    
    cvv: (valor) => {
      if (!valor) return 'CVV obrigatório';
      const tamanhoEsperado = bandeira === 'amex' ? 4 : 3;
      if (valor.length !== tamanhoEsperado) {
        return `CVV deve ter ${tamanhoEsperado} dígitos`;
      }
      return '';
    },
    
    cep: (valor) => {
      const cepLimpo = valor.replace(/\D/g, '');
      if (!cepLimpo) return 'CEP obrigatório';
      if (cepLimpo.length !== 8) return 'CEP incompleto';
      return '';
    },
    
    numero: (valor) => {
      if (!valor) return 'Número obrigatório';
      return '';
    },
    
    valor: (valor) => {
      const numero = parseFloat(valor.replace(/[^\d,]/g, '').replace(',', '.'));
      if (!numero || numero <= 0) return 'Valor deve ser maior que zero';
      if (numero > 10000) return 'Valor máximo: R$ 10.000,00';
      return '';
    }
  };

  // Handlers
  const handleChange = (e) => {
    const { name, value } = e.target;
    let valorFormatado = value;
    
    // Aplicar formatações
    switch (name) {
      case 'numeroCartao':
        valorFormatado = formatarNumeroCartao(value);
        const novaBandeira = identificarBandeira(value);
        setBandeira(novaBandeira);
        break;
      case 'dataValidade':
        valorFormatado = formatarDataValidade(value);
        break;
      case 'cep':
        valorFormatado = formatarCEP(value);
        if (value.replace(/\D/g, '').length === 8) {
          buscarCEP(value);
        }
        break;
      case 'cvv':
        valorFormatado = value.replace(/\D/g, '').substring(0, 4);
        break;
      case 'valor':
        if (value.length > formData.valor.length) {
          valorFormatado = formatarValor(value);
        }
        break;
    }
    
    setFormData(prev => ({
      ...prev,
      [name]: valorFormatado
    }));

    // Validar se já foi tocado
    if (tocados[name]) {
      const erro = validacoes[name] ? validacoes[name](valorFormatado) : '';
      setErros(prev => ({
        ...prev,
        [name]: erro
      }));
    }
  };

  const handleBlur = (e) => {
    const { name, value } = e.target;
    
    setTocados(prev => ({
      ...prev,
      [name]: true
    }));

    const erro = validacoes[name] ? validacoes[name](value) : '';
    setErros(prev => ({
      ...prev,
      [name]: erro
    }));
  };

  const validarFormulario = () => {
    const novosErros = {};
    
    Object.keys(validacoes).forEach(campo => {
      const erro = validacoes[campo](formData[campo]);
      if (erro) {
        novosErros[campo] = erro;
      }
    });
    
    setErros(novosErros);
    return Object.keys(novosErros).length === 0;
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    
    // Marcar todos como tocados
    const todosTocados = {};
    Object.keys(validacoes).forEach(campo => {
      todosTocados[campo] = true;
    });
    setTocados(todosTocados);
    
    if (validarFormulario()) {
      setProcessando(true);
      
      // Simular processamento
      await new Promise(resolve => setTimeout(resolve, 2000));
      
      setPagamentoSucesso(true);
      setProcessando(false);
    }
  };

  const getInputClass = (campo) => {
    if (!tocados[campo]) return '';
    return erros[campo] ? 'campo-erro' : 'campo-valido';
  };

  if (pagamentoSucesso) {
    return (
      <div style={{ 
        textAlign: 'center', 
        padding: '40px',
        backgroundColor: '#e8f5e9',
        borderRadius: '8px'
      }}>
        <h2 style={{ color: '#4caf50' }}>✅ Pagamento Realizado com Sucesso!</h2>
        <p>Valor: {formData.valor}</p>
        <p>Cartão: **** **** **** {formData.numeroCartao.slice(-4)}</p>
        <button onClick={() => {
          setPagamentoSucesso(false);
          setFormData({
            nomeCartao: '',
            numeroCartao: '',
            dataValidade: '',
            cvv: '',
            cep: '',
            endereco: '',
            numero: '',
            complemento: '',
            cidade: '',
            estado: '',
            valor: ''
          });
          setTocados({});
          setErros({});
        }}>
          Novo Pagamento
        </button>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '500px', margin: '0 auto' }}>
      <h1>Pagamento</h1>
      
      <fieldset>
        <legend>Dados do Cartão</legend>
        
        <div className="campo-grupo">
          <label>Nome do Titular *</label>
          <input
            type="text"
            name="nomeCartao"
            value={formData.nomeCartao}
            onChange={handleChange}
            onBlur={handleBlur}
            className={getInputClass('nomeCartao')}
            placeholder="NOME COMO NO CARTÃO"
            style={{ textTransform: 'uppercase' }}
          />
          {erros.nomeCartao && tocados.nomeCartao && (
            <span className="erro">{erros.nomeCartao}</span>
          )}
        </div>

        <div className="campo-grupo">
          <label>
            Número do Cartão * 
            {bandeira && <span className="bandeira">{bandeira.toUpperCase()}</span>}
          </label>
          <input
            type="text"
            name="numeroCartao"
            value={formData.numeroCartao}
            onChange={handleChange}
            onBlur={handleBlur}
            className={getInputClass('numeroCartao')}
            placeholder="0000 0000 0000 0000"
            maxLength="23"
          />
          {erros.numeroCartao && tocados.numeroCartao && (
            <span className="erro">{erros.numeroCartao}</span>
          )}
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
          <div className="campo-grupo">
            <label>Validade *</label>
            <input
              type="text"
              name="dataValidade"
              value={formData.dataValidade}
              onChange={handleChange}
              onBlur={handleBlur}
              className={getInputClass('dataValidade')}
              placeholder="MM/AA"
              maxLength="5"
            />
            {erros.dataValidade && tocados.dataValidade && (
              <span className="erro">{erros.dataValidade}</span>
            )}
          </div>

          <div className="campo-grupo">
            <label>CVV *</label>
            <input
              type="text"
              name="cvv"
              value={formData.cvv}
              onChange={handleChange}
              onBlur={handleBlur}
              className={getInputClass('cvv')}
              placeholder={bandeira === 'amex' ? '0000' : '000'}
              maxLength={bandeira === 'amex' ? '4' : '3'}
            />
            {erros.cvv && tocados.cvv && (
              <span className="erro">{erros.cvv}</span>
            )}
          </div>
        </div>
      </fieldset>

      <fieldset>
        <legend>Endereço de Cobrança</legend>
        
        <div className="campo-grupo">
          <label>CEP *</label>
          <input
            type="text"
            name="cep"
            value={formData.cep}
            onChange={handleChange}
            onBlur={handleBlur}
            className={getInputClass('cep')}
            placeholder="00000-000"
            maxLength="9"
            disabled={buscandoCep}
          />
          {buscandoCep && <span>Buscando endereço...</span>}
          {erros.cep && tocados.cep && (
            <span className="erro">{erros.cep}</span>
          )}
        </div>

        <div className="campo-grupo">
          <label>Endereço</label>
          <input
            type="text"
            name="endereco"
            value={formData.endereco}
            onChange={handleChange}
            readOnly={buscandoCep}
          />
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '10px' }}>
          <div className="campo-grupo">
            <label>Número *</label>
            <input
              type="text"
              name="numero"
              value={formData.numero}
              onChange={handleChange}
              onBlur={handleBlur}
              className={getInputClass('numero')}
            />
            {erros.numero && tocados.numero && (
              <span className="erro">{erros.numero}</span>
            )}
          </div>

          <div className="campo-grupo">
            <label>Complemento</label>
            <input
              type="text"
              name="complemento"
              value={formData.complemento}
              onChange={handleChange}
            />
          </div>
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '10px' }}>
          <div className="campo-grupo">
            <label>Cidade</label>
            <input
              type="text"
              name="cidade"
              value={formData.cidade}
              onChange={handleChange}
              readOnly={buscandoCep}
            />
          </div>

          <div className="campo-grupo">
            <label>Estado</label>
            <input
              type="text"
              name="estado"
              value={formData.estado}
              onChange={handleChange}
              readOnly={buscandoCep}
              maxLength="2"
            />
          </div>
        </div>
      </fieldset>

      <fieldset>
        <legend>Valor do Pagamento</legend>
        
        <div className="campo-grupo">
          <label>Valor *</label>
          <input
            type="text"
            name="valor"
            value={formData.valor}
            onChange={handleChange}
            onBlur={handleBlur}
            className={getInputClass('valor')}
            placeholder="R$ 0,00"
            style={{ fontSize: '24px', fontWeight: 'bold' }}
          />
          {erros.valor && tocados.valor && (
            <span className="erro">{erros.valor}</span>
          )}
        </div>
      </fieldset>

      <button
        type="submit"
        disabled={processando}
        style={{
          width: '100%',
          padding: '15px',
          fontSize: '18px',
          backgroundColor: processando ? '#ccc' : '#4caf50',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: processando ? 'not-allowed' : 'pointer',
          marginTop: '20px'
        }}
      >
        {processando ? 'Processando...' : `Pagar ${formData.valor || 'R$ 0,00'}`}
      </button>

      <style jsx>{`
        fieldset {
          border: 1px solid #ddd;
          border-radius: 4px;
          padding: 20px;
          margin-bottom: 20px;
        }
        
        legend {
          font-weight: bold;
          padding: 0 10px;
        }
        
        .campo-grupo {
          margin-bottom: 15px;
        }
        
        label {
          display: block;
          margin-bottom: 5px;
          font-weight: 500;
        }
        
        input {
          width: 100%;
          padding: 10px;
          border: 2px solid #ddd;
          border-radius: 4px;
          font-size: 16px;
          transition: all 0.3s;
        }
        
        input:focus {
          outline: none;
          border-color: #2196f3;
        }
        
        .campo-erro {
          border-color: #f44336;
          background-color: #ffebee;
        }
        
        .campo-valido {
          border-color: #4caf50;
          background-color: #e8f5e9;
        }
        
        .erro {
          color: #f44336;
          font-size: 12px;
          margin-top: 5px;
          display: block;
        }
        
        .bandeira {
          background: #2196f3;
          color: white;
          padding: 2px 8px;
          border-radius: 4px;
          font-size: 12px;
          margin-left: 10px;
        }
      `}</style>
    </form>
  );
}

export default FormularioPagamento;

Desafios Extras

  1. Parcelamento: Adicione opção de parcelamento com cálculo de juros
  2. Múltiplos Cartões: Permitir dividir pagamento em vários cartões
  3. Cartões Salvos: Simular cartões salvos para seleção rápida
  4. Análise de Fraude: Adicionar simulação de verificação antifraude
  5. Cupons de Desconto: Sistema de cupons com validação
3 content items