Files
textbee/web/lib/format.ts
isra el 497a14074a refactor: centralize data fetching into typed react-query hooks
Introduce a typed data layer under lib/api (query-key factory, response
types, feature hooks) plus shared lib/format and lib/status helpers, and
migrate the canonical dashboard consumers (subscription-info, overview,
device-list, api-keys) onto it, deleting their duplicated inline queries,
mutations and formatters.

List hooks keep the raw { data: [] } envelope in the cache and unwrap
per-observer with react-query `select`, so shared keys like ['devices']
stay compatible with the not-yet-migrated components that still read the
raw shape (avoids a cache-shape collision surfaced by the dashboard e2e).

Adds unit tests for the formatters and the hooks (against MSW). Build,
19 unit tests, and 2 e2e all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 01:36:16 +03:00

48 lines
1.4 KiB
TypeScript

// Shared formatting helpers. These were previously duplicated inline across
// subscription-info, device-list, overview and others.
// A plan limit of -1 means unlimited; null/undefined render as 0.
export function formatLimit(value: number | null | undefined): string {
if (value === -1) return 'Unlimited'
if (value == null) return '0'
return value.toLocaleString()
}
// Amounts come from the backend in minor units (cents). Null amount = Free.
export function formatPrice(
amount: number | null | undefined,
currency: string | null | undefined
): string {
if (amount == null || currency == null) return 'Free'
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency.toUpperCase() || 'USD',
minimumFractionDigits: 2,
}).format(amount / 100)
}
export function getBillingInterval(
interval: string | null | undefined
): string {
if (!interval) return ''
return interval.toLowerCase() === 'month' ? 'monthly' : 'yearly'
}
export function formatDate(value: string | number | Date | null | undefined) {
if (!value) return 'N/A'
return new Date(value).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
}
// "past_due" -> "Past Due"
export function titleCaseStatus(status: string | null | undefined): string {
if (!status) return ''
return status
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
}