arbret/frontend/app/exchange/components/PriceDisplay.tsx
counterweight 246553c402
Phase 6: Translate User Pages - exchange, trades, invites, profile
- Expand exchange.json with all exchange page strings (page, steps, detailsStep, bookingStep, confirmationStep, priceDisplay)
- Create trades.json translation files for es, en, ca
- Create invites.json translation files for es, en, ca
- Create profile.json translation files for es, en, ca
- Translate exchange page and all components (ExchangeDetailsStep, BookingStep, ConfirmationStep, StepIndicator, PriceDisplay)
- Translate trades page (titles, sections, buttons, status labels)
- Translate invites page (titles, sections, status badges, copy button)
- Translate profile page (form labels, hints, placeholders, messages)
- Update IntlProvider to load all new namespaces
- All frontend tests passing
2025-12-25 22:19:13 +01:00

132 lines
3.7 KiB
TypeScript

"use client";
import { CSSProperties } from "react";
import { components } from "../../generated/api";
import { useTranslation } from "../../hooks/useTranslation";
type ExchangePriceResponse = components["schemas"]["ExchangePriceResponse"];
interface PriceDisplayProps {
priceData: ExchangePriceResponse | null;
isLoading: boolean;
error: string | null;
lastUpdate: Date | null;
direction: "buy" | "sell";
agreedPrice: number;
}
/**
* Format price for display
*/
function formatPrice(price: number): string {
return `${price.toLocaleString("de-DE", { maximumFractionDigits: 0 })}`;
}
const styles: Record<string, CSSProperties> = {
priceCard: {
background: "rgba(255, 255, 255, 0.03)",
border: "1px solid rgba(255, 255, 255, 0.08)",
borderRadius: "12px",
padding: "1rem 1.5rem",
marginBottom: "1.5rem",
},
priceRow: {
display: "flex",
alignItems: "center",
gap: "0.75rem",
flexWrap: "wrap",
},
priceLabel: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.5)",
fontSize: "0.9rem",
},
priceValue: {
fontFamily: "'DM Mono', monospace",
color: "#fff",
fontSize: "1.1rem",
fontWeight: 500,
},
priceDivider: {
color: "rgba(255, 255, 255, 0.2)",
margin: "0 0.25rem",
},
premiumBadge: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "0.75rem",
fontWeight: 600,
padding: "0.2rem 0.5rem",
borderRadius: "4px",
marginLeft: "0.25rem",
background: "rgba(255, 255, 255, 0.1)",
color: "rgba(255, 255, 255, 0.7)",
},
priceTimestamp: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "0.75rem",
color: "rgba(255, 255, 255, 0.4)",
marginTop: "0.5rem",
},
staleWarning: {
color: "#f87171",
fontWeight: 600,
},
priceLoading: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.5)",
textAlign: "center" as const,
},
priceError: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "#f87171",
textAlign: "center" as const,
},
};
/**
* Component that displays exchange price information.
* Shows market price, agreed price, premium percentage, and last update time.
*/
export function PriceDisplay({
priceData,
isLoading,
error,
lastUpdate,
direction,
agreedPrice,
}: PriceDisplayProps) {
const t = useTranslation("exchange");
const marketPrice = priceData?.price?.market_price ?? 0;
const premiumPercent = priceData?.price?.premium_percentage ?? 5;
const isPriceStale = priceData?.price?.is_stale ?? false;
return (
<div style={styles.priceCard}>
{isLoading && !priceData ? (
<div style={styles.priceLoading}>{t("priceDisplay.loading")}</div>
) : error && !priceData?.price ? (
<div style={styles.priceError}>{error}</div>
) : (
<>
<div style={styles.priceRow}>
<span style={styles.priceLabel}>{t("priceDisplay.market")}</span>
<span style={styles.priceValue}>{formatPrice(marketPrice)}</span>
<span style={styles.priceDivider}></span>
<span style={styles.priceLabel}>{t("priceDisplay.ourPrice")}</span>
<span style={styles.priceValue}>{formatPrice(agreedPrice)}</span>
<span style={styles.premiumBadge}>
{direction === "buy" ? "+" : "-"}
{premiumPercent}%
</span>
</div>
{lastUpdate && (
<div style={styles.priceTimestamp}>
{t("priceDisplay.updated")} {lastUpdate.toLocaleTimeString()}
{isPriceStale && <span style={styles.staleWarning}> {t("priceDisplay.stale")}</span>}
</div>
)}
</>
)}
</div>
);
}