From 4948d137d38f034d17ff151f9824ac17199b5627 Mon Sep 17 00:00:00 2001 From: Kanishka TOMAR Date: Sun, 17 May 2026 20:00:22 +0000 Subject: [PATCH 1/2] Cetain chnaages --- services/dashboard/src/App.tsx | 5 +- services/dashboard/src/layout/MainLayout.tsx | 287 +++++++++++++-- services/dashboard/src/pages/Dashboard.tsx | 206 ++++++++--- .../dashboard/src/pages/Documentation.tsx | 347 ++++++++++++++++++ services/dashboard/src/pages/Profile.tsx | 10 + 5 files changed, 763 insertions(+), 92 deletions(-) create mode 100644 services/dashboard/src/pages/Documentation.tsx create mode 100644 services/dashboard/src/pages/Profile.tsx diff --git a/services/dashboard/src/App.tsx b/services/dashboard/src/App.tsx index c3bbb55..f9b588c 100644 --- a/services/dashboard/src/App.tsx +++ b/services/dashboard/src/App.tsx @@ -5,7 +5,8 @@ import GraphView from "./pages/GraphView"; import Cases from "./pages/Cases"; import Alerts from "./pages/Alerts"; import Reports from "./pages/Reports"; - +import Profile from "./pages/Profile"; +import Documentation from "./pages/Documentation"; function App() { return ( @@ -17,6 +18,8 @@ function App() { } /> } /> } /> + } /> + } /> diff --git a/services/dashboard/src/layout/MainLayout.tsx b/services/dashboard/src/layout/MainLayout.tsx index 66b6047..c1762e4 100644 --- a/services/dashboard/src/layout/MainLayout.tsx +++ b/services/dashboard/src/layout/MainLayout.tsx @@ -1,48 +1,263 @@ -import { Outlet, NavLink } from "react-router-dom"; -import { Activity, ShieldAlert, GitGraph, Briefcase, FileText } from "lucide-react"; +import { createContext, useContext, useEffect, useState } from "react"; +import { Outlet, NavLink, Link, useNavigate } from "react-router-dom"; +import { motion } from "framer-motion"; +import { + Activity, + ShieldAlert, + GitGraph, + Briefcase, + FileText, + Menu, + X, + Search, + MapPin, + ShieldCheck, + Moon, + Sun, +} from "lucide-react"; + +type DashboardAnchorContextType = { + registerDashboardAnchor: (node: HTMLDivElement | null) => void; + requestDashboardScroll: () => void; +}; + +type ThemeContextType = { + isDarkMode: boolean; +}; + +const DashboardAnchorContext = createContext(null); +const ThemeContext = createContext(null); + +export function useDashboardAnchor() { + const context = useContext(DashboardAnchorContext); + if (!context) { + throw new Error("useDashboardAnchor must be used within MainLayout"); + } + return context; +} + +export function useThemeMode() { + const context = useContext(ThemeContext); + if (!context) { + throw new Error("useThemeMode must be used within MainLayout"); + } + return context; +} export default function MainLayout() { + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const [isProfileOpen, setIsProfileOpen] = useState(false); + const [isDarkMode, setIsDarkMode] = useState(true); + const [dashboardAnchor, setDashboardAnchor] = useState(null); + const [pendingDashboardScroll, setPendingDashboardScroll] = useState(false); + const navigate = useNavigate(); + + useEffect(() => { + if (pendingDashboardScroll && dashboardAnchor) { + dashboardAnchor.scrollIntoView({ behavior: "smooth", block: "start" }); + setPendingDashboardScroll(false); + } + }, [pendingDashboardScroll, dashboardAnchor]); + + useEffect(() => { + document.body.style.overflow = isSidebarOpen ? "hidden" : ""; + return () => { + document.body.style.overflow = ""; + }; + }, [isSidebarOpen]); + + const requestDashboardScroll = () => { + setPendingDashboardScroll(true); + navigate("/dashboard"); + setIsSidebarOpen(false); + }; + + const rootThemeClasses = isDarkMode + ? "bg-[#0B111E] text-slate-100" + : "bg-[#F4F6F9] text-slate-950"; + + const headerSurface = isDarkMode + ? "bg-[#0F172A]/80 border-slate-800 text-slate-100" + : "bg-white/90 border-slate-200 text-slate-950"; + + const surfacePanel = isDarkMode + ? "bg-[#1E293B] border-slate-700 text-slate-100" + : "bg-white border-slate-200 text-slate-950"; + + const profilePopupSurface = isDarkMode + ? "bg-slate-800 border border-slate-700 text-slate-100" + : "bg-white border border-gray-200 text-gray-900 shadow-2xl shadow-gray-400/30"; + + const profileAccentButton = isDarkMode + ? "bg-emerald-500 text-slate-950 hover:bg-emerald-400" + : "bg-emerald-700 text-white hover:bg-emerald-600"; + return ( -
- {/* Sidebar */} - - - {/* Main Content */} -
-
-

Investigation Hub

-
-
- + + +
+
+
+
+ + +
+

FundGuard

+

BANK FRAUD DETECTION PLATFORM

+
+ +
+ + System Scope: India Operations +
+
+ +
+ + +
+ +
+ + setIsDarkMode((current) => !current)} + whileTap={{ scale: 0.95 }} + animate={{ rotate: isDarkMode ? 360 : 0 }} + transition={{ duration: 0.35, ease: "easeInOut" }} + className="grid h-10 w-10 place-items-center rounded-full border border-white/5 bg-[#0f1724] text-slate-100 transition hover:bg-[#111827]" + aria-label="Toggle dark mode" + > + {isDarkMode ? : } + +
+ + {isProfileOpen && ( +
+
+
+

USER ACCOUNT

+

Yjsjs

+

FG-98765-IN

+
+ +
+
+ )} +
+
+
+
+ + setIsSidebarOpen(false)} + initial={false} + animate={{ opacity: isSidebarOpen ? 1 : 0 }} + /> + + +
+
+

Feature Center

+

FundGuard Workspace

