← Back
import { useState, useEffect } from 'react';
import { usePrivy } from '@privy-io/react-auth';
import { DEFAULT_CONFIG } from '../../hooks/useCopySubscriptions';
import type { CopyConfig, AllocMode } from '../../hooks/useCopySubscriptions';
import { useT } from '../../i18n/LanguageContext';
import '../../redesign/editorial.css';

const ALLOC_MODES: { key: AllocMode; labelKey: string }[] = [
  { key: 'percent', labelKey: 'ccm.modePercent' },
  { key: 'fixed', labelKey: 'ccm.modeFixed' },
  { key: 'proportional', labelKey: 'ccm.modeProp' },
];

const CATEGORIES = ['Sports', 'Crypto', 'Politics', 'Economy', 'Pop Culture'];

// One-time risk acceptance, persisted locally PER USER (so a second tester on a shared
// browser still gets the disclaimer). Bump the suffix to re-prompt everyone.
const RISK_KEY_BASE = 'polycopy_risk_accepted_v1';

// Number input that allows fully clearing the field while typing (standard UX).
// Holds a local string buffer; commits a number on change, reverts to last value on empty blur.
function NumInput({ value, onChange, className, min, max }: {
  value: number;
  onChange: (v: number) => void;
  className?: string;
  min?: number;
  max?: number;
}) {
  const [text, setText] = useState(String(value));
  useEffect(() => { setText(String(value)); }, [value]);
  return (
    <input
      type="number"
      min={min}
      max={max}
      className={className}
      value={text}
      onChange={e => {
        setText(e.target.value);
        if (e.target.value !== '') {
          // clamp to min/max so out-of-range / negative input can't reach config (server also validates)
          let v = Number(e.target.value);
          if (Number.isFinite(v)) {
            if (min != null) v = Math.max(min, v);
            if (max != null) v = Math.min(max, v);
            onChange(v);
          }
        }
      }}
      onBlur={() => { if (text === '') setText(String(value)); }}
    />
  );
}

interface Props {
  leaderLabel: string;
  initial?: CopyConfig;
  editing?: boolean;
  onSave: (config: CopyConfig) => void;
  onClose: () => void;
}

export default function CopyConfigModal({ leaderLabel, initial, editing, onSave, onClose }: Props) {
  const { t, lang } = useT();
  const { user } = usePrivy();
  const riskKey = `${RISK_KEY_BASE}:${(user?.id || 'anon').toLowerCase()}`;
  const [c, setC] = useState<CopyConfig>(initial ?? DEFAULT_CONFIG);
  const [confirming, setConfirming] = useState(false);
  const [accepted, setAccepted] = useState(false);
  const set = <K extends keyof CopyConfig>(k: K, v: CopyConfig[K]) => setC(p => ({ ...p, [k]: v }));

  // New subscriptions must pass a one-time (per-user) risk disclosure; editing an existing one skips it.
  const handleStart = () => {
    if (editing || localStorage.getItem(riskKey) === '1') { onSave(c); return; }
    setConfirming(true);
  };
  const confirmRisk = () => {
    try { localStorage.setItem(riskKey, '1'); } catch { /* ignore */ }
    onSave(c);
  };

  const toggleCat = (cat: string) =>
    set('categories', c.categories.includes(cat)
      ? c.categories.filter(x => x !== cat)
      : [...c.categories, cat]);

  return (
    <div className="ccm-overlay pk-ccm" onClick={onClose}>
      <div className="ccm-modal" onClick={e => e.stopPropagation()}>
        {confirming ? (
          <>
            <div className="ccm-head">
              <span className="ccm-title">{t('risk.title')}</span>
              <button className="ccm-x" onClick={() => setConfirming(false)}>✕</button>
            </div>
            <div className="ccm-body">
              <p className="ccm-hint">{t('risk.p1')}</p>
              <p className="ccm-hint">{t('risk.p2')}</p>
              <p className="ccm-hint">{t('risk.p3')}</p>
              <p className="ccm-hint">{t('risk.p4')}</p>
              <label className="ccm-field ccm-field-row" style={{ gap: 8, cursor: 'pointer' }}>
                <input type="checkbox" checked={accepted} onChange={e => setAccepted(e.target.checked)} />
                <span className="ccm-label">{t('risk.accept')}</span>
              </label>
            </div>
            <div className="ccm-foot">
              <button className="ccm-cancel" onClick={() => setConfirming(false)}>{t('ccm.cancel')}</button>
              <button className="ccm-save" disabled={!accepted} onClick={confirmRisk}>{t('risk.confirm')}</button>
            </div>
          </>
        ) : (
        <>
        <div className="ccm-head">
          <span className="ccm-title">{editing ? t('ccm.titleEdit') : t('ccm.titleNew')} · {leaderLabel}</span>
          <button className="ccm-x" onClick={onClose}>✕</button>
        </div>

        <div className="ccm-body">
          {/* Direction: COPY (follow) vs FADE (bet against) — counter-trading, chunk B */}
          <div className="ccm-field">
            <label className="ccm-label">{lang === 'ru' ? 'Режим' : 'Mode'}</label>
            <div className="ccm-seg">
              <button className={`ccm-seg-btn ${(c.direction ?? 'copy') === 'copy' ? 'active' : ''}`}
                onClick={() => set('direction', 'copy')}>⇄ COPY</button>
              <button className={`ccm-seg-btn pk-seg-fade ${c.direction === 'fade' ? 'active' : ''}`}
                onClick={() => set('direction', 'fade')}>💀 FADE</button>
            </div>
            {c.direction === 'fade' && (
              <>
                <p className="ccm-hint">{lang === 'ru'
                  ? 'Лидер покупает YES@p → ты покупаешь NO@(1−p). Ставка ПРОТИВ лидера.'
                  : 'Leader buys YES@p → you buy NO@(1−p). Bet AGAINST the leader.'}</p>
                <label className="ccm-label" style={{ marginTop: 10 }}>{lang === 'ru'
                  ? 'Risk-band — не фейдить крайних фаворитов' : 'Risk band — don’t fade extreme favorites'}</label>
                <div className="ccm-inline">
                  <NumInput min={1} max={49} className="ccm-input"
                    value={Math.round((c.fadeMin ?? 0.20) * 100)}
                    onChange={v => set('fadeMin', Math.min(0.49, Math.max(0.01, v / 100)))} />
                  <span className="ccm-unit">¢ min</span>
                  <NumInput min={51} max={99} className="ccm-input"
                    value={Math.round((c.fadeMax ?? 0.80) * 100)}
                    onChange={v => set('fadeMax', Math.min(0.99, Math.max(0.51, v / 100)))} />
                  <span className="ccm-unit">¢ max</span>
                </div>
                <p className="ccm-hint">{lang === 'ru'
                  ? 'Фейдим только если цена лидера в этом диапазоне (по умолч. 20–80¢).'
                  : 'Fade only when the leader price is in this range (default 20–80¢).'}</p>
              </>
            )}
          </div>

          {/* Аллокация */}
          <div className="ccm-field">
            <label className="ccm-label">{t('ccm.alloc')}</label>
            <div className="ccm-seg">
              {ALLOC_MODES.map(m => (
                <button key={m.key}
                  className={`ccm-seg-btn ${c.allocMode === m.key ? 'active' : ''}`}
                  onClick={() => set('allocMode', m.key)}>
                  {t(m.labelKey)}
                </button>
              ))}
            </div>
            {c.allocMode !== 'proportional' && (
              <div className="ccm-inline">
                <NumInput min={0} className="ccm-input"
                  value={c.allocValue}
                  onChange={v => set('allocValue', v)} />
                <span className="ccm-unit">{c.allocMode === 'percent' ? t('ccm.unitPercent') : t('ccm.unitDollar')}</span>
              </div>
            )}
            {c.allocMode === 'proportional' && (
              <p className="ccm-hint">{t('ccm.propHint')}</p>
            )}
          </div>

          {/* Лимиты */}
          <div className="ccm-row2">
            <div className="ccm-field">
              <label className="ccm-label">{t('ccm.maxTrade')}</label>
              <div className="ccm-inline">
                <span className="ccm-unit">$</span>
                <NumInput min={0} className="ccm-input"
                  value={c.maxPerTrade}
                  onChange={v => set('maxPerTrade', v)} />
              </div>
            </div>
            <div className="ccm-field">
              <label className="ccm-label">{t('ccm.maxExp')}</label>
              <div className="ccm-inline">
                <span className="ccm-unit">$</span>
                <NumInput min={0} className="ccm-input"
                  value={c.maxExposure}
                  onChange={v => set('maxExposure', v)} />
              </div>
            </div>
          </div>

          {/* Категории */}
          <div className="ccm-field">
            <label className="ccm-label">{t('ccm.categories')} <span className="ccm-muted">{t('ccm.catEmpty')}</span></label>
            <div className="ccm-chips">
              {CATEGORIES.map(cat => (
                <button key={cat}
                  className={`ccm-chip ${c.categories.includes(cat) ? 'active' : ''}`}
                  onClick={() => toggleCat(cat)}>
                  {cat}
                </button>
              ))}
            </div>
          </div>

          {/* Диапазон цен */}
          <div className="ccm-field">
            <label className="ccm-label">{t('ccm.priceRange')} <span className="ccm-muted">{t('ccm.noLongshots')}</span></label>
            <div className="ccm-inline">
              <NumInput min={1} max={99} className="ccm-input ccm-input-sm"
                value={c.priceMin} onChange={v => set('priceMin', v)} />
              <span className="ccm-unit">¢ —</span>
              <NumInput min={1} max={99} className="ccm-input ccm-input-sm"
                value={c.priceMax} onChange={v => set('priceMax', v)} />
              <span className="ccm-unit">¢</span>
            </div>
          </div>

          {/* Зеркалить выходы */}
          <div className="ccm-field ccm-field-row">
            <label className="ccm-label">{t('ccm.mirror')}</label>
            <button className={`ccm-switch ${c.mirrorExits ? 'on' : ''}`}
              onClick={() => set('mirrorExits', !c.mirrorExits)}>
              <span className="ccm-switch-dot" />
            </button>
          </div>

          {/* Стоп по просадке */}
          <div className="ccm-field">
            <label className="ccm-label">{t('ccm.drawdown')} <span className="ccm-muted">{t('ccm.drawdownOff')}</span></label>
            <div className="ccm-inline">
              <NumInput min={0} max={100} className="ccm-input ccm-input-sm"
                value={c.drawdownStop} onChange={v => set('drawdownStop', v)} />
              <span className="ccm-unit">%</span>
            </div>
          </div>
        </div>

        <div className="ccm-foot">
          <button className="ccm-cancel" onClick={onClose}>{t('ccm.cancel')}</button>
          <button className="ccm-save" onClick={handleStart}>
            {editing ? t('ccm.save') : t('ccm.start')}
          </button>
        </div>
        </>
        )}
      </div>
    </div>
  );
}

📜 Git History

b9c19bfchore(poli): reconcile local Flow/Insider/manual-trade work with deployed state3 days ago
1587787feat(poli): chunk B3 — FADE direction + risk-band in CopyConfigModal10 days ago
edefc19feat(poli): editorial Copy Config modal reskin (chunk 6)11 days ago
6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...