adding base project
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
ShieldCheck,
|
||||
Lock,
|
||||
LayoutGrid,
|
||||
Activity,
|
||||
Key,
|
||||
ShieldAlert,
|
||||
CheckCircle,
|
||||
MailCheck,
|
||||
User,
|
||||
Sliders,
|
||||
FileText,
|
||||
Copy,
|
||||
ExternalLink,
|
||||
Info,
|
||||
} from 'lucide-react';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { DashboardLayout } from '../components/layout/DashboardLayout';
|
||||
import { Badge } from '../components/common/Badge';
|
||||
import { Button } from '../components/common/Button';
|
||||
import { Card } from '../components/common/Card';
|
||||
import { MOCK_STATS, MOCK_ACTIVITIES } from '../services/mockData';
|
||||
import '../styles/dashboard.css';
|
||||
|
||||
export function Dashboard() {
|
||||
const { user, token, isDemoMode, logout } = useAuth();
|
||||
const [activeTab, setActiveTab] = useState('dashboard');
|
||||
const [showTokenModal, setShowTokenModal] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
// Icon mapping for stats
|
||||
const statIcons = {
|
||||
ShieldCheck,
|
||||
Lock,
|
||||
LayoutGrid,
|
||||
Activity,
|
||||
};
|
||||
|
||||
// Icon mapping for activities
|
||||
const activityIcons = {
|
||||
Key,
|
||||
ShieldAlert,
|
||||
CheckCircle,
|
||||
MailCheck,
|
||||
};
|
||||
|
||||
const handleCopyToken = () => {
|
||||
if (token) {
|
||||
navigator.clipboard.writeText(token);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardLayout activeTab={activeTab} setActiveTab={setActiveTab}>
|
||||
{/* Hero Welcome Banner */}
|
||||
<section className="dashboard-hero animate-fade-in">
|
||||
<div className="hero-left">
|
||||
<div className="hero-tag">
|
||||
<ShieldCheck size={16} />
|
||||
<span>Identity & Access Controller</span>
|
||||
</div>
|
||||
<h1 className="hero-title">
|
||||
Welcome back, <span className="gradient-text">{user?.firstName || 'Alex'}</span>
|
||||
</h1>
|
||||
<p className="hero-description">
|
||||
AuthSec base project is operational. Your session is protected with Spring Boot JWT
|
||||
authentication and granular role-based security boundaries.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="hero-right">
|
||||
<div className="hero-token-pill">
|
||||
<Key size={14} />
|
||||
<span>JWT: {token ? `${token.slice(0, 14)}...` : 'demo-token'}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
icon={Key}
|
||||
onClick={() => setShowTokenModal(true)}
|
||||
>
|
||||
Inspect Token
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={logout}
|
||||
>
|
||||
Sign Out
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Metric Stats Cards */}
|
||||
<section className="stats-grid">
|
||||
{MOCK_STATS.map((stat) => {
|
||||
const Icon = statIcons[stat.icon] || Activity;
|
||||
return (
|
||||
<div key={stat.id} className="stat-card">
|
||||
<div className="stat-card-header">
|
||||
<span className="stat-card-title">{stat.title}</span>
|
||||
<div className="stat-card-icon-box">
|
||||
<Icon size={20} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="stat-card-value">{stat.value}</div>
|
||||
|
||||
<div className="stat-card-footer">
|
||||
<span>{stat.subtitle}</span>
|
||||
<span className={`stat-trend ${stat.status}`}>{stat.trend}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
|
||||
{/* Quick Action Buttons */}
|
||||
<section>
|
||||
<div className="section-header">
|
||||
<h2 className="section-title">Quick Actions</h2>
|
||||
</div>
|
||||
|
||||
<div className="quick-actions-grid">
|
||||
<div className="quick-action-card" onClick={() => setShowTokenModal(true)}>
|
||||
<div className="action-icon-circle">
|
||||
<Key size={20} />
|
||||
</div>
|
||||
<div className="action-details">
|
||||
<span className="action-name">Security Token</span>
|
||||
<span className="action-desc">View JWT payload & auth headers</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="quick-action-card"
|
||||
onClick={() => alert(`User Profile:\nName: ${user?.fullname || user?.firstName}\nEmail: ${user?.email}\nRoles: ${user?.roles?.join(', ')}`)}
|
||||
>
|
||||
<div className="action-icon-circle" style={{ background: 'linear-gradient(135deg, #06b6d4, #3b82f6)' }}>
|
||||
<User size={20} />
|
||||
</div>
|
||||
<div className="action-details">
|
||||
<span className="action-name">User Profile</span>
|
||||
<span className="action-desc">Account ID #{user?.userId || '101'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="quick-action-card"
|
||||
onClick={() => alert('System Parameters: Spring Boot Service Port 9292\nCORS: Enabled\nToken Lifetime: 24h')}
|
||||
>
|
||||
<div className="action-icon-circle" style={{ background: 'linear-gradient(135deg, #8b5cf6, #ec4899)' }}>
|
||||
<Sliders size={20} />
|
||||
</div>
|
||||
<div className="action-details">
|
||||
<span className="action-name">System Parameters</span>
|
||||
<span className="action-desc">Endpoints & security policies</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="quick-action-card"
|
||||
onClick={() => setActiveTab('security')}
|
||||
>
|
||||
<div className="action-icon-circle" style={{ background: 'linear-gradient(135deg, #10b981, #06b6d4)' }}>
|
||||
<FileText size={20} />
|
||||
</div>
|
||||
<div className="action-details">
|
||||
<span className="action-name">Audit Reports</span>
|
||||
<span className="action-desc">Inspect security logs</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Live Security Activity Stream */}
|
||||
<section className="activity-card">
|
||||
<div className="section-header">
|
||||
<div>
|
||||
<h2 className="section-title">Recent Security Events</h2>
|
||||
<p style={{ fontSize: '0.85rem', color: 'var(--text-muted)' }}>
|
||||
Audited session entries recorded for this workspace
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="primary" size="md">
|
||||
Live Stream
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="activity-list">
|
||||
{MOCK_ACTIVITIES.map((act) => {
|
||||
const Icon = activityIcons[act.icon] || Key;
|
||||
return (
|
||||
<div key={act.id} className="activity-item">
|
||||
<div className="activity-left">
|
||||
<div className="activity-icon">
|
||||
<Icon size={18} />
|
||||
</div>
|
||||
<div className="activity-info">
|
||||
<span className="activity-title">{act.title}</span>
|
||||
<span className="activity-desc">
|
||||
{act.description} • <strong style={{ color: 'var(--text-secondary)' }}>IP: {act.ip}</strong>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="activity-right">
|
||||
<Badge
|
||||
variant={
|
||||
act.badge === 'SUCCESS' ? 'success' : act.badge === 'SECURITY' ? 'warning' : 'info'
|
||||
}
|
||||
>
|
||||
{act.badge}
|
||||
</Badge>
|
||||
<span className="activity-time">{act.timestamp}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Token Inspector Modal */}
|
||||
{showTokenModal && (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
backdropFilter: 'blur(6px)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1000,
|
||||
padding: '20px',
|
||||
}}
|
||||
onClick={() => setShowTokenModal(false)}
|
||||
>
|
||||
<div
|
||||
className="custom-card animate-fade-in"
|
||||
style={{ maxWidth: '580px', width: '100%', maxHeight: '90vh', overflowY: 'auto' }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<Key size={20} color="var(--color-primary-light)" />
|
||||
<h3 style={{ fontSize: '1.25rem' }}>Session Token Inspector</h3>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={() => setShowTokenModal(false)}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p style={{ fontSize: '0.85rem', color: 'var(--text-secondary)', marginBottom: '16px' }}>
|
||||
This Bearer token is automatically attached to API requests in the <code>Authorization</code> header.
|
||||
</p>
|
||||
|
||||
<div className="session-info-card">
|
||||
<div className="session-info-row">
|
||||
<span>Account Email:</span>
|
||||
<strong>{user?.email || 'N/A'}</strong>
|
||||
</div>
|
||||
<div className="session-info-row">
|
||||
<span>User ID:</span>
|
||||
<strong>{user?.userId || '101'}</strong>
|
||||
</div>
|
||||
<div className="session-info-row">
|
||||
<span>Assigned Roles:</span>
|
||||
<strong>{Array.isArray(user?.roles) ? user.roles.join(', ') : 'ROLE_ADMIN'}</strong>
|
||||
</div>
|
||||
<div className="session-info-row">
|
||||
<span>Mode:</span>
|
||||
<strong>{isDemoMode ? 'Sandbox Demo Mode' : 'Live Spring Boot'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '16px' }}>
|
||||
<label style={{ fontSize: '0.8rem', fontWeight: 600, color: 'var(--text-secondary)' }}>
|
||||
Raw JWT Token:
|
||||
</label>
|
||||
<textarea
|
||||
readOnly
|
||||
value={token || ''}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '80px',
|
||||
background: 'var(--bg-input)',
|
||||
color: 'var(--text-main)',
|
||||
border: '1px solid var(--border-subtle)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
padding: '10px',
|
||||
fontSize: '0.75rem',
|
||||
fontFamily: 'monospace',
|
||||
resize: 'none',
|
||||
marginTop: '6px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px', marginTop: '20px' }}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
icon={Copy}
|
||||
onClick={handleCopyToken}
|
||||
>
|
||||
{copied ? 'Copied to Clipboard!' : 'Copy Token'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => setShowTokenModal(false)}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user