254 lines
8.5 KiB
React
254 lines
8.5 KiB
React
import React, { useState } from 'react';
|
|
import {
|
|
ShieldCheck,
|
|
ArrowRight,
|
|
RotateCcw,
|
|
AlertCircle,
|
|
Sparkles,
|
|
Lock,
|
|
Loader2,
|
|
MessageSquare,
|
|
Bookmark
|
|
} from 'lucide-react';
|
|
import Header from './components/Header';
|
|
import SecurityBadge from './components/SecurityBadge';
|
|
import UploadZone from './components/UploadZone';
|
|
import PasswordInput from './components/PasswordInput';
|
|
import CertificateInfo from './components/CertificateInfo';
|
|
import OutputFiles from './components/OutputFiles';
|
|
import OpenSslReference from './components/OpenSslReference';
|
|
import HostGuideModal from './components/HostGuideModal';
|
|
import AppWPP from './components/wpp/AppWPP';
|
|
import CustomerPortal from './components/portal/CustomerPortal';
|
|
import { convertPfx } from './utils/certConverter';
|
|
|
|
export default function App() {
|
|
const [activeTab, setActiveTab] = useState('portal'); // 'portal' | 'cert' | 'wpp' (default to portal since user asked for it!)
|
|
const [file, setFile] = useState(null);
|
|
const [arrayBuffer, setArrayBuffer] = useState(null);
|
|
const [baseName, setBaseName] = useState('certificado');
|
|
const [password, setPassword] = useState('');
|
|
const [isConverting, setIsConverting] = useState(false);
|
|
const [conversionResult, setConversionResult] = useState(null);
|
|
const [errorMessage, setErrorMessage] = useState('');
|
|
const [isHostingGuideOpen, setIsHostingGuideOpen] = useState(false);
|
|
|
|
// Manipula seleção de arquivo .pfx
|
|
const handleFileSelected = (selectedFile) => {
|
|
setFile(selectedFile);
|
|
setConversionResult(null);
|
|
setErrorMessage('');
|
|
|
|
// Extrai nome base sem a extensão
|
|
const nameWithoutExt = selectedFile.name.replace(/\.[^/.]+$/, '');
|
|
setBaseName(nameWithoutExt || 'certificado');
|
|
|
|
// Lê os bytes brutos do arquivo
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
setArrayBuffer(e.target.result);
|
|
};
|
|
reader.onerror = () => {
|
|
setErrorMessage('Falha ao ler o arquivo selecionado no seu computador.');
|
|
};
|
|
reader.readAsArrayBuffer(selectedFile);
|
|
};
|
|
|
|
// Limpa tudo para recomeçar
|
|
const handleFileCleared = () => {
|
|
setFile(null);
|
|
setArrayBuffer(null);
|
|
setPassword('');
|
|
setConversionResult(null);
|
|
setErrorMessage('');
|
|
setBaseName('certificado');
|
|
};
|
|
|
|
// Executa a conversão criptográfica
|
|
const handleConvert = () => {
|
|
if (!arrayBuffer) {
|
|
setErrorMessage('Por favor, selecione um arquivo de certificado .pfx primeiro.');
|
|
return;
|
|
}
|
|
|
|
setIsConverting(true);
|
|
setErrorMessage('');
|
|
|
|
// Executa em pequeno timeout para permitir renderizar o estado de carregamento
|
|
setTimeout(() => {
|
|
try {
|
|
const result = convertPfx(arrayBuffer, password);
|
|
|
|
if (result.success) {
|
|
setConversionResult(result);
|
|
setErrorMessage('');
|
|
} else {
|
|
setConversionResult(null);
|
|
setErrorMessage(result.error);
|
|
}
|
|
} catch (err) {
|
|
setConversionResult(null);
|
|
setErrorMessage('Ocorreu um erro inesperado ao converter: ' + (err.message || ''));
|
|
} finally {
|
|
setIsConverting(false);
|
|
}
|
|
}, 80);
|
|
};
|
|
|
|
return (
|
|
<div className="app-layout">
|
|
{/* Barra de Navegação Superior com Alternador de Apps */}
|
|
<Header
|
|
activeTab={activeTab}
|
|
onSelectTab={setActiveTab}
|
|
onOpenHostingGuide={() => setIsHostingGuideOpen(true)}
|
|
/>
|
|
|
|
{/* Conteúdo Principal Dinâmico */}
|
|
<main className="main-content">
|
|
{activeTab === 'portal' && (
|
|
/* Área do Cliente & Central de Acessos */
|
|
<CustomerPortal />
|
|
)}
|
|
|
|
{activeTab === 'cert' && (
|
|
/* Conversor de Certificados PFX */
|
|
<div className="container">
|
|
{/* Banner de Segurança & Privacidade */}
|
|
<SecurityBadge />
|
|
|
|
{/* Card Principal de Conversão */}
|
|
<section className="card-converter">
|
|
<div className="card-header-bar">
|
|
<div className="card-header-title">
|
|
<Sparkles size={20} className="text-cyan" />
|
|
<h2>Conversão de Certificado Digital (.pfx)</h2>
|
|
</div>
|
|
<span className="step-badge">Etapa 1 de 2</span>
|
|
</div>
|
|
|
|
{/* Zona de Upload / Drag & Drop */}
|
|
<UploadZone
|
|
file={file}
|
|
onFileSelected={handleFileSelected}
|
|
onFileCleared={handleFileCleared}
|
|
/>
|
|
|
|
{/* Campo de Senha (habilitado quando há arquivo) */}
|
|
{file && (
|
|
<div className="form-controls-wrapper">
|
|
<PasswordInput
|
|
password={password}
|
|
onChange={setPassword}
|
|
onSubmit={handleConvert}
|
|
disabled={isConverting}
|
|
/>
|
|
|
|
{/* Botões de Ação */}
|
|
<div className="actions-bar">
|
|
<button
|
|
type="button"
|
|
className="btn-convert"
|
|
onClick={handleConvert}
|
|
disabled={isConverting}
|
|
>
|
|
{isConverting ? (
|
|
<>
|
|
<Loader2 size={18} className="animate-spin" />
|
|
<span>Processando Criptografia...</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Lock size={18} />
|
|
<span>Converter para .CRT, .PEM e .KEY</span>
|
|
<ArrowRight size={18} />
|
|
</>
|
|
)}
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
className="btn-reset"
|
|
onClick={handleFileCleared}
|
|
disabled={isConverting}
|
|
title="Limpar e selecionar outro arquivo"
|
|
>
|
|
<RotateCcw size={16} />
|
|
<span>Limpar</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Mensagem de Erro amigável */}
|
|
{errorMessage && (
|
|
<div className="alert-error-box">
|
|
<AlertCircle size={20} />
|
|
<div className="alert-error-text">
|
|
<strong>Atenção:</strong> {errorMessage}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</section>
|
|
|
|
{/* Exibição dos Resultados após conversão */}
|
|
{conversionResult && (
|
|
<section className="results-container">
|
|
{/* Metadados e Validade do Certificado */}
|
|
<CertificateInfo metadata={conversionResult.metadata} />
|
|
|
|
{/* Arquivos Gerados com Abas, Cópia e Download */}
|
|
<OutputFiles
|
|
results={conversionResult}
|
|
baseName={baseName}
|
|
metadata={conversionResult.metadata}
|
|
/>
|
|
|
|
{/* Referência Técnica dos Comandos OpenSSL equivalentes */}
|
|
<OpenSslReference baseName={baseName} />
|
|
</section>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'wpp' && (
|
|
/* Aplicativo WPPConnect Integrado */
|
|
<div className="wpp-view-wrapper">
|
|
<AppWPP />
|
|
</div>
|
|
)}
|
|
</main>
|
|
|
|
{/* Rodapé Informativo */}
|
|
<footer className="footer">
|
|
<div className="footer-content">
|
|
<p>
|
|
{activeTab === 'portal' && 'InflavioApps — Área do Cliente & Central de Acessos C2 Sistemas.'}
|
|
{activeTab === 'cert' && 'InflavioApps — Conversão PKCS#12 / X.509 em conformidade com padrões ICP-Brasil e OpenSSL.'}
|
|
{activeTab === 'wpp' && 'InflavioApps — Gerenciador de Sessões WPPConnect integrado ao portal.'}
|
|
</p>
|
|
<div className="footer-links">
|
|
<button
|
|
type="button"
|
|
className="link-plain"
|
|
onClick={() => setIsHostingGuideOpen(true)}
|
|
>
|
|
Como Hospedar esta Aplicação
|
|
</button>
|
|
<span>•</span>
|
|
<span className="footer-shield">
|
|
<ShieldCheck size={14} /> 100% no cliente (Executa em qualquer navegador)
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
{/* Modal com Guia Passo a Passo de Hospedagem */}
|
|
<HostGuideModal
|
|
isOpen={isHostingGuideOpen}
|
|
onClose={() => setIsHostingGuideOpen(false)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|