Cloche — Influencer Trial Codes

Admin

Admin Access

Log in as an admin to manage influencer codes.

function getCookie(n){for(var c=document.cookie.split(";"),i=0;ie.json()).then(e=>{e.success&&renderData(e)})}function renderData(n){allCodes=n.codes||[];const e=n.stats||{};document.getElementById("statTotal").textContent=e.total||0,document.getElementById("statAvail").textContent=e.available||0,document.getElementById("statRedeemed").textContent=e.redeemed||0,document.getElementById("statActive").textContent=e.active_trials||0,document.getElementById("statConverted").textContent=e.converted||0,renderTable(allCodes)}function filterTable(){const n=document.getElementById("searchInput").value.toLowerCase(),e=allCodes.filter(t=>t.code.toLowerCase().includes(n)||(t.influencer_name||"").toLowerCase().includes(n)||(t.influencer_handle||"").toLowerCase().includes(n)||(t.redeemed_by_email||"").toLowerCase().includes(n));renderTable(e)}function renderTable(n){const e=document.getElementById("codesBody");if(!n.length){e.innerHTML='

No codes yet. Generate one on the left!

';return}e.innerHTML=n.map(t=>{const r=!t.is_redeemed,i=t.converted,d=t.is_redeemed&&!i&&t.days_remaining>0,c=t.is_redeemed&&!i&&t.days_remaining<=0;let o;r?o='Available':i?o=' Converted':d?o=`Active \xB7 ${t.days_remaining}d left`:o='Trial Ended';const l=d?`
`:"",a=t.influencer_trial_end_date?new Date(t.influencer_trial_end_date).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}):"\u2014",s=t.redeemed_at?new Date(t.redeemed_at).toLocaleDateString("en-US",{month:"short",day:"numeric"}):"\u2014",u=t.redeem_url||"";return`
${t.code}
${escHtml(t.influencer_name||"\u2014")}
${t.influencer_handle?`
${escHtml(t.influencer_handle)}
`:""} ${t.notes?`
${escHtml(t.notes.substring(0,30))}${t.notes.length>30?"\u2026":""}
`:""} ${o} ${t.redeemed_by_email?`
${escHtml(t.redeemed_by_email)}
${s}
`:'\u2014'} ${a!=="\u2014"?`
${a}
${l}`:'\u2014'} ${i?'
Paid
':d?``:'\u2014'} `}).join("")}function copyText(n,e){navigator.clipboard.writeText(n).then(()=>{const t=e.textContent;e.innerHTML=' Copied!',e.classList.add("copied"),setTimeout(()=>{e.textContent=t,e.classList.remove("copied")},1800)})}function escHtml(n){return n?n.replace(/&/g,"&").replace(//g,">").replace(/"/g,"""):""}async function generateCodes(){const n=document.getElementById("infName").value.trim();if(!n){toast(' Influencer name is required');return}const e=document.getElementById("infHandle").value.trim(),t=document.getElementById("infNotes").value.trim(),r=parseInt(document.getElementById("infCount").value)||1,i=document.getElementById("genBtn");i.disabled=!0,i.innerHTML=' Generating\u2026';try{const d=r>1?"/api/admin/influencer-codes/bulk":"/api/admin/influencer-codes",c={"Content-Type":"application/json"};_tk&&(c.Authorization="Bearer "+_tk);const o=await(await fetch(d,{method:"POST",credentials:"include",headers:c,body:JSON.stringify({influencer_name:n,influencer_handle:e||void 0,notes:t||void 0,count:r})})).json();if(!o.success){toast(' '+(o.message||"Failed"));return}const l=o.codes?o.codes:[o.code],a=document.getElementById("resultCodesWrap");a.innerHTML=l.map(s=>`
${s.code}
${s.redeem_url||window.location.origin+"/redeem?code="+s.code}
`).join('
'),document.getElementById("codeResult").style.display="block",document.getElementById("infName").value="",document.getElementById("infHandle").value="",document.getElementById("infNotes").value="",document.getElementById("infCount").value="1",toast(` ${l.length} code${l.length>1?"s":""} created for ${n}`),loadCodes()}catch{toast(' Network error')}finally{i.disabled=!1,i.innerHTML="+ Generate Code"}}async function markConverted(n){try{const e=_tk?{Authorization:"Bearer "+_tk}:{};await fetch(`/api/admin/influencer-codes/${n}/mark-converted`,{method:"PATCH",credentials:"include",headers:e}),toast(' Marked as converted'),loadCodes()}catch{toast(' Failed')}}function toast(n){const e=document.getElementById("toast");e.textContent=n,e.classList.add("show"),setTimeout(()=>e.classList.remove("show"),3e3)}const urlParams=new URLSearchParams(window.location.search),urlSecret=urlParams.get("admin_secret")||urlParams.get("s");urlSecret&&(document.getElementById("secretInput").value=urlSecret,login());