import { getParentDashboard, saveSessionFeedbackResponse, submitPlayerFilmLink, createPlayerFilmUpload, completePlayerFilmUpload } from 'backend/portalData.web'; $w.onReady(function () { const dashboard = $w('#html1'); let loaded = false; async function loadDashboard(force = false) { if (loaded && !force) return; loaded = true; try { const data = await getParentDashboard(); dashboard.postMessage({ type: 'PORTAL_DATA', players: data.players || [] }); } catch (error) { loaded = false; dashboard.postMessage({ type: 'PORTAL_ERROR', message: 'Please sign in with the parent account connected to your Academy player.' }); } } async function refreshDashboard() { loaded = false; await loadDashboard(true); } dashboard.onMessage(async (event) => { const message = event.data || {}; try { if (message.type === 'PORTAL_READY') await loadDashboard(); if (message.type === 'PLAYER_NOTE_RESPONSE') { await saveSessionFeedbackResponse({ noteId: message.noteId, reply: message.reply, rating: message.rating }); await refreshDashboard(); } if (message.type === 'PLAYER_FILM_LINK_SUBMIT') { await submitPlayerFilmLink(message); await refreshDashboard(); } if (message.type === 'PLAYER_FILM_UPLOAD_REQUEST') { const upload = await createPlayerFilmUpload(message); dashboard.postMessage({ type: 'PLAYER_FILM_UPLOAD_URL', ...upload }); } if (message.type === 'PLAYER_FILM_UPLOAD_COMPLETE') { await completePlayerFilmUpload(message.submissionId); await refreshDashboard(); } } catch (error) { dashboard.postMessage({ type: 'PLAYER_ACTION_ERROR', message: error?.message || 'Could not complete this action.' }); } }); loadDashboard(); });
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style> *{box-sizing:border-box}body{margin:0;background:#f5f6f8;color:#20242a;font:14px Arial}.s{max-width:1200px;margin:auto;padding:18px}.hero{background:#141518;color:white;border-radius:14px;padding:20px;display:flex;justify-content:space-between}.hero h1{margin:3px 0;font-size:28px}.hero p{margin:0;color:#d5d7dc}.tag,.btn{background:#ed1c24;color:white;border:0;border-radius:8px;padding:10px 13px;font-weight:bold}.tag{height:34px;font-size:11px}.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin:12px 0}.stat,.card,.note{background:white;border:1px solid #e2e5ea;border-radius:12px;padding:14px}.stat b{display:block;font-size:22px}.stat span,.muted{color:#667085;font-size:12px}.work{display:grid;grid-template-columns:1fr 1.08fr;gap:12px}.work>.card:first-child{max-height:620px;overflow:auto}.tools{display:flex;gap:7px;margin-bottom:10px}.tools input{flex:1}input,select,textarea{width:100%;border:1px solid #cfd4dc;border-radius:7px;padding:10px;font:inherit;background:white}textarea{min-height:92px}.list{max-height:none}.row{border:1px solid #e2e5ea;border-radius:9px;padding:11px;margin:6px 0;display:flex;justify-content:space-between;cursor:pointer}.row.on{border-color:#ed1c24}.name,label{font-weight:bold}.pill{font-size:10px;font-weight:bold;background:#edf0f4;border-radius:20px;padding:4px 7px;height:22px}.Active{background:#e8f7f1;color:#16865b}.Invited{background:#fff3df;color:#955600}.head,.acts{display:flex;justify-content:space-between;align-items:center}.head h2{margin:0 0 4px}.grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.field label{display:block;font-size:12px;margin-bottom:5px;color:#475467}.wide{grid-column:1/-1}.help{background:#fff8e9;color:#795000;border-radius:8px;padding:10px;margin-top:10px;font-size:12px;line-height:1.45}.acts{margin-top:12px}.right{display:flex;align-items:center;gap:7px}.secondary{background:#edf0f4;color:#20242a}.danger{background:#fff0f0;color:#b20f16;border:1px solid #ffc7ca}.status{font-size:12px;color:#667085}.ok{color:#16865b}.error{color:#b20f16}.hide{display:none}@media(max-width:1000px){body{font-size:16px}.s{padding:10px}.hero{padding:17px;display:block}.hero h1{font-size:29px;line-height:1.1}.hero p{font-size:15px;line-height:1.4}.tag{display:inline-block;height:auto;margin-top:12px;font-size:12px}.stats{grid-template-columns:1fr 1fr;gap:8px;margin:10px 0}.stat{padding:13px}.stat b{font-size:25px}.stat span,.muted{font-size:13px}.work{grid-template-columns:1fr;gap:10px}.work>.card:first-child{height:auto;max-height:none;overflow:visible}.card,.note{padding:13px}.grid{grid-template-columns:1fr;gap:12px}.wide{grid-column:auto}.tools{display:grid;grid-template-columns:1fr auto;gap:8px}.tools input{grid-column:1/-1;min-width:100%}.tools select{min-width:0}.btn,input,select,textarea{min-height:46px;font-size:16px}.btn{padding:11px 15px}.row{padding:14px 12px;margin:8px 0}.name{font-size:16px}.pill{height:auto;font-size:12px;padding:5px 9px}.head{align-items:flex-start}.head h2{font-size:24px}.field label{font-size:14px}.help{font-size:14px;padding:12px}.acts{align-items:stretch;gap:10px}.right{flex-wrap:wrap;justify-content:flex-end}.status{width:100%;font-size:14px}.right .btn{flex:1}.danger{min-width:100px}} </style></head><body><div class="s"><div class="hero"><div><small>SOCCER SPEED TRAINING ACADEMY</small><h1>Admin Players</h1><p>Create profiles, assign coaches, and connect parent logins.</p></div><b class="tag">ADMIN ONLY</b></div><div id="stats" class="stats"></div><main id="app" class="note">Loading Academy roster…</main></div><script> const $=x=>document.getElementById(x),esc=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const COACHES=[ {id:'65ea13df-34f6-49f2-a4ab-7d60f2d0afff',name:'Chad Swieca'}, {id:'4f42c947-934f-4149-b1dd-ee6e64096a1f',name:'Colin Kirk'}, {id:'054bf207-eb39-46a9-9bb9-dfff2db7cec5',name:'Quavas Kirk'}, {id:'86fe7762-06c5-4687-b70a-e3c44ae8b6bc',name:'Reid Strain'} ]; let S={players:[],pick:'',search:'',status:'All'}; function blank(){return{id:'',name:'',status:'Active',birthYear:'',location:'',pod:'',playingLevel:'',academyTier:'',primaryPosition:'',dominantFoot:'',coachMemberId:'',guardianMemberId:'',parentEmail:'',relationship:'Parent',developmentSummary:''}} function cur(){return S.players.find(x=>x.id===S.pick)||blank()}function opt(a,v){return a.map(x=>`<option ${x===v?'selected':''}>${x}</option>`).join('')}function field(label,name,p,type='input',wide=''){let value=esc(p[name]||'');return `<div class="field ${wide}"><label>${label}</label><${type} name="${name}" ${name==='name'?'required':''}>${type==='textarea'?value:''}</${type}>`.replace(`<input name="${name}" ${name==='name'?'required':''}></input>`,`<input name="${name}" ${name==='name'?'required':''} value="${value}">`)+'</div>'}function coachOptions(v){let known=COACHES.some(c=>c.id===v);return `<option value="">Unassigned</option>${!known&&v?`<option value="${esc(v)}" selected>Current assignment</option>`:''}${COACHES.map(c=>`<option value="${esc(c.id)}" ${c.id===v?'selected':''}>${esc(c.name)}</option>`).join('')}`} function drawStats(){let a=S.players.filter(x=>x.status==='Active');$('stats').innerHTML=[['Total records',S.players.length],['Active players',a.length],['Need coach',a.filter(x=>!x.coachMemberId).length],['Need parent link',a.filter(x=>!x.guardianMemberId).length]].map(x=>`<div class="stat"><b>${x[1]}</b><span>${x[0]}</span></div>`).join('')} function roster(){let q=S.search.toLowerCase(),rows=S.players.filter(p=>(S.status==='All'||p.status===S.status)&&(!q||[p.name,p.pod,p.location,p.academyTier].join(' ').toLowerCase().includes(q)));return `<section class="card"><div class="tools"><input id="search" placeholder="Search players" value="${esc(S.search)}"><select id="filter">${opt(['All','Active','Invited','Archived'],S.status)}</select><button id="add" class="btn">+ Add</button></div><div class="list">${rows.map(p=>`<div class="row ${p.id===S.pick?'on':''}" data-id="${esc(p.id)}"><div><div class="name">${esc(p.name)}</div><div class="muted">${esc([p.birthYear,p.pod,p.location,p.academyTier].filter(Boolean).join(' · '))}</div></div><span class="pill ${esc(p.status)}">${esc(p.status)}</span></div>`).join('')||'<div class="note">No matching players.</div>'}</div></section>`} function form(){let p=cur();return `<section class="card"><div class="head"><div><h2>${p.id?'Edit':'Add'} Player</h2><span class="muted">Roster, assignments and portal access</span></div>${p.id?`<span class="pill ${esc(p.status)}">${esc(p.status)}</span>`:''}</div><form id="form" data-id="${esc(p.id)}"><div class="grid">${field('Player full name *','name',p,'input','wide')}<div class="field"><label>Assigned coach</label><select name="coachMemberId">${coachOptions(p.coachMemberId)}</select></div><div class="field"><label>Status</label><select name="status">${opt(['Active','Invited','Archived'],p.status)}</select></div>${field('Birth year','birthYear',p)}<div class="field"><label>Location</label><select name="location"><option></option>${opt(['Northbrook','Barrington','West Chicago'],p.location)}</select></div>${field('Age / ability pod','pod',p)}${field('Playing level','playingLevel',p)}<div class="field"><label>Academy tier</label><select name="academyTier"><option></option>${opt(['Bronze','Silver','Gold'],p.academyTier)}</select></div>${field('Primary position','primaryPosition',p)}<div class="field"><label>Dominant foot</label><select name="dominantFoot"><option></option>${opt(['Right','Left','Both'],p.dominantFoot)}</select></div><div class="field"><label>Parent email address</label><input name="parentEmail" type="email" value="${esc(p.parentEmail||'')}" placeholder="parent@example.com"></div><div class="field"><label>Relationship</label><select name="relationship">${opt(['Parent','Guardian','Player'],p.relationship)}</select></div>${field('Development summary','developmentSummary',p,'textarea','wide')}</div><div class="help"><b>Coach assignment:</b> choose an approved coach by name. The correct Wix member ID is saved automatically.<br><b>Parent invitation:</b> enter the parent's email and save. Wix will create or locate the member, email a secure password-setup invitation, and connect the account to this player.</div><div class="acts"><button type="button" id="archive" class="btn danger ${p.id&&p.status!=='Archived'?'':'hide'}">Archive</button><div class="right"><span id="msg" class="status"></span><button type="button" id="clear" class="btn secondary">Clear</button><button class="btn">Save & Invite Parent</button></div></div></form></section>`} function status(t,c='') {let x=$('msg');if(x){x.textContent=t;x.className='status '+c}}function reportHeight(){requestAnimationFrame(()=>parent.postMessage({type:'ADMIN_HEIGHT',height:document.documentElement.scrollHeight+4},'*'))}function render(){drawStats();$('app').className='';$('app').innerHTML=`<div class="work">${roster()}${form()}</div>`;bind();reportHeight()} function bind(){$('search').oninput=e=>{S.search=e.target.value;render()};$('filter').onchange=e=>{S.status=e.target.value;render()};$('add').onclick=$('clear').onclick=()=>{S.pick='';render()};document.querySelectorAll('.row').forEach(x=>x.onclick=()=>{S.pick=x.dataset.id;render()});$('form').onsubmit=e=>{e.preventDefault();status('Saving…');parent.postMessage({type:'ADMIN_PLAYER_SAVE',playerId:e.target.dataset.id,values:Object.fromEntries(new FormData(e.target))},'*')};let a=$('archive');if(a)a.onclick=()=>{if(confirm('Archive this player? Historical data will be kept.'))parent.postMessage({type:'ADMIN_PLAYER_ARCHIVE',playerId:$('form').dataset.id},'*')}} onmessage=e=>{let m=e.data||{};if(m.type==='ADMIN_DATA'){S.players=m.players||[];render()}if(m.type==='ADMIN_SAVED'){S.pick=m.id||S.pick;status(m.inviteSent?'Saved and parent invitation sent.':'Saved.','ok');parent.postMessage({type:'ADMIN_REFRESH'},'*')}if(m.type==='ADMIN_ERROR'){if($('form'))status(m.message||'Action failed.','error');else{$('app').className='note error';$('app').textContent=m.message||'Administrator access is required.'}}};onresize=reportHeight;parent.postMessage({type:'ADMIN_READY'},'*'); </script></body></html>
