From 303b30f170a4b4def059d3399849a6017584347c Mon Sep 17 00:00:00 2001 From: Joey Collado Date: Sun, 8 Feb 2026 18:54:52 +0800 Subject: [PATCH 1/3] feature/PoR: Live Security Stream & Dynamic Charts --- client/app/globals.css | 3 +- .../components/charts/metal-price-history.tsx | 94 +++++++++++++ client/components/dashboard-layout.tsx | 2 +- client/components/shared/MotionWrapper.tsx | 38 ++++-- .../shared/live-security-terminal.tsx | 129 ++++++++++++++++++ client/components/views/dashboard-view.tsx | 24 ++-- client/components/views/por-view.tsx | 46 +------ client/pnpm-lock.yaml | 36 +++++ 8 files changed, 312 insertions(+), 60 deletions(-) create mode 100644 client/components/charts/metal-price-history.tsx create mode 100644 client/components/shared/live-security-terminal.tsx diff --git a/client/app/globals.css b/client/app/globals.css index ac468bd..3108cde 100644 --- a/client/app/globals.css +++ b/client/app/globals.css @@ -116,7 +116,6 @@ @layer base { * { @apply border-border outline-ring/50; - /* Global scrollbar for Firefox */ scrollbar-width: thin; scrollbar-color: rgba(212, 175, 55, 0.6) #0A0A0A; @@ -132,6 +131,8 @@ /* 2% Noise texture - De-digitalizing */ position: relative; + + } body::before { diff --git a/client/components/charts/metal-price-history.tsx b/client/components/charts/metal-price-history.tsx new file mode 100644 index 0000000..d092458 --- /dev/null +++ b/client/components/charts/metal-price-history.tsx @@ -0,0 +1,94 @@ +'use client' + +import React from 'react' +import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' +import { useAPAXStore } from '@/lib/store' + +// mock data generator +const generateHistoricalData = (currentPrice: number) => { + return [ + { time: '08:00', price: currentPrice * 0.982 }, + { time: '10:00', price: currentPrice * 0.988 }, + { time: '12:00', price: currentPrice * 0.995 }, + { time: '14:00', price: currentPrice * 0.991 }, + { time: '16:00', price: currentPrice * 1.008 }, + { time: '18:00', price: currentPrice * 1.003 }, + { time: '20:00', price: currentPrice }, + ] +} + +export function MetalPriceHistoryChart() { + const { metalPrices } = useAPAXStore() + const chartData = generateHistoricalData(metalPrices.gold) + + return ( +
+
+ + + + + + + + + + + + + + + + + { + if (active && payload && payload.length) { + return ( +
+

+ {payload[0].payload.time} UTC +

+

+ ${payload[0].value?.toLocaleString(undefined, { minimumFractionDigits: 2 })} +

+
+ ); + } + return null; + }} + /> + + +
+
+
+ ) +} \ No newline at end of file diff --git a/client/components/dashboard-layout.tsx b/client/components/dashboard-layout.tsx index d550719..aa6d3a9 100644 --- a/client/components/dashboard-layout.tsx +++ b/client/components/dashboard-layout.tsx @@ -82,7 +82,7 @@ export function DashboardLayout({ children}: DashboardLayoutProps) { {/* Main Content Area */}
- + {children} diff --git a/client/components/shared/MotionWrapper.tsx b/client/components/shared/MotionWrapper.tsx index eba2545..5a25f5e 100644 --- a/client/components/shared/MotionWrapper.tsx +++ b/client/components/shared/MotionWrapper.tsx @@ -6,19 +6,21 @@ import { motion, HTMLMotionProps, Variants } from 'framer-motion'; interface MotionWrapperProps extends HTMLMotionProps<'div'> { children: React.ReactNode; delay?: number; - stagger?: boolean; // prop for orchestration + stagger?: boolean; } const variants: Variants = { - initial: { opacity: 0, y: 20 }, + initial: { + opacity: 0, + y: 20 + }, animate: (delay: number) => ({ opacity: 1, y: 0, transition: { delay, - duration: 0.4, - ease: [0.25, 0.1, 0.25, 1], - // if this is a container, stagger the children + duration: 0.5, + ease: [0.16, 1, 0.3, 1], staggerChildren: 0.05, delayChildren: delay, } @@ -26,17 +28,19 @@ const variants: Variants = { exit: { opacity: 0, y: -20, - transition: { duration: 0.2 } + transition: { duration: 0.3, ease: [0.16, 1, 0.3, 1] } } }; -// item variant for children so they can inherit the stagger export const childVariants: Variants = { initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0, - transition: { duration: 0.4, ease: [0.25, 0.1, 0.25, 1] } + transition: { + duration: 0.4, + ease: [0.16, 1, 0.3, 1] + } } }; @@ -55,12 +59,24 @@ export const MotionWrapper = ({ exit="exit" custom={delay} className={className} + style={{ + contain: 'paint', + willChange: 'transform, opacity' + }} {...props} > {stagger - ? React.Children.map(children, (child) => ( - {child} - )) + ? React.Children.map(children, (child) => { + if (!React.isValidElement(child)) return child; + return ( + + {child} + + ); + }) : children} ); diff --git a/client/components/shared/live-security-terminal.tsx b/client/components/shared/live-security-terminal.tsx new file mode 100644 index 0000000..52d0353 --- /dev/null +++ b/client/components/shared/live-security-terminal.tsx @@ -0,0 +1,129 @@ +'use client' + +import React, { useEffect, useState, useRef, memo } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import { AuditLog } from '@/lib/store' + + +const ScanningBeam = () => ( +
+
+ +
+
+
+
+ +
+
+) + +const AuditLogCard = memo(({ + log, + index, + isLast, + totalLogs, + beamDuration +}: { + log: AuditLog; + index: number; + isLast: boolean; + totalLogs: number; + beamDuration: number; +}) => { + const revealDelay = (index / totalLogs) * beamDuration; + + return ( + +
+
+ {!isLast && ( +
+ )} +
+
+
+

+ [{log.event}] +

+ + T+{(index * 0.8).toFixed(1)}s + +
+

+ {log.details} +

+
+ TX_SIG + + {log.txHash} + +
+
+ + ) +}) + +AuditLogCard.displayName = 'AuditLogCard' + +export function LiveSecurityTerminal({ logs }: { logs: AuditLog[] }) { + const [isBeamVisible, setIsBeamVisible] = useState(false) + const [beamKey, setBeamKey] = useState(0) + const containerRef = useRef(null) + + const BEAM_DURATION = 5.0 + + useEffect(() => { + if (logs.length > 0) { + setIsBeamVisible(true) + setBeamKey(prev => prev + 1) + } + }, [logs.length]) + + return ( +
+ + {isBeamVisible && ( + setIsBeamVisible(false)} + className="absolute inset-x-0 z-30 pointer-events-none" + style={{ height: '100%', willChange: 'transform' }} + > +
+ +
+
+ )} +
+ +
+ {logs.map((log, i) => ( + + ))} +
+
+ ) +} \ No newline at end of file diff --git a/client/components/views/dashboard-view.tsx b/client/components/views/dashboard-view.tsx index c0ed4a3..1d83958 100644 --- a/client/components/views/dashboard-view.tsx +++ b/client/components/views/dashboard-view.tsx @@ -1,9 +1,10 @@ -'use client' +"use client"; -import Image from 'next/image' -import { PortfolioOverview } from '@/components/portfolio-overview' -import { AssetAllocationChart } from '@/components/asset-allocation-chart' -import { ShariaCertificationHub } from '@/components/sharia-certification-hub' +import Image from "next/image"; +import { PortfolioOverview } from "@/components/portfolio-overview"; +import { AssetAllocationChart } from "@/components/asset-allocation-chart"; +import { ShariaCertificationHub } from "@/components/sharia-certification-hub"; +import { MetalPriceHistoryChart } from "@/components/charts/metal-price-history"; export function DashboardView() { return ( @@ -15,10 +16,14 @@ export function DashboardView() {
- Network Connectivity: 100% + + Network Connectivity: 100% +
Latest Block: #8,421,093 - Vault A1-X: Re-verified by Lead Auditor + + Vault A1-X: Re-verified by Lead Auditor + Last Synced: 2s ago
))} @@ -44,6 +49,9 @@ export function DashboardView() {
+ {/* Metal price history chart */} + + {/* Portfolio Overview Stats */} @@ -55,5 +63,5 @@ export function DashboardView() {
- ) + ); } diff --git a/client/components/views/por-view.tsx b/client/components/views/por-view.tsx index 27140c3..f004542 100644 --- a/client/components/views/por-view.tsx +++ b/client/components/views/por-view.tsx @@ -8,6 +8,7 @@ import { Badge } from '@/components/ui/badge' import { Progress } from '@/components/ui/progress' import { ScrollArea } from '@/components/ui/scroll-area' import { useAPAXStore, formatWeight } from '@/lib/store' +import { LiveSecurityTerminal } from '@/components/shared/live-security-terminal' export function PorView() { const { vaultData, auditLogs } = useAPAXStore() @@ -196,7 +197,7 @@ export function PorView() { {/* Total Tokens */} -
+

Total APX Tokens Minted

@@ -282,45 +283,12 @@ export function PorView() {

- - -
- {auditLogs.map((log, index) => ( -
-
-
- {index < auditLogs.length - 1 && ( -
- )} -
-
-
-

- [{log.event.toUpperCase()}] -

- - {formatTimeAgo(log.timestamp)} - -
-

- {log.details} -

-
- TX::HASH - - {log.txHash} - -
-
-
- ))} -
+ + + {/* LST */} + + - {/* Scanning Line Effect */} -
diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 5dedbb3..57a2756 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -353,89 +353,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -502,24 +518,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.1.6': resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} @@ -1259,24 +1279,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -1457,41 +1481,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -2353,24 +2385,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} From 680e34018b35a1d33d95610a99065934b02721d4 Mon Sep 17 00:00:00 2001 From: Joey Collado Date: Sun, 8 Feb 2026 20:01:09 +0800 Subject: [PATCH 2/3] finalized --- .../components/charts/metal-price-history.tsx | 20 ++++-- .../shared/live-security-terminal.tsx | 70 ++++++++++--------- client/components/views/por-view.tsx | 2 +- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/client/components/charts/metal-price-history.tsx b/client/components/charts/metal-price-history.tsx index d092458..4b88bd7 100644 --- a/client/components/charts/metal-price-history.tsx +++ b/client/components/charts/metal-price-history.tsx @@ -1,10 +1,10 @@ 'use client' -import React from 'react' +import React, { useMemo } from 'react' import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' import { useAPAXStore } from '@/lib/store' -// mock data generator +// mock data generate const generateHistoricalData = (currentPrice: number) => { return [ { time: '08:00', price: currentPrice * 0.982 }, @@ -18,9 +18,15 @@ const generateHistoricalData = (currentPrice: number) => { } export function MetalPriceHistoryChart() { + + //global state access const { metalPrices } = useAPAXStore() - const chartData = generateHistoricalData(metalPrices.gold) + + const chartData = useMemo(() => + generateHistoricalData(metalPrices.gold), + [metalPrices.gold]) + //chart return (
@@ -51,7 +57,7 @@ export function MetalPriceHistoryChart() { className="font-vault opacity-60" /> - + { if (active && payload && payload.length) { return ( -
-

+

+

{payload[0].payload.time} UTC

-

+

${payload[0].value?.toLocaleString(undefined, { minimumFractionDigits: 2 })}

diff --git a/client/components/shared/live-security-terminal.tsx b/client/components/shared/live-security-terminal.tsx index 52d0353..7bed426 100644 --- a/client/components/shared/live-security-terminal.tsx +++ b/client/components/shared/live-security-terminal.tsx @@ -1,19 +1,18 @@ 'use client' -import React, { useEffect, useState, useRef, memo } from 'react' +import React, { useEffect, useState, memo } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { AuditLog } from '@/lib/store' +/* scanning effect */ const ScanningBeam = () => (
-
-
) @@ -31,18 +30,20 @@ const AuditLogCard = memo(({ totalLogs: number; beamDuration: number; }) => { + // reveal sync const revealDelay = (index / totalLogs) * beamDuration; return (
@@ -50,21 +51,22 @@ const AuditLogCard = memo(({
)}
+
-

+

[{log.event}]

- - T+{(index * 0.8).toFixed(1)}s + + {new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' })}
-

+

{log.details}

-
- TX_SIG - +
+ TX_SIG + {log.txHash}
@@ -78,28 +80,25 @@ AuditLogCard.displayName = 'AuditLogCard' export function LiveSecurityTerminal({ logs }: { logs: AuditLog[] }) { const [isBeamVisible, setIsBeamVisible] = useState(false) const [beamKey, setBeamKey] = useState(0) - const containerRef = useRef(null) - const BEAM_DURATION = 5.0 + const displayLogs = logs.slice(-20); + const BEAM_DURATION = 4 // beam speed useEffect(() => { - if (logs.length > 0) { + if (displayLogs.length > 0) { setIsBeamVisible(true) setBeamKey(prev => prev + 1) } - }, [logs.length]) + }, [logs.length]) return ( -
- +
+ {isBeamVisible && ( setIsBeamVisible(false)} className="absolute inset-x-0 z-30 pointer-events-none" @@ -112,17 +111,20 @@ export function LiveSecurityTerminal({ logs }: { logs: AuditLog[] }) { )} -
- {logs.map((log, i) => ( - - ))} +
+ {/* reversed column layout */} +
+ {displayLogs.map((log, i) => ( + + ))} +
) diff --git a/client/components/views/por-view.tsx b/client/components/views/por-view.tsx index f004542..64caf84 100644 --- a/client/components/views/por-view.tsx +++ b/client/components/views/por-view.tsx @@ -283,7 +283,7 @@ export function PorView() {
- + {/* LST */} From 80fca709d5f6d1eb5487d21061b7fde018d022f2 Mon Sep 17 00:00:00 2001 From: Joey Collado Date: Sun, 8 Feb 2026 20:21:11 +0800 Subject: [PATCH 3/3] minor polish --- .../shared/live-security-terminal.tsx | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/client/components/shared/live-security-terminal.tsx b/client/components/shared/live-security-terminal.tsx index 7bed426..d896b1c 100644 --- a/client/components/shared/live-security-terminal.tsx +++ b/client/components/shared/live-security-terminal.tsx @@ -1,11 +1,10 @@ 'use client' -import React, { useEffect, useState, memo } from 'react' +import React, { useEffect, useState, useRef, memo } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { AuditLog } from '@/lib/store' - -/* scanning effect */ +// scanning effect const ScanningBeam = () => (
@@ -30,7 +29,7 @@ const AuditLogCard = memo(({ totalLogs: number; beamDuration: number; }) => { - // reveal sync + //reveal sync const revealDelay = (index / totalLogs) * beamDuration; return ( @@ -80,25 +79,31 @@ AuditLogCard.displayName = 'AuditLogCard' export function LiveSecurityTerminal({ logs }: { logs: AuditLog[] }) { const [isBeamVisible, setIsBeamVisible] = useState(false) const [beamKey, setBeamKey] = useState(0) - const displayLogs = logs.slice(-20); const BEAM_DURATION = 4 // beam speed useEffect(() => { - if (displayLogs.length > 0) { - setIsBeamVisible(true) - setBeamKey(prev => prev + 1) + if (logs.length > 0) { + setIsBeamVisible(false); + + const timer = setTimeout(() => { + setIsBeamVisible(true); + setBeamKey(prev => prev + 1); + }, 50); // small buffer to stabilize the engine + + return () => clearTimeout(timer); } - }, [logs.length]) + }, [logs.length]); // watch the count for new log renders return (
- + {isBeamVisible && ( setIsBeamVisible(false)} className="absolute inset-x-0 z-30 pointer-events-none" @@ -111,8 +116,8 @@ export function LiveSecurityTerminal({ logs }: { logs: AuditLog[] }) { )} -
- {/* reversed column layout */} +
+ {/* reverse column layout */}
{displayLogs.map((log, i) => (