feat: add app lock screen requiring master password (C2s33131782*) on launch
This commit is contained in:
+23
@@ -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 <AppLockScreen onUnlock={handleUnlockApp} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app-layout">
|
||||
{/* Barra de Navegação Superior com Alternador de Apps */}
|
||||
<Header
|
||||
activeTab={activeTab}
|
||||
onSelectTab={setActiveTab}
|
||||
onLockApp={handleLockApp}
|
||||
/>
|
||||
|
||||
{/* Conteúdo Principal Dinâmico */}
|
||||
|
||||
@@ -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 (
|
||||
<div className="lockscreen-overlay">
|
||||
<div className={`lockscreen-card ${isShaking ? 'lockscreen-shake' : ''}`}>
|
||||
{/* Glow de Fundo */}
|
||||
<div className="lockscreen-glow" />
|
||||
|
||||
{/* Topo: Logo & Identidade */}
|
||||
<div className="lockscreen-header">
|
||||
<div className="lockscreen-logo-wrapper">
|
||||
<img
|
||||
src="/c2logo.png"
|
||||
alt="C2 Logo"
|
||||
className="lockscreen-logo"
|
||||
onError={(e) => {
|
||||
e.target.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="lockscreen-title-group">
|
||||
<div className="lockscreen-title-row">
|
||||
<h1 className="lockscreen-brand">InflavioApps</h1>
|
||||
<span className="lockscreen-badge">Acesso Restrito</span>
|
||||
</div>
|
||||
<p className="lockscreen-sub">
|
||||
Informe a senha padrão da equipe para acessar as aplicações.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Formulário de Desbloqueio */}
|
||||
<form onSubmit={handleUnlock} className="lockscreen-form">
|
||||
<div className="lockscreen-input-group">
|
||||
<div className="lockscreen-input-wrapper">
|
||||
<Lock size={18} className="lockscreen-input-icon text-muted" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
className={`password-input lockscreen-input ${error ? 'input-error' : ''}`}
|
||||
placeholder="Digite a senha de acesso..."
|
||||
value={password}
|
||||
onChange={(e) => {
|
||||
setPassword(e.target.value);
|
||||
if (error) setError(false);
|
||||
}}
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-toggle-cred-pw lockscreen-eye-btn"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
title={showPassword ? 'Ocultar senha' : 'Ver senha'}
|
||||
>
|
||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="alert-error-box lockscreen-error">
|
||||
<AlertCircle size={16} />
|
||||
<span>Senha incorreta! Acesso restrito à equipe autorizada.</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="lockscreen-options">
|
||||
<label className="lockscreen-remember-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={rememberDevice}
|
||||
onChange={(e) => setRememberDevice(e.target.checked)}
|
||||
className="lockscreen-checkbox"
|
||||
/>
|
||||
<span>Lembrar autenticação neste navegador</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" className="btn-convert lockscreen-submit-btn">
|
||||
<Unlock size={18} />
|
||||
<span>Desbloquear Aplicação</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Rodapé de Segurança */}
|
||||
<div className="lockscreen-footer">
|
||||
<ShieldCheck size={14} className="text-emerald" />
|
||||
<span>Protegido com autenticação padrão C2 Sistemas</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 }) {
|
||||
<span className="nav-badge-pill pill-portal">23</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Ações Rápidas: Bloquear / Sair */}
|
||||
<div className="header-actions">
|
||||
{onLockApp && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn-header-lock"
|
||||
onClick={onLockApp}
|
||||
title="Bloquear aplicação e exigir senha para reabrir"
|
||||
>
|
||||
<Lock size={14} />
|
||||
<span>Bloquear</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
+258
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user