"use client"; import { useEffect, useState, useCallback } from "react"; import { Permission } from "../../auth-context"; import { adminApi } from "../../api"; import { sharedStyles } from "../../styles/shared"; import { Header } from "../../components/Header"; import { useRequireAuth } from "../../hooks/useRequireAuth"; import { components } from "../../generated/api"; type PriceHistoryRecord = components["schemas"]["PriceHistoryResponse"]; export default function AdminPriceHistoryPage() { const [records, setRecords] = useState([]); const [error, setError] = useState(null); const [isLoadingData, setIsLoadingData] = useState(true); const [isFetching, setIsFetching] = useState(false); const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.VIEW_AUDIT, fallbackRedirect: "/", }); const fetchRecords = useCallback(async () => { setError(null); setIsLoadingData(true); try { const data = await adminApi.getPriceHistory(); setRecords(data); } catch (err) { setRecords([]); setError(err instanceof Error ? err.message : "Failed to load price history"); } finally { setIsLoadingData(false); } }, []); const handleFetchNow = async () => { setIsFetching(true); setError(null); try { await adminApi.fetchPrice(); await fetchRecords(); } catch (err) { setError(err instanceof Error ? err.message : "Failed to fetch price"); } finally { setIsFetching(false); } }; useEffect(() => { if (user && isAuthorized) { fetchRecords(); } }, [user, isAuthorized, fetchRecords]); const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleString(); }; const formatPrice = (price: number) => { return new Intl.NumberFormat("en-US", { style: "currency", currency: "EUR", minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(price); }; if (isLoading) { return (
Loading...
); } if (!user || !isAuthorized) { return null; } return (

Bitcoin Price History

{records.length} records
{error && ( )} {!error && isLoadingData && ( )} {!error && !isLoadingData && records.length === 0 && ( )} {!error && !isLoadingData && records.map((record) => ( ))}
Source Pair Price Timestamp
{error}
Loading...
No price records yet. Click "Fetch Now" to get the current price.
{record.source} {record.pair} {formatPrice(record.price)} {formatDate(record.timestamp)}
); } const pageStyles: Record = { content: { flex: 1, padding: "2rem", overflowY: "auto", }, tableCard: { background: "rgba(255, 255, 255, 0.03)", backdropFilter: "blur(10px)", border: "1px solid rgba(255, 255, 255, 0.08)", borderRadius: "20px", padding: "1.5rem", boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)", maxWidth: "900px", margin: "0 auto", }, tableHeader: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "1rem", flexWrap: "wrap", gap: "1rem", }, tableTitle: { fontFamily: "'Instrument Serif', Georgia, serif", fontSize: "1.5rem", fontWeight: 400, color: "#fff", margin: 0, }, headerActions: { display: "flex", alignItems: "center", gap: "1rem", }, totalCount: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", color: "rgba(255, 255, 255, 0.4)", }, refreshBtn: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", padding: "0.5rem 1rem", background: "rgba(255, 255, 255, 0.08)", border: "1px solid rgba(255, 255, 255, 0.15)", borderRadius: "8px", color: "rgba(255, 255, 255, 0.8)", cursor: "pointer", }, fetchBtn: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", padding: "0.5rem 1rem", background: "linear-gradient(135deg, #a78bfa, #8b5cf6)", border: "none", borderRadius: "8px", color: "#fff", cursor: "pointer", fontWeight: 500, }, tableWrapper: { overflowX: "auto", }, table: { width: "100%", borderCollapse: "collapse", fontFamily: "'DM Sans', system-ui, sans-serif", }, th: { textAlign: "left", padding: "0.75rem 1rem", fontSize: "0.75rem", fontWeight: 600, color: "rgba(255, 255, 255, 0.4)", textTransform: "uppercase", letterSpacing: "0.05em", borderBottom: "1px solid rgba(255, 255, 255, 0.08)", }, tr: { borderBottom: "1px solid rgba(255, 255, 255, 0.04)", }, td: { padding: "0.875rem 1rem", fontSize: "0.875rem", color: "rgba(255, 255, 255, 0.7)", }, tdPrice: { padding: "0.875rem 1rem", fontSize: "1rem", color: "#fbbf24", fontWeight: 600, fontFamily: "'DM Mono', monospace", }, tdDate: { padding: "0.875rem 1rem", fontSize: "0.75rem", color: "rgba(255, 255, 255, 0.4)", }, emptyRow: { padding: "2rem 1rem", textAlign: "center", color: "rgba(255, 255, 255, 0.3)", fontSize: "0.875rem", }, errorRow: { padding: "2rem 1rem", textAlign: "center", color: "#f87171", fontSize: "0.875rem", }, }; const styles = { ...sharedStyles, ...pageStyles };