+
+ +
+ +
+ +
+ +
-
-
+ + ); } -function NavItem({ to, icon, label }: { to: string; icon: React.ReactNode; label: string }) { +function SidebarLink({ + icon, + label, + to, + onClick, +}: { + icon: React.ReactNode; + label: string; + to?: string; + onClick: () => void; +}) { + if (to) { + return ( + + `flex items-center gap-3 rounded-2xl border border-slate-700 px-4 py-3 text-sm font-medium transition ${ + isActive ? "bg-emerald-500/10 text-emerald-300" : "text-slate-300 hover:bg-white/5" + }` + } + > + {icon} + {label} + + ); + } + return ( - - `flex items-center gap-3 px-4 py-3 rounded-lg transition-colors font-medium ${ - isActive ? "bg-gray-800 text-safe" : "text-gray-400 hover:text-gray-200 hover:bg-gray-800/50" - }` - } + ); -} \ No newline at end of file +} diff --git a/services/dashboard/src/pages/Dashboard.tsx b/services/dashboard/src/pages/Dashboard.tsx index 01e1018..973c966 100644 --- a/services/dashboard/src/pages/Dashboard.tsx +++ b/services/dashboard/src/pages/Dashboard.tsx @@ -1,5 +1,8 @@ import { useEffect, useRef, useState } from "react"; +import { motion } from "framer-motion"; +import { Activity, AlertTriangle, ShieldAlert, Zap, Layers } from "lucide-react"; import { API_BASE_URL, WS_URL } from "../config/endpoints"; +import { useDashboardAnchor, useThemeMode } from "../layout/MainLayout"; type RiskEvent = { transaction_id?: string; @@ -13,6 +16,8 @@ type RiskEvent = { }; export default function Dashboard() { + const { registerDashboardAnchor } = useDashboardAnchor(); + const { isDarkMode } = useThemeMode(); const [alerts, setAlerts] = useState([]); const [latestEvent, setLatestEvent] = useState(null); const [wsConnected, setWsConnected] = useState(false); @@ -27,19 +32,14 @@ export default function Dashboard() { }); useEffect(() => { - // Load historical data on mount fetch(`${API_BASE_URL}/api/stats`) - .then(r => r.json()) - .then(data => { - setStats(prev => ({...prev, ...data})); - }) + .then((r) => r.json()) + .then((data) => setStats((prev) => ({ ...prev, ...data }))) .catch(console.error); fetch(`${API_BASE_URL}/api/recent-alerts?limit=10`) - .then(r => r.json()) - .then(data => { - setAlerts(data); - }) + .then((r) => r.json()) + .then((data) => setAlerts(data)) .catch(console.error); }, []); @@ -49,12 +49,10 @@ export default function Dashboard() { ws.onopen = () => { streamStartedAt.current = Date.now(); setWsConnected(true); - console.log("Connected to Risk Engine WS"); }; ws.onmessage = (event) => { const data = JSON.parse(event.data) as RiskEvent; - setLatestEvent(data); setStats((prev) => { const liveEvents = prev.liveEvents + 1; @@ -86,62 +84,160 @@ export default function Dashboard() { }; }, []); + const surface = isDarkMode + ? "bg-[#1E293B] border-slate-700 text-slate-100" + : "bg-[#F8FAFC] border-slate-200 text-slate-950"; + const panelSurface = isDarkMode + ? "bg-[#112131] border-slate-700 text-slate-100" + : "bg-white border-slate-200 text-slate-950"; + return ( -
-

Live Overview

-
- 0} /> - 0} /> - - - 0} /> +
+
+

LIVE OVERVIEW

+

Operational Risk Insights

+
+ +
+ } + label="LIVE INGESTED EVENTS" + value={stats.liveEvents.toString()} + isDarkMode={isDarkMode} + themeSurface={surface} + /> + } + label="ACTIVE RISK ALERTS" + value={stats.activeAlerts.toString()} + highlight={stats.activeAlerts > 0} + isDarkMode={isDarkMode} + themeSurface={surface} + /> + } + label="CORE REJECT RATE" + value={stats.fraudRate} + isDarkMode={isDarkMode} + themeSurface={surface} + /> + } + label="TRANS / MIN (RPM)" + value={stats.transMin} + isDarkMode={isDarkMode} + themeSurface={surface} + /> + } + label="HIGH RISK NODES" + value={stats.highRisk.toString()} + isDarkMode={isDarkMode} + themeSurface={surface} + />
-
-
-

Recent Signals

-
- {alerts.length === 0 ?

Listening for live alerts...

: alerts.map((a, i) => ( -
-
- {a.transaction_id} - {a.decision} -
-
Score: {a.unified_score?.toFixed(2)}
-
- ))} + +
+ +
+

📡 Real-Time Fraud Telemetry Stream

-
-
-

Risk Engine Real-time Analysis

-
- Stream status: {wsConnected ? "CONNECTED" : "DISCONNECTED"} +
+

+ System standing by... Listening for active fraud risk vectors and transaction telemetry data lines. +

- {latestEvent ? ( -
-
Latest Event
-
{latestEvent.transaction_id}
-
Decision: {latestEvent.decision}
-
Unified score: {latestEvent.unified_score?.toFixed(2) ?? "0.00"}
+ + + +
+
+

🧠 Risk Engine Live AI Assessment

+

+ Kafka ➔ Dashboard API ➔ WebSocket Stream +

- ) : ( -

- Waiting for live transactions. This panel updates for every event, including APPROVE decisions. -

- )} -

Kafka -> Dashboard API -> WebSocket stream

-
+
+
+
+

+ The assessment engine monitors ingest throughput, anomaly spikes, and risk correlation across the operational pipeline. +

+

+ When active connections reopen, the stream badge will reflect live status and connection latency in real time. +

