From f01101cab078ffef845182e6f0ae60870ad6262d Mon Sep 17 00:00:00 2001 From: fpereirasilv Date: Thu, 15 Oct 2026 13:39:37 -0300 Subject: [PATCH] feat: add app lock screen requiring master password (C2s33131782*) on launch --- src/App.jsx | 23 +++ src/components/AppLockScreen.jsx | 128 +++++++++++++++ src/components/Header.jsx | 19 ++- src/index.css | 258 +++++++++++++++++++++++++++++++ 4 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 src/components/AppLockScreen.jsx diff --git a/src/App.jsx b/src/App.jsx index 320e0df..7259984 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -19,9 +19,17 @@ import OutputFiles from './components/OutputFiles'; import OpenSslReference from './components/OpenSslReference'; import AppWPP from './components/wpp/AppWPP'; import CustomerPortal from './components/portal/CustomerPortal'; +import AppLockScreen from './components/AppLockScreen'; import { convertPfx } from './utils/certConverter'; export default function App() { + const [isUnlocked, setIsUnlocked] = useState(() => { + return ( + sessionStorage.getItem('inflavio_app_authenticated') === 'true' || + localStorage.getItem('inflavio_app_authenticated') === 'true' + ); + }); + 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); @@ -31,6 +39,16 @@ export default function App() { const [conversionResult, setConversionResult] = useState(null); const [errorMessage, setErrorMessage] = useState(''); + const handleUnlockApp = () => { + setIsUnlocked(true); + }; + + const handleLockApp = () => { + sessionStorage.removeItem('inflavio_app_authenticated'); + localStorage.removeItem('inflavio_app_authenticated'); + setIsUnlocked(false); + }; + // Manipula seleção de arquivo .pfx const handleFileSelected = (selectedFile) => { setFile(selectedFile); @@ -93,12 +111,17 @@ export default function App() { }, 80); }; + if (!isUnlocked) { + return ; + } + return (
{/* Barra de Navegação Superior com Alternador de Apps */}
{/* Conteúdo Principal Dinâmico */} diff --git a/src/components/AppLockScreen.jsx b/src/components/AppLockScreen.jsx new file mode 100644 index 0000000..f4ddacd --- /dev/null +++ b/src/components/AppLockScreen.jsx @@ -0,0 +1,128 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Lock, Unlock, Eye, EyeOff, AlertCircle, ShieldCheck } from 'lucide-react'; +import { SUPPORT_MASTER_PASSWORD } from '../utils/customerPortalData'; + +export default function AppLockScreen({ onUnlock }) { + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [error, setError] = useState(false); + const [rememberDevice, setRememberDevice] = useState(true); + const [isShaking, setIsShaking] = useState(false); + const inputRef = useRef(null); + + useEffect(() => { + inputRef.current?.focus(); + }, []); + + const handleUnlock = (e) => { + e?.preventDefault(); + if (password === SUPPORT_MASTER_PASSWORD) { + setError(false); + if (rememberDevice) { + localStorage.setItem('inflavio_app_authenticated', 'true'); + } else { + sessionStorage.setItem('inflavio_app_authenticated', 'true'); + } + onUnlock(); + } else { + setError(true); + setIsShaking(true); + setTimeout(() => setIsShaking(false), 500); + setPassword(''); + inputRef.current?.focus(); + } + }; + + return ( +
+
+ {/* Glow de Fundo */} +
+ + {/* Topo: Logo & Identidade */} +
+
+ C2 Logo { + e.target.style.display = 'none'; + }} + /> +
+ +
+
+

InflavioApps

+ Acesso Restrito +
+

+ Informe a senha padrão da equipe para acessar as aplicações. +

+
+
+ + {/* Formulário de Desbloqueio */} +
+
+
+ + { + setPassword(e.target.value); + if (error) setError(false); + }} + autoComplete="current-password" + required + /> + +
+
+ + {error && ( +
+ + Senha incorreta! Acesso restrito à equipe autorizada. +
+ )} + +
+ +
+ + +
+ + {/* Rodapé de Segurança */} +
+ + Protegido com autenticação padrão C2 Sistemas +
+
+
+ ); +} diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 2a145da..4a7344b 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -1,7 +1,7 @@ import React from 'react'; -import { ShieldCheck, MessageSquare, Bookmark } from 'lucide-react'; +import { ShieldCheck, MessageSquare, Bookmark, Lock } from 'lucide-react'; -export default function Header({ activeTab, onSelectTab }) { +export default function Header({ activeTab, onSelectTab, onLockApp }) { const getBrandDetails = () => { switch (activeTab) { case 'cert': @@ -80,6 +80,21 @@ export default function Header({ activeTab, onSelectTab }) { 23
+ + {/* Ações Rápidas: Bloquear / Sair */} +
+ {onLockApp && ( + + )} +
); diff --git a/src/index.css b/src/index.css index 1c82230..072084a 100644 --- a/src/index.css +++ b/src/index.css @@ -2616,5 +2616,263 @@ body { max-width: 440px; } +/* Header Lock / Sair Button */ +.btn-header-lock { + display: inline-flex; + align-items: center; + gap: 0.4rem; + background-color: rgba(239, 68, 68, 0.08); + border: 1px solid rgba(239, 68, 68, 0.25); + color: #f87171; + padding: 0.45rem 0.75rem; + border-radius: 8px; + font-size: 0.8125rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.btn-header-lock:hover { + background-color: rgba(239, 68, 68, 0.18); + border-color: #ef4444; + color: #fca5a5; + box-shadow: 0 2px 10px rgba(239, 68, 68, 0.2); +} + +/* App Lock Screen Styles */ +.lockscreen-overlay { + position: fixed; + inset: 0; + width: 100vw; + height: 100vh; + z-index: 99999; + background: radial-gradient(circle at 50% 30%, #0f172a 0%, #020617 100%); + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; +} + +.lockscreen-card { + position: relative; + width: 100%; + max-width: 440px; + background: rgba(15, 23, 42, 0.85); + border: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: 0 25px 60px -15px rgba(0, 0, 0, 0.7), 0 0 40px rgba(14, 165, 233, 0.1); + border-radius: 20px; + padding: 2.25rem 2rem; + backdrop-filter: blur(20px); + display: flex; + flex-direction: column; + gap: 1.5rem; + overflow: hidden; +} + +.lockscreen-glow { + position: absolute; + top: -60px; + left: 50%; + transform: translateX(-50%); + width: 180px; + height: 120px; + background: radial-gradient(circle, rgba(14, 165, 233, 0.25) 0%, transparent 70%); + filter: blur(30px); + pointer-events: none; +} + +.lockscreen-header { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 1rem; +} + +.lockscreen-logo-wrapper { + width: 72px; + height: 72px; + background: rgba(30, 41, 59, 0.7); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 18px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + padding: 0.65rem; +} + +.lockscreen-logo { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.lockscreen-title-group { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.4rem; +} + +.lockscreen-title-row { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.lockscreen-brand { + font-size: 1.5rem; + font-weight: 700; + color: #f8fafc; + letter-spacing: -0.02em; + margin: 0; +} + +.lockscreen-badge { + font-size: 0.675rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 0.2rem 0.5rem; + border-radius: 6px; + background: rgba(239, 68, 68, 0.15); + color: #f87171; + border: 1px solid rgba(239, 68, 68, 0.3); +} + +.lockscreen-sub { + font-size: 0.85rem; + color: #94a3b8; + margin: 0; + max-width: 320px; + line-height: 1.4; +} + +.lockscreen-form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.lockscreen-input-group { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.lockscreen-input-wrapper { + position: relative; + display: flex; + align-items: center; +} + +.lockscreen-input-icon { + position: absolute; + left: 0.875rem; + pointer-events: none; +} + +.lockscreen-input { + width: 100%; + padding-left: 2.6rem !important; + padding-right: 2.75rem !important; + font-size: 0.95rem; + border-radius: 10px; + background: rgba(30, 41, 59, 0.85); + border: 1px solid rgba(255, 255, 255, 0.12); + color: #f8fafc; + height: 46px; + transition: all 0.2s ease; +} + +.lockscreen-input:focus { + border-color: #0ea5e9; + box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.2); + outline: none; +} + +.lockscreen-eye-btn { + position: absolute; + right: 0.75rem; + padding: 0.35rem; + color: #94a3b8; + border-radius: 6px; +} + +.lockscreen-eye-btn:hover { + color: #f8fafc; +} + +.lockscreen-error { + margin: 0; + padding: 0.65rem 0.85rem; + font-size: 0.825rem; + border-radius: 8px; +} + +.lockscreen-options { + display: flex; + align-items: center; + justify-content: flex-start; +} + +.lockscreen-remember-label { + display: inline-flex; + align-items: center; + gap: 0.5rem; + font-size: 0.8125rem; + color: #94a3b8; + cursor: pointer; + user-select: none; +} + +.lockscreen-checkbox { + accent-color: #0ea5e9; + cursor: pointer; +} + +.lockscreen-submit-btn { + width: 100%; + height: 46px; + justify-content: center; + font-size: 0.95rem; + font-weight: 600; + border-radius: 10px; + background: linear-gradient(135deg, #0284c7 0%, #0369a1 100%); + border: 1px solid rgba(56, 189, 248, 0.3); + box-shadow: 0 4px 15px rgba(2, 132, 199, 0.3); + cursor: pointer; + gap: 0.5rem; +} + +.lockscreen-submit-btn:hover { + background: linear-gradient(135deg, #0369a1 0%, #075985 100%); + box-shadow: 0 6px 20px rgba(2, 132, 199, 0.4); +} + +.lockscreen-footer { + display: flex; + align-items: center; + justify-content: center; + gap: 0.45rem; + font-size: 0.75rem; + color: #64748b; + border-top: 1px solid rgba(255, 255, 255, 0.05); + padding-top: 1rem; +} + +/* Shake Animation on Wrong Password */ +@keyframes lockscreenShake { + 0%, 100% { transform: translateX(0); } + 20%, 60% { transform: translateX(-8px); } + 40%, 80% { transform: translateX(8px); } +} + +.lockscreen-shake { + animation: lockscreenShake 0.45s ease-in-out; +} + +