// Student portal · Signals feed (read-only). window.SignalsFeed // Shown at the top of the dashboard to PAID students (basic/inter/adv). // Polls every 15s; chimes + toast on a fresh signal. Same signal rows the // coaches see; students cannot edit — they just read and (if pushed) also // get the Telegram DM from n8n. (function(){ const { useState, useEffect, useRef } = React; const SOUND_KEY='cg_signal_sound'; function soundOn(){ return localStorage.getItem(SOUND_KEY)!=='0'; } function chime(){ if(!soundOn()) return; try{ const AC=window.AudioContext||window.webkitAudioContext; if(!AC) return; const ac=chime._ac||(chime._ac=new AC()); if(ac.state==='suspended') ac.resume(); const now=ac.currentTime; [[784,0],[1046.5,0.1],[1318.5,0.2]].forEach(p=>{ const o=ac.createOscillator(),g=ac.createGain(); o.type='sine'; o.frequency.value=p[0]; o.connect(g); g.connect(ac.destination); const t=now+p[1]; g.gain.setValueAtTime(0.0001,t); g.gain.exponentialRampToValueAtTime(0.13,t+0.02); g.gain.exponentialRampToValueAtTime(0.0001,t+0.24); o.start(t); o.stop(t+0.26); }); }catch(e){} } function toast(text){ const el=document.createElement('div'); el.textContent=text; el.style.cssText='position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:9999;background:var(--navy-900);color:var(--ink);border:1px solid var(--border-gold);border-radius:999px;padding:11px 20px;font-family:var(--font-mono);font-size:.82rem;box-shadow:0 12px 40px rgba(0,0,0,.5)'; document.body.appendChild(el); setTimeout(()=>{ el.style.transition='opacity .5s'; el.style.opacity='0'; setTimeout(()=>el.remove(),500); },4200); } const fmtWhen = d => d ? new Date(d).toLocaleString(undefined,{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}) : ''; const STATUS_META={ open:{label:'Open',color:'var(--gold-2)',bg:'var(--gold-soft)'}, tp1_hit:{label:'TP1 hit — running',color:'#4a9eda',bg:'rgba(74,158,218,.12)'}, tp2_hit:{label:'TP2 hit — full win ✓',color:'var(--green)',bg:'rgba(46,160,90,.12)'}, sl_hit:{label:'SL hit ✕',color:'var(--red)',bg:'rgba(214,69,69,.12)'}, cancelled:{label:'Cancelled',color:'var(--ink-faint)',bg:'var(--surface-2)'}, closed:{label:'Closed',color:'#7aa2f7',bg:'rgba(122,162,247,.12)'} }; function Row({r, onImg}){ const sm=STATUS_META[r.status]||STATUS_META.open; const dc=r.direction==='buy'?'var(--green)':'var(--red)'; const faded = r.status==='cancelled'; return
{r.photo_url && chartonImg(r.photo_url)} style={{width:120,height:84,objectFit:'cover',borderRadius:'var(--radius-sm)',border:'1px solid var(--border)',cursor:'zoom-in'}}/>}
{r.pair} {r.direction==='buy'?'▲ BUY':'▼ SELL'} {r.timeframe && {r.timeframe}} {sm.label}
{['Entry '+(r.entry||'—'),'SL '+(r.stop_loss||'—'),'TP1 '+(r.take_profit_1||r.take_profit||'—'),r.take_profit_2?('TP2 '+r.take_profit_2):null,r.confidence?('Conf '+r.confidence.toUpperCase()):null,r.risk_note?('Risk '+r.risk_note):null].filter(Boolean).join(' · ')}
{r.message &&
{r.message}
} {r.result_note &&
Note: {r.result_note}
}
{fmtWhen(r.created_at)} · {r.coach_name||'Coach'}
; } function SignalsFeed({client, eligible}){ const [rows,setRows]=useState(null); const [limit,setLimit]=useState(6); const [collapsed,setCollapsed]=useState(false); const [zoom,setZoom]=useState(null); const seen=useRef(new Set()); const first=useRef(true); async function load(){ const { data } = await client.from('signals').select('*').order('created_at',{ascending:false}).limit(50); if(!data) return; if(first.current){ data.forEach(r=>seen.current.add(r.id)); first.current=false; } else { const fresh=data.filter(r=>!seen.current.has(r.id)); data.forEach(r=>seen.current.add(r.id)); if(fresh.length){ chime(); toast('📊 New signal · '+fresh[0].pair+' '+fresh[0].direction.toUpperCase()); } } setRows(data); } useEffect(()=>{ if(!eligible) return; load(); const t=setInterval(load,15000); return ()=>clearInterval(t); },[eligible]); if(!eligible) return null; if(rows===null) return
Loading signals…
; if(rows.length===0) return null; return
📊
VIP Signals
Live calls from your coaches · also sent to your Telegram
{!collapsed &&
{rows.slice(0,limit).map(r=>)} {rows.length>limit && }
} {zoom &&
setZoom(null)} style={{position:'fixed',inset:0,zIndex:300,background:'rgba(4,7,14,.85)',backdropFilter:'blur(6px)',display:'grid',placeItems:'center',padding:24,cursor:'zoom-out'}}> chart
}
; } window.SignalsFeed = SignalsFeed; })();