From 2529a0b0ca8b322c676a56d8dcff9ee5546e3514 Mon Sep 17 00:00:00 2001 From: thesn10 <38666407+thesn10@users.noreply.github.com> Date: Wed, 24 Jun 2026 23:16:55 +0200 Subject: [PATCH] feat: add CSS globals, format utils, and queryClient --- app/src/lib/format.ts | 34 ++++++++++++++++++++++ app/src/lib/queryClient.ts | 11 ++++++++ app/src/lib/utils.ts | 6 ++++ app/src/styles/globals.css | 58 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 app/src/lib/format.ts create mode 100644 app/src/lib/queryClient.ts create mode 100644 app/src/lib/utils.ts create mode 100644 app/src/styles/globals.css diff --git a/app/src/lib/format.ts b/app/src/lib/format.ts new file mode 100644 index 0000000..039585a --- /dev/null +++ b/app/src/lib/format.ts @@ -0,0 +1,34 @@ +export function abbrev(pk: string | null | undefined): string { + if (!pk) return '—' + return `${pk.slice(0, 4)}…${pk.slice(-4)}` +} + +export function fmtSol(lamports: bigint | number): string { + const v = Number(lamports) / 1e9 + return v.toLocaleString(undefined, { maximumFractionDigits: 4 }) + ' SOL' +} + +export function fmtTok(raw: bigint | number, decimals: number, symbol: string): string { + const v = Number(raw) / Math.pow(10, decimals) + return v.toLocaleString(undefined, { maximumFractionDigits: Math.min(decimals, 2) }) + ' ' + symbol +} + +export function relTime(ts: number): string { + const d = Math.floor(Date.now() / 1000) - ts + if (d < 60) return `${d}s ago` + if (d < 3600) return `${Math.floor(d / 60)}m ago` + if (d < 86400) return `${Math.floor(d / 3600)}h ago` + return `${Math.floor(d / 86400)}d ago` +} + +export function exact(ts: number): string { + return new Date(ts * 1000).toUTCString() +} + +export function priceStr( + currency: { __kind: 'Sol' } | { __kind: 'Spl'; decimals: number; symbol?: string }, + price: bigint | number, +): string { + if (currency.__kind === 'Sol') return fmtSol(price) + return fmtTok(price, currency.decimals, (currency as { symbol?: string }).symbol ?? 'SPL') +} diff --git a/app/src/lib/queryClient.ts b/app/src/lib/queryClient.ts new file mode 100644 index 0000000..6ca7e37 --- /dev/null +++ b/app/src/lib/queryClient.ts @@ -0,0 +1,11 @@ +import { QueryClient } from '@tanstack/react-query' + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 10_000, + gcTime: 5 * 60 * 1000, + retry: 2, + }, + }, +}) diff --git a/app/src/lib/utils.ts b/app/src/lib/utils.ts new file mode 100644 index 0000000..fed2fe9 --- /dev/null +++ b/app/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/app/src/styles/globals.css b/app/src/styles/globals.css new file mode 100644 index 0000000..3701c31 --- /dev/null +++ b/app/src/styles/globals.css @@ -0,0 +1,58 @@ +@import "tailwindcss"; + +@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap'); + +:root { + --bg: oklch(0.165 0.022 282); + --bg2: oklch(0.205 0.026 282); + --bg3: oklch(0.25 0.03 283); + --bd: oklch(0.33 0.032 286); + --bdSoft: oklch(0.27 0.028 284); + --navbg: oklch(0.18 0.024 282 / .82); + --tx: oklch(0.96 0.008 285); + --mut: oklch(0.68 0.022 284); + --radius: 13px; + --acc: #9945FF; + --acc2: #14F195; + --acc2light: #7df5c4; + --accGlow: rgba(153,69,255,.55); + --accSoft: rgba(153,69,255,.13); + --accBd: rgba(153,69,255,.4); + --danger: #FF7A59; + --dangerSoft: rgba(255,122,89,.12); + --font-body: 'Space Grotesk', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', monospace; +} + +/* Tailwind theme tokens for connectorkit components */ +@theme { + --color-primary: var(--acc); + --color-primary-foreground: #0b0613; + --color-background: var(--bg); + --color-foreground: var(--tx); + --color-popover: var(--bg2); + --color-popover-foreground: var(--tx); + --color-border: var(--bd); + --color-input: var(--bd); + --color-muted: var(--bg3); + --color-accent: var(--bg3); + --color-accent-foreground: var(--tx); + --color-destructive: var(--danger); +} + +* { box-sizing: border-box; } +body { + margin: 0; + background: var(--bg); + color: var(--tx); + font-family: var(--font-body); +} +input, button, select, textarea { font-family: inherit; font-size: inherit; color: inherit; } +button { cursor: pointer; border: none; background: none; } +::selection { background: rgba(153,69,255,.35); } +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-thumb { background: rgba(255,255,255,.13); border-radius: 8px; } +::-webkit-scrollbar-track { background: transparent; } +@keyframes scToast { from { opacity: 0; transform: translate(-50%,10px); } to { opacity: 1; transform: translate(-50%,0); } } +@keyframes scSpin { to { transform: rotate(360deg); } } +@keyframes scPulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }