refactor: Extract SatsDisplay component and fix page IDs
- Extract SatsDisplay component to shared components directory - Fix page IDs: rename 'admin-appointments' to 'admin-trades' - Fix trades page using correct 'trades' page ID
This commit is contained in:
parent
ce8f5a1183
commit
4e6f38e4a1
5 changed files with 66 additions and 169 deletions
|
|
@ -5,6 +5,7 @@ import { useEffect, useState, useCallback } from "react";
|
|||
import { Permission } from "../../auth-context";
|
||||
import { api } from "../../api";
|
||||
import { Header } from "../../components/Header";
|
||||
import { SatsDisplay } from "../../components/SatsDisplay";
|
||||
import { useRequireAuth } from "../../hooks/useRequireAuth";
|
||||
import { components } from "../../generated/api";
|
||||
import { formatDateTime } from "../../utils/date";
|
||||
|
|
@ -25,59 +26,6 @@ function formatEur(cents: number): string {
|
|||
return `€${(cents / 100).toLocaleString("de-DE")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format satoshi amount with styled components
|
||||
* Leading zeros are subtle, main digits are prominent
|
||||
*/
|
||||
function SatsDisplay({ sats }: { sats: number }) {
|
||||
const btc = sats / 100_000_000;
|
||||
const btcStr = btc.toFixed(8);
|
||||
const [whole, decimal] = btcStr.split(".");
|
||||
|
||||
const part1 = decimal.slice(0, 2);
|
||||
const part2 = decimal.slice(2, 5);
|
||||
const part3 = decimal.slice(5, 8);
|
||||
|
||||
const fullDecimal = part1 + part2 + part3;
|
||||
let firstNonZero = fullDecimal.length;
|
||||
for (let i = 0; i < fullDecimal.length; i++) {
|
||||
if (fullDecimal[i] !== "0") {
|
||||
firstNonZero = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const subtleStyle: React.CSSProperties = {
|
||||
opacity: 0.45,
|
||||
fontWeight: 400,
|
||||
};
|
||||
|
||||
const renderPart = (part: string, startIdx: number) => {
|
||||
return part.split("").map((char, i) => {
|
||||
const globalIdx = startIdx + i;
|
||||
const isSubtle = globalIdx < firstNonZero;
|
||||
return (
|
||||
<span key={globalIdx} style={isSubtle ? subtleStyle : undefined}>
|
||||
{char}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<span style={{ fontFamily: "'DM Mono', monospace" }}>
|
||||
<span style={subtleStyle}>₿</span>
|
||||
<span style={subtleStyle}> {whole}.</span>
|
||||
{renderPart(part1, 0)}
|
||||
<span> </span>
|
||||
{renderPart(part2, 2)}
|
||||
<span> </span>
|
||||
{renderPart(part3, 5)}
|
||||
<span> sats</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status display properties
|
||||
*/
|
||||
|
|
@ -230,7 +178,7 @@ export default function AdminTradesPage() {
|
|||
|
||||
return (
|
||||
<main style={layoutStyles.main}>
|
||||
<Header currentPage="admin-appointments" />
|
||||
<Header currentPage="admin-trades" />
|
||||
<div style={styles.content}>
|
||||
<h1 style={typographyStyles.pageTitle}>Trades</h1>
|
||||
<p style={typographyStyles.pageSubtitle}>Manage Bitcoin exchange trades</p>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ type PageId =
|
|||
| "trades"
|
||||
| "admin-invites"
|
||||
| "admin-availability"
|
||||
| "admin-appointments"
|
||||
| "admin-trades"
|
||||
| "admin-price-history";
|
||||
|
||||
interface HeaderProps {
|
||||
|
|
@ -37,7 +37,7 @@ const REGULAR_NAV_ITEMS: NavItem[] = [
|
|||
];
|
||||
|
||||
const ADMIN_NAV_ITEMS: NavItem[] = [
|
||||
{ id: "admin-appointments", label: "Trades", href: "/admin/trades", adminOnly: true },
|
||||
{ id: "admin-trades", label: "Trades", href: "/admin/trades", adminOnly: true },
|
||||
{ id: "admin-availability", label: "Availability", href: "/admin/availability", adminOnly: true },
|
||||
{ id: "admin-invites", label: "Invites", href: "/admin/invites", adminOnly: true },
|
||||
{ id: "admin-price-history", label: "Prices", href: "/admin/price-history", adminOnly: true },
|
||||
|
|
|
|||
59
frontend/app/components/SatsDisplay.tsx
Normal file
59
frontend/app/components/SatsDisplay.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import React from "react";
|
||||
|
||||
/**
|
||||
* Format satoshi amount with styled components.
|
||||
* Leading zeros are subtle, main digits are prominent.
|
||||
* e.g., 1876088 -> "₿ 0.01" (subtle) + "876 088 sats" (prominent)
|
||||
*/
|
||||
export function SatsDisplay({ sats }: { sats: number }) {
|
||||
const btc = sats / 100_000_000;
|
||||
const btcStr = btc.toFixed(8);
|
||||
const [whole, decimal] = btcStr.split(".");
|
||||
|
||||
// Group decimal into chunks: first 2, then two groups of 3
|
||||
const part1 = decimal.slice(0, 2);
|
||||
const part2 = decimal.slice(2, 5);
|
||||
const part3 = decimal.slice(5, 8);
|
||||
|
||||
// Find where meaningful digits start (first non-zero after decimal)
|
||||
const fullDecimal = part1 + part2 + part3;
|
||||
let firstNonZero = fullDecimal.length;
|
||||
for (let i = 0; i < fullDecimal.length; i++) {
|
||||
if (fullDecimal[i] !== "0") {
|
||||
firstNonZero = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the display with subtle leading zeros and prominent digits
|
||||
const subtleStyle: React.CSSProperties = {
|
||||
opacity: 0.45,
|
||||
fontWeight: 400,
|
||||
};
|
||||
|
||||
// Determine which parts are subtle vs prominent
|
||||
const renderPart = (part: string, startIdx: number) => {
|
||||
return part.split("").map((char, i) => {
|
||||
const globalIdx = startIdx + i;
|
||||
const isSubtle = globalIdx < firstNonZero;
|
||||
return (
|
||||
<span key={globalIdx} style={isSubtle ? subtleStyle : undefined}>
|
||||
{char}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<span style={{ fontFamily: "'DM Mono', monospace" }}>
|
||||
<span style={subtleStyle}>₿</span>
|
||||
<span style={subtleStyle}> {whole}.</span>
|
||||
{renderPart(part1, 0)}
|
||||
<span> </span>
|
||||
{renderPart(part2, 2)}
|
||||
<span> </span>
|
||||
{renderPart(part3, 5)}
|
||||
<span> sats</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { useRouter } from "next/navigation";
|
|||
import { Permission } from "../auth-context";
|
||||
import { api } from "../api";
|
||||
import { Header } from "../components/Header";
|
||||
import { SatsDisplay } from "../components/SatsDisplay";
|
||||
import { useRequireAuth } from "../hooks/useRequireAuth";
|
||||
import { components } from "../generated/api";
|
||||
import { formatDate, formatTime, getDateRange } from "../utils/date";
|
||||
|
|
@ -30,65 +31,6 @@ function formatEur(cents: number): string {
|
|||
return `€${(cents / 100).toLocaleString("de-DE")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format satoshi amount with styled components
|
||||
* Leading zeros are subtle, main digits are prominent
|
||||
* e.g., 1876088 -> "₿ 0.01" (subtle) + "876 088 sats" (prominent)
|
||||
*/
|
||||
function SatsDisplay({ sats }: { sats: number }) {
|
||||
const btc = sats / 100_000_000;
|
||||
const btcStr = btc.toFixed(8);
|
||||
const [whole, decimal] = btcStr.split(".");
|
||||
|
||||
// Group decimal into chunks: first 2, then two groups of 3
|
||||
const part1 = decimal.slice(0, 2);
|
||||
const part2 = decimal.slice(2, 5);
|
||||
const part3 = decimal.slice(5, 8);
|
||||
|
||||
// Find where meaningful digits start (first non-zero after decimal)
|
||||
const fullDecimal = part1 + part2 + part3;
|
||||
let firstNonZero = fullDecimal.length;
|
||||
for (let i = 0; i < fullDecimal.length; i++) {
|
||||
if (fullDecimal[i] !== "0") {
|
||||
firstNonZero = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the display with subtle leading zeros and prominent digits
|
||||
const subtleStyle: React.CSSProperties = {
|
||||
opacity: 0.45,
|
||||
fontWeight: 400,
|
||||
};
|
||||
|
||||
// Determine which parts are subtle vs prominent
|
||||
const renderPart = (part: string, startIdx: number) => {
|
||||
const chars = part.split("").map((char, i) => {
|
||||
const globalIdx = startIdx + i;
|
||||
const isSubtle = globalIdx < firstNonZero;
|
||||
return (
|
||||
<span key={globalIdx} style={isSubtle ? subtleStyle : undefined}>
|
||||
{char}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
return chars;
|
||||
};
|
||||
|
||||
return (
|
||||
<span style={{ fontFamily: "'DM Mono', monospace" }}>
|
||||
<span style={subtleStyle}>₿</span>
|
||||
<span style={subtleStyle}> {whole}.</span>
|
||||
{renderPart(part1, 0)}
|
||||
<span> </span>
|
||||
{renderPart(part2, 2)}
|
||||
<span> </span>
|
||||
{renderPart(part3, 5)}
|
||||
<span> sats</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format price for display
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useEffect, useState, useCallback } from "react";
|
|||
import { Permission } from "../auth-context";
|
||||
import { api } from "../api";
|
||||
import { Header } from "../components/Header";
|
||||
import { SatsDisplay } from "../components/SatsDisplay";
|
||||
import { useRequireAuth } from "../hooks/useRequireAuth";
|
||||
import { components } from "../generated/api";
|
||||
import { formatDateTime } from "../utils/date";
|
||||
|
|
@ -25,59 +26,6 @@ function formatEur(cents: number): string {
|
|||
return `€${(cents / 100).toLocaleString("de-DE")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format satoshi amount with styled components
|
||||
* Leading zeros are subtle, main digits are prominent
|
||||
*/
|
||||
function SatsDisplay({ sats }: { sats: number }) {
|
||||
const btc = sats / 100_000_000;
|
||||
const btcStr = btc.toFixed(8);
|
||||
const [whole, decimal] = btcStr.split(".");
|
||||
|
||||
const part1 = decimal.slice(0, 2);
|
||||
const part2 = decimal.slice(2, 5);
|
||||
const part3 = decimal.slice(5, 8);
|
||||
|
||||
const fullDecimal = part1 + part2 + part3;
|
||||
let firstNonZero = fullDecimal.length;
|
||||
for (let i = 0; i < fullDecimal.length; i++) {
|
||||
if (fullDecimal[i] !== "0") {
|
||||
firstNonZero = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const subtleStyle: React.CSSProperties = {
|
||||
opacity: 0.45,
|
||||
fontWeight: 400,
|
||||
};
|
||||
|
||||
const renderPart = (part: string, startIdx: number) => {
|
||||
return part.split("").map((char, i) => {
|
||||
const globalIdx = startIdx + i;
|
||||
const isSubtle = globalIdx < firstNonZero;
|
||||
return (
|
||||
<span key={globalIdx} style={isSubtle ? subtleStyle : undefined}>
|
||||
{char}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<span style={{ fontFamily: "'DM Mono', monospace" }}>
|
||||
<span style={subtleStyle}>₿</span>
|
||||
<span style={subtleStyle}> {whole}.</span>
|
||||
{renderPart(part1, 0)}
|
||||
<span> </span>
|
||||
{renderPart(part2, 2)}
|
||||
<span> </span>
|
||||
{renderPart(part3, 5)}
|
||||
<span> sats</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status display properties
|
||||
*/
|
||||
|
|
@ -192,7 +140,7 @@ export default function TradesPage() {
|
|||
|
||||
return (
|
||||
<main style={layoutStyles.main}>
|
||||
<Header currentPage="appointments" />
|
||||
<Header currentPage="trades" />
|
||||
<div style={styles.content}>
|
||||
<h1 style={typographyStyles.pageTitle}>My Trades</h1>
|
||||
<p style={typographyStyles.pageSubtitle}>View and manage your Bitcoin trades</p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue