SPB Git

Repository Public

Settings.tsx 5.7 KB 55 lines tsx

1import { useContext, useState } from 'react';2import type { Tab } from '../App';3import { useNetworks, useSettings } from '../hooks';4import { applyTheme } from '../theme';5import { I18n, useT } from '../i18n';6import { Btn, Card, H, Icon } from '../ui';78function Seg<T extends string>({ value, options, onChange, label }: { value: T; options: [T, string][]; onChange: (v: T) => void; label: string }) {9  return <div className="flex rounded-2xl bg-elev p-1" role="radiogroup" aria-label={label}>{options.map(([v, l]) =>10    <button key={v} role="radio" aria-checked={value === v} onClick={() => onChange(v)} className={`min-h-11 flex-1 rounded-xl text-[14px] ${value === v ? 'bg-surface font-semibold shadow-sm' : 'text-mute'}`}>{l}</button>)}</div>;11}12function Toggle({ on, onChange, label }: { on: boolean; onChange: (v: boolean) => void; label: string }) {13  return <button role="switch" aria-checked={on} aria-label={label} onClick={() => onChange(!on)} className="grid h-11 w-16 shrink-0 place-items-center"><span className={`relative block h-8 w-14 rounded-full transition ${on ? 'bg-emerald' : 'bg-line'}`}><span className={`absolute top-1 h-6 w-6 rounded-full bg-white transition-all ${on ? 'left-7' : 'left-1'}`} /></span></button>;14}1516export function Settings({ lock, go }: { lock: () => void; go: (t: Tab) => void }) {17  const t = useT(); const { locale, setLocale } = useContext(I18n); const { settings, update } = useSettings(); const nets = useNetworks();18  const [theme, setTheme] = useState<'light' | 'dark'>(document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light');19  return (20    <div className="space-y-4">21      <h1 className="pt-2 text-[30px] font-semibold tracking-[-0.02em]">{t('settings')}</h1>22      <button onClick={() => go('security')} className="card flex w-full items-center gap-3.5 p-4 text-left">23        <span className="grid h-11 w-11 place-items-center rounded-full bg-elev"><Icon n="shield" /></span>24        <span className="flex-1 text-[15.5px] font-semibold">{t('securityLink')}</span><Icon n="chev" className="h-5 w-5 text-mute" /></button>25      {/* v6-alerts: entry points that leave the five phone tabs as they are */}26      <button onClick={() => go('alerts')} className="card flex w-full items-center gap-3.5 p-4 text-left">27        <span className="grid h-11 w-11 place-items-center rounded-full bg-elev"><Icon n="bell" /></span>28        <span className="flex-1 text-[15.5px] font-semibold">{t('alTitle')}</span><Icon n="chev" className="h-5 w-5 text-mute" /></button>29      <button onClick={() => go('monitor')} className="card flex w-full items-center gap-3.5 p-4 text-left">30        <span className="grid h-11 w-11 place-items-center rounded-full bg-elev"><Icon n="bars" /></span>31        <span className="flex-1 text-[15.5px] font-semibold">{t('moTitle')}</span><Icon n="chev" className="h-5 w-5 text-mute" /></button>32      <button onClick={() => go('agents')} className="card flex w-full items-center gap-3.5 p-4 text-left lg:hidden">33        <span className="grid h-11 w-11 place-items-center rounded-full bg-elev"><Icon n="key" /></span>34        <span className="flex-1 text-[15.5px] font-semibold">{t('agents')}</span><Icon n="chev" className="h-5 w-5 text-mute" /></button>35      <Card className="space-y-6">36        <div><H>{t('language')}</H><Seg label={t('language')} value={locale} options={[['fr', 'Français'], ['en', 'English']]} onChange={(l) => { setLocale(l); void update({ locale: l }); }} /></div>37        <div><H>{t('currency')}</H><Seg label={t('currency')} value={settings.currency} options={[['CAD', 'CAD'], ['USD', 'USD']]} onChange={(c) => void update({ currency: c })} /></div>38        <div><H>{t('theme')}</H><Seg label={t('theme')} value={theme} options={[['light', t('light')], ['dark', t('dark')]]} onChange={(v) => { localStorage.setItem('spw-theme', v); applyTheme(v); setTheme(v); }} /></div>39        <div className="flex items-center justify-between gap-4"><span>{t('hide')}</span><Toggle label={t('hide')} on={settings.hideBalances} onChange={(v) => void update({ hideBalances: v })} /></div>40        <div className="flex items-center justify-between gap-4"><span><span className="block">{t('prices')}</span><span className="block text-xs text-mute">{t('pricesNote')}</span></span><Toggle label={t('prices')} on={settings.prices} onChange={(v) => void update({ prices: v })} /></div>41      </Card>42      <Card><H>{t('rpcStatus')}</H>43        <ul className="space-y-3 text-[13px]">{(nets.data ?? []).map((n) => <li key={n.id}>44          <div className="flex items-center justify-between"><span className="font-medium">{n.name}</span><span className={`inline-flex items-center gap-1 text-xs ${n.health?.identityVerified ? 'text-emerald' : 'text-warn'}`}><Icon n={n.health?.identityVerified ? 'check' : 'warn'} className="h-3.5 w-3.5" />{n.health?.identityVerified ? t('identityOk') : t('identityKo')}</span></div>45          <div className="mt-1 space-y-0.5 text-xs text-mute">{(n.health?.endpoints ?? []).map((e) => <div key={e.url} className="flex justify-between gap-3"><span className="truncate font-mono">{e.url}</span><span className={e.ok ? '' : 'text-warn'}>{e.ok ? `${e.ms} ms` : t('unavailable')}</span></div>)}46            {n.health?.height && <div>{t('block')} {n.health.height.toLocaleString('en-US')}</div>}</div></li>)}</ul></Card>47      <Card className="space-y-3">48        <a href="/api/v1/export/activity.csv" className="inline-flex min-h-[50px] w-full items-center justify-center gap-2 rounded-2xl bg-elev px-4 text-[15.5px] font-medium"><Icon n="down" className="h-4 w-4" />{t('exportCsv')}</a>49        <Btn kind="ghost" full onClick={lock}><Icon n="lock" />{t('logout')}</Btn>50        <p className="text-center text-xs text-mute">SP Wallet · {t('version')} 1.0.0 · {t('mainnet')}</p>51      </Card>52    </div>53  );54}55
Files main
Menu

Theme