diff --git a/frontend/app/(protected)/dashboard/page.tsx b/frontend/app/(protected)/dashboard/page.tsx new file mode 100644 index 0000000..c06f86c --- /dev/null +++ b/frontend/app/(protected)/dashboard/page.tsx @@ -0,0 +1,286 @@ +'use client'; + +import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import Link from 'next/router'; +import { + FileText, ShieldAlert, Clock, ShieldCheck, + ArrowRight, UploadCloud, ArrowUpDown +} from 'lucide-react'; +import { + ResponsiveContainer, BarChart, Bar, XAxis, + YAxis, Tooltip, Cell +} from 'recharts'; + +interface Document { + id: string; + name: string; + status: 'PENDING' | 'ANALYZING' | 'VERIFIED' | 'FLAGGED' | 'REJECTED'; + riskScore: number; + createdAt: string; + reviewed?: boolean; +} + +export default function DashboardPage() { + const [documents, setDocuments] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [sortAsc, setSortAsc] = useState(false); + + // 1. Core Core REST Hydration Matrix + const fetchDashboardData = useCallback(async () => { + try { + const response = await fetch('/api/documents'); + if (response.ok) { + const data = await response.json(); + setDocuments(data); + } + } catch (error) { + console.error('Failed to resolve portfolio collection registries:', error); + } finally { + setIsLoading(false); + } + }, []); + + // 2. Real-Time WebSockets Sync Loop Integration (FE-06 Compliance) + useEffect(() => { + fetchDashboardData(); + + // Establish WebSocket stream pipeline link handles + const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const socket = new WebSocket(`${wsProtocol}//${window.location.host}/api/ws`); + + socket.onmessage = (event) => { + try { + const payload = JSON.parse(event.data); + // Intercept real-time update markers to trigger instant atomic UI synchronization + if (payload.type === 'documentStatus') { + fetchDashboardData(); + } + } catch (err) { + console.error('Failed to parse websocket synchronization frames:', err); + } + }; + + return () => socket.close(); + }, [fetchDashboardData]); + + // 3. Client-Side Data Aggregate Computations + const stats = useMemo(() => { + const total = documents.length; + if (total === 0) return { total: 0, avgRisk: 0, pending: 0, verified: 0 }; + + const sumRisk = documents.reduce((acc, doc) => acc + doc.riskScore, 0); + const pending = documents.filter(d => d.status === 'PENDING' || d.status === 'ANALYZING').length; + const verified = documents.filter(d => d.status === 'VERIFIED').length; + + return { + total, + avgRisk: Math.round(sumRisk / total), + pending, + verified + }; + }, [documents]); + + // 4. Transform Metrics for Chart.js / Recharts Engine Mapping + const chartData = useMemo(() => { + const counts = { PENDING: 0, VERIFIED: 0, FLAGGED: 0, REJECTED: 0 }; + documents.forEach(doc => { + const status = doc.status === 'ANALYZING' ? 'PENDING' : doc.status; + if (status in counts) counts[status as keyof typeof counts]++; + }); + + return [ + { name: 'Pending', count: counts.PENDING, color: '#f59e0b' }, + { name: 'Verified', count: counts.VERIFIED, color: '#10b981' }, + { name: 'Flagged', count: counts.FLAGGED, color: '#ef4444' }, + { name: 'Rejected', count: counts.REJECTED, color: '#64748b' }, + ]; + }, [documents]); + + // 5. Filter Actions & Sorting Realignment Operations + const flaggedActionItems = useMemo(() => { + return documents.filter(d => d.status === 'FLAGGED' && !d.reviewed); + }, [documents]); + + const recentDocuments = useMemo(() => { + return [...documents] + .sort((a, b) => { + const timeA = new Date(a.createdAt).getTime(); + const timeB = new Date(b.createdAt).getTime(); + return sortAsc ? timeA - timeB : timeB - timeA; + }) + .slice(0, 5); + }, [documents, sortAsc]); + + const getRiskColor = (score: number) => { + if (score <= 30) return 'text-emerald-500'; + if (score <= 60) return 'text-amber-500'; + return 'text-rose-500'; + }; + + if (isLoading) { + return ( +
+ Loading system dashboard... +
+ ); + } + + // EMPTY STATE FALLBACK PATH + if (documents.length === 0) { + return ( +
+
+ +
+

No documents tracking logs found

+

+ Upload your first verification vector target file to deploy active AI scoring metrics and lock secure ledger assets. +