+
+
+
+ + + 🔴 Operational Stream: + + DISCONNECTED +
+
); } -function MetricCard({ label, value, alert }: { label: string; value: string; alert?: boolean }) { +function MetricCard({ + icon, + label, + value, + highlight, + isDarkMode, + themeSurface, +}: { + icon: React.ReactNode; + label: string; + value: string; + highlight?: boolean; + isDarkMode: boolean; + themeSurface: string; +}) { + const liveBadge = highlight + ? "inline-flex items-center gap-2 rounded-full bg-red-500/10 text-red-400 px-2.5 py-1 text-[10px] font-extrabold" + : "inline-flex items-center gap-2 rounded-full bg-emerald-500/10 text-emerald-400 px-2.5 py-1 text-[10px] font-extrabold"; + return ( -
-
{label}
-
+ +
+
+
+ {icon} + {label} +
+
+ {/* + {highlight && } + {highlight ? "LIVE" : "Live"} + */} +
+
+
{value}
-
+ + + + {highlight && } + {highlight ? "LIVE" : "Live"} + + +
+ ); } diff --git a/services/dashboard/src/pages/Documentation.tsx b/services/dashboard/src/pages/Documentation.tsx new file mode 100644 index 0000000..4fa59f9 --- /dev/null +++ b/services/dashboard/src/pages/Documentation.tsx @@ -0,0 +1,347 @@ +import React, { useState } from "react"; +import { + ShieldCheck, + Search, + BookOpen, + Brain, + Database, + Activity, + Bell, + Network, + BarChart3, + Cpu, + ArrowRight, + Home, + Workflow, + AlertTriangle, +} from "lucide-react"; + +const sidebarItems = [ + { + title: "Overview", + items: [ + { + id: "intro", + icon: , + label: "Introduction", + }, + { + id: "architecture", + icon: , + label: "Architecture", + }, + { + id: "features", + icon: , + label: "Features", + }, + { + id: "dataflow", + icon: , + label: "Data Flow", + }, + ], + }, + { + title: "Detection", + items: [ + { + id: "fraud", + icon: , + label: "Fraud Methods", + }, + { + id: "risk", + icon: , + label: "Risk Factors", + }, + { + id: "ml", + icon: , + label: "ML Models", + }, + { + id: "graph", + icon: , + label: "Graph Engine", + }, + ], + }, + { + title: "Operations", + items: [ + { + id: "monitoring", + icon: , + label: "Monitoring", + }, + { + id: "alerts", + icon: , + label: "Alerts", + }, + { + id: "deployment", + icon: , + label: "Deployment", + }, + ], + }, +]; + +const cards = [ + { + title: "Fraud Detection", + desc: "Advanced fraud prevention methods and layered detection logic.", + icon: , + }, + { + title: "Risk Scoring", + desc: "AI-powered transaction risk assessment engine.", + icon: , + }, + { + title: "ML Models", + desc: "Machine learning pipelines for anomaly detection.", + icon: , + }, + { + title: "Graph Analytics", + desc: "Relationship mapping and fraud network discovery.", + icon: , + }, + { + title: "Rule Engine", + desc: "Custom fraud rules and real-time business logic.", + icon: , + }, + { + title: "Architecture", + desc: "Microservices infrastructure and deployment overview.", + icon: , + }, +]; + +const Documentation = () => { + const [activeDoc, setActiveDoc] = useState("intro"); + + return ( +
+ {/* Sidebar */} + + + {/* Main Content */} +
+ {/* Topbar */} +
+
+ + + +
+
+ + {/* Hero */} +
+
+
+ +
+
+
+ +
+ +

+ FundGuard Documentation +

+ +

+ AI Powered Fraud Detection +

+ +

+ Comprehensive guides for + understanding, deploying, and + scaling intelligent banking fraud + prevention infrastructure. +

+
+ +
+
+ +
+
+
+
+
+ + {/* Overview Cards */} +
+
+

+ Documentation Overview +

+ +

+ Explore fraud detection modules, + machine learning pipelines, and + architecture. +

+
+ +
+ {cards.map((card, index) => ( +
+
+ {card.icon} +
+ +

+ {card.title} +

+ +

+ {card.desc} +

+ +
+ +
+
+ ))} +
+
+ + {/* Pipeline */} +
+
+

+ Fraud Detection Pipeline +

+ +

+ End-to-end workflow for identifying + suspicious banking activities. +

+ +
+ {[ + "Data Ingestion", + "Risk Analysis", + "Decision Engine", + "Alert Review", + "Final Outcome", + ].map((step, index) => ( +
+
+ {index + 1} +
+ +

+ {step} +

+ +

+ Real-time fraud monitoring and + intelligent analysis pipeline. +

+
+ ))} +
+
+
+
+
+ ); +}; + +export default Documentation; \ No newline at end of file diff --git a/services/dashboard/src/pages/Profile.tsx b/services/dashboard/src/pages/Profile.tsx new file mode 100644 index 0000000..e95b4e2 --- /dev/null +++ b/services/dashboard/src/pages/Profile.tsx @@ -0,0 +1,10 @@ +function Profile() { + return ( +
+

Profile Page

+

Welcome to the profile component.

+
+ ); +} + +export default Profile; \ No newline at end of file From f53767938b159c55b0604dbca49b2316b7a32d9e Mon Sep 17 00:00:00 2001 From: Kanishka TOMAR Date: Thu, 28 May 2026 18:52:11 +0000 Subject: [PATCH 2/2] Updated fraud dashboard clone --- flutter_profile_popup.dart | 684 ++++++++++++++ main.dart | 835 ++++++++++++++++++ services/dashboard/src/App.tsx | 30 +- .../dashboard/src/context/AuthContext.tsx | 64 ++ services/dashboard/src/layout/MainLayout.tsx | 134 ++- .../dashboard/src/pages/Authentication.tsx | 450 ++++++++++ services/dashboard/src/pages/Cases.tsx | 119 ++- services/dashboard/src/pages/Dashboard.tsx | 20 +- .../dashboard/src/pages/Documentation.tsx | 287 ++++-- services/dashboard/src/pages/GraphView.tsx | 399 ++++++++- services/dashboard/src/pages/Reports.tsx | 71 +- 11 files changed, 2873 insertions(+), 220 deletions(-) create mode 100644 flutter_profile_popup.dart create mode 100644 main.dart create mode 100644 services/dashboard/src/context/AuthContext.tsx create mode 100644 services/dashboard/src/pages/Authentication.tsx diff --git a/flutter_profile_popup.dart b/flutter_profile_popup.dart new file mode 100644 index 0000000..fc4d518 --- /dev/null +++ b/flutter_profile_popup.dart @@ -0,0 +1,684 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const FundGuardProfileApp()); +} + +/// Main Application Widget - Dark Theme with Profile Popup Menu +class FundGuardProfileApp extends StatefulWidget { + const FundGuardProfileApp({Key? key}) : super(key: key); + + @override + State createState() => _FundGuardProfileAppState(); +} + +class _FundGuardProfileAppState extends State { + // Global authentication state + bool _isAuthenticated = false; + String _userRole = ''; + String _phoneNumber = ''; + String _accountNumber = ''; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'FundGuard Profile Popup', + debugShowCheckedModeBanner: false, + themeMode: ThemeMode.dark, + darkTheme: _buildDarkTheme(), + home: _isAuthenticated + ? DashboardScreen( + userRole: _userRole, + phoneNumber: _phoneNumber, + accountNumber: _accountNumber, + onLogout: _handleLogout, + ) + : LoginScreen( + onLoginSuccess: _handleLoginSuccess, + ), + ); + } + + /// Dark Theme Configuration + ThemeData _buildDarkTheme() { + return ThemeData( + useMaterial3: true, + brightness: Brightness.dark, + scaffoldBackgroundColor: const Color(0xFF0F1419), + primaryColor: const Color(0xFF6200EE), + colorScheme: const ColorScheme.dark( + primary: Color(0xFF6200EE), + secondary: Color(0xFF03DAC6), + surface: Color(0xFF151D2A), + background: Color(0xFF0F1419), + error: Color(0xFFCF6679), + onBackground: Color(0xFFE0E0E0), + ), + appBarTheme: const AppBarTheme( + backgroundColor: Color(0xFF151D2A), + elevation: 0, + centerTitle: true, + titleTextStyle: TextStyle( + color: Color(0xFFE0E0E0), + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6200EE), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: const Color(0xFF1E1E1E), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: Color(0xFF6200EE), width: 1.5), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: Color(0xFF333333), width: 1.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: Color(0xFF6200EE), width: 2), + ), + labelStyle: const TextStyle(color: Color(0xFFB0B0B0)), + hintStyle: const TextStyle(color: Color(0xFF666666)), + ), + textTheme: const TextTheme( + displayLarge: TextStyle(color: Color(0xFFE0E0E0), fontSize: 32, fontWeight: FontWeight.bold), + headlineSmall: TextStyle(color: Color(0xFFE0E0E0), fontSize: 20, fontWeight: FontWeight.w600), + bodyLarge: TextStyle(color: Color(0xFFE0E0E0), fontSize: 16), + bodyMedium: TextStyle(color: Color(0xFFB0B0B0), fontSize: 14), + ), + ); + } + + void _handleLoginSuccess(String phoneNumber, String accountNumber, String userRole) { + setState(() { + _isAuthenticated = true; + _userRole = userRole; + _phoneNumber = phoneNumber; + _accountNumber = accountNumber; + }); + } + + void _handleLogout() { + setState(() { + _isAuthenticated = false; + _userRole = ''; + _phoneNumber = ''; + _accountNumber = ''; + }); + } +} + +/// ============================================================================ +/// LOGIN SCREEN - Phone Number + Account Number Authentication +/// ============================================================================ +class LoginScreen extends StatefulWidget { + final Function(String, String, String) onLoginSuccess; + + const LoginScreen({ + Key? key, + required this.onLoginSuccess, + }) : super(key: key); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + final _phoneController = TextEditingController(); + final _accountController = TextEditingController(); + final _formKey = GlobalKey(); + bool _isLoading = false; + + @override + void dispose() { + _phoneController.dispose(); + _accountController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('FundGuard')), + body: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 48), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Secure Login', + style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)), + ), + const SizedBox(height: 12), + const Text( + 'Enter your credentials to continue', + style: TextStyle(fontSize: 14, color: Color(0xFFB0B0B0)), + ), + const SizedBox(height: 48), + TextFormField( + controller: _phoneController, + keyboardType: TextInputType.phone, + maxLength: 10, + decoration: const InputDecoration( + labelText: 'Phone Number', + hintText: 'Enter 10-digit phone number', + prefixIcon: Icon(Icons.phone), + ), + validator: (value) { + if (value == null || value.isEmpty) return 'Phone number is required'; + if (value.length != 10) return 'Phone number must be 10 digits'; + if (!RegExp(r'^[0-9]+$').hasMatch(value)) return 'Phone number must contain only digits'; + return null; + }, + ), + const SizedBox(height: 24), + TextFormField( + controller: _accountController, + keyboardType: TextInputType.number, + maxLength: 9, + decoration: const InputDecoration( + labelText: 'Account Number', + hintText: 'Enter 9-digit account number', + prefixIcon: Icon(Icons.account_balance), + ), + validator: (value) { + if (value == null || value.isEmpty) return 'Account number is required'; + if (value.length != 9) return 'Account number must be 9 digits'; + if (!RegExp(r'^[0-9]+$').hasMatch(value)) return 'Account number must contain only digits'; + return null; + }, + ), + const SizedBox(height: 48), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _performLogin, + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : const Text('Login'), + ), + ), + const SizedBox(height: 24), + Card( + color: const Color(0xFF151D2A), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + 'Test Credentials:', + style: TextStyle(color: Color(0xFF6200EE), fontWeight: FontWeight.w600, fontSize: 12), + ), + SizedBox(height: 8), + Text( + 'Phone: 5555555555\nA/C: 555555555', + style: TextStyle(color: Color(0xFFB0B0B0), fontSize: 12, fontFamily: 'monospace'), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } + + void _performLogin() async { + if (!_formKey.currentState!.validate()) return; + + setState(() => _isLoading = true); + await Future.delayed(const Duration(milliseconds: 800)); + + final phone = _phoneController.text.trim(); + final account = _accountController.text.trim(); + + if (phone == '5555555555' && account == '555555555') { + widget.onLoginSuccess(phone, account, 'Admin'); + setState(() => _isLoading = false); + return; + } + + setState(() => _isLoading = false); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Invalid Credentials for testing'), + backgroundColor: Color(0xFFCF6679), + duration: Duration(seconds: 2), + ), + ); + } +} + +/// ============================================================================ +/// DASHBOARD SCREEN - Main Interface with Profile Popup Menu +/// ============================================================================ +class DashboardScreen extends StatefulWidget { + final String userRole; + final String phoneNumber; + final String accountNumber; + final VoidCallback onLogout; + + const DashboardScreen({ + Key? key, + required this.userRole, + required this.phoneNumber, + required this.accountNumber, + required this.onLogout, + }) : super(key: key); + + @override + State createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('FundGuard Dashboard'), + elevation: 0, + actions: [ + // ============================================================================ + // PROFILE POPUP MENU BUTTON - Circular Avatar Trigger + // ============================================================================ + Padding( + padding: const EdgeInsets.only(right: 16), + child: ProfilePopupMenu( + phoneNumber: widget.phoneNumber, + accountNumber: widget.accountNumber, + onLogout: _handleLogout, + ), + ), + ], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Welcome to FundGuard', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)), + ), + const SizedBox(height: 12), + const Text( + 'Your secure financial fraud detection platform', + style: TextStyle(fontSize: 14, color: Color(0xFFB0B0B0)), + ), + const SizedBox(height: 32), + Card( + color: const Color(0xFF151D2A), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'System Status', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildStatusMetric('Active', '24/7', Colors.green), + _buildStatusMetric('Alerts', '3', Colors.orange), + _buildStatusMetric('Protected', '1.2M', Colors.blue), + ], + ), + ], + ), + ), + ), + const SizedBox(height: 24), + Card( + color: const Color(0xFF151D2A), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Recent Activity', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + const SizedBox(height: 16), + _buildActivityItem('Fraud Detection Model Updated', '2 hours ago'), + _buildActivityItem('Risk Score Recalculated', '4 hours ago'), + _buildActivityItem('New Compliance Report Generated', '1 day ago'), + ], + ), + ), + ), + ], + ), + ), + ); + } + + void _handleLogout() { + widget.onLogout(); + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (BuildContext context) => LoginScreen( + onLoginSuccess: (phone, account, role) { + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => DashboardScreen( + userRole: role, + phoneNumber: phone, + accountNumber: account, + onLogout: widget.onLogout, + ), + ), + ); + }, + ), + ), + (Route route) => false, + ); + } + + Widget _buildStatusMetric(String label, String value, Color color) { + return Column( + children: [ + Text(value, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: color)), + const SizedBox(height: 4), + Text(label, style: const TextStyle(fontSize: 12, color: Color(0xFFB0B0B0))), + ], + ); + } + + Widget _buildActivityItem(String title, String timestamp) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text(title, style: const TextStyle(fontSize: 14, color: Color(0xFFE0E0E0))), + ), + Text(timestamp, style: const TextStyle(fontSize: 12, color: Color(0xFF808080))), + ], + ), + ); + } +} + +/// ============================================================================ +/// PROFILE POPUP MENU WIDGET - Custom PopupMenuButton with Enhanced Styling +/// ============================================================================ +class ProfilePopupMenu extends StatefulWidget { + final String phoneNumber; + final String accountNumber; + final VoidCallback onLogout; + + const ProfilePopupMenu({ + Key? key, + required this.phoneNumber, + required this.accountNumber, + required this.onLogout, + }) : super(key: key); + + @override + State createState() => _ProfilePopupMenuState(); +} + +class _ProfilePopupMenuState extends State { + @override + Widget build(BuildContext context) { + return PopupMenuButton( + offset: const Offset(0, 50), // Position menu slightly below avatar + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: const BorderSide(color: Color(0xFF263345), width: 1.5), + ), + color: const Color(0xFF151D2A), // Deep dark blue/grey background + elevation: 8, + itemBuilder: (BuildContext context) => [ + // ========== ITEM 1: Account Session Metadata Header (Non-clickable) ========== + PopupMenuItem( + enabled: false, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Account Session', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Color(0xFF999999), + letterSpacing: 0.5, + ), + ), + const SizedBox(height: 8), + Text( + 'Phone: ${widget.phoneNumber}', + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Color(0xFFE0E0E0), + ), + ), + const SizedBox(height: 4), + Text( + 'A/C: ${widget.accountNumber}', + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w400, + color: Color(0xFFB0B0B0), + ), + ), + ], + ), + ), + // Divider + const PopupMenuDivider(height: 12), + + // ========== ITEM 2: Manage Profile Button ========== + PopupMenuItem( + value: 'manage', + onTap: () { + // Dismiss popup and navigate to ProfileRoutePage + Future.delayed(const Duration(milliseconds: 100), () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => const ProfileRoutePage()), + ); + }); + }, + child: Row( + children: const [ + Icon(Icons.manage_accounts, color: Color(0xFF6200EE), size: 20), + SizedBox(width: 12), + Text( + 'Manage Profile', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Color(0xFFE0E0E0), + ), + ), + ], + ), + ), + + // ========== ITEM 3: Logout Action (Destructive) ========== + PopupMenuItem( + value: 'logout', + onTap: () { + // Dismiss popup and perform logout + Future.delayed(const Duration(milliseconds: 100), () { + _showLogoutConfirmation(context); + }); + }, + child: Row( + children: const [ + Icon(Icons.logout, color: Colors.redAccent, size: 20), + SizedBox(width: 12), + Text( + 'Logout', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.redAccent, + ), + ), + ], + ), + ), + ], + child: Center( + child: CircleAvatar( + radius: 22, + backgroundColor: const Color(0xFF6200EE), + child: const Text( + 'R', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ); + } + + void _showLogoutConfirmation(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirm Logout'), + content: const Text('Are you sure you want to logout?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + widget.onLogout(); + }, + child: const Text('Logout', style: TextStyle(color: Colors.redAccent)), + ), + ], + ); + }, + ); + } +} + +/// ============================================================================ +/// PROFILE ROUTE PAGE - Profile Management Screen +/// ============================================================================ +class ProfileRoutePage extends StatelessWidget { + const ProfileRoutePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Manage Profile'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Profile Settings', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)), + ), + const SizedBox(height: 32), + Card( + color: const Color(0xFF151D2A), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Account Information', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + const SizedBox(height: 20), + _buildSettingRow('Email', 'user@fundguard.com'), + const Divider(height: 24, color: Color(0xFF333333)), + _buildSettingRow('Notifications', 'Enabled'), + const Divider(height: 24, color: Color(0xFF333333)), + _buildSettingRow('Two-Factor Auth', 'Active'), + ], + ), + ), + ), + const SizedBox(height: 32), + Card( + color: const Color(0xFF151D2A), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Security Settings', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6200EE), + padding: const EdgeInsets.symmetric(vertical: 14), + ), + child: const Text('Change Password'), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildSettingRow(String label, String value) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, style: const TextStyle(fontSize: 14, color: Color(0xFFB0B0B0))), + Text(value, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFFE0E0E0))), + ], + ); + } +} diff --git a/main.dart b/main.dart new file mode 100644 index 0000000..96ab9e9 --- /dev/null +++ b/main.dart @@ -0,0 +1,835 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const FundGuardPremiumApp()); +} + +/// Main Application - Premium Dark Mode Dashboard with Profile Popup +class FundGuardPremiumApp extends StatefulWidget { + const FundGuardPremiumApp({Key? key}) : super(key: key); + + @override + State createState() => _FundGuardPremiumAppState(); +} + +class _FundGuardPremiumAppState extends State { + bool _isAuthenticated = false; + String _userRole = ''; + String _phoneNumber = ''; + String _accountNumber = ''; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'FundGuard Premium', + debugShowCheckedModeBanner: false, + themeMode: ThemeMode.dark, + darkTheme: _buildPremiumDarkTheme(), + home: _isAuthenticated + ? DashboardScreen( + userRole: _userRole, + phoneNumber: _phoneNumber, + accountNumber: _accountNumber, + onLogout: _handleLogout, + ) + : LoginScreen( + onLoginSuccess: _handleLoginSuccess, + ), + ); + } + + /// Premium Dark Theme with Glassmorphic Elements + ThemeData _buildPremiumDarkTheme() { + return ThemeData( + useMaterial3: true, + brightness: Brightness.dark, + scaffoldBackgroundColor: const Color(0xFF0D131C), + primaryColor: const Color(0xFF6366F1), + colorScheme: const ColorScheme.dark( + primary: Color(0xFF6366F1), + secondary: Color(0xFF38BDF8), + surface: Color(0xFF0D131C), + background: Color(0xFF0D131C), + error: Color(0xFFF87171), + onBackground: Color(0xFFE5E7EB), + ), + appBarTheme: const AppBarTheme( + backgroundColor: Color(0xFF111820), + elevation: 0, + centerTitle: true, + titleTextStyle: TextStyle( + color: Color(0xFFE5E7EB), + fontSize: 18, + fontWeight: FontWeight.w600, + letterSpacing: 0.3, + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6366F1), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + textStyle: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + letterSpacing: 0.2, + ), + elevation: 4, + ), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: const Color(0xFF111820), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: Color(0xFF1E293B), width: 1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: Color(0xFF1E293B), width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: Color(0xFF6366F1), width: 1.5), + ), + labelStyle: const TextStyle(color: Color(0xFF9CA3AF), fontSize: 14), + hintStyle: const TextStyle(color: Color(0xFF6B7280), fontSize: 14), + ), + textTheme: const TextTheme( + displayLarge: TextStyle( + color: Color(0xFFE5E7EB), + fontSize: 32, + fontWeight: FontWeight.w700, + letterSpacing: -0.5, + ), + headlineSmall: TextStyle( + color: Color(0xFFE5E7EB), + fontSize: 20, + fontWeight: FontWeight.w600, + letterSpacing: 0.1, + ), + bodyLarge: TextStyle( + color: Color(0xFFE5E7EB), + fontSize: 15, + fontWeight: FontWeight.w400, + ), + bodyMedium: TextStyle( + color: Color(0xFF9CA3AF), + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + ); + } + + void _handleLoginSuccess(String phoneNumber, String accountNumber, String userRole) { + setState(() { + _isAuthenticated = true; + _userRole = userRole; + _phoneNumber = phoneNumber; + _accountNumber = accountNumber; + }); + } + + void _handleLogout() { + setState(() { + _isAuthenticated = false; + _userRole = ''; + _phoneNumber = ''; + _accountNumber = ''; + }); + } +} + +/// ============================================================================ +/// LOGIN SCREEN - Premium Authentication Interface +/// ============================================================================ +class LoginScreen extends StatefulWidget { + final Function(String, String, String) onLoginSuccess; + + const LoginScreen({Key? key, required this.onLoginSuccess}) : super(key: key); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + final _phoneController = TextEditingController(); + final _accountController = TextEditingController(); + final _formKey = GlobalKey(); + bool _isLoading = false; + + @override + void dispose() { + _phoneController.dispose(); + _accountController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('FundGuard')), + body: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 48), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Welcome Back', + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.w700, + color: Color(0xFFE5E7EB), + letterSpacing: -0.5, + ), + ), + const SizedBox(height: 12), + const Text( + 'Sign in to your secure account', + style: TextStyle(fontSize: 15, color: Color(0xFF9CA3AF), letterSpacing: 0.2), + ), + const SizedBox(height: 48), + TextFormField( + controller: _phoneController, + keyboardType: TextInputType.phone, + maxLength: 10, + decoration: const InputDecoration( + labelText: 'Phone Number', + hintText: 'Enter 10-digit phone number', + prefixIcon: Icon(Icons.phone_outlined, size: 20), + ), + validator: (value) { + if (value == null || value.isEmpty) return 'Phone number is required'; + if (value.length != 10) return 'Phone number must be 10 digits'; + if (!RegExp(r'^[0-9]+$').hasMatch(value)) return 'Phone number must contain only digits'; + return null; + }, + ), + const SizedBox(height: 24), + TextFormField( + controller: _accountController, + keyboardType: TextInputType.number, + maxLength: 9, + decoration: const InputDecoration( + labelText: 'Account Number', + hintText: 'Enter 9-digit account number', + prefixIcon: Icon(Icons.account_balance_outlined, size: 20), + ), + validator: (value) { + if (value == null || value.isEmpty) return 'Account number is required'; + if (value.length != 9) return 'Account number must be 9 digits'; + if (!RegExp(r'^[0-9]+$').hasMatch(value)) return 'Account number must contain only digits'; + return null; + }, + ), + const SizedBox(height: 48), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _performLogin, + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator(strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white)), + ) + : const Text('Sign In'), + ), + ), + const SizedBox(height: 28), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFF111820), + border: Border.all(color: const Color(0xFF1E293B), width: 1), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + 'Demo Credentials:', + style: TextStyle(color: Color(0xFF6366F1), fontWeight: FontWeight.w600, fontSize: 12, letterSpacing: 0.5), + ), + SizedBox(height: 8), + Text( + 'Phone: 5555555555\nAccount: 555555555', + style: TextStyle(color: Color(0xFF9CA3AF), fontSize: 13, fontFamily: 'monospace'), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + void _performLogin() async { + if (!_formKey.currentState!.validate()) return; + + setState(() => _isLoading = true); + await Future.delayed(const Duration(milliseconds: 1000)); + + final phone = _phoneController.text.trim(); + final account = _accountController.text.trim(); + + if (phone == '5555555555' && account == '555555555') { + widget.onLoginSuccess(phone, account, 'Admin'); + setState(() => _isLoading = false); + return; + } + + setState(() => _isLoading = false); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Invalid Credentials for testing'), + backgroundColor: Color(0xFFF87171), + duration: Duration(seconds: 2), + ), + ); + } +} + +/// ============================================================================ +/// DASHBOARD SCREEN - Premium Dark Mode with Profile Popup +/// ============================================================================ +class DashboardScreen extends StatefulWidget { + final String userRole; + final String phoneNumber; + final String accountNumber; + final VoidCallback onLogout; + + const DashboardScreen({ + Key? key, + required this.userRole, + required this.phoneNumber, + required this.accountNumber, + required this.onLogout, + }) : super(key: key); + + @override + State createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Dashboard'), + elevation: 0, + actions: [ + Padding( + padding: const EdgeInsets.only(right: 16), + child: PremiumProfilePopupMenu( + phoneNumber: widget.phoneNumber, + accountNumber: widget.accountNumber, + onLogout: _handleLogout, + ), + ), + ], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Dashboard', + style: TextStyle(fontSize: 28, fontWeight: FontWeight.w700, color: Color(0xFFE5E7EB), letterSpacing: -0.3), + ), + const SizedBox(height: 8), + const Text( + 'Secure fraud detection and prevention platform', + style: TextStyle(fontSize: 14, color: Color(0xFF9CA3AF), letterSpacing: 0.1), + ), + const SizedBox(height: 32), + _buildMetricsCard(), + const SizedBox(height: 24), + _buildActivityCard(), + ], + ), + ), + ); + } + + void _handleLogout() { + widget.onLogout(); + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (BuildContext context) => LoginScreen( + onLoginSuccess: (phone, account, role) { + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => DashboardScreen( + userRole: role, + phoneNumber: phone, + accountNumber: account, + onLogout: widget.onLogout, + ), + ), + ); + }, + ), + ), + (Route route) => false, + ); + } + + Widget _buildMetricsCard() { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: const Color(0xFF111820), + border: Border.all(color: const Color(0xFF1E293B), width: 1), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow(color: Colors.black54, blurRadius: 12, offset: const Offset(0, 4)), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'System Health', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFFE5E7EB)), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildMetric('Active', '24/7', Colors.emerald), + _buildMetric('Alerts', '3', Colors.amber), + _buildMetric('Protected', '1.2M', const Color(0xFF38BDF8)), + ], + ), + ], + ), + ); + } + + Widget _buildMetric(String label, String value, Color color) { + return Column( + children: [ + Text(value, style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: color)), + const SizedBox(height: 6), + Text(label, style: const TextStyle(fontSize: 12, color: Color(0xFF6B7280), letterSpacing: 0.3)), + ], + ); + } + + Widget _buildActivityCard() { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: const Color(0xFF111820), + border: Border.all(color: const Color(0xFF1E293B), width: 1), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow(color: Colors.black54, blurRadius: 12, offset: const Offset(0, 4)), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Recent Activity', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFFE5E7EB)), + ), + const SizedBox(height: 16), + _buildActivityItem('Fraud Model Updated', '2 hours ago'), + _buildActivityItem('Risk Score Recalculated', '4 hours ago'), + _buildActivityItem('Compliance Report Generated', '1 day ago'), + ], + ), + ); + } + + Widget _buildActivityItem(String title, String time) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title, style: const TextStyle(fontSize: 14, color: Color(0xFFE5E7EB))), + Text(time, style: const TextStyle(fontSize: 12, color: Color(0xFF6B7280))), + ], + ), + ); + } +} + +/// ============================================================================ +/// PREMIUM PROFILE POPUP MENU - Glassmorphic Design with Modern Aesthetics +/// ============================================================================ +class PremiumProfilePopupMenu extends StatefulWidget { + final String phoneNumber; + final String accountNumber; + final VoidCallback onLogout; + + const PremiumProfilePopupMenu({ + Key? key, + required this.phoneNumber, + required this.accountNumber, + required this.onLogout, + }) : super(key: key); + + @override + State createState() => _PremiumProfilePopupMenuState(); +} + +class _PremiumProfilePopupMenuState extends State { + @override + Widget build(BuildContext context) { + return PopupMenuButton( + offset: const Offset(0, 60), // Precise offset for perfect alignment + constraints: const BoxConstraints(minWidth: 280, maxWidth: 320), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide(color: Color(0xFF1E293B), width: 1.2), + ), + color: const Color(0xFF0D131C), // Ultra-deep slate-blue + elevation: 0, + shadowColor: Colors.black54, + itemBuilder: (BuildContext context) => [ + // ========== BLOCK 1: Premium Session Header (Non-clickable) ========== + PopupMenuItem( + enabled: false, + padding: EdgeInsets.zero, + child: Container( + decoration: BoxDecoration( + color: const Color(0xFF151F2E), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Session Status Badge + Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.emerald, + boxShadow: [ + BoxShadow( + color: Colors.emerald.withOpacity(0.5), + blurRadius: 4, + ), + ], + ), + ), + const SizedBox(width: 8), + const Text( + 'SYSTEM SESSION ACTIVE', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: Colors.emerald, + letterSpacing: 1.5, + ), + ), + ], + ), + const SizedBox(height: 14), + // Account Details + Text( + 'Phone: ${widget.phoneNumber}', + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: Color(0xFFE5E7EB), + letterSpacing: 0.2, + ), + ), + const SizedBox(height: 6), + Text( + 'A/C: ${widget.accountNumber}', + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w400, + color: Color(0xFFA1A5B0), + letterSpacing: 0.1, + ), + ), + ], + ), + ), + ), + // Custom Divider + PopupMenuItem( + enabled: false, + padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0), + child: Container( + height: 1, + color: const Color(0xFF1E293B).withOpacity(0.4), + ), + ), + + // ========== BLOCK 2: Manage Profile Action Link ========== + PopupMenuItem( + value: 'manage', + padding: EdgeInsets.zero, + onTap: () { + Future.delayed(const Duration(milliseconds: 50), () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => const ProfileRoutePage()), + ); + }); + }, + child: InkWell( + splashColor: const Color(0xFF38BDF8).withOpacity(0.1), + highlightColor: const Color(0xFF38BDF8).withOpacity(0.05), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + child: Row( + children: [ + const Icon(Icons.tune_rounded, color: Color(0xFF38BDF8), size: 20), + const SizedBox(width: 12), + const Expanded( + child: Text( + 'Manage Profile', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Color(0xFFE5E7EB), + letterSpacing: 0.2, + ), + ), + ), + Icon(Icons.chevron_right_rounded, color: Colors.white.withOpacity(0.3), size: 18), + ], + ), + ), + ), + ), + + // ========== BLOCK 3: High-Visibility Logout Action ========== + PopupMenuItem( + value: 'logout', + padding: EdgeInsets.zero, + onTap: () { + Future.delayed(const Duration(milliseconds: 50), () { + _showLogoutConfirmation(context); + }); + }, + child: InkWell( + splashColor: const Color(0xFFF87171).withOpacity(0.1), + highlightColor: const Color(0xFFF87171).withOpacity(0.05), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + child: Row( + children: [ + const Icon(Icons.power_settings_new_rounded, color: Color(0xFFF87171), size: 20), + const SizedBox(width: 12), + const Expanded( + child: Text( + 'Sign Out', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Color(0xFFF87171), + letterSpacing: 0.2, + ), + ), + ), + ], + ), + ), + ), + ), + ], + child: Center( + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black54, + blurRadius: 15, + offset: const Offset(0, 4), + ), + ], + ), + child: CircleAvatar( + radius: 22, + // Smooth linear gradient: Deep Purple to Neon Indigo + backgroundColor: Colors.transparent, + backgroundImage: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF7C3AED), // Deep Purple + Color(0xFF6366F1), // Neon Indigo + ], + ).createShader(const Rect.fromLTWH(0, 0, 44, 44)) as ImageProvider, + child: const Text( + 'R', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w700, + letterSpacing: 0.2, + ), + ), + ), + ), + ), + ); + } + + void _showLogoutConfirmation(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: const Color(0xFF111820), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide(color: Color(0xFF1E293B), width: 1), + ), + title: const Text( + 'Confirm Sign Out', + style: TextStyle( + color: Color(0xFFE5E7EB), + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + content: const Text( + 'Are you sure you want to sign out of your account?', + style: TextStyle( + color: Color(0xFF9CA3AF), + fontSize: 14, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text( + 'Cancel', + style: TextStyle(color: Color(0xFF6B7280)), + ), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + widget.onLogout(); + }, + child: const Text( + 'Sign Out', + style: TextStyle(color: Color(0xFFF87171), fontWeight: FontWeight.w600), + ), + ), + ], + ); + }, + ); + } +} + +/// ============================================================================ +/// PROFILE ROUTE PAGE - Premium Profile Management +/// ============================================================================ +class ProfileRoutePage extends StatelessWidget { + const ProfileRoutePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Profile Settings'), + leading: IconButton( + icon: const Icon(Icons.arrow_back_rounded), + onPressed: () => Navigator.pop(context), + ), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Manage Your Account', + style: TextStyle(fontSize: 26, fontWeight: FontWeight.w700, color: Color(0xFFE5E7EB), letterSpacing: -0.3), + ), + const SizedBox(height: 8), + const Text( + 'Update your profile information and security settings', + style: TextStyle(fontSize: 14, color: Color(0xFF9CA3AF)), + ), + const SizedBox(height: 32), + _buildSettingsCard( + 'Account Information', + [ + _buildSettingRow('Email', 'user@fundguard.com'), + const SizedBox(height: 16), + _buildSettingRow('Status', 'Active'), + const SizedBox(height: 16), + _buildSettingRow('Member Since', '2026'), + ], + ), + const SizedBox(height: 24), + _buildSettingsCard( + 'Security', + [ + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.lock_outline_rounded, size: 18), + label: const Text('Change Password'), + ), + ), + ], + ), + ], + ), + ), + ); + } + + static Widget _buildSettingsCard(String title, List children) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: const Color(0xFF111820), + border: Border.all(color: const Color(0xFF1E293B), width: 1), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFFE5E7EB)), + ), + const SizedBox(height: 16), + ...children, + ], + ), + ); + } + + static Widget _buildSettingRow(String label, String value) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, style: const TextStyle(fontSize: 14, color: Color(0xFF9CA3AF))), + Text(value, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFFE5E7EB))), + ], + ); + } +} diff --git a/services/dashboard/src/App.tsx b/services/dashboard/src/App.tsx index f9b588c..7e702bd 100644 --- a/services/dashboard/src/App.tsx +++ b/services/dashboard/src/App.tsx @@ -1,4 +1,5 @@ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; +import { AuthProvider } from "./context/AuthContext"; import MainLayout from "./layout/MainLayout"; import Dashboard from "./pages/Dashboard"; import GraphView from "./pages/GraphView"; @@ -7,21 +8,26 @@ import Alerts from "./pages/Alerts"; import Reports from "./pages/Reports"; import Profile from "./pages/Profile"; import Documentation from "./pages/Documentation"; +import Authentication from "./pages/Authentication"; + function App() { return ( - - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - + + + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); } diff --git a/services/dashboard/src/context/AuthContext.tsx b/services/dashboard/src/context/AuthContext.tsx new file mode 100644 index 0000000..aea0365 --- /dev/null +++ b/services/dashboard/src/context/AuthContext.tsx @@ -0,0 +1,64 @@ +import React, { createContext, useContext, useState, useCallback, useEffect } from "react"; + +type AuthContextType = { + isLoggedIn: boolean; + userEmail: string | null; + login: (email: string) => void; + logout: () => void; +}; + +const AuthContext = createContext(null); + +export function useAuth() { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within AuthProvider"); + } + return context; +} + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [isLoggedIn, setIsLoggedIn] = useState(() => { + try { + const stored = localStorage.getItem("isLoggedIn"); + return stored === "true"; + } catch { + return false; + } + }); + const [userEmail, setUserEmail] = useState(() => { + try { + return localStorage.getItem("userEmail"); + } catch { + return null; + } + }); + + const login = useCallback((email: string) => { + setIsLoggedIn(true); + setUserEmail(email); + try { + localStorage.setItem("isLoggedIn", "true"); + localStorage.setItem("userEmail", email); + } catch { + // LocalStorage not available + } + }, []); + + const logout = useCallback(() => { + setIsLoggedIn(false); + setUserEmail(null); + try { + localStorage.removeItem("isLoggedIn"); + localStorage.removeItem("userEmail"); + } catch { + // LocalStorage not available + } + }, []); + + return ( + + {children} + + ); +} diff --git a/services/dashboard/src/layout/MainLayout.tsx b/services/dashboard/src/layout/MainLayout.tsx index c1762e4..6dfd102 100644 --- a/services/dashboard/src/layout/MainLayout.tsx +++ b/services/dashboard/src/layout/MainLayout.tsx @@ -1,6 +1,7 @@ import { createContext, useContext, useEffect, useState } from "react"; import { Outlet, NavLink, Link, useNavigate } from "react-router-dom"; import { motion } from "framer-motion"; +import { useAuth } from "../context/AuthContext"; import { Activity, ShieldAlert, @@ -14,6 +15,10 @@ import { ShieldCheck, Moon, Sun, + LogOut, + KeyRound, + Settings, + ChevronRight, } from "lucide-react"; type DashboardAnchorContextType = { @@ -51,6 +56,7 @@ export default function MainLayout() { const [dashboardAnchor, setDashboardAnchor] = useState(null); const [pendingDashboardScroll, setPendingDashboardScroll] = useState(false); const navigate = useNavigate(); + const { isLoggedIn, logout } = useAuth(); useEffect(() => { if (pendingDashboardScroll && dashboardAnchor) { @@ -66,6 +72,16 @@ export default function MainLayout() { }; }, [isSidebarOpen]); + // Keep a `dark` class on the document element so Tailwind `dark:` variants work + useEffect(() => { + try { + if (isDarkMode) document.documentElement.classList.add("dark"); + else document.documentElement.classList.remove("dark"); + } catch (e) { + // ignore in non-browser test environments + } + }, [isDarkMode]); + const requestDashboardScroll = () => { setPendingDashboardScroll(true); navigate("/dashboard"); @@ -92,6 +108,17 @@ export default function MainLayout() { ? "bg-emerald-500 text-slate-950 hover:bg-emerald-400" : "bg-emerald-700 text-white hover:bg-emerald-600"; + // Redirect unauthenticated users to auth page + useEffect(() => { + if (!isLoggedIn) { + const currentPath = window.location.pathname; + if (currentPath !== "/auth" && !currentPath.startsWith("/auth")) { + // Optional: Redirect to auth if not on auth page + // navigate("/auth"); + } + } + }, [isLoggedIn]); + return ( Features - setIsDarkMode((current) => !current)} whileTap={{ scale: 0.95 }} @@ -141,36 +168,87 @@ export default function MainLayout() { aria-label="Toggle dark mode" > {isDarkMode ? : } - -
- - {isProfileOpen && ( -
-
-
-

USER ACCOUNT

-

Yjsjs

-

FG-98765-IN

+ */} + + {/* Auth Section */} + {!isLoggedIn ? ( +
+ + +
+ ) : ( +
+ + {isProfileOpen && ( + + {/* ========== BLOCK 1: Premium Account Session Header ========== */} +
+
+ + Admin Account +
+
+

Phone: 5555555555

+

A/C No: 555555555

+
+ + {/* ========== BLOCK 2: Manage Profile Button ========== */} -
-
- )} -
+ + {/* Divider */} +
+ + {/* ========== BLOCK 3: Logout Action ========== */} + + + )} +
+ )}
@@ -204,6 +282,16 @@ export default function MainLayout() {