import { useState } from 'react';
import { Link } from 'react-router-dom';
import { useTheme } from '../hooks/useTheme';
import { useAccent, ACCENTS } from '../hooks/useAccent';
import { useT } from '../i18n/LanguageContext';
import { useIsAdmin } from '../hooks/useIsAdmin';
import { LANGUAGES } from '../i18n/translations';
const PRICE_FMT_KEY = 'sz_price_format';
type PriceFmt = 'pct' | 'cents';
function getStoredPriceFmt(): PriceFmt {
try { return (localStorage.getItem(PRICE_FMT_KEY) as PriceFmt) || 'pct'; } catch { return 'pct'; }
}
export default function SettingsPage() {
const { theme, toggleTheme } = useTheme();
const { accent, setAccent } = useAccent();
const { t, lang, setLang } = useT();
const isAdmin = useIsAdmin();
const [priceFmt, setPriceFmt] = useState<PriceFmt>(getStoredPriceFmt);
const [cleared, setCleared] = useState(false);
const handlePriceFmt = (fmt: PriceFmt) => {
setPriceFmt(fmt);
try { localStorage.setItem(PRICE_FMT_KEY, fmt); } catch { /* ignore */ }
};
const clearCache = () => {
try {
const theme = localStorage.getItem('sz_theme');
const savedLang = localStorage.getItem('sz_lang');
const savedAccent = localStorage.getItem('sz_accent');
localStorage.clear();
if (theme) localStorage.setItem('sz_theme', theme);
if (savedLang) localStorage.setItem('sz_lang', savedLang);
if (savedAccent) localStorage.setItem('sz_accent', savedAccent);
setCleared(true);
setTimeout(() => setCleared(false), 2000);
} catch { /* ignore */ }
};
const [updating, setUpdating] = useState(false);
// Force the latest build: workbox caches the JS bundle in Cache Storage, and an
// already-installed SW keeps serving it โ so unregistering alone often reloads into
// the SAME stale version. Nuke Cache Storage too, then unregister, then hard reload.
const clearSW = async () => {
setUpdating(true);
try {
if ('caches' in window) {
const keys = await caches.keys();
await Promise.all(keys.map(k => caches.delete(k)));
}
if ('serviceWorker' in navigator) {
const regs = await navigator.serviceWorker.getRegistrations();
for (const r of regs) await r.unregister();
}
} catch { /* ignore */ }
window.location.reload();
};
return (
<div className="set-page">
<h2 className="set-title">{t('settings.title')}</h2>
{/* Appearance */}
<section className="set-section">
<h3 className="set-section-title">{t('settings.appearance')}</h3>
<div className="set-row" onClick={toggleTheme}>
<div className="set-row-info">
<span className="set-row-label">{t('settings.theme')}</span>
<span className="set-row-desc">{t('settings.theme.desc')}</span>
</div>
<div className="set-toggle-wrap">
<span className="set-toggle-val">{theme === 'dark' ? t('settings.dark') : t('settings.light')}</span>
<div className={`set-toggle ${theme === 'light' ? 'set-toggle-on' : ''}`}>
<div className="set-toggle-knob" />
</div>
</div>
</div>
<div className="set-row">
<div className="set-row-info">
<span className="set-row-label">{t('settings.language')}</span>
<span className="set-row-desc">{t('settings.language.desc')}</span>
</div>
<div className="set-pills">
{LANGUAGES.map(l => (
<button
key={l.code}
className={`pill pill-sm ${lang === l.code ? 'pill-active' : ''}`}
onClick={() => setLang(l.code)}
>
{l.label}
</button>
))}
</div>
</div>
<div className="set-row">
<div className="set-row-info">
<span className="set-row-label">{t('settings.accent')}</span>
<span className="set-row-desc">{t('settings.accent.desc')}</span>
</div>
<div className="accent-swatches">
{ACCENTS.map(a => (
<div
key={a.id}
className={`accent-sw ${accent === a.id ? 'accent-sw-on' : ''}`}
style={{ background: a.color }}
onClick={() => setAccent(a.id)}
title={a.label}
/>
))}
</div>
</div>
</section>
{/* Display */}
<section className="set-section">
<h3 className="set-section-title">{t('settings.display')}</h3>
<div className="set-row">
<div className="set-row-info">
<span className="set-row-label">{t('settings.priceFormat')}</span>
<span className="set-row-desc">{t('settings.priceFormat.desc')}</span>
</div>
<div className="set-pills">
<button className={`pill pill-sm ${priceFmt === 'pct' ? 'pill-active' : ''}`}
onClick={() => handlePriceFmt('pct')}>65%</button>
<button className={`pill pill-sm ${priceFmt === 'cents' ? 'pill-active' : ''}`}
onClick={() => handlePriceFmt('cents')}>65ยข</button>
</div>
</div>
</section>
{/* Data */}
<section className="set-section">
<h3 className="set-section-title">{t('settings.data')}</h3>
<div className="set-row" onClick={clearCache}>
<div className="set-row-info">
<span className="set-row-label">{t('settings.clearData')}</span>
<span className="set-row-desc">{t('settings.clearData.desc')}</span>
</div>
<button className="set-btn">{cleared ? t('settings.cleared') : t('settings.clear')}</button>
</div>
<div className="set-row" onClick={clearSW}>
<div className="set-row-info">
<span className="set-row-label">{t('settings.resetPwa')}</span>
<span className="set-row-desc">{t('settings.resetPwa.desc')}</span>
</div>
<button className="set-btn" disabled={updating}>{updating ? t('settings.updating') : t('settings.reset')}</button>
</div>
</section>
{/* Admin โ Builder Analytics (allowlisted emails only) */}
{isAdmin && (
<section className="set-section">
<h3 className="set-section-title">{t('settings.admin')}</h3>
<Link to="/analytics/szhub2026" className="set-row" style={{ textDecoration: 'none' }}>
<div className="set-row-info">
<span className="set-row-label">Builder Analytics</span>
<span className="set-row-desc">{t('settings.adminDesc')}</span>
</div>
<span className="set-btn">{t('settings.open')}</span>
</Link>
<Link to="/analytics/users" className="set-row" style={{ textDecoration: 'none' }}>
<div className="set-row-info">
<span className="set-row-label">User Analytics</span>
<span className="set-row-desc">{t('settings.userAnalyticsDesc')}</span>
</div>
<span className="set-btn">{t('settings.open')}</span>
</Link>
</section>
)}
{/* About */}
<section className="set-section">
<h3 className="set-section-title">{t('settings.about')}</h3>
<div className="set-about">
<div className="set-about-row">
<span>{t('settings.version')}</span><span>0.2.0</span>
</div>
<div className="set-about-row">
<span>{t('settings.dataSource')}</span><span>Polymarket Gamma API</span>
</div>
<div className="set-about-row">
<span>{t('settings.builder')}</span><span>SZHub</span>
</div>
<div className="set-about-row">
<span>{t('settings.apiStatus')}</span>
<span className="set-status-dot" />
</div>
</div>
<p className="set-disclaimer">{t('settings.disclaimer')}</p>
</section>
</div>
);
}
๐ Git History
03a2d80chore(save): session 2026-06-22 โ Phase2 redesign + 3mo history + realized-PnL fix; staged _impl edits11 days ago
Show last diff
Loading...