+ +
+ ); + } + + return ( +
+ + {/* SECTION 1: STATS CARD ROW */} +
+
+
+ Total Document Base + {stats.total} +
+ +
+
+
+ Avg Portfolio Risk + + {stats.avgRisk}% + +
+ +
+
+
+ Pending Anchors + {stats.pending} +
+ +
+
+
+ Stellar Verified + {stats.verified} +
+ +
+
+ + {/* SECTION 2: CHARTS AND ACTIVE INCIDENT THREAT ACTION TRACKS */} +
+ + {/* Horizontal Distribution Engine */} +
+

Portfolio Risk Distribution

+
+ + + + + + + {chartData.map((entry, index) => ( + + ))} + + + +
+
+ + {/* Action Items List Card */} +
+
+

+ Attention Required +

+ {flaggedActionItems.length === 0 ? ( +

Zero unreviewed compliance threats reported.

+ ) : ( +
    + {flaggedActionItems.map(item => ( +
  • + {item.name} + + Risk: {item.riskScore}% + +
  • + ))} +
+ )} +
+
+
+ + {/* SECTION 3: RECENT ACTIVITY DOCUMENT LEDGER REPOSITORY */} +
+
+

Recent Documents Repository

+ +
+ +
+ + + + + + + + + + + + {recentDocuments.map(doc => ( + + + + + + + + ))} + +
Document NameLifecycle StatusThreat Vector Score setSortAsc(!sortAsc)}> +
+ Ingestion Date +
+
Actions
{doc.name} + + {doc.status} + + + {doc.riskScore}% + + {new Date(doc.createdAt).toLocaleDateString(undefined, { dateStyle: 'medium' })} + + + Inspect Detail + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/app/(protected)/documents/page.tsx b/frontend/app/(protected)/documents/page.tsx new file mode 100644 index 0000000..11db364 --- /dev/null +++ b/frontend/app/(protected)/documents/page.tsx @@ -0,0 +1,337 @@ +'use client'; + +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { + Search, Filter, SlidersHorizontal, ChevronLeft, ChevronRight, + ChevronsLeft, ChevronsRight, Eye, CheckCircle, Archive, Download, Trash2 +} from 'lucide-react'; + +interface DocumentItem { + id: string; + name: string; + status: 'PENDING' | 'ANALYZING' | 'VERIFIED' | 'FLAGGED' | 'REJECTED'; + riskScore: number; + createdAt: string; +} + +export default function DocumentsListPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + + // 1. Synchronized URL Search Param States + const queryParam = searchParams?.get('q') || ''; + const statusParam = searchParams?.get('status') || 'ALL'; + const sortParam = searchParams?.get('sort') || 'newest'; + const pageParam = parseInt(searchParams?.get('page') || '1', 10); + const riskParam = parseInt(searchParams?.get('maxRisk') || '100', 10); + + // Local state mirrors for smooth input interaction + const [searchInput, setSearchInput] = useState(queryParam); + const [riskSlider, setRiskSlider] = useState(riskParam); + const [documents, setDocuments] = useState([]); + const [totalDocs, setTotalDocs] = useState(0); + const [isLoading, setIsLoading] = useState(true); + const [selectedIds, setSelectedIds] = useState([]); + + const limit = 10; + const totalPages = Math.ceil(totalDocs / limit) || 1; + + // 2. Centralized State Parameter Ingestion Engine + const updateQueryParams = useCallback((updates: Record) => { + const params = new URLSearchParams(searchParams?.toString() || ''); + Object.entries(updates).forEach(([key, value]) => { + if (value === 'ALL' || value === '' || (key === 'page' && value === 1)) { + params.delete(key); + } else { + params.set(key, String(value)); + } + }); + router.push(`?${params.toString()}`); + }, [searchParams, router]); + + // 3. 300ms Search Input Debounce Effect Hook + useEffect(() => { + const timer = setTimeout(() => { + if (searchInput !== queryParam) { + updateQueryParams({ q: searchInput, page: 1 }); + } + }, 300); + return () => clearTimeout(timer); + }, [searchInput, queryParam, updateQueryParams]); + + // 4. REST Data Layer Collection Ingestion Matrix + const fetchDocuments = useCallback(async () => { + setIsLoading(true); + try { + // Constructs the standard request query matching active filter params + const endpoint = queryParam ? `/api/documents/search?q=${encodeURIComponent(queryParam)}&` : '/api/documents?'; + const queryString = new URLSearchParams({ + status: statusParam, + sort: sortParam, + page: String(pageParam), + limit: String(limit), + maxRisk: String(riskParam) + }).toString(); + + const response = await fetch(`${endpoint}${queryString}`); + if (response.ok) { + const data = await response.json(); + // Adjust array properties based on whether your API wraps data inside a meta envelope + setDocuments(data.items || data); + setTotalDocs(data.total || data.length); + } + } catch (error) { + console.error('Failed to resolve document collections:', error); + } finally { + setIsLoading(false); + } + }, [queryParam, statusParam, sortParam, pageParam, riskParam]); + + useEffect(() => { + fetchDocuments(); + }, [fetchDocuments]); + + // 5. Bulk Operation Command Dispatches + const handleSelectRow = (id: string) => { + setSelectedIds(prev => prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id]); + }; + + const handleSelectAll = () => { + if (selectedIds.length === documents.length) { + setSelectedIds([]); + } else { + setSelectedIds(documents.map(d => d.id)); + } + }; + + const handleBulkArchive = async () => { + if (selectedIds.length === 0) return; + try { + await fetch('/api/documents/bulk-archive', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ ids: selectedIds }), + }); + setSelectedIds([]); + fetchDocuments(); + } catch (err) { + console.error('Bulk archival operation failed:', err); + } + }; + + const handleVerifyTrigger = async (id: string) => { + try { + const response = await fetch(`/api/documents/${id}/verify`, { method: 'POST' }); + if (response.ok) fetchDocuments(); + } catch (err) { + console.error('Manual validation initialization dropped:', err); + } + }; + + return ( +
+ + {/* Upper Management Summary Metadata Context Banner */} +
+

