feat(ui): implement global currency selection

- Add global currency store (sat/msat/usd)
- Add currency toggle to site header
- Update currency formatting logic (integers for sats, 2 decimals for USD)
- Connect dashboard and balances pages to global currency state
This commit is contained in:
Shroominic
2025-11-23 21:57:14 +00:00
committed by Cursor Agent
parent 50220e7132
commit 604a4035fa
4 changed files with 85 additions and 4 deletions
+58
View File
@@ -0,0 +1,58 @@
'use client';
import { useCurrencyStore } from '@/lib/stores/currency';
import { useQuery } from '@tanstack/react-query';
import { fetchBtcUsdPrice, btcToSatsRate } from '@/lib/exchange-rate';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import { useEffect } from 'react';
import type { DisplayUnit } from '@/lib/types/units';
export function CurrencyToggle() {
const { displayUnit, setDisplayUnit } = useCurrencyStore();
const { data: btcUsdPrice } = useQuery({
queryKey: ['btc-usd-price'],
queryFn: fetchBtcUsdPrice,
refetchInterval: 120_000,
staleTime: 60_000,
});
const usdPerSat = btcUsdPrice ? btcToSatsRate(btcUsdPrice) : null;
useEffect(() => {
if (displayUnit === 'usd' && usdPerSat === null) {
setDisplayUnit('sat');
}
}, [displayUnit, usdPerSat, setDisplayUnit]);
return (
<ToggleGroup
type='single'
value={displayUnit}
onValueChange={(value) => {
if (value) {
setDisplayUnit(value as DisplayUnit);
}
}}
variant='outline'
size='sm'
className="mr-2"
>
<ToggleGroupItem value='msat' aria-label="Toggle msat" title="Millisatoshis">
mSAT
</ToggleGroupItem>
<ToggleGroupItem value='sat' aria-label="Toggle sat" title="Satoshis">
sat
</ToggleGroupItem>
<ToggleGroupItem
value='usd'
disabled={!usdPerSat}
aria-label="Toggle usd"
title="US Dollar (approx)"
>
USD
</ToggleGroupItem>
</ToggleGroup>
);
}
+2
View File
@@ -8,6 +8,7 @@ import { useRouter } from 'next/navigation';
import { adminLogout } from '@/lib/api/services/auth';
import { toast } from 'sonner';
import { ThemeToggle } from '@/components/theme-toggle';
import { CurrencyToggle } from '@/components/currency-toggle';
export function SiteHeader() {
const router = useRouter();
@@ -35,6 +36,7 @@ export function SiteHeader() {
<h1 className='text-base font-medium lg:hidden'>Routstr Node</h1>
</div>
<div className='flex items-center gap-2'>
<CurrencyToggle />
<ThemeToggle />
<Button
variant='ghost'
+4 -4
View File
@@ -25,7 +25,8 @@ export function formatFromMsat(
if (displayUnit === 'sat') {
const sats = amountMsat / 1000;
return `${sats.toLocaleString()} sats`;
// Format as integer for sats
return `${Math.floor(sats).toLocaleString()} sats`;
}
if (usdPerSat === null) {
@@ -34,12 +35,11 @@ export function formatFromMsat(
const sats = amountMsat / 1000;
const usd = sats * usdPerSat;
const precision = Math.abs(usd) >= 1 ? 2 : 4;
const formatter = new Intl.NumberFormat(undefined, {
style: 'currency',
currency: 'USD',
minimumFractionDigits: precision,
maximumFractionDigits: Math.max(precision, 6),
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
return formatter.format(usd);
}
+21
View File
@@ -0,0 +1,21 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { DisplayUnit } from '@/lib/types/units';
interface CurrencyState {
displayUnit: DisplayUnit;
setDisplayUnit: (unit: DisplayUnit) => void;
}
export const useCurrencyStore = create<CurrencyState>()(
persist(
(set) => ({
displayUnit: 'sat',
setDisplayUnit: (unit) => set({ displayUnit: unit }),
}),
{
name: 'currency-storage',
}
)
);