โ† Back
โ˜†
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...