Document Repository Matrix

+

Audit, monitor, filter, and anchor your enterprise security asset indices.

+
+ + {/* COMPACT CONFIGURATION CONTROL FILTER GRID */} +
+
+ + {/* Live Search Input wrapper */} +
+ + setSearchInput(e.target.value)} + className="w-full bg-slate-950 border border-slate-800 rounded-lg pl-9 pr-4 py-2 text-xs focus:outline-none focus:border-blue-500 text-slate-200 transition-colors" + /> +
+ + {/* Status lifecycle options */} +
+ + +
+ + {/* Sorting parameter filters */} +
+ + +
+ + {/* Threat Range Slider wrapper */} +
+
+ Max Allowed Risk + {riskSlider}% +
+ setRiskSlider(parseInt(e.target.value, 10))} + onMouseUp={() => updateQueryParams({ maxRisk: riskSlider, page: 1 })} + onTouchEnd={() => updateQueryParams({ maxRisk: riskSlider, page: 1 })} + className="w-full h-1 bg-slate-950 rounded-lg appearance-none cursor-pointer accent-blue-500" + /> +
+
+ + {/* BULK ACTION INTERCEPTOR BAR */} + {selectedIds.length > 0 && ( +
+ {selectedIds.length} items flagged for execution +
+ + +
+ )} +
+ + {/* INTERACTIVE COMPONENT REPOSITORY DATA FRAME TABLE */} +
+ {isLoading ? ( +
Syncing data ledger tracking states...
+ ) : documents.length === 0 ? ( +
+

No matching tracking registries found

+

Adjust query parameter scopes or filter parameters to locate the target metrics.

+
+ ) : ( +
+ + + + + + + + + + + + + {documents.map((doc) => ( + + + + + + + + + ))} + +
+ + Asset NameLifecycle StatusRisk ScoreIngestion TimestampOperational Actions
+ handleSelectRow(doc.id)} + className="rounded border-slate-800 bg-slate-950 text-blue-500 focus:ring-0 focus:ring-offset-0 cursor-pointer w-3.5 h-3.5" + /> + {doc.name} + + {doc.status} + + + {doc.riskScore}% + + {new Date(doc.createdAt).toLocaleDateString(undefined, { dateStyle: 'medium' })} + + + {doc.status === 'PENDING' && ( + + )} + +
+
+ )} + + {/* INTEGRATED PAGINATION DRAWER CONTROLS */} +
+ Page {pageParam} of {totalPages} +
+ + + + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/app/(protected)/onboarding/page.tsx b/frontend/app/(protected)/onboarding/page.tsx new file mode 100644 index 0000000..c90f6a5 --- /dev/null +++ b/frontend/app/(protected)/onboarding/page.tsx @@ -0,0 +1,304 @@ +'use client'; + +import React, { useState, useEffect, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; +import { + Sparkles, ShieldCheck, UploadCloud, AlertCircle, + CheckCircle2, Clock, FileText, ChevronRight, ChevronLeft +} from 'lucide-react'; + +const ONBOARDING_STORAGE_KEY = 'smalda:onboarding:completed'; + +export default function OnboardingPage() { + const router = useRouter(); + const [currentStep, setCurrentStep] = useState(1); + const [isUploading, setIsUploading] = useState(false); + const [uploadError, setUploadError] = useState(null); + const [dragActive, setDragActive] = useState(false); + + // 1. Check eligibility criteria: show only for accounts < 24h old with 0 documents + useEffect(() => { + const checkOnboardingEligibility = async () => { + const isCompleted = localStorage.getItem(ONBOARDING_STORAGE_KEY); + if (isCompleted === 'true') { + router.replace('/dashboard'); + return; + } + + try { + // Fetch current user details and document counts from your API services + const [userRes, docsRes] = await Promise.all([ + fetch('/api/user/profile'), + fetch('/api/documents') + ]); + + if (userRes.ok && docsRes.ok) { + const user = await userRes.json(); + const docs = await docsRes.json(); + + const accountCreatedTime = new Date(user.createdAt).getTime(); + const oneDayInMs = 24 * 60 * 60 * 1000; + const isNewAccount = (Date.now() - accountCreatedTime) < oneDayInMs; + const hasZeroDocs = docs.length === 0; + + // Bypass onboarding if they aren't a new user or already have historical assets + if (!isNewAccount || !hasZeroDocs) { + completeOnboarding(); + } + } + } catch (err) { + console.error('Failed to verify onboarding eligibility barriers:', err); + } + }; + + checkOnboardingEligibility(); + }, [router]); + + const completeOnboarding = useCallback(() => { + localStorage.setItem(ONBOARDING_STORAGE_KEY, 'true'); + router.push('/dashboard'); + }, [router]); + + // 2. Step 2 Drag and Drop Event Interceptors + const handleDrag = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.type === "dragenter" || e.type === "dragover") { + setDragActive(true); + } else if (e.type === "dragleave") { + setDragActive(false); + } + }; + + const handleDrop = async (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + + if (e.dataTransfer.files && e.dataTransfer.files[0]) { + await uploadDocument(e.dataTransfer.files[0]); + } + }; + + const handleFileInput = async (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + await uploadDocument(e.target.files[0]); + } + }; + + // 3. API Upload Connection + const uploadDocument = async (file: File) => { + setIsUploading(true); + setUploadError(null); + + const formData = new FormData(); + formData.append('file', file); + + try { + const response = await fetch('/api/documents/upload', { + method: 'POST', + body: formData, + }); + + if (!response.ok) throw new Error('Upload transaction declined by endpoint.'); + + // Advance to next step once file is processed + setCurrentStep(3); + } catch (err: any) { + setUploadError(err.message || 'Something went wrong during ingestion.'); + } finally { + setIsUploading(false); + } + }; + + return ( +
+ {/* Upper Progress Banner Header */} +
+
+ + SMALDA Onboarding +
+ +
+ + {/* Main Multi-step Sliding Track Window */} +
+ + {/* STEP 1: WELCOME INFO */} + {currentStep === 1 && ( +
+

Welcome to SMALDA

+

+ SMALDA bridges the gap between documents and secure evaluation data metrics. Let's unpack the core underlying engine pipelines in plain language: +

+
+
+ +
+

Automated AI Risk Scoring

+

Our engine scans uploaded documents for validation anomalies, assigning contextual threat markers automatically.

+
+
+
+ +
+

Stellar Blockchain Verification

+

A unique cryptographic fingerprint of your file is anchored onto the Stellar ledger to lock proof-of-authenticity permanently.

+
+
+
+
+ )} + + {/* STEP 2: DRAG & DROP UPLOAD */} + {currentStep === 2 && ( +
+
+

Upload Your First File

+

Kickstart system tracking by submitting an analysis target document.

+
+ +
+ + + +

or drag and drop your file boundary box here

+ + {uploadError && ( +
+ + {uploadError} +
+ )} +
+
+ )} + + {/* STEP 3: STATUS MAP DEFINITIONS */} + {currentStep === 3 && ( +
+
+

Understanding Tracking Statuses

+

Here is how to monitor document evaluation files across their lifecycle pipelines:

+
+ +
+
+ +
+ PENDING + File is securely queued. +
+
+
+ +
+ ANALYZING + AI engines evaluating file parameters. +
+
+
+ +
+ VERIFIED + Cryptographic proof verified on Stellar. +
+
+
+ +
+ FLAGGED + Risk indicators exceeded threshold lines. +
+
+
+
+ )} + + {/* STEP 4: COMPLETE */} + {currentStep === 4 && ( +
+
+ +
+

Setup Complete!

+

+ Your tracking onboarding is finished. You are ready to open your analytics command matrix dashboard. +

+
+ )} +
+ + {/* Footer Controls & Navigation Step Gauges */} + +
+ ); +} \ No newline at end of file