commerce-kit 0.36.11 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +2 -2
- package/dist/browser.js.map +1 -1
- package/dist/feedback-toolbar.js +2 -2
- package/dist/feedback-toolbar.js.map +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/feedback-toolbar.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{useEffect as
|
|
2
|
-
`)},H=e=>{let n=e;for(;n;){if(n instanceof HTMLElement&&n.dataset.ynsFeedbackUi==="true")return!0;n=n.parentElement}return!1},Q=new Set(["HTML","HEAD","SCRIPT","STYLE","NOSCRIPT"]),Z=1e4,ee=e=>{let n=new Date(e),r=n.getTime()-Date.now(),s=n.toLocaleString(void 0,{weekday:"short",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"});if(r<=0)return`Any moment now (estimated by ${s})`;let c=Math.ceil(r/6e4);if(c<60)return`~${c} minutes (by ${s})`;let p=Math.round(r/36e5);if(p<24)return`~${p} ${p===1?"hour":"hours"} (by ${s})`;let l=Math.round(r/864e5);return`~${l} ${l===1?"day":"days"} (by ${s})`};function te(){let[e,n]=h(null),[r,s]=h(!0),[c,p]=h(!1),[l,a]=h(null),[d,f]=h(null),[y,S]=h(!1),[$,P]=h(!1),b=M(null);E(()=>{if(b.current=q(),!b.current){s(!1);return}let t=!1,o=new AbortController,m=async()=>{try{let v=await fetch(`${b.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include",signal:o.signal});if(t)return;if(!v.ok){n(null);return}let w=await v.json();if(t)return;n(w),w.sessionStatus==="done"&&window.location.reload()}catch{t||n(null)}finally{t||s(!1)}};m();let u=window.setInterval(m,Z);return()=>{t=!0,o.abort(),window.clearInterval(u)}},[]),E(()=>{!e||e.canComment||(p(!1),a(null),S(!1),f(null))},[e]),E(()=>{if(c)return document.body.style.cursor="crosshair",()=>{document.body.style.cursor=""}},[c]),E(()=>{if(!c)return;let t=document.createElement("div");t.dataset.ynsFeedbackUi="true",t.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483644","border: 2px dashed #10b981","background: rgba(16, 185, 129, 0.08)","border-radius: 3px","display: none","transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s"].join(";");let o=document.createElement("div");o.dataset.ynsFeedbackUi="true",o.textContent="Click to comment",o.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483645","background: #059669","color: #fff","font-size: 11px","font-family: ui-sans-serif, system-ui, sans-serif","padding: 3px 8px","border-radius: 4px","white-space: nowrap","display: none","box-shadow: 0 2px 6px rgba(0,0,0,0.15)"].join(";"),document.documentElement.appendChild(t),document.documentElement.appendChild(o);let m=null,u=w=>{let k=document.elementFromPoint(w.clientX,w.clientY);if(!k||Q.has(k.tagName)||H(k)){t.style.display="none",o.style.display="none",m=null;return}m=k,requestAnimationFrame(()=>{if(m!==k)return;let C=k.getBoundingClientRect();t.style.top=`${C.top}px`,t.style.left=`${C.left}px`,t.style.width=`${C.width}px`,t.style.height=`${C.height}px`,t.style.display="block",o.style.left=`${w.clientX+14}px`,o.style.top=`${w.clientY+14}px`,o.style.display="block"})},v=()=>{t.style.display="none",o.style.display="none",m=null};return document.addEventListener("mousemove",u,!0),document.addEventListener("mouseleave",v),()=>{document.removeEventListener("mousemove",u,!0),document.removeEventListener("mouseleave",v),t.remove(),o.remove()}},[c]),E(()=>{if(!c)return;let t=o=>{let m=o.target;if(!(m instanceof Element)||H(m))return;o.preventDefault(),o.stopPropagation();let u=m.getBoundingClientRect(),v=u.width>0?(o.clientX-u.left)/u.width:.5,w=u.height>0?(o.clientY-u.top)/u.height:.5;a({cssSelector:G(m),pagePath:window.location.pathname,surroundingHtml:K(m),rect:{top:u.top+window.scrollY,left:u.left+window.scrollX,width:u.width,height:u.height},clickX:o.clientX+window.scrollX,clickY:o.clientY+window.scrollY,offsetXRatio:Math.min(1,Math.max(0,v)),offsetYRatio:Math.min(1,Math.max(0,w))}),p(!1)};return document.addEventListener("click",t,{capture:!0}),()=>document.removeEventListener("click",t,{capture:!0})},[c]);let R=async()=>{if(!b.current)return;let t=await fetch(`${b.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include"});if(!t.ok)return;let o=await t.json();n(o)},x=async(t,o)=>{!b.current||!e||!l||!(await fetch(`${b.current}/api/feedback-comments`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({feedbackSessionId:e.feedbackSessionId,content:t,pagePath:l.pagePath,cssSelector:l.cssSelector,surroundingHtml:l.surroundingHtml,offsetXRatio:l.offsetXRatio,offsetYRatio:l.offsetYRatio,...o.length>0?{attachments:o}:{}})})).ok||(a(null),p(!0),await R())},T=async(t,o,m)=>{!b.current||!(await fetch(`${b.current}/api/feedback-comments/${t}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:o,attachments:m})})).ok||(f(null),await R())},I=async t=>{!b.current||!(await fetch(`${b.current}/api/feedback-comments/${t}`,{method:"DELETE",credentials:"include"})).ok||await R()},F=async()=>{if(!(!b.current||!e)&&window.confirm("Finalize this feedback session? You won't be able to add more comments.")){P(!0);try{let t=await fetch(`${b.current}/api/feedback-sessions/${e.feedbackSessionId}/finalize`,{method:"POST",credentials:"include"});if(!t.ok)return;let m=(await t.json().catch(()=>null))?.status??"processing";n(u=>u&&{...u,canComment:!1,sessionStatus:m})}finally{P(!1)}}},_=t=>{if(t.pagePath!==window.location.pathname){window.location.assign(t.pagePath);return}let o=null;try{o=document.querySelector(t.cssSelector)}catch{o=null}o&&(o.scrollIntoView({behavior:"smooth",block:"center"}),f(t.id))};if(r||!e)return null;if(!e.canComment)return e.sessionStatus!=="processing"&&e.sessionStatus!=="in_review"?null:i("div",{"data-yns-feedback-ui":"true",children:i(ne,{progress:e.progress,eta:e.eta,status:e.sessionStatus})});let A=e.comments.filter(t=>t.pagePath===window.location.pathname&&t.status!=="done");return g("div",{"data-yns-feedback-ui":"true",children:[A.map((t,o)=>i(re,{pin:t,number:o+1,editing:d===t.id,feedbackSessionId:e.feedbackSessionId,apiBase:b.current,onStartEdit:()=>f(t.id),onCancelEdit:()=>f(null),onSave:(m,u)=>T(t.id,m,u),onRemove:()=>I(t.id)},t.id)),l&&i(se,{pending:l,feedbackSessionId:e.feedbackSessionId,apiBase:b.current,onCancel:()=>{a(null),p(!0)},onSave:(t,o)=>x(t,o)}),y&&i(ae,{comments:e.comments,currentPath:window.location.pathname,onClose:()=>S(!1),onSelect:_}),g("div",{style:ue,children:[i("button",{type:"button",onClick:()=>p(t=>!t),style:c?pe:D,children:c?"Cancel":"Add comment"}),i("button",{type:"button",onClick:()=>S(t=>!t),style:me,children:y?"Hide list":`List (${e.comments.length})`}),i("button",{type:"button",onClick:F,disabled:$,style:fe,children:$?"Finalizing\u2026":"Finalize"}),i("span",{style:be,children:c?"Click any element to comment":`${A.length} on this page`})]})]})}function ne({progress:e,eta:n,status:r}){let[,s]=h(0);E(()=>{let a=window.setInterval(()=>s(d=>d+1),6e4);return()=>window.clearInterval(a)},[]);let c=ee(n);return g("div",{style:Be,children:[i("style",{children:oe}),g("div",{style:Ne,children:[i(ie,{}),i("span",{style:ze,children:"Submitted"}),i("strong",{style:{fontSize:13},children:r==="in_review"?"Feedback under review":"Applying feedback"})]}),g("div",{style:Me,children:[i("span",{children:"Review progress"}),i("span",{style:{opacity:.7},children:e.label})]}),i("div",{style:Ue,children:i("div",{style:{...je,width:`${e.fillPct}%`}})}),g("p",{style:Xe,children:["Estimated delivery: ",i("span",{style:{fontWeight:600},children:c})]})]})}var oe="@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }";function ie({size:e=14}){return i("span",{"aria-hidden":!0,style:{display:"inline-block",width:e,height:e,border:"2px solid rgba(16, 185, 129, 0.25)",borderTopColor:"#10b981",borderRadius:999,animation:"yns-feedback-spin 0.9s linear infinite"}})}function re({pin:e,number:n,editing:r,feedbackSessionId:s,apiBase:c,onStartEdit:p,onCancelEdit:l,onSave:a,onRemove:d}){let f=de(e.cssSelector,e.offsetXRatio,e.offsetYRatio);return f?g("div",{style:{position:"absolute",top:f.top,left:f.left,zIndex:2147483600,pointerEvents:"auto"},children:[i("button",{type:"button",onClick:p,style:V,title:e.content,children:n}),r&&i(O,{initial:e.content,initialAttachments:e.attachments,feedbackSessionId:s,apiBase:c,onCancel:l,onSave:a,onRemove:d})]}):null}function se({pending:e,feedbackSessionId:n,apiBase:r,onCancel:s,onSave:c}){return g(He,{children:[i("div",{style:{position:"absolute",top:e.rect.top+e.rect.height*e.offsetYRatio-12,left:e.rect.left+e.rect.width*e.offsetXRatio-12,zIndex:2147483600,pointerEvents:"none"},children:i("div",{style:V,children:"\u2022"})}),i("div",{style:{position:"absolute",top:e.clickY+10,left:e.clickX+10,zIndex:2147483647},children:i(O,{initial:"",initialAttachments:[],feedbackSessionId:n,apiBase:r,onCancel:s,onSave:c})})]})}function ae({comments:e,currentPath:n,onClose:r,onSelect:s}){let c=new Map;for(let l of e){let a=c.get(l.pagePath)??[];a.push(l),c.set(l.pagePath,a)}let p=Array.from(c.keys()).sort((l,a)=>l===n?-1:a===n?1:l.localeCompare(a));return g("div",{style:Pe,children:[g("div",{style:Re,children:[g("strong",{style:{fontSize:14},children:["Comments (",e.length,")"]}),i("button",{type:"button",onClick:r,style:U,children:"Close"})]}),i("div",{style:Ie,children:e.length===0?i("div",{style:$e,children:"No comments yet. Click \u201CAdd comment\u201D to leave one."}):p.map(l=>g("div",{style:{marginBottom:12},children:[i("div",{style:Te,children:l===n?`${l} \xB7 current`:l}),(c.get(l)??[]).map((a,d)=>g("button",{type:"button",onClick:()=>s(a),style:Ae,disabled:a.status==="done"&&!1,children:[i("span",{style:Le,children:d+1}),i("span",{style:Fe,children:a.content.length>140?`${a.content.slice(0,140)}\u2026`:a.content}),a.status==="done"&&i("span",{style:_e,children:"done"})]},a.id))]},l))})]})}var z=5,le=5*1024*1024,ce=e=>new Promise(n=>{let r=URL.createObjectURL(e),s=new window.Image;s.onload=()=>{URL.revokeObjectURL(r),n({width:s.naturalWidth,height:s.naturalHeight})},s.onerror=()=>{URL.revokeObjectURL(r),n(null)},s.src=r});function O({initial:e,initialAttachments:n,feedbackSessionId:r,apiBase:s,onCancel:c,onSave:p,onRemove:l}){let[a,d]=h(e),[f,y]=h(n),[S,$]=h(!1),[P,b]=h(!1),[R,x]=h(null),T=M(null),I=M(null);E(()=>{let t=T.current;if(!t)return;t.focus();let o=t.value.length;t.setSelectionRange(o,o)},[]);let F=async t=>{t.preventDefault();let o=a.trim();if(o){$(!0);try{await p(o,f)}finally{$(!1)}}},_=async t=>{if(!t||t.length===0||!s)return;x(null);let o=z-f.length;if(o<=0){x(`Max ${z} images per comment`);return}let m=Array.from(t).slice(0,o);for(let u of m){if(!u.type.startsWith("image/")){x(`"${u.name}" is not an image`);return}if(u.size>le){x(`"${u.name}" exceeds 5 MB`);return}}b(!0);try{let u=await Promise.all(m.map(ce)),v=new FormData;m.forEach((C,L)=>{v.append("file",C),v.append("width",u[L]?.width?String(u[L]?.width):""),v.append("height",u[L]?.height?String(u[L]?.height):"")});let w=await fetch(`${s}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(r)}`,{method:"POST",credentials:"include",body:v});if(!w.ok){let C=await w.json().catch(()=>null);x(C?.error??"Upload failed");return}let k=await w.json();y(C=>[...C,...k.uploads])}catch{x("Upload failed")}finally{b(!1),I.current&&(I.current.value="")}},A=t=>{y(o=>o.filter(m=>m.url!==t))};return g("form",{onSubmit:F,style:ge,children:[i("textarea",{ref:T,value:a,onChange:t=>d(t.target.value),placeholder:"Leave a comment\u2026",style:he,rows:3}),f.length>0&&i("div",{style:ve,children:f.map(t=>g("div",{style:Ce,children:[i("img",{src:t.url,alt:"",style:xe}),i("button",{type:"button",onClick:()=>A(t.url),style:ke,disabled:S||P,"aria-label":"Remove attachment",children:"\xD7"})]},t.url))}),R&&i("div",{style:Ee,children:R}),i("input",{ref:I,type:"file",accept:"image/*",multiple:!0,onChange:t=>void _(t.target.files),style:{display:"none"}}),g("div",{style:ye,children:[l&&i("button",{type:"button",onClick:()=>l(),style:we,disabled:S,children:"Delete"}),i("button",{type:"button",onClick:()=>I.current?.click(),style:U,disabled:S||P||f.length>=z,children:P?"Uploading\u2026":`Attach image${f.length>0?` (${f.length})`:""}`}),i("div",{style:{flex:1}}),i("button",{type:"button",onClick:c,style:U,disabled:S,children:"Cancel"}),i("button",{type:"submit",style:Se,disabled:S||P||!a.trim(),children:S?"Saving\u2026":"Save"})]})]})}function de(e,n,r){let[s,c]=h(null);return E(()=>{let p=()=>{let a=null;try{a=document.querySelector(e)}catch{a=null}if(!a){c(null);return}let d=a.getBoundingClientRect();c({top:d.top+window.scrollY+d.height*r-12,left:d.left+window.scrollX+d.width*n-12})};p();let l=new ResizeObserver(p);try{let a=document.querySelector(e);a&&l.observe(a)}catch{}return window.addEventListener("scroll",p,!0),window.addEventListener("resize",p),()=>{l.disconnect(),window.removeEventListener("scroll",p,!0),window.removeEventListener("resize",p)}},[e,n,r]),s}var ue={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",alignItems:"center",gap:8,padding:"8px 12px",background:"rgba(17, 17, 17, 0.92)",color:"white",borderRadius:999,boxShadow:"0 8px 24px rgba(0,0,0,0.25)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',fontSize:14,pointerEvents:"auto"},D={border:"none",background:"white",color:"#111",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},pe={...D,background:"#ef4444",color:"white"},me={border:"1px solid rgba(255,255,255,0.4)",background:"transparent",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:500,fontSize:13},fe={border:"none",background:"#10b981",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},be={opacity:.8,fontSize:12},V={width:24,height:24,borderRadius:999,background:"#10b981",color:"white",border:"2px solid white",cursor:"pointer",fontSize:12,fontWeight:700,boxShadow:"0 2px 6px rgba(0,0,0,0.3)",display:"inline-flex",alignItems:"center",justifyContent:"center",padding:0},ge={background:"white",border:"1px solid #e5e7eb",borderRadius:8,padding:12,width:280,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',display:"flex",flexDirection:"column",gap:8,color:"#111"},he={width:"100%",border:"1px solid #d1d5db",borderRadius:6,padding:8,fontSize:14,resize:"vertical",fontFamily:"inherit",color:"#111",background:"white",boxSizing:"border-box"},ye={display:"flex",alignItems:"center",gap:6},j={border:"1px solid transparent",borderRadius:6,padding:"6px 10px",fontSize:13,cursor:"pointer",fontWeight:500},Se={...j,background:"#111",color:"white"},U={...j,background:"transparent",color:"#374151",borderColor:"#d1d5db"},we={...j,background:"transparent",color:"#b91c1c",borderColor:"#fecaca"},ve={display:"flex",flexWrap:"wrap",gap:6},Ce={position:"relative",width:56,height:56,borderRadius:6,overflow:"hidden",border:"1px solid #e5e7eb",background:"#f9fafb"},xe={width:"100%",height:"100%",objectFit:"cover",display:"block"},ke={position:"absolute",top:2,right:2,width:18,height:18,borderRadius:999,border:"none",background:"rgba(17, 17, 17, 0.85)",color:"white",fontSize:13,lineHeight:"16px",cursor:"pointer",padding:0,display:"inline-flex",alignItems:"center",justifyContent:"center"},Ee={color:"#b91c1c",fontSize:12,margin:0},Pe={position:"fixed",top:0,right:0,bottom:0,width:360,maxWidth:"92vw",background:"white",borderLeft:"1px solid #e5e7eb",boxShadow:"-12px 0 32px rgba(0,0,0,0.12)",zIndex:2147483646,display:"flex",flexDirection:"column",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111"},Re={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e5e7eb"},Ie={flex:1,overflow:"auto",padding:"12px 12px 24px"},$e={color:"#6b7280",fontSize:13,padding:"24px 8px",textAlign:"center"},Te={fontSize:11,textTransform:"uppercase",letterSpacing:.5,color:"#6b7280",padding:"4px 4px 6px",wordBreak:"break-all"},Ae={display:"flex",alignItems:"flex-start",gap:8,width:"100%",textAlign:"left",background:"white",border:"1px solid #e5e7eb",borderRadius:6,padding:"8px 10px",cursor:"pointer",fontSize:13,marginBottom:6,color:"#111"},Le={flexShrink:0,width:22,height:22,borderRadius:999,background:"#10b981",color:"white",fontWeight:700,fontSize:11,display:"inline-flex",alignItems:"center",justifyContent:"center"},Fe={flex:1,whiteSpace:"pre-wrap",wordBreak:"break-word"},_e={flexShrink:0,background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:600,padding:"2px 6px",borderRadius:4,alignSelf:"center"},Be={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",flexDirection:"column",gap:8,padding:"12px 16px",width:"min(420px, calc(100vw - 32px))",background:"white",border:"1px solid #e5e7eb",borderRadius:12,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111",pointerEvents:"auto"},Ne={display:"flex",alignItems:"center",gap:8},ze={background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:700,padding:"2px 8px",borderRadius:999,textTransform:"uppercase",letterSpacing:.5},Me={display:"flex",justifyContent:"space-between",fontSize:12},Ue={width:"100%",height:6,background:"#f3f4f6",borderRadius:999,overflow:"hidden"},je={height:"100%",background:"#10b981",borderRadius:999,transition:"width 500ms"},Xe={margin:0,fontSize:12,color:"#6b7280"};function Y(){if(console.log("[YNS Feedback Toolbar] mountFeedbackToolbar() called",{isWindow:typeof window<"u",vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,alreadyMounted:typeof document<"u"&&!!document.getElementById(B)}),typeof window>"u")return null;if(process.env.NEXT_PUBLIC_VERCEL_ENV!=="preview")return console.log("[YNS Feedback Toolbar] gate failed \u2014 NEXT_PUBLIC_VERCEL_ENV is",process.env.NEXT_PUBLIC_VERCEL_ENV),null;if(document.getElementById(B))return null;let e=document.createElement("div");e.id=B,e.dataset.ynsFeedbackUi="true",document.body.appendChild(e);let n=W(e);return n.render(i(te,{})),{unmount:()=>{n.unmount(),e.remove()}}}typeof window<"u"&&console.log("[YNS Feedback Toolbar] module evaluated",{vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,readyState:document.readyState,willAutoMount:process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"});typeof window<"u"&&process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{Y()}):Y());export{Y as mountFeedbackToolbar};
|
|
1
|
+
import{useEffect as P,useRef as U,useState as h}from"react";import{createRoot as G}from"react-dom/client";import{Fragment as V,jsx as i,jsxs as g}from"react/jsx-runtime";var N="yns-feedback-toolbar-root",q=()=>{if(typeof window>"u")return null;let e=(process.env.NEXT_PUBLIC_YNS_API_BASE??"").trim();if(e)return e.replace(/\/$/,"");let n=window.location.hostname,r=window.location.protocol;return n.endsWith(".yns.store")?`${r}//yns.store`:n.endsWith(".yns.cx")?`${r}//yns.cx`:window.location.origin},J=e=>{if(e.id)return`#${CSS.escape(e.id)}`;let n=[],r=e;for(;r&&r.nodeType===Node.ELEMENT_NODE&&n.length<6;){let s=r.tagName.toLowerCase(),c=r.getAttribute("class");if(c){let a=c.split(/\s+/).filter(Boolean).slice(0,2).map(d=>`.${CSS.escape(d)}`).join("");s+=a}let p=r.parentElement,l=r.tagName;if(p){let a=Array.from(p.children).filter(d=>d.tagName===l);if(a.length>1){let d=a.indexOf(r)+1;s+=`:nth-of-type(${d})`}}n.unshift(s),r=p}return n.join(" > ")},K=["id","class","data-testid","aria-label","role","name","href","src"],z=e=>{let n=[];for(let r of K){let s=e.getAttribute(r);if(!s)continue;let c=s.length>80?`${s.slice(0,80)}\u2026`:s;n.push(` ${r}="${c.replace(/"/g,""")}"`)}return n.join("")},j=e=>{let n=(e.textContent??"").replace(/\s+/g," ").trim();return n?n.length>100?`${n.slice(0,100)}\u2026`:n:""},Q=e=>{let n=[],r=e;for(;r&&r!==document.documentElement&&n.length<5;){let d=r.parentElement;if(!d)break;n.unshift(d),r=d}let s=[],c=0;for(let d of n){let f=" ".repeat(c);s.push(`${f}<${d.tagName.toLowerCase()}${z(d)}>`),c++}let p=" ".repeat(c),l=e.tagName.toLowerCase(),a=j(e);if(s.push(`${p}<${l}${z(e)}>${a?`${a}</${l}>`:""} \u2190 TARGET`),!a){let d=" ".repeat(c+1),f=Array.from(e.children).slice(0,6);for(let y of f){let S=j(y);s.push(`${d}<${y.tagName.toLowerCase()}${z(y)}>${S?`${S}</${y.tagName.toLowerCase()}>`:""}`)}e.children.length>6&&s.push(`${d}\u2026 (${e.children.length-6} more children)`),s.push(`${p}</${l}>`)}for(let d=n.length-1;d>=0;d--){let f=" ".repeat(d),y=n[d];y&&s.push(`${f}</${y.tagName.toLowerCase()}>`)}return s.join(`
|
|
2
|
+
`)},X=e=>{let n=e;for(;n;){if(n instanceof HTMLElement&&n.dataset.ynsFeedbackUi==="true")return!0;n=n.parentElement}return!1},Z=new Set(["HTML","HEAD","SCRIPT","STYLE","NOSCRIPT"]),ee=1e4,te=e=>{let n=new Date(e),r=n.getTime()-Date.now(),s=n.toLocaleString(void 0,{weekday:"short",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"});if(r<=0)return`Any moment now (estimated by ${s})`;let c=Math.ceil(r/6e4);if(c<60)return`~${c} minutes (by ${s})`;let p=Math.round(r/36e5);if(p<24)return`~${p} ${p===1?"hour":"hours"} (by ${s})`;let l=Math.round(r/864e5);return`~${l} ${l===1?"day":"days"} (by ${s})`};function ne(){let[e,n]=h(null),[r,s]=h(!0),[c,p]=h(!1),[l,a]=h(null),[d,f]=h(null),[y,S]=h(!1),[T,k]=h(!1),b=U(null);P(()=>{if(b.current=q(),!b.current){s(!1);return}let t=!1,o=new AbortController,m=async()=>{try{let v=await fetch(`${b.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include",signal:o.signal});if(t)return;if(!v.ok){n(null);return}let w=await v.json();if(t)return;n(w),w.sessionStatus==="done"&&window.location.reload()}catch{t||n(null)}finally{t||s(!1)}};m();let u=window.setInterval(m,ee);return()=>{t=!0,o.abort(),window.clearInterval(u)}},[]),P(()=>{!e||e.canComment||(p(!1),a(null),S(!1),f(null))},[e]),P(()=>{if(c)return document.body.style.cursor="crosshair",()=>{document.body.style.cursor=""}},[c]),P(()=>{if(!c)return;let t=document.createElement("div");t.dataset.ynsFeedbackUi="true",t.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483644","border: 2px dashed #10b981","background: rgba(16, 185, 129, 0.08)","border-radius: 3px","display: none","transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s"].join(";");let o=document.createElement("div");o.dataset.ynsFeedbackUi="true",o.textContent="Click to comment",o.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483645","background: #059669","color: #fff","font-size: 11px","font-family: ui-sans-serif, system-ui, sans-serif","padding: 3px 8px","border-radius: 4px","white-space: nowrap","display: none","box-shadow: 0 2px 6px rgba(0,0,0,0.15)"].join(";"),document.documentElement.appendChild(t),document.documentElement.appendChild(o);let m=null,u=w=>{let E=document.elementFromPoint(w.clientX,w.clientY);if(!E||Z.has(E.tagName)||X(E)){t.style.display="none",o.style.display="none",m=null;return}m=E,requestAnimationFrame(()=>{if(m!==E)return;let C=E.getBoundingClientRect();t.style.top=`${C.top}px`,t.style.left=`${C.left}px`,t.style.width=`${C.width}px`,t.style.height=`${C.height}px`,t.style.display="block",o.style.left=`${w.clientX+14}px`,o.style.top=`${w.clientY+14}px`,o.style.display="block"})},v=()=>{t.style.display="none",o.style.display="none",m=null};return document.addEventListener("mousemove",u,!0),document.addEventListener("mouseleave",v),()=>{document.removeEventListener("mousemove",u,!0),document.removeEventListener("mouseleave",v),t.remove(),o.remove()}},[c]),P(()=>{if(!c)return;let t=o=>{let m=o.target;if(!(m instanceof Element)||X(m))return;o.preventDefault(),o.stopPropagation();let u=m.getBoundingClientRect(),v=u.width>0?(o.clientX-u.left)/u.width:.5,w=u.height>0?(o.clientY-u.top)/u.height:.5;a({cssSelector:J(m),pagePath:window.location.pathname,surroundingHtml:Q(m),rect:{top:u.top+window.scrollY,left:u.left+window.scrollX,width:u.width,height:u.height},clickX:o.clientX+window.scrollX,clickY:o.clientY+window.scrollY,offsetXRatio:Math.min(1,Math.max(0,v)),offsetYRatio:Math.min(1,Math.max(0,w))}),p(!1)};return document.addEventListener("click",t,{capture:!0}),()=>document.removeEventListener("click",t,{capture:!0})},[c]);let R=async()=>{if(!b.current)return;let t=await fetch(`${b.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include"});if(!t.ok)return;let o=await t.json();n(o)},x=async(t,o)=>{!b.current||!e||!l||!(await fetch(`${b.current}/api/feedback-comments`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({feedbackSessionId:e.feedbackSessionId,content:t,pagePath:l.pagePath,cssSelector:l.cssSelector,surroundingHtml:l.surroundingHtml,offsetXRatio:l.offsetXRatio,offsetYRatio:l.offsetYRatio,...o.length>0?{attachments:o}:{}})})).ok||(a(null),p(!0),await R())},$=async(t,o,m)=>{!b.current||!(await fetch(`${b.current}/api/feedback-comments/${t}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:o,attachments:m})})).ok||(f(null),await R())},I=async t=>{!b.current||!(await fetch(`${b.current}/api/feedback-comments/${t}`,{method:"DELETE",credentials:"include"})).ok||await R()},_=async()=>{if(!(!b.current||!e)&&window.confirm("Finalize this feedback session? You won't be able to add more comments.")){k(!0);try{let t=await fetch(`${b.current}/api/feedback-sessions/${e.feedbackSessionId}/finalize`,{method:"POST",credentials:"include"});if(!t.ok)return;let m=(await t.json().catch(()=>null))?.status??"processing";n(u=>u&&{...u,canComment:!1,sessionStatus:m})}finally{k(!1)}}},B=t=>{if(t.pagePath!==window.location.pathname){window.location.assign(t.pagePath);return}let o=null;try{o=document.querySelector(t.cssSelector)}catch{o=null}o&&(o.scrollIntoView({behavior:"smooth",block:"center"}),f(t.id))};if(r||!e)return null;if(!e.canComment)return e.sessionStatus!=="processing"&&e.sessionStatus!=="in_review"?null:i("div",{"data-yns-feedback-ui":"true",children:i(oe,{progress:e.progress,eta:e.eta,status:e.sessionStatus})});let A=e.comments.filter(t=>t.pagePath===window.location.pathname&&t.status!=="done");return g("div",{"data-yns-feedback-ui":"true",children:[A.map((t,o)=>i(se,{pin:t,number:o+1,editing:d===t.id,feedbackSessionId:e.feedbackSessionId,apiBase:b.current,onStartEdit:()=>f(t.id),onCancelEdit:()=>f(null),onSave:(m,u)=>$(t.id,m,u),onRemove:()=>I(t.id)},t.id)),l&&i(ae,{pending:l,feedbackSessionId:e.feedbackSessionId,apiBase:b.current,onCancel:()=>{a(null),p(!0)},onSave:(t,o)=>x(t,o)}),y&&i(le,{comments:e.comments,currentPath:window.location.pathname,onClose:()=>S(!1),onSelect:B}),g("div",{style:pe,children:[i("button",{type:"button",onClick:()=>p(t=>!t),style:c?me:O,children:c?"Cancel":"Add comment"}),i("button",{type:"button",onClick:()=>S(t=>!t),style:fe,children:y?"Hide list":`List (${e.comments.length})`}),i("button",{type:"button",onClick:_,disabled:T,style:be,children:T?"Finalizing\u2026":"Finalize"}),i("span",{style:ge,children:c?"Click any element to comment":`${A.length} on this page`})]})]})}function oe({progress:e,eta:n,status:r}){let[,s]=h(0);P(()=>{let a=window.setInterval(()=>s(d=>d+1),6e4);return()=>window.clearInterval(a)},[]);let c=te(n);return g("div",{style:Ue,children:[i("style",{children:ie}),g("div",{style:je,children:[i(re,{}),i("span",{style:Xe,children:"Submitted"}),i("strong",{style:{fontSize:13},children:r==="in_review"?"Feedback under review":"Applying feedback"})]}),g("div",{style:He,children:[i("span",{children:"Review progress"}),i("span",{style:{opacity:.7},children:e.label})]}),i("div",{style:Ye,children:i("div",{style:{...Oe,width:`${e.fillPct}%`}})}),g("p",{style:De,children:["Estimated delivery: ",i("span",{style:{fontWeight:600},children:c})]})]})}var ie="@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }";function re({size:e=14}){return i("span",{"aria-hidden":!0,style:{display:"inline-block",width:e,height:e,border:"2px solid rgba(16, 185, 129, 0.25)",borderTopColor:"#10b981",borderRadius:999,animation:"yns-feedback-spin 0.9s linear infinite"}})}function se({pin:e,number:n,editing:r,feedbackSessionId:s,apiBase:c,onStartEdit:p,onCancelEdit:l,onSave:a,onRemove:d}){let f=ue(e.cssSelector,e.offsetXRatio,e.offsetYRatio);return f?g("div",{style:{position:"absolute",top:f.top,left:f.left,zIndex:2147483600,pointerEvents:"auto"},children:[i("button",{type:"button",onClick:p,style:D,title:e.content,children:n}),r&&i(Y,{initial:e.content,initialAttachments:e.attachments,feedbackSessionId:s,apiBase:c,onCancel:l,onSave:a,onRemove:d})]}):null}function ae({pending:e,feedbackSessionId:n,apiBase:r,onCancel:s,onSave:c}){return g(V,{children:[i("div",{style:{position:"absolute",top:e.rect.top+e.rect.height*e.offsetYRatio-12,left:e.rect.left+e.rect.width*e.offsetXRatio-12,zIndex:2147483600,pointerEvents:"none"},children:i("div",{style:D,children:"\u2022"})}),i("div",{style:{position:"absolute",top:e.clickY+10,left:e.clickX+10,zIndex:2147483647},children:i(Y,{initial:"",initialAttachments:[],feedbackSessionId:n,apiBase:r,onCancel:s,onSave:c})})]})}function le({comments:e,currentPath:n,onClose:r,onSelect:s}){let c=new Map;for(let l of e){let a=c.get(l.pagePath)??[];a.push(l),c.set(l.pagePath,a)}let p=Array.from(c.keys()).sort((l,a)=>l===n?-1:a===n?1:l.localeCompare(a));return g("div",{style:$e,children:[g("div",{style:Ae,children:[g("strong",{style:{fontSize:14},children:["Comments (",e.length,")"]}),i("button",{type:"button",onClick:r,style:W,children:"Close"})]}),i("div",{style:Le,children:e.length===0?i("div",{style:Fe,children:"No comments yet. Click \u201CAdd comment\u201D to leave one."}):p.map(l=>g("div",{style:{marginBottom:12},children:[i("div",{style:_e,children:l===n?`${l} \xB7 current`:l}),(c.get(l)??[]).map((a,d)=>g("button",{type:"button",onClick:()=>s(a),style:Be,disabled:a.status==="done"&&!1,children:[i("span",{style:Ne,children:d+1}),i("span",{style:ze,children:a.content.length>140?`${a.content.slice(0,140)}\u2026`:a.content}),a.status==="done"&&i("span",{style:Me,children:"done"})]},a.id))]},l))})]})}var M=5,ce=5*1024*1024,de=e=>new Promise(n=>{let r=URL.createObjectURL(e),s=new window.Image;s.onload=()=>{URL.revokeObjectURL(r),n({width:s.naturalWidth,height:s.naturalHeight})},s.onerror=()=>{URL.revokeObjectURL(r),n(null)},s.src=r});function Y({initial:e,initialAttachments:n,feedbackSessionId:r,apiBase:s,onCancel:c,onSave:p,onRemove:l}){let[a,d]=h(e),[f,y]=h(n),[S,T]=h(!1),[k,b]=h(!1),[R,x]=h(null),$=U(null),I=U(null);P(()=>{let t=$.current;if(!t)return;t.focus();let o=t.value.length;t.setSelectionRange(o,o)},[]);let _=async t=>{t.preventDefault();let o=a.trim();if(o){T(!0);try{await p(o,f)}finally{T(!1)}}},B=async t=>{if(!t||t.length===0||!s)return;x(null);let o=M-f.length;if(o<=0){x(`Max ${M} images per comment`);return}let m=Array.from(t).slice(0,o);for(let u of m){if(!u.type.startsWith("image/")){x(`"${u.name}" is not an image`);return}if(u.size>ce){x(`"${u.name}" exceeds 5 MB`);return}}b(!0);try{let u=await Promise.all(m.map(de)),v=new FormData;m.forEach((C,L)=>{v.append("file",C),v.append("width",u[L]?.width?String(u[L]?.width):""),v.append("height",u[L]?.height?String(u[L]?.height):"")});let w=await fetch(`${s}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(r)}`,{method:"POST",credentials:"include",body:v});if(!w.ok){let C=await w.json().catch(()=>null);x(C?.error??"Upload failed");return}let E=await w.json();y(C=>[...C,...E.uploads])}catch{x("Upload failed")}finally{b(!1),I.current&&(I.current.value="")}},A=t=>{y(o=>o.filter(m=>m.url!==t))};return g("form",{onSubmit:_,style:he,children:[i("textarea",{ref:$,value:a,onChange:t=>d(t.target.value),placeholder:"Leave a comment\u2026",style:ye,rows:3}),f.length>0&&i("div",{style:Ce,children:f.map(t=>g("div",{style:ke,children:[i("img",{src:t.url,alt:"",style:xe}),i("button",{type:"button",onClick:()=>A(t.url),style:Ee,disabled:S||k,"aria-label":"Remove attachment",children:"\xD7"})]},t.url))}),R&&i("div",{style:Pe,children:R}),i("input",{ref:I,type:"file",accept:"image/*",multiple:!0,onChange:t=>void B(t.target.files),style:{display:"none"}}),g("div",{style:Se,children:[l&&i("button",{type:"button",onClick:()=>l(),style:ve,disabled:S,children:"Delete"}),i("button",{type:"button",onClick:()=>I.current?.click(),style:Re,disabled:S||k||f.length>=M,"aria-label":k?"Uploading\u2026":"Attach image",title:k?"Uploading\u2026":"Attach image",children:k?i(Te,{}):i(Ie,{})}),i("div",{style:{flex:1}}),i("button",{type:"button",onClick:c,style:W,disabled:S,children:"Cancel"}),i("button",{type:"submit",style:we,disabled:S||k||!a.trim(),children:S?"Saving\u2026":"Save"})]})]})}function ue(e,n,r){let[s,c]=h(null);return P(()=>{let p=()=>{let a=null;try{a=document.querySelector(e)}catch{a=null}if(!a){c(null);return}let d=a.getBoundingClientRect();c({top:d.top+window.scrollY+d.height*r-12,left:d.left+window.scrollX+d.width*n-12})};p();let l=new ResizeObserver(p);try{let a=document.querySelector(e);a&&l.observe(a)}catch{}return window.addEventListener("scroll",p,!0),window.addEventListener("resize",p),()=>{l.disconnect(),window.removeEventListener("scroll",p,!0),window.removeEventListener("resize",p)}},[e,n,r]),s}var pe={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",alignItems:"center",gap:8,padding:"8px 12px",background:"rgba(17, 17, 17, 0.92)",color:"white",borderRadius:999,boxShadow:"0 8px 24px rgba(0,0,0,0.25)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',fontSize:14,pointerEvents:"auto"},O={border:"none",background:"white",color:"#111",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},me={...O,background:"#ef4444",color:"white"},fe={border:"1px solid rgba(255,255,255,0.4)",background:"transparent",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:500,fontSize:13},be={border:"none",background:"#10b981",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},ge={opacity:.8,fontSize:12},D={width:24,height:24,borderRadius:999,background:"#10b981",color:"white",border:"2px solid white",cursor:"pointer",fontSize:12,fontWeight:700,boxShadow:"0 2px 6px rgba(0,0,0,0.3)",display:"inline-flex",alignItems:"center",justifyContent:"center",padding:0},he={background:"white",border:"1px solid #e5e7eb",borderRadius:8,padding:12,width:280,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',display:"flex",flexDirection:"column",gap:8,color:"#111"},ye={width:"100%",border:"1px solid #d1d5db",borderRadius:6,padding:8,fontSize:14,resize:"vertical",fontFamily:"inherit",color:"#111",background:"white",boxSizing:"border-box"},Se={display:"flex",alignItems:"center",gap:6},F={border:"1px solid transparent",borderRadius:6,padding:"6px 10px",fontSize:13,cursor:"pointer",fontWeight:500},we={...F,background:"#111",color:"white"},W={...F,background:"transparent",color:"#374151",borderColor:"#d1d5db"},ve={...F,background:"transparent",color:"#b91c1c",borderColor:"#fecaca"},Ce={display:"flex",flexWrap:"wrap",gap:6},ke={position:"relative",width:56,height:56,borderRadius:6,overflow:"hidden",border:"1px solid #e5e7eb",background:"#f9fafb"},xe={width:"100%",height:"100%",objectFit:"cover",display:"block"},Ee={position:"absolute",top:2,right:2,width:18,height:18,borderRadius:999,border:"none",background:"rgba(17, 17, 17, 0.85)",color:"white",fontSize:13,lineHeight:"16px",cursor:"pointer",padding:0,display:"inline-flex",alignItems:"center",justifyContent:"center"},Pe={color:"#b91c1c",fontSize:12,margin:0},Re={...F,background:"transparent",color:"#374151",borderColor:"#d1d5db",width:30,height:30,padding:0,display:"inline-flex",alignItems:"center",justifyContent:"center",flexShrink:0};function Ie(){return g("svg",{role:"img","aria-label":"Attach image",width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i("title",{children:"Attach image"}),i("path",{d:"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.93 8.83l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"})]})}function Te(){return g(V,{children:[i("style",{children:"@keyframes yns-attach-spin { to { transform: rotate(360deg); } }"}),i("span",{"aria-hidden":!0,style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(55, 65, 81, 0.25)",borderTopColor:"#374151",borderRadius:999,animation:"yns-attach-spin 0.9s linear infinite"}})]})}var $e={position:"fixed",top:0,right:0,bottom:0,width:360,maxWidth:"92vw",background:"white",borderLeft:"1px solid #e5e7eb",boxShadow:"-12px 0 32px rgba(0,0,0,0.12)",zIndex:2147483646,display:"flex",flexDirection:"column",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111"},Ae={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e5e7eb"},Le={flex:1,overflow:"auto",padding:"12px 12px 24px"},Fe={color:"#6b7280",fontSize:13,padding:"24px 8px",textAlign:"center"},_e={fontSize:11,textTransform:"uppercase",letterSpacing:.5,color:"#6b7280",padding:"4px 4px 6px",wordBreak:"break-all"},Be={display:"flex",alignItems:"flex-start",gap:8,width:"100%",textAlign:"left",background:"white",border:"1px solid #e5e7eb",borderRadius:6,padding:"8px 10px",cursor:"pointer",fontSize:13,marginBottom:6,color:"#111"},Ne={flexShrink:0,width:22,height:22,borderRadius:999,background:"#10b981",color:"white",fontWeight:700,fontSize:11,display:"inline-flex",alignItems:"center",justifyContent:"center"},ze={flex:1,whiteSpace:"pre-wrap",wordBreak:"break-word"},Me={flexShrink:0,background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:600,padding:"2px 6px",borderRadius:4,alignSelf:"center"},Ue={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",flexDirection:"column",gap:8,padding:"12px 16px",width:"min(420px, calc(100vw - 32px))",background:"white",border:"1px solid #e5e7eb",borderRadius:12,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111",pointerEvents:"auto"},je={display:"flex",alignItems:"center",gap:8},Xe={background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:700,padding:"2px 8px",borderRadius:999,textTransform:"uppercase",letterSpacing:.5},He={display:"flex",justifyContent:"space-between",fontSize:12},Ye={width:"100%",height:6,background:"#f3f4f6",borderRadius:999,overflow:"hidden"},Oe={height:"100%",background:"#10b981",borderRadius:999,transition:"width 500ms"},De={margin:0,fontSize:12,color:"#6b7280"};function H(){if(console.log("[YNS Feedback Toolbar] mountFeedbackToolbar() called",{isWindow:typeof window<"u",vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,alreadyMounted:typeof document<"u"&&!!document.getElementById(N)}),typeof window>"u")return null;if(process.env.NEXT_PUBLIC_VERCEL_ENV!=="preview")return console.log("[YNS Feedback Toolbar] gate failed \u2014 NEXT_PUBLIC_VERCEL_ENV is",process.env.NEXT_PUBLIC_VERCEL_ENV),null;if(document.getElementById(N))return null;let e=document.createElement("div");e.id=N,e.dataset.ynsFeedbackUi="true",document.body.appendChild(e);let n=G(e);return n.render(i(ne,{})),{unmount:()=>{n.unmount(),e.remove()}}}typeof window<"u"&&console.log("[YNS Feedback Toolbar] module evaluated",{vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,readyState:document.readyState,willAutoMount:process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"});typeof window<"u"&&process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{H()}):H());export{H as mountFeedbackToolbar};
|
|
3
3
|
//# sourceMappingURL=feedback-toolbar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/feedback-toolbar.tsx"],"sourcesContent":["/**\n * Feedback session toolbar — side-effect entry.\n *\n * Importing `commerce-kit/feedback-toolbar` (or `commerce-kit/browser` for the\n * combined entry) mounts a floating toolbar onto the page that lets reviewers\n * leave click-anchored comments. Comments persist via YNS API endpoints,\n * authenticated with the `better-auth` session cookie sent through\n * `credentials: \"include\"`.\n *\n * Gated on `process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\"` — only Vercel\n * preview deploys get the toolbar. Sandbox dev (env undefined) and production\n * (env === \"production\") skip it.\n */\n\nimport { type CSSProperties, type FormEvent, useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst MOUNT_NODE_ID = \"yns-feedback-toolbar-root\";\n\ninterface FeedbackAttachment {\n\turl: string;\n\tcontentType: string;\n\twidth?: number;\n\theight?: number;\n}\n\ninterface FeedbackComment {\n\tid: string;\n\tpagePath: string;\n\tcssSelector: string;\n\tcontent: string;\n\tstatus: \"todo\" | \"done\";\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n\tattachments: FeedbackAttachment[];\n}\n\ntype SessionStatus = \"created\" | \"in_progress\" | \"processing\" | \"in_review\" | \"done\";\n\ninterface ReviewProgress {\n\tfillPct: number;\n\tlabel: string;\n}\n\ninterface ActiveSession {\n\tfeedbackSessionId: string;\n\tcomments: FeedbackComment[];\n\tcanComment: boolean;\n\tsessionStatus: SessionStatus;\n\tcommentTotal: number;\n\tcommentDone: number;\n\teta: string;\n\tprogress: ReviewProgress;\n}\n\ninterface PendingPin {\n\tcssSelector: string;\n\tpagePath: string;\n\tsurroundingHtml: string;\n\trect: { top: number; left: number; width: number; height: number };\n\tclickX: number;\n\tclickY: number;\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n}\n\nconst buildApiBase = (): string | null => {\n\tif (typeof window === \"undefined\") return null;\n\tconst override = (process.env.NEXT_PUBLIC_YNS_API_BASE ?? \"\").trim();\n\tif (override) return override.replace(/\\/$/, \"\");\n\n\t// Toolbar runs on a per-store preview deploy (mystore-preview.yns.{store|cx})\n\t// served by the merchant's Vercel project — that host has no YNS API. Aim\n\t// at the apex `yns.store` / `yns.cx`, which yns-app serves and where the\n\t// API lives. Cookies scoped to `.yns.store` / `.yns.cx` travel along.\n\tconst host = window.location.hostname;\n\tconst protocol = window.location.protocol;\n\tif (host.endsWith(\".yns.store\")) return `${protocol}//yns.store`;\n\tif (host.endsWith(\".yns.cx\")) return `${protocol}//yns.cx`;\n\treturn window.location.origin;\n};\n\nconst computeCssSelector = (el: Element): string => {\n\tif (el.id) return `#${CSS.escape(el.id)}`;\n\tconst path: string[] = [];\n\tlet node: Element | null = el;\n\twhile (node && node.nodeType === Node.ELEMENT_NODE && path.length < 6) {\n\t\tlet part = node.tagName.toLowerCase();\n\t\tconst className = node.getAttribute(\"class\");\n\t\tif (className) {\n\t\t\tconst classes = className\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.slice(0, 2)\n\t\t\t\t.map((c) => `.${CSS.escape(c)}`)\n\t\t\t\t.join(\"\");\n\t\t\tpart += classes;\n\t\t}\n\t\tconst parent: Element | null = node.parentElement;\n\t\tconst tag = node.tagName;\n\t\tif (parent) {\n\t\t\tconst siblings: Element[] = Array.from(parent.children).filter((c) => c.tagName === tag);\n\t\t\tif (siblings.length > 1) {\n\t\t\t\tconst idx = siblings.indexOf(node) + 1;\n\t\t\t\tpart += `:nth-of-type(${idx})`;\n\t\t\t}\n\t\t}\n\t\tpath.unshift(part);\n\t\tnode = parent;\n\t}\n\treturn path.join(\" > \");\n};\n\nconst ATTRS_OF_INTEREST = [\"id\", \"class\", \"data-testid\", \"aria-label\", \"role\", \"name\", \"href\", \"src\"];\n\nconst formatAttrs = (el: Element): string => {\n\tconst parts: string[] = [];\n\tfor (const attr of ATTRS_OF_INTEREST) {\n\t\tconst v = el.getAttribute(attr);\n\t\tif (!v) continue;\n\t\tconst trimmed = v.length > 80 ? `${v.slice(0, 80)}…` : v;\n\t\tparts.push(` ${attr}=\"${trimmed.replace(/\"/g, \""\")}\"`);\n\t}\n\treturn parts.join(\"\");\n};\n\nconst formatTextContent = (el: Element): string => {\n\tconst text = (el.textContent ?? \"\").replace(/\\s+/g, \" \").trim();\n\tif (!text) return \"\";\n\treturn text.length > 100 ? `${text.slice(0, 100)}…` : text;\n};\n\nconst buildSurroundingHtml = (target: Element): string => {\n\tconst ancestors: Element[] = [];\n\tlet cur: Element | null = target;\n\twhile (cur && cur !== document.documentElement && ancestors.length < 5) {\n\t\tconst parent: Element | null = cur.parentElement;\n\t\tif (!parent) break;\n\t\tancestors.unshift(parent);\n\t\tcur = parent;\n\t}\n\n\tconst lines: string[] = [];\n\tlet depth = 0;\n\tfor (const ancestor of ancestors) {\n\t\tconst indent = \" \".repeat(depth);\n\t\tlines.push(`${indent}<${ancestor.tagName.toLowerCase()}${formatAttrs(ancestor)}>`);\n\t\tdepth++;\n\t}\n\n\tconst targetIndent = \" \".repeat(depth);\n\tconst targetTag = target.tagName.toLowerCase();\n\tconst targetText = formatTextContent(target);\n\tlines.push(\n\t\t`${targetIndent}<${targetTag}${formatAttrs(target)}>${\n\t\t\ttargetText ? `${targetText}</${targetTag}>` : \"\"\n\t\t} ← TARGET`,\n\t);\n\n\tif (!targetText) {\n\t\tconst childIndent = \" \".repeat(depth + 1);\n\t\tconst children = Array.from(target.children).slice(0, 6);\n\t\tfor (const child of children) {\n\t\t\tconst childText = formatTextContent(child);\n\t\t\tlines.push(\n\t\t\t\t`${childIndent}<${child.tagName.toLowerCase()}${formatAttrs(child)}>${\n\t\t\t\t\tchildText ? `${childText}</${child.tagName.toLowerCase()}>` : \"\"\n\t\t\t\t}`,\n\t\t\t);\n\t\t}\n\t\tif (target.children.length > 6) {\n\t\t\tlines.push(`${childIndent}… (${target.children.length - 6} more children)`);\n\t\t}\n\t\tlines.push(`${targetIndent}</${targetTag}>`);\n\t}\n\n\tfor (let i = ancestors.length - 1; i >= 0; i--) {\n\t\tconst indent = \" \".repeat(i);\n\t\tconst ancestor = ancestors[i];\n\t\tif (!ancestor) continue;\n\t\tlines.push(`${indent}</${ancestor.tagName.toLowerCase()}>`);\n\t}\n\n\treturn lines.join(\"\\n\");\n};\n\nconst isInsideToolbar = (el: Element | null): boolean => {\n\tlet node = el;\n\twhile (node) {\n\t\tif (node instanceof HTMLElement && node.dataset.ynsFeedbackUi === \"true\") return true;\n\t\tnode = node.parentElement;\n\t}\n\treturn false;\n};\n\nconst IGNORED_TAGS = new Set([\"HTML\", \"HEAD\", \"SCRIPT\", \"STYLE\", \"NOSCRIPT\"]);\n\nconst POLL_INTERVAL_MS = 10_000;\n\n// Format a server-computed ETA as remaining + absolute label. The deadline\n// math itself lives in yns-app's `lib/feedback-comments-api.ts` so the toolbar\n// and YNS lock screen stay in sync.\nconst formatEta = (etaIso: string): string => {\n\tconst eta = new Date(etaIso);\n\tconst remainingMs = eta.getTime() - Date.now();\n\tconst dateLabel = eta.toLocaleString(undefined, {\n\t\tweekday: \"short\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\thour: \"numeric\",\n\t\tminute: \"2-digit\",\n\t});\n\n\tif (remainingMs <= 0) return `Any moment now (estimated by ${dateLabel})`;\n\tconst remainingMin = Math.ceil(remainingMs / 60_000);\n\tif (remainingMin < 60) return `~${remainingMin} minutes (by ${dateLabel})`;\n\tconst remainingHours = Math.round(remainingMs / 3_600_000);\n\tif (remainingHours < 24) {\n\t\treturn `~${remainingHours} ${remainingHours === 1 ? \"hour\" : \"hours\"} (by ${dateLabel})`;\n\t}\n\tconst remainingDays = Math.round(remainingMs / 86_400_000);\n\treturn `~${remainingDays} ${remainingDays === 1 ? \"day\" : \"days\"} (by ${dateLabel})`;\n};\n\nfunction FeedbackToolbar() {\n\tconst [session, setSession] = useState<ActiveSession | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\tconst [pinMode, setPinMode] = useState(false);\n\tconst [pending, setPending] = useState<PendingPin | null>(null);\n\tconst [editingId, setEditingId] = useState<string | null>(null);\n\tconst [sidebarOpen, setSidebarOpen] = useState(false);\n\tconst [finalizing, setFinalizing] = useState(false);\n\tconst apiBase = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tapiBase.current = buildApiBase();\n\t\tif (!apiBase.current) {\n\t\t\tsetLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tlet cancelled = false;\n\t\tconst controller = new AbortController();\n\n\t\tconst fetchOnce = async () => {\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(\n\t\t\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t\t\t{ credentials: \"include\", signal: controller.signal },\n\t\t\t\t);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\tsetSession(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst data = (await res.json()) as ActiveSession;\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetSession(data);\n\t\t\t\t// Hard-refresh once the AI run lands and the preview is up to date.\n\t\t\t\t// Out of scope to diff anything smarter — the new deploy replaces\n\t\t\t\t// the page entirely so a full reload is appropriate here.\n\t\t\t\tif (data.sessionStatus === \"done\") {\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tif (!cancelled) setSession(null);\n\t\t\t} finally {\n\t\t\t\tif (!cancelled) setLoading(false);\n\t\t\t}\n\t\t};\n\n\t\tvoid fetchOnce();\n\t\tconst interval = window.setInterval(fetchOnce, POLL_INTERVAL_MS);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tcontroller.abort();\n\t\t\twindow.clearInterval(interval);\n\t\t};\n\t}, []);\n\n\t// When the session stops accepting comments (finalized → in_review, or\n\t// closed), tear down any in-flight UI so the hover overlay/popover/sidebar\n\t// don't linger after the toolbar's main render returns null.\n\tuseEffect(() => {\n\t\tif (!session || session.canComment) return;\n\t\tsetPinMode(false);\n\t\tsetPending(null);\n\t\tsetSidebarOpen(false);\n\t\tsetEditingId(null);\n\t}, [session]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tdocument.body.style.cursor = \"crosshair\";\n\t\treturn () => {\n\t\t\tdocument.body.style.cursor = \"\";\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.dataset.ynsFeedbackUi = \"true\";\n\t\toverlay.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483644\",\n\t\t\t\"border: 2px dashed #10b981\",\n\t\t\t\"background: rgba(16, 185, 129, 0.08)\",\n\t\t\t\"border-radius: 3px\",\n\t\t\t\"display: none\",\n\t\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t\t].join(\";\");\n\n\t\tconst label = document.createElement(\"div\");\n\t\tlabel.dataset.ynsFeedbackUi = \"true\";\n\t\tlabel.textContent = \"Click to comment\";\n\t\tlabel.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483645\",\n\t\t\t\"background: #059669\",\n\t\t\t\"color: #fff\",\n\t\t\t\"font-size: 11px\",\n\t\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\t\"padding: 3px 8px\",\n\t\t\t\"border-radius: 4px\",\n\t\t\t\"white-space: nowrap\",\n\t\t\t\"display: none\",\n\t\t\t\"box-shadow: 0 2px 6px rgba(0,0,0,0.15)\",\n\t\t].join(\";\");\n\n\t\tdocument.documentElement.appendChild(overlay);\n\t\tdocument.documentElement.appendChild(label);\n\n\t\tlet hovered: Element | null = null;\n\t\tconst handleMove = (e: MouseEvent) => {\n\t\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\t\tif (!el || IGNORED_TAGS.has(el.tagName) || isInsideToolbar(el)) {\n\t\t\t\toverlay.style.display = \"none\";\n\t\t\t\tlabel.style.display = \"none\";\n\t\t\t\thovered = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thovered = el;\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (hovered !== el) return;\n\t\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\t\toverlay.style.top = `${rect.top}px`;\n\t\t\t\toverlay.style.left = `${rect.left}px`;\n\t\t\t\toverlay.style.width = `${rect.width}px`;\n\t\t\t\toverlay.style.height = `${rect.height}px`;\n\t\t\t\toverlay.style.display = \"block\";\n\t\t\t\tlabel.style.left = `${e.clientX + 14}px`;\n\t\t\t\tlabel.style.top = `${e.clientY + 14}px`;\n\t\t\t\tlabel.style.display = \"block\";\n\t\t\t});\n\t\t};\n\n\t\tconst handleLeave = () => {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tlabel.style.display = \"none\";\n\t\t\thovered = null;\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleLeave);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", handleMove, true);\n\t\t\tdocument.removeEventListener(\"mouseleave\", handleLeave);\n\t\t\toverlay.remove();\n\t\t\tlabel.remove();\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tconst handleClick = (event: MouseEvent) => {\n\t\t\tconst target = event.target;\n\t\t\tif (!(target instanceof Element)) return;\n\t\t\tif (isInsideToolbar(target)) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst rect = target.getBoundingClientRect();\n\t\t\tconst offsetXRatio = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0.5;\n\t\t\tconst offsetYRatio = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;\n\t\t\tsetPending({\n\t\t\t\tcssSelector: computeCssSelector(target),\n\t\t\t\tpagePath: window.location.pathname,\n\t\t\t\tsurroundingHtml: buildSurroundingHtml(target),\n\t\t\t\trect: {\n\t\t\t\t\ttop: rect.top + window.scrollY,\n\t\t\t\t\tleft: rect.left + window.scrollX,\n\t\t\t\t\twidth: rect.width,\n\t\t\t\t\theight: rect.height,\n\t\t\t\t},\n\t\t\t\tclickX: event.clientX + window.scrollX,\n\t\t\t\tclickY: event.clientY + window.scrollY,\n\t\t\t\toffsetXRatio: Math.min(1, Math.max(0, offsetXRatio)),\n\t\t\t\toffsetYRatio: Math.min(1, Math.max(0, offsetYRatio)),\n\t\t\t});\n\t\t\tsetPinMode(false);\n\t\t};\n\t\tdocument.addEventListener(\"click\", handleClick, { capture: true });\n\t\treturn () => document.removeEventListener(\"click\", handleClick, { capture: true });\n\t}, [pinMode]);\n\n\tconst refreshComments = async () => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(\n\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t{ credentials: \"include\" },\n\t\t);\n\t\tif (!res.ok) return;\n\t\tconst data = (await res.json()) as ActiveSession;\n\t\tsetSession(data);\n\t};\n\n\tconst submitNewComment = async (content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current || !session || !pending) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments`, {\n\t\t\tmethod: \"POST\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tfeedbackSessionId: session.feedbackSessionId,\n\t\t\t\tcontent,\n\t\t\t\tpagePath: pending.pagePath,\n\t\t\t\tcssSelector: pending.cssSelector,\n\t\t\t\tsurroundingHtml: pending.surroundingHtml,\n\t\t\t\toffsetXRatio: pending.offsetXRatio,\n\t\t\t\toffsetYRatio: pending.offsetYRatio,\n\t\t\t\t...(attachments.length > 0 ? { attachments } : {}),\n\t\t\t}),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetPending(null);\n\t\tsetPinMode(true);\n\t\tawait refreshComments();\n\t};\n\n\tconst updateComment = async (id: string, content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"PATCH\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ content, attachments }),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetEditingId(null);\n\t\tawait refreshComments();\n\t};\n\n\tconst removeComment = async (id: string) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"DELETE\",\n\t\t\tcredentials: \"include\",\n\t\t});\n\t\tif (!res.ok) return;\n\t\tawait refreshComments();\n\t};\n\n\tconst finalizeSession = async () => {\n\t\tif (!apiBase.current || !session) return;\n\t\tif (!window.confirm(\"Finalize this feedback session? You won't be able to add more comments.\")) return;\n\t\tsetFinalizing(true);\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase.current}/api/feedback-sessions/${session.feedbackSessionId}/finalize`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\" },\n\t\t\t);\n\t\t\tif (!res.ok) return;\n\t\t\t// Flip BOTH canComment and sessionStatus locally. Without the status\n\t\t\t// flip, the render guard sees `canComment=false` + `status=in_progress`\n\t\t\t// (a state the SubmittedPanel branch returns null for) and the toolbar\n\t\t\t// disappears for the ~10s gap until the next poll catches up.\n\t\t\tconst body = (await res.json().catch(() => null)) as { status?: SessionStatus } | null;\n\t\t\tconst nextStatus: SessionStatus = body?.status ?? \"processing\";\n\t\t\tsetSession((prev) => (prev ? { ...prev, canComment: false, sessionStatus: nextStatus } : prev));\n\t\t} finally {\n\t\t\tsetFinalizing(false);\n\t\t}\n\t};\n\n\tconst scrollToComment = (comment: FeedbackComment) => {\n\t\tif (comment.pagePath !== window.location.pathname) {\n\t\t\twindow.location.assign(comment.pagePath);\n\t\t\treturn;\n\t\t}\n\t\tlet el: Element | null = null;\n\t\ttry {\n\t\t\tel = document.querySelector(comment.cssSelector);\n\t\t} catch {\n\t\t\tel = null;\n\t\t}\n\t\tif (!el) return;\n\t\tel.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n\t\tsetEditingId(comment.id);\n\t};\n\n\tif (loading || !session) return null;\n\n\t// Session has been finalized for AI review. Show a small status panel with\n\t// the same progress + ETA shown in YNS, but no commenting controls. Polling\n\t// continues in the background; on `done` we hard-reload to pick up the\n\t// fresh deploy.\n\tif (!session.canComment) {\n\t\tif (session.sessionStatus !== \"processing\" && session.sessionStatus !== \"in_review\") return null;\n\t\treturn (\n\t\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t\t<SubmittedPanel progress={session.progress} eta={session.eta} status={session.sessionStatus} />\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst visiblePins = session.comments.filter(\n\t\t(c) => c.pagePath === window.location.pathname && c.status !== \"done\",\n\t);\n\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t{visiblePins.map((pin, idx) => (\n\t\t\t\t<PinOverlay\n\t\t\t\t\tkey={pin.id}\n\t\t\t\t\tpin={pin}\n\t\t\t\t\tnumber={idx + 1}\n\t\t\t\t\tediting={editingId === pin.id}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonStartEdit={() => setEditingId(pin.id)}\n\t\t\t\t\tonCancelEdit={() => setEditingId(null)}\n\t\t\t\t\tonSave={(content, attachments) => updateComment(pin.id, content, attachments)}\n\t\t\t\t\tonRemove={() => removeComment(pin.id)}\n\t\t\t\t/>\n\t\t\t))}\n\n\t\t\t{pending && (\n\t\t\t\t<PendingCommentPopover\n\t\t\t\t\tpending={pending}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonCancel={() => {\n\t\t\t\t\t\tsetPending(null);\n\t\t\t\t\t\tsetPinMode(true);\n\t\t\t\t\t}}\n\t\t\t\t\tonSave={(content, attachments) => submitNewComment(content, attachments)}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t{sidebarOpen && (\n\t\t\t\t<CommentsSidebar\n\t\t\t\t\tcomments={session.comments}\n\t\t\t\t\tcurrentPath={window.location.pathname}\n\t\t\t\t\tonClose={() => setSidebarOpen(false)}\n\t\t\t\t\tonSelect={scrollToComment}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => setPinMode((v) => !v)}\n\t\t\t\t\tstyle={pinMode ? toolbarButtonActiveStyle : toolbarButtonStyle}\n\t\t\t\t>\n\t\t\t\t\t{pinMode ? \"Cancel\" : \"Add comment\"}\n\t\t\t\t</button>\n\t\t\t\t<button type=\"button\" onClick={() => setSidebarOpen((v) => !v)} style={toolbarButtonGhostStyle}>\n\t\t\t\t\t{sidebarOpen ? \"Hide list\" : `List (${session.comments.length})`}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={finalizeSession}\n\t\t\t\t\tdisabled={finalizing}\n\t\t\t\t\tstyle={toolbarButtonFinalizeStyle}\n\t\t\t\t>\n\t\t\t\t\t{finalizing ? \"Finalizing…\" : \"Finalize\"}\n\t\t\t\t</button>\n\t\t\t\t<span style={toolbarHintStyle}>\n\t\t\t\t\t{pinMode ? \"Click any element to comment\" : `${visiblePins.length} on this page`}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SubmittedPanel({\n\tprogress,\n\teta: etaIso,\n\tstatus,\n}: {\n\tprogress: ReviewProgress;\n\teta: string;\n\tstatus: SessionStatus;\n}) {\n\t// Re-render every minute so the relative ETA label stays fresh between\n\t// the 10s data polls (which only re-render when the API payload changes).\n\tconst [, setTick] = useState(0);\n\tuseEffect(() => {\n\t\tconst id = window.setInterval(() => setTick((t) => t + 1), 60_000);\n\t\treturn () => window.clearInterval(id);\n\t}, []);\n\n\tconst eta = formatEta(etaIso);\n\tconst isInReview = status === \"in_review\";\n\tconst headline = isInReview ? \"Feedback under review\" : \"Applying feedback\";\n\n\treturn (\n\t\t<div style={submittedPanelStyle}>\n\t\t\t<style>{spinnerKeyframes}</style>\n\t\t\t<div style={submittedHeaderStyle}>\n\t\t\t\t<Spinner />\n\t\t\t\t<span style={submittedBadgeStyle}>Submitted</span>\n\t\t\t\t<strong style={{ fontSize: 13 }}>{headline}</strong>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressLabelStyle}>\n\t\t\t\t<span>Review progress</span>\n\t\t\t\t<span style={{ opacity: 0.7 }}>{progress.label}</span>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressTrackStyle}>\n\t\t\t\t<div style={{ ...submittedProgressFillStyle, width: `${progress.fillPct}%` }} />\n\t\t\t</div>\n\t\t\t<p style={submittedEtaStyle}>\n\t\t\t\tEstimated delivery: <span style={{ fontWeight: 600 }}>{eta}</span>\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n\n// Inline keyframes — toolbar avoids relying on a global stylesheet.\nconst spinnerKeyframes = `@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }`;\n\nfunction Spinner({ size = 14 }: { size?: number }) {\n\treturn (\n\t\t<span\n\t\t\taria-hidden\n\t\t\tstyle={{\n\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tborder: \"2px solid rgba(16, 185, 129, 0.25)\",\n\t\t\t\tborderTopColor: \"#10b981\",\n\t\t\t\tborderRadius: 999,\n\t\t\t\tanimation: \"yns-feedback-spin 0.9s linear infinite\",\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction PinOverlay({\n\tpin,\n\tnumber,\n\tediting,\n\tfeedbackSessionId,\n\tapiBase,\n\tonStartEdit,\n\tonCancelEdit,\n\tonSave,\n\tonRemove,\n}: {\n\tpin: FeedbackComment;\n\tnumber: number;\n\tediting: boolean;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonStartEdit: () => void;\n\tonCancelEdit: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove: () => Promise<void>;\n}) {\n\tconst target = useTargetPosition(pin.cssSelector, pin.offsetXRatio, pin.offsetYRatio);\n\tif (!target) return null;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\tposition: \"absolute\",\n\t\t\t\ttop: target.top,\n\t\t\t\tleft: target.left,\n\t\t\t\tzIndex: 2147483600,\n\t\t\t\tpointerEvents: \"auto\",\n\t\t\t}}\n\t\t>\n\t\t\t<button type=\"button\" onClick={onStartEdit} style={pinDotStyle} title={pin.content}>\n\t\t\t\t{number}\n\t\t\t</button>\n\t\t\t{editing && (\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial={pin.content}\n\t\t\t\t\tinitialAttachments={pin.attachments}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancelEdit}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t\tonRemove={onRemove}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction PendingCommentPopover({\n\tpending,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n}: {\n\tpending: PendingPin;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n}) {\n\treturn (\n\t\t<>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.rect.top + pending.rect.height * pending.offsetYRatio - 12,\n\t\t\t\t\tleft: pending.rect.left + pending.rect.width * pending.offsetXRatio - 12,\n\t\t\t\t\tzIndex: 2147483600,\n\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div style={pinDotStyle}>•</div>\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.clickY + 10,\n\t\t\t\t\tleft: pending.clickX + 10,\n\t\t\t\t\tzIndex: 2147483647,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial=\"\"\n\t\t\t\t\tinitialAttachments={[]}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancel}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n\nfunction CommentsSidebar({\n\tcomments,\n\tcurrentPath,\n\tonClose,\n\tonSelect,\n}: {\n\tcomments: FeedbackComment[];\n\tcurrentPath: string;\n\tonClose: () => void;\n\tonSelect: (comment: FeedbackComment) => void;\n}) {\n\tconst groups = new Map<string, FeedbackComment[]>();\n\tfor (const c of comments) {\n\t\tconst list = groups.get(c.pagePath) ?? [];\n\t\tlist.push(c);\n\t\tgroups.set(c.pagePath, list);\n\t}\n\tconst paths = Array.from(groups.keys()).sort((a, b) => {\n\t\tif (a === currentPath) return -1;\n\t\tif (b === currentPath) return 1;\n\t\treturn a.localeCompare(b);\n\t});\n\n\treturn (\n\t\t<div style={sidebarStyle}>\n\t\t\t<div style={sidebarHeaderStyle}>\n\t\t\t\t<strong style={{ fontSize: 14 }}>Comments ({comments.length})</strong>\n\t\t\t\t<button type=\"button\" onClick={onClose} style={ghostButtonStyle}>\n\t\t\t\t\tClose\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div style={sidebarScrollStyle}>\n\t\t\t\t{comments.length === 0 ? (\n\t\t\t\t\t<div style={sidebarEmptyStyle}>No comments yet. Click “Add comment” to leave one.</div>\n\t\t\t\t) : (\n\t\t\t\t\tpaths.map((path) => (\n\t\t\t\t\t\t<div key={path} style={{ marginBottom: 12 }}>\n\t\t\t\t\t\t\t<div style={sidebarPathStyle}>{path === currentPath ? `${path} · current` : path}</div>\n\t\t\t\t\t\t\t{(groups.get(path) ?? []).map((c, idx) => (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tkey={c.id}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => onSelect(c)}\n\t\t\t\t\t\t\t\t\tstyle={sidebarItemStyle}\n\t\t\t\t\t\t\t\t\tdisabled={c.status === \"done\" && false}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemIndexStyle}>{idx + 1}</span>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemTextStyle}>\n\t\t\t\t\t\t\t\t\t\t{c.content.length > 140 ? `${c.content.slice(0, 140)}…` : c.content}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{c.status === \"done\" && <span style={sidebarItemDoneStyle}>done</span>}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nconst MAX_ATTACHMENTS = 5;\nconst MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nconst readImageDimensions = (file: File): Promise<{ width: number; height: number } | null> =>\n\tnew Promise((resolve) => {\n\t\tconst url = URL.createObjectURL(file);\n\t\tconst img = new window.Image();\n\t\timg.onload = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve({ width: img.naturalWidth, height: img.naturalHeight });\n\t\t};\n\t\timg.onerror = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve(null);\n\t\t};\n\t\timg.src = url;\n\t});\n\nfunction EditPopover({\n\tinitial,\n\tinitialAttachments,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n\tonRemove,\n}: {\n\tinitial: string;\n\tinitialAttachments: FeedbackAttachment[];\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove?: () => Promise<void>;\n}) {\n\tconst [value, setValue] = useState(initial);\n\tconst [attachments, setAttachments] = useState<FeedbackAttachment[]>(initialAttachments);\n\tconst [saving, setSaving] = useState(false);\n\tconst [uploading, setUploading] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst textareaRef = useRef<HTMLTextAreaElement | null>(null);\n\tconst fileInputRef = useRef<HTMLInputElement | null>(null);\n\n\tuseEffect(() => {\n\t\tconst el = textareaRef.current;\n\t\tif (!el) return;\n\t\tel.focus();\n\t\tconst len = el.value.length;\n\t\tel.setSelectionRange(len, len);\n\t}, []);\n\n\tconst handleSubmit = async (e: FormEvent) => {\n\t\te.preventDefault();\n\t\tconst content = value.trim();\n\t\tif (!content) return;\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tawait onSave(content, attachments);\n\t\t} finally {\n\t\t\tsetSaving(false);\n\t\t}\n\t};\n\n\tconst handleFiles = async (files: FileList | null) => {\n\t\tif (!files || files.length === 0 || !apiBase) return;\n\t\tsetError(null);\n\t\tconst slots = MAX_ATTACHMENTS - attachments.length;\n\t\tif (slots <= 0) {\n\t\t\tsetError(`Max ${MAX_ATTACHMENTS} images per comment`);\n\t\t\treturn;\n\t\t}\n\t\tconst picked = Array.from(files).slice(0, slots);\n\t\tfor (const f of picked) {\n\t\t\tif (!f.type.startsWith(\"image/\")) {\n\t\t\t\tsetError(`\"${f.name}\" is not an image`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (f.size > MAX_ATTACHMENT_BYTES) {\n\t\t\t\tsetError(`\"${f.name}\" exceeds 5 MB`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tsetUploading(true);\n\t\ttry {\n\t\t\tconst dims = await Promise.all(picked.map(readImageDimensions));\n\t\t\tconst fd = new FormData();\n\t\t\tpicked.forEach((file, i) => {\n\t\t\t\tfd.append(\"file\", file);\n\t\t\t\tfd.append(\"width\", dims[i]?.width ? String(dims[i]?.width) : \"\");\n\t\t\t\tfd.append(\"height\", dims[i]?.height ? String(dims[i]?.height) : \"\");\n\t\t\t});\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(feedbackSessionId)}`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\", body: fd },\n\t\t\t);\n\t\t\tif (!res.ok) {\n\t\t\t\tconst body = (await res.json().catch(() => null)) as { error?: string } | null;\n\t\t\t\tsetError(body?.error ?? \"Upload failed\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst body = (await res.json()) as { uploads: FeedbackAttachment[] };\n\t\t\tsetAttachments((prev) => [...prev, ...body.uploads]);\n\t\t} catch {\n\t\t\tsetError(\"Upload failed\");\n\t\t} finally {\n\t\t\tsetUploading(false);\n\t\t\tif (fileInputRef.current) fileInputRef.current.value = \"\";\n\t\t}\n\t};\n\n\tconst removeAttachment = (url: string) => {\n\t\tsetAttachments((prev) => prev.filter((a) => a.url !== url));\n\t};\n\n\treturn (\n\t\t<form onSubmit={handleSubmit} style={popoverStyle}>\n\t\t\t<textarea\n\t\t\t\tref={textareaRef}\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => setValue(e.target.value)}\n\t\t\t\tplaceholder=\"Leave a comment…\"\n\t\t\t\tstyle={textareaStyle}\n\t\t\t\trows={3}\n\t\t\t/>\n\t\t\t{attachments.length > 0 && (\n\t\t\t\t<div style={attachmentRowStyle}>\n\t\t\t\t\t{attachments.map((att) => (\n\t\t\t\t\t\t<div key={att.url} style={attachmentThumbWrapStyle}>\n\t\t\t\t\t\t\t<img src={att.url} alt=\"\" style={attachmentThumbStyle} />\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => removeAttachment(att.url)}\n\t\t\t\t\t\t\t\tstyle={attachmentRemoveStyle}\n\t\t\t\t\t\t\t\tdisabled={saving || uploading}\n\t\t\t\t\t\t\t\taria-label=\"Remove attachment\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{error && <div style={errorTextStyle}>{error}</div>}\n\t\t\t<input\n\t\t\t\tref={fileInputRef}\n\t\t\t\ttype=\"file\"\n\t\t\t\taccept=\"image/*\"\n\t\t\t\tmultiple\n\t\t\t\tonChange={(e) => void handleFiles(e.target.files)}\n\t\t\t\tstyle={{ display: \"none\" }}\n\t\t\t/>\n\t\t\t<div style={popoverActionsStyle}>\n\t\t\t\t{onRemove && (\n\t\t\t\t\t<button type=\"button\" onClick={() => onRemove()} style={dangerButtonStyle} disabled={saving}>\n\t\t\t\t\t\tDelete\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => fileInputRef.current?.click()}\n\t\t\t\t\tstyle={ghostButtonStyle}\n\t\t\t\t\tdisabled={saving || uploading || attachments.length >= MAX_ATTACHMENTS}\n\t\t\t\t>\n\t\t\t\t\t{uploading\n\t\t\t\t\t\t? \"Uploading…\"\n\t\t\t\t\t\t: `Attach image${attachments.length > 0 ? ` (${attachments.length})` : \"\"}`}\n\t\t\t\t</button>\n\t\t\t\t<div style={{ flex: 1 }} />\n\t\t\t\t<button type=\"button\" onClick={onCancel} style={ghostButtonStyle} disabled={saving}>\n\t\t\t\t\tCancel\n\t\t\t\t</button>\n\t\t\t\t<button type=\"submit\" style={primaryButtonStyle} disabled={saving || uploading || !value.trim()}>\n\t\t\t\t\t{saving ? \"Saving…\" : \"Save\"}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</form>\n\t);\n}\n\nfunction useTargetPosition(selector: string, offsetXRatio: number, offsetYRatio: number) {\n\tconst [pos, setPos] = useState<{ top: number; left: number } | null>(null);\n\n\tuseEffect(() => {\n\t\tconst update = () => {\n\t\t\tlet el: Element | null = null;\n\t\t\ttry {\n\t\t\t\tel = document.querySelector(selector);\n\t\t\t} catch {\n\t\t\t\tel = null;\n\t\t\t}\n\t\t\tif (!el) {\n\t\t\t\tsetPos(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst r = el.getBoundingClientRect();\n\t\t\tsetPos({\n\t\t\t\ttop: r.top + window.scrollY + r.height * offsetYRatio - 12,\n\t\t\t\tleft: r.left + window.scrollX + r.width * offsetXRatio - 12,\n\t\t\t});\n\t\t};\n\n\t\tupdate();\n\t\tconst ro = new ResizeObserver(update);\n\t\ttry {\n\t\t\tconst el = document.querySelector(selector);\n\t\t\tif (el) ro.observe(el);\n\t\t} catch {\n\t\t\t// swallow invalid selector\n\t\t}\n\t\twindow.addEventListener(\"scroll\", update, true);\n\t\twindow.addEventListener(\"resize\", update);\n\t\treturn () => {\n\t\t\tro.disconnect();\n\t\t\twindow.removeEventListener(\"scroll\", update, true);\n\t\t\twindow.removeEventListener(\"resize\", update);\n\t\t};\n\t}, [selector, offsetXRatio, offsetYRatio]);\n\n\treturn pos;\n}\n\n// Inline styles — toolbar is injected into arbitrary stores, so we avoid relying\n// on any CSS framework being present.\nconst toolbarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n\tpadding: \"8px 12px\",\n\tbackground: \"rgba(17, 17, 17, 0.92)\",\n\tcolor: \"white\",\n\tborderRadius: 999,\n\tboxShadow: \"0 8px 24px rgba(0,0,0,0.25)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tfontSize: 14,\n\tpointerEvents: \"auto\",\n};\n\nconst toolbarButtonStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"white\",\n\tcolor: \"#111\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarButtonActiveStyle: CSSProperties = {\n\t...toolbarButtonStyle,\n\tbackground: \"#ef4444\",\n\tcolor: \"white\",\n};\n\nconst toolbarButtonGhostStyle: CSSProperties = {\n\tborder: \"1px solid rgba(255,255,255,0.4)\",\n\tbackground: \"transparent\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n\tfontSize: 13,\n};\n\nconst toolbarButtonFinalizeStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarHintStyle: CSSProperties = {\n\topacity: 0.8,\n\tfontSize: 12,\n};\n\nconst pinDotStyle: CSSProperties = {\n\twidth: 24,\n\theight: 24,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tborder: \"2px solid white\",\n\tcursor: \"pointer\",\n\tfontSize: 12,\n\tfontWeight: 700,\n\tboxShadow: \"0 2px 6px rgba(0,0,0,0.3)\",\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tpadding: 0,\n};\n\nconst popoverStyle: CSSProperties = {\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 8,\n\tpadding: 12,\n\twidth: 280,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tcolor: \"#111\",\n};\n\nconst textareaStyle: CSSProperties = {\n\twidth: \"100%\",\n\tborder: \"1px solid #d1d5db\",\n\tborderRadius: 6,\n\tpadding: 8,\n\tfontSize: 14,\n\tresize: \"vertical\",\n\tfontFamily: \"inherit\",\n\tcolor: \"#111\",\n\tbackground: \"white\",\n\tboxSizing: \"border-box\",\n};\n\nconst popoverActionsStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 6,\n};\n\nconst baseButton: CSSProperties = {\n\tborder: \"1px solid transparent\",\n\tborderRadius: 6,\n\tpadding: \"6px 10px\",\n\tfontSize: 13,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n};\n\nconst primaryButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"#111\",\n\tcolor: \"white\",\n};\n\nconst ghostButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n};\n\nconst dangerButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#b91c1c\",\n\tborderColor: \"#fecaca\",\n};\n\nconst attachmentRowStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tflexWrap: \"wrap\",\n\tgap: 6,\n};\n\nconst attachmentThumbWrapStyle: CSSProperties = {\n\tposition: \"relative\",\n\twidth: 56,\n\theight: 56,\n\tborderRadius: 6,\n\toverflow: \"hidden\",\n\tborder: \"1px solid #e5e7eb\",\n\tbackground: \"#f9fafb\",\n};\n\nconst attachmentThumbStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: \"100%\",\n\tobjectFit: \"cover\",\n\tdisplay: \"block\",\n};\n\nconst attachmentRemoveStyle: CSSProperties = {\n\tposition: \"absolute\",\n\ttop: 2,\n\tright: 2,\n\twidth: 18,\n\theight: 18,\n\tborderRadius: 999,\n\tborder: \"none\",\n\tbackground: \"rgba(17, 17, 17, 0.85)\",\n\tcolor: \"white\",\n\tfontSize: 13,\n\tlineHeight: \"16px\",\n\tcursor: \"pointer\",\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst errorTextStyle: CSSProperties = {\n\tcolor: \"#b91c1c\",\n\tfontSize: 12,\n\tmargin: 0,\n};\n\nconst sidebarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\ttop: 0,\n\tright: 0,\n\tbottom: 0,\n\twidth: 360,\n\tmaxWidth: \"92vw\",\n\tbackground: \"white\",\n\tborderLeft: \"1px solid #e5e7eb\",\n\tboxShadow: \"-12px 0 32px rgba(0,0,0,0.12)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n};\n\nconst sidebarHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"space-between\",\n\tpadding: \"12px 16px\",\n\tborderBottom: \"1px solid #e5e7eb\",\n};\n\nconst sidebarScrollStyle: CSSProperties = {\n\tflex: 1,\n\toverflow: \"auto\",\n\tpadding: \"12px 12px 24px\",\n};\n\nconst sidebarEmptyStyle: CSSProperties = {\n\tcolor: \"#6b7280\",\n\tfontSize: 13,\n\tpadding: \"24px 8px\",\n\ttextAlign: \"center\",\n};\n\nconst sidebarPathStyle: CSSProperties = {\n\tfontSize: 11,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n\tcolor: \"#6b7280\",\n\tpadding: \"4px 4px 6px\",\n\twordBreak: \"break-all\",\n};\n\nconst sidebarItemStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"flex-start\",\n\tgap: 8,\n\twidth: \"100%\",\n\ttextAlign: \"left\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 6,\n\tpadding: \"8px 10px\",\n\tcursor: \"pointer\",\n\tfontSize: 13,\n\tmarginBottom: 6,\n\tcolor: \"#111\",\n};\n\nconst sidebarItemIndexStyle: CSSProperties = {\n\tflexShrink: 0,\n\twidth: 22,\n\theight: 22,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tfontWeight: 700,\n\tfontSize: 11,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst sidebarItemTextStyle: CSSProperties = {\n\tflex: 1,\n\twhiteSpace: \"pre-wrap\",\n\twordBreak: \"break-word\",\n};\n\nconst sidebarItemDoneStyle: CSSProperties = {\n\tflexShrink: 0,\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 600,\n\tpadding: \"2px 6px\",\n\tborderRadius: 4,\n\talignSelf: \"center\",\n};\n\nconst submittedPanelStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tpadding: \"12px 16px\",\n\twidth: \"min(420px, calc(100vw - 32px))\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 12,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n\tpointerEvents: \"auto\",\n};\n\nconst submittedHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n};\n\nconst submittedBadgeStyle: CSSProperties = {\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 700,\n\tpadding: \"2px 8px\",\n\tborderRadius: 999,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n};\n\nconst submittedProgressLabelStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tjustifyContent: \"space-between\",\n\tfontSize: 12,\n};\n\nconst submittedProgressTrackStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: 6,\n\tbackground: \"#f3f4f6\",\n\tborderRadius: 999,\n\toverflow: \"hidden\",\n};\n\nconst submittedProgressFillStyle: CSSProperties = {\n\theight: \"100%\",\n\tbackground: \"#10b981\",\n\tborderRadius: 999,\n\ttransition: \"width 500ms\",\n};\n\nconst submittedEtaStyle: CSSProperties = {\n\tmargin: 0,\n\tfontSize: 12,\n\tcolor: \"#6b7280\",\n};\n\nexport function mountFeedbackToolbar(): { unmount: () => void } | null {\n\tconsole.log(\"[YNS Feedback Toolbar] mountFeedbackToolbar() called\", {\n\t\tisWindow: typeof window !== \"undefined\",\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\talreadyMounted: typeof document !== \"undefined\" && Boolean(document.getElementById(MOUNT_NODE_ID)),\n\t});\n\tif (typeof window === \"undefined\") return null;\n\tif (process.env.NEXT_PUBLIC_VERCEL_ENV !== \"preview\") {\n\t\tconsole.log(\n\t\t\t\"[YNS Feedback Toolbar] gate failed — NEXT_PUBLIC_VERCEL_ENV is\",\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\t);\n\t\treturn null;\n\t}\n\tif (document.getElementById(MOUNT_NODE_ID)) return null;\n\n\tconst container = document.createElement(\"div\");\n\tcontainer.id = MOUNT_NODE_ID;\n\tcontainer.dataset.ynsFeedbackUi = \"true\";\n\tdocument.body.appendChild(container);\n\n\tconst root = createRoot(container);\n\troot.render(<FeedbackToolbar />);\n\n\treturn {\n\t\tunmount: () => {\n\t\t\troot.unmount();\n\t\t\tcontainer.remove();\n\t\t},\n\t};\n}\n\n// Auto-mount on import. Wait for DOM ready.\nif (typeof window !== \"undefined\") {\n\tconsole.log(\"[YNS Feedback Toolbar] module evaluated\", {\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\treadyState: document.readyState,\n\t\twillAutoMount: process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\",\n\t});\n}\nif (typeof window !== \"undefined\" && process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\") {\n\tif (document.readyState === \"loading\") {\n\t\tdocument.addEventListener(\"DOMContentLoaded\", () => {\n\t\t\tmountFeedbackToolbar();\n\t\t});\n\t} else {\n\t\tmountFeedbackToolbar();\n\t}\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","createRoot","Fragment","jsx","jsxs","MOUNT_NODE_ID","buildApiBase","override","host","protocol","computeCssSelector","el","path","node","part","className","classes","c","parent","tag","siblings","idx","ATTRS_OF_INTEREST","formatAttrs","parts","attr","v","trimmed","formatTextContent","text","buildSurroundingHtml","target","ancestors","cur","lines","depth","ancestor","indent","targetIndent","targetTag","targetText","childIndent","children","child","childText","i","isInsideToolbar","IGNORED_TAGS","POLL_INTERVAL_MS","formatEta","etaIso","eta","remainingMs","dateLabel","remainingMin","remainingHours","remainingDays","FeedbackToolbar","session","setSession","loading","setLoading","pinMode","setPinMode","pending","setPending","editingId","setEditingId","sidebarOpen","setSidebarOpen","finalizing","setFinalizing","apiBase","cancelled","controller","fetchOnce","res","data","interval","overlay","label","hovered","handleMove","e","rect","handleLeave","handleClick","event","offsetXRatio","offsetYRatio","refreshComments","submitNewComment","content","attachments","updateComment","id","removeComment","finalizeSession","nextStatus","prev","scrollToComment","comment","SubmittedPanel","visiblePins","pin","PinOverlay","PendingCommentPopover","CommentsSidebar","toolbarStyle","toolbarButtonActiveStyle","toolbarButtonStyle","toolbarButtonGhostStyle","toolbarButtonFinalizeStyle","toolbarHintStyle","progress","status","setTick","t","submittedPanelStyle","spinnerKeyframes","submittedHeaderStyle","Spinner","submittedBadgeStyle","submittedProgressLabelStyle","submittedProgressTrackStyle","submittedProgressFillStyle","submittedEtaStyle","size","number","editing","feedbackSessionId","onStartEdit","onCancelEdit","onSave","onRemove","useTargetPosition","pinDotStyle","EditPopover","onCancel","comments","currentPath","onClose","onSelect","groups","list","paths","a","b","sidebarStyle","sidebarHeaderStyle","ghostButtonStyle","sidebarScrollStyle","sidebarEmptyStyle","sidebarPathStyle","sidebarItemStyle","sidebarItemIndexStyle","sidebarItemTextStyle","sidebarItemDoneStyle","MAX_ATTACHMENTS","MAX_ATTACHMENT_BYTES","readImageDimensions","file","resolve","url","img","initial","initialAttachments","value","setValue","setAttachments","saving","setSaving","uploading","setUploading","error","setError","textareaRef","fileInputRef","len","handleSubmit","handleFiles","files","slots","picked","f","dims","fd","body","removeAttachment","popoverStyle","textareaStyle","attachmentRowStyle","att","attachmentThumbWrapStyle","attachmentThumbStyle","attachmentRemoveStyle","errorTextStyle","popoverActionsStyle","dangerButtonStyle","primaryButtonStyle","selector","pos","setPos","update","r","ro","baseButton","mountFeedbackToolbar","container","root"]}
|
|
1
|
+
{"version":3,"sources":["../src/feedback-toolbar.tsx"],"sourcesContent":["/**\n * Feedback session toolbar — side-effect entry.\n *\n * Importing `commerce-kit/feedback-toolbar` (or `commerce-kit/browser` for the\n * combined entry) mounts a floating toolbar onto the page that lets reviewers\n * leave click-anchored comments. Comments persist via YNS API endpoints,\n * authenticated with the `better-auth` session cookie sent through\n * `credentials: \"include\"`.\n *\n * Gated on `process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\"` — only Vercel\n * preview deploys get the toolbar. Sandbox dev (env undefined) and production\n * (env === \"production\") skip it.\n */\n\nimport { type CSSProperties, type FormEvent, useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst MOUNT_NODE_ID = \"yns-feedback-toolbar-root\";\n\ninterface FeedbackAttachment {\n\turl: string;\n\tcontentType: string;\n\twidth?: number;\n\theight?: number;\n}\n\ninterface FeedbackComment {\n\tid: string;\n\tpagePath: string;\n\tcssSelector: string;\n\tcontent: string;\n\tstatus: \"todo\" | \"done\";\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n\tattachments: FeedbackAttachment[];\n}\n\ntype SessionStatus = \"created\" | \"in_progress\" | \"processing\" | \"in_review\" | \"done\";\n\ninterface ReviewProgress {\n\tfillPct: number;\n\tlabel: string;\n}\n\ninterface ActiveSession {\n\tfeedbackSessionId: string;\n\tcomments: FeedbackComment[];\n\tcanComment: boolean;\n\tsessionStatus: SessionStatus;\n\tcommentTotal: number;\n\tcommentDone: number;\n\teta: string;\n\tprogress: ReviewProgress;\n}\n\ninterface PendingPin {\n\tcssSelector: string;\n\tpagePath: string;\n\tsurroundingHtml: string;\n\trect: { top: number; left: number; width: number; height: number };\n\tclickX: number;\n\tclickY: number;\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n}\n\nconst buildApiBase = (): string | null => {\n\tif (typeof window === \"undefined\") return null;\n\tconst override = (process.env.NEXT_PUBLIC_YNS_API_BASE ?? \"\").trim();\n\tif (override) return override.replace(/\\/$/, \"\");\n\n\t// Toolbar runs on a per-store preview deploy (mystore-preview.yns.{store|cx})\n\t// served by the merchant's Vercel project — that host has no YNS API. Aim\n\t// at the apex `yns.store` / `yns.cx`, which yns-app serves and where the\n\t// API lives. Cookies scoped to `.yns.store` / `.yns.cx` travel along.\n\tconst host = window.location.hostname;\n\tconst protocol = window.location.protocol;\n\tif (host.endsWith(\".yns.store\")) return `${protocol}//yns.store`;\n\tif (host.endsWith(\".yns.cx\")) return `${protocol}//yns.cx`;\n\treturn window.location.origin;\n};\n\nconst computeCssSelector = (el: Element): string => {\n\tif (el.id) return `#${CSS.escape(el.id)}`;\n\tconst path: string[] = [];\n\tlet node: Element | null = el;\n\twhile (node && node.nodeType === Node.ELEMENT_NODE && path.length < 6) {\n\t\tlet part = node.tagName.toLowerCase();\n\t\tconst className = node.getAttribute(\"class\");\n\t\tif (className) {\n\t\t\tconst classes = className\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.slice(0, 2)\n\t\t\t\t.map((c) => `.${CSS.escape(c)}`)\n\t\t\t\t.join(\"\");\n\t\t\tpart += classes;\n\t\t}\n\t\tconst parent: Element | null = node.parentElement;\n\t\tconst tag = node.tagName;\n\t\tif (parent) {\n\t\t\tconst siblings: Element[] = Array.from(parent.children).filter((c) => c.tagName === tag);\n\t\t\tif (siblings.length > 1) {\n\t\t\t\tconst idx = siblings.indexOf(node) + 1;\n\t\t\t\tpart += `:nth-of-type(${idx})`;\n\t\t\t}\n\t\t}\n\t\tpath.unshift(part);\n\t\tnode = parent;\n\t}\n\treturn path.join(\" > \");\n};\n\nconst ATTRS_OF_INTEREST = [\"id\", \"class\", \"data-testid\", \"aria-label\", \"role\", \"name\", \"href\", \"src\"];\n\nconst formatAttrs = (el: Element): string => {\n\tconst parts: string[] = [];\n\tfor (const attr of ATTRS_OF_INTEREST) {\n\t\tconst v = el.getAttribute(attr);\n\t\tif (!v) continue;\n\t\tconst trimmed = v.length > 80 ? `${v.slice(0, 80)}…` : v;\n\t\tparts.push(` ${attr}=\"${trimmed.replace(/\"/g, \""\")}\"`);\n\t}\n\treturn parts.join(\"\");\n};\n\nconst formatTextContent = (el: Element): string => {\n\tconst text = (el.textContent ?? \"\").replace(/\\s+/g, \" \").trim();\n\tif (!text) return \"\";\n\treturn text.length > 100 ? `${text.slice(0, 100)}…` : text;\n};\n\nconst buildSurroundingHtml = (target: Element): string => {\n\tconst ancestors: Element[] = [];\n\tlet cur: Element | null = target;\n\twhile (cur && cur !== document.documentElement && ancestors.length < 5) {\n\t\tconst parent: Element | null = cur.parentElement;\n\t\tif (!parent) break;\n\t\tancestors.unshift(parent);\n\t\tcur = parent;\n\t}\n\n\tconst lines: string[] = [];\n\tlet depth = 0;\n\tfor (const ancestor of ancestors) {\n\t\tconst indent = \" \".repeat(depth);\n\t\tlines.push(`${indent}<${ancestor.tagName.toLowerCase()}${formatAttrs(ancestor)}>`);\n\t\tdepth++;\n\t}\n\n\tconst targetIndent = \" \".repeat(depth);\n\tconst targetTag = target.tagName.toLowerCase();\n\tconst targetText = formatTextContent(target);\n\tlines.push(\n\t\t`${targetIndent}<${targetTag}${formatAttrs(target)}>${\n\t\t\ttargetText ? `${targetText}</${targetTag}>` : \"\"\n\t\t} ← TARGET`,\n\t);\n\n\tif (!targetText) {\n\t\tconst childIndent = \" \".repeat(depth + 1);\n\t\tconst children = Array.from(target.children).slice(0, 6);\n\t\tfor (const child of children) {\n\t\t\tconst childText = formatTextContent(child);\n\t\t\tlines.push(\n\t\t\t\t`${childIndent}<${child.tagName.toLowerCase()}${formatAttrs(child)}>${\n\t\t\t\t\tchildText ? `${childText}</${child.tagName.toLowerCase()}>` : \"\"\n\t\t\t\t}`,\n\t\t\t);\n\t\t}\n\t\tif (target.children.length > 6) {\n\t\t\tlines.push(`${childIndent}… (${target.children.length - 6} more children)`);\n\t\t}\n\t\tlines.push(`${targetIndent}</${targetTag}>`);\n\t}\n\n\tfor (let i = ancestors.length - 1; i >= 0; i--) {\n\t\tconst indent = \" \".repeat(i);\n\t\tconst ancestor = ancestors[i];\n\t\tif (!ancestor) continue;\n\t\tlines.push(`${indent}</${ancestor.tagName.toLowerCase()}>`);\n\t}\n\n\treturn lines.join(\"\\n\");\n};\n\nconst isInsideToolbar = (el: Element | null): boolean => {\n\tlet node = el;\n\twhile (node) {\n\t\tif (node instanceof HTMLElement && node.dataset.ynsFeedbackUi === \"true\") return true;\n\t\tnode = node.parentElement;\n\t}\n\treturn false;\n};\n\nconst IGNORED_TAGS = new Set([\"HTML\", \"HEAD\", \"SCRIPT\", \"STYLE\", \"NOSCRIPT\"]);\n\nconst POLL_INTERVAL_MS = 10_000;\n\n// Format a server-computed ETA as remaining + absolute label. The deadline\n// math itself lives in yns-app's `lib/feedback-comments-api.ts` so the toolbar\n// and YNS lock screen stay in sync.\nconst formatEta = (etaIso: string): string => {\n\tconst eta = new Date(etaIso);\n\tconst remainingMs = eta.getTime() - Date.now();\n\tconst dateLabel = eta.toLocaleString(undefined, {\n\t\tweekday: \"short\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\thour: \"numeric\",\n\t\tminute: \"2-digit\",\n\t});\n\n\tif (remainingMs <= 0) return `Any moment now (estimated by ${dateLabel})`;\n\tconst remainingMin = Math.ceil(remainingMs / 60_000);\n\tif (remainingMin < 60) return `~${remainingMin} minutes (by ${dateLabel})`;\n\tconst remainingHours = Math.round(remainingMs / 3_600_000);\n\tif (remainingHours < 24) {\n\t\treturn `~${remainingHours} ${remainingHours === 1 ? \"hour\" : \"hours\"} (by ${dateLabel})`;\n\t}\n\tconst remainingDays = Math.round(remainingMs / 86_400_000);\n\treturn `~${remainingDays} ${remainingDays === 1 ? \"day\" : \"days\"} (by ${dateLabel})`;\n};\n\nfunction FeedbackToolbar() {\n\tconst [session, setSession] = useState<ActiveSession | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\tconst [pinMode, setPinMode] = useState(false);\n\tconst [pending, setPending] = useState<PendingPin | null>(null);\n\tconst [editingId, setEditingId] = useState<string | null>(null);\n\tconst [sidebarOpen, setSidebarOpen] = useState(false);\n\tconst [finalizing, setFinalizing] = useState(false);\n\tconst apiBase = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tapiBase.current = buildApiBase();\n\t\tif (!apiBase.current) {\n\t\t\tsetLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tlet cancelled = false;\n\t\tconst controller = new AbortController();\n\n\t\tconst fetchOnce = async () => {\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(\n\t\t\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t\t\t{ credentials: \"include\", signal: controller.signal },\n\t\t\t\t);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\tsetSession(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst data = (await res.json()) as ActiveSession;\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetSession(data);\n\t\t\t\t// Hard-refresh once the AI run lands and the preview is up to date.\n\t\t\t\t// Out of scope to diff anything smarter — the new deploy replaces\n\t\t\t\t// the page entirely so a full reload is appropriate here.\n\t\t\t\tif (data.sessionStatus === \"done\") {\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tif (!cancelled) setSession(null);\n\t\t\t} finally {\n\t\t\t\tif (!cancelled) setLoading(false);\n\t\t\t}\n\t\t};\n\n\t\tvoid fetchOnce();\n\t\tconst interval = window.setInterval(fetchOnce, POLL_INTERVAL_MS);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tcontroller.abort();\n\t\t\twindow.clearInterval(interval);\n\t\t};\n\t}, []);\n\n\t// When the session stops accepting comments (finalized → in_review, or\n\t// closed), tear down any in-flight UI so the hover overlay/popover/sidebar\n\t// don't linger after the toolbar's main render returns null.\n\tuseEffect(() => {\n\t\tif (!session || session.canComment) return;\n\t\tsetPinMode(false);\n\t\tsetPending(null);\n\t\tsetSidebarOpen(false);\n\t\tsetEditingId(null);\n\t}, [session]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tdocument.body.style.cursor = \"crosshair\";\n\t\treturn () => {\n\t\t\tdocument.body.style.cursor = \"\";\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.dataset.ynsFeedbackUi = \"true\";\n\t\toverlay.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483644\",\n\t\t\t\"border: 2px dashed #10b981\",\n\t\t\t\"background: rgba(16, 185, 129, 0.08)\",\n\t\t\t\"border-radius: 3px\",\n\t\t\t\"display: none\",\n\t\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t\t].join(\";\");\n\n\t\tconst label = document.createElement(\"div\");\n\t\tlabel.dataset.ynsFeedbackUi = \"true\";\n\t\tlabel.textContent = \"Click to comment\";\n\t\tlabel.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483645\",\n\t\t\t\"background: #059669\",\n\t\t\t\"color: #fff\",\n\t\t\t\"font-size: 11px\",\n\t\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\t\"padding: 3px 8px\",\n\t\t\t\"border-radius: 4px\",\n\t\t\t\"white-space: nowrap\",\n\t\t\t\"display: none\",\n\t\t\t\"box-shadow: 0 2px 6px rgba(0,0,0,0.15)\",\n\t\t].join(\";\");\n\n\t\tdocument.documentElement.appendChild(overlay);\n\t\tdocument.documentElement.appendChild(label);\n\n\t\tlet hovered: Element | null = null;\n\t\tconst handleMove = (e: MouseEvent) => {\n\t\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\t\tif (!el || IGNORED_TAGS.has(el.tagName) || isInsideToolbar(el)) {\n\t\t\t\toverlay.style.display = \"none\";\n\t\t\t\tlabel.style.display = \"none\";\n\t\t\t\thovered = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thovered = el;\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (hovered !== el) return;\n\t\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\t\toverlay.style.top = `${rect.top}px`;\n\t\t\t\toverlay.style.left = `${rect.left}px`;\n\t\t\t\toverlay.style.width = `${rect.width}px`;\n\t\t\t\toverlay.style.height = `${rect.height}px`;\n\t\t\t\toverlay.style.display = \"block\";\n\t\t\t\tlabel.style.left = `${e.clientX + 14}px`;\n\t\t\t\tlabel.style.top = `${e.clientY + 14}px`;\n\t\t\t\tlabel.style.display = \"block\";\n\t\t\t});\n\t\t};\n\n\t\tconst handleLeave = () => {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tlabel.style.display = \"none\";\n\t\t\thovered = null;\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleLeave);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", handleMove, true);\n\t\t\tdocument.removeEventListener(\"mouseleave\", handleLeave);\n\t\t\toverlay.remove();\n\t\t\tlabel.remove();\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tconst handleClick = (event: MouseEvent) => {\n\t\t\tconst target = event.target;\n\t\t\tif (!(target instanceof Element)) return;\n\t\t\tif (isInsideToolbar(target)) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst rect = target.getBoundingClientRect();\n\t\t\tconst offsetXRatio = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0.5;\n\t\t\tconst offsetYRatio = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;\n\t\t\tsetPending({\n\t\t\t\tcssSelector: computeCssSelector(target),\n\t\t\t\tpagePath: window.location.pathname,\n\t\t\t\tsurroundingHtml: buildSurroundingHtml(target),\n\t\t\t\trect: {\n\t\t\t\t\ttop: rect.top + window.scrollY,\n\t\t\t\t\tleft: rect.left + window.scrollX,\n\t\t\t\t\twidth: rect.width,\n\t\t\t\t\theight: rect.height,\n\t\t\t\t},\n\t\t\t\tclickX: event.clientX + window.scrollX,\n\t\t\t\tclickY: event.clientY + window.scrollY,\n\t\t\t\toffsetXRatio: Math.min(1, Math.max(0, offsetXRatio)),\n\t\t\t\toffsetYRatio: Math.min(1, Math.max(0, offsetYRatio)),\n\t\t\t});\n\t\t\tsetPinMode(false);\n\t\t};\n\t\tdocument.addEventListener(\"click\", handleClick, { capture: true });\n\t\treturn () => document.removeEventListener(\"click\", handleClick, { capture: true });\n\t}, [pinMode]);\n\n\tconst refreshComments = async () => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(\n\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t{ credentials: \"include\" },\n\t\t);\n\t\tif (!res.ok) return;\n\t\tconst data = (await res.json()) as ActiveSession;\n\t\tsetSession(data);\n\t};\n\n\tconst submitNewComment = async (content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current || !session || !pending) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments`, {\n\t\t\tmethod: \"POST\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tfeedbackSessionId: session.feedbackSessionId,\n\t\t\t\tcontent,\n\t\t\t\tpagePath: pending.pagePath,\n\t\t\t\tcssSelector: pending.cssSelector,\n\t\t\t\tsurroundingHtml: pending.surroundingHtml,\n\t\t\t\toffsetXRatio: pending.offsetXRatio,\n\t\t\t\toffsetYRatio: pending.offsetYRatio,\n\t\t\t\t...(attachments.length > 0 ? { attachments } : {}),\n\t\t\t}),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetPending(null);\n\t\tsetPinMode(true);\n\t\tawait refreshComments();\n\t};\n\n\tconst updateComment = async (id: string, content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"PATCH\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ content, attachments }),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetEditingId(null);\n\t\tawait refreshComments();\n\t};\n\n\tconst removeComment = async (id: string) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"DELETE\",\n\t\t\tcredentials: \"include\",\n\t\t});\n\t\tif (!res.ok) return;\n\t\tawait refreshComments();\n\t};\n\n\tconst finalizeSession = async () => {\n\t\tif (!apiBase.current || !session) return;\n\t\tif (!window.confirm(\"Finalize this feedback session? You won't be able to add more comments.\")) return;\n\t\tsetFinalizing(true);\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase.current}/api/feedback-sessions/${session.feedbackSessionId}/finalize`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\" },\n\t\t\t);\n\t\t\tif (!res.ok) return;\n\t\t\t// Flip BOTH canComment and sessionStatus locally. Without the status\n\t\t\t// flip, the render guard sees `canComment=false` + `status=in_progress`\n\t\t\t// (a state the SubmittedPanel branch returns null for) and the toolbar\n\t\t\t// disappears for the ~10s gap until the next poll catches up.\n\t\t\tconst body = (await res.json().catch(() => null)) as { status?: SessionStatus } | null;\n\t\t\tconst nextStatus: SessionStatus = body?.status ?? \"processing\";\n\t\t\tsetSession((prev) => (prev ? { ...prev, canComment: false, sessionStatus: nextStatus } : prev));\n\t\t} finally {\n\t\t\tsetFinalizing(false);\n\t\t}\n\t};\n\n\tconst scrollToComment = (comment: FeedbackComment) => {\n\t\tif (comment.pagePath !== window.location.pathname) {\n\t\t\twindow.location.assign(comment.pagePath);\n\t\t\treturn;\n\t\t}\n\t\tlet el: Element | null = null;\n\t\ttry {\n\t\t\tel = document.querySelector(comment.cssSelector);\n\t\t} catch {\n\t\t\tel = null;\n\t\t}\n\t\tif (!el) return;\n\t\tel.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n\t\tsetEditingId(comment.id);\n\t};\n\n\tif (loading || !session) return null;\n\n\t// Session has been finalized for AI review. Show a small status panel with\n\t// the same progress + ETA shown in YNS, but no commenting controls. Polling\n\t// continues in the background; on `done` we hard-reload to pick up the\n\t// fresh deploy.\n\tif (!session.canComment) {\n\t\tif (session.sessionStatus !== \"processing\" && session.sessionStatus !== \"in_review\") return null;\n\t\treturn (\n\t\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t\t<SubmittedPanel progress={session.progress} eta={session.eta} status={session.sessionStatus} />\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst visiblePins = session.comments.filter(\n\t\t(c) => c.pagePath === window.location.pathname && c.status !== \"done\",\n\t);\n\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t{visiblePins.map((pin, idx) => (\n\t\t\t\t<PinOverlay\n\t\t\t\t\tkey={pin.id}\n\t\t\t\t\tpin={pin}\n\t\t\t\t\tnumber={idx + 1}\n\t\t\t\t\tediting={editingId === pin.id}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonStartEdit={() => setEditingId(pin.id)}\n\t\t\t\t\tonCancelEdit={() => setEditingId(null)}\n\t\t\t\t\tonSave={(content, attachments) => updateComment(pin.id, content, attachments)}\n\t\t\t\t\tonRemove={() => removeComment(pin.id)}\n\t\t\t\t/>\n\t\t\t))}\n\n\t\t\t{pending && (\n\t\t\t\t<PendingCommentPopover\n\t\t\t\t\tpending={pending}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonCancel={() => {\n\t\t\t\t\t\tsetPending(null);\n\t\t\t\t\t\tsetPinMode(true);\n\t\t\t\t\t}}\n\t\t\t\t\tonSave={(content, attachments) => submitNewComment(content, attachments)}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t{sidebarOpen && (\n\t\t\t\t<CommentsSidebar\n\t\t\t\t\tcomments={session.comments}\n\t\t\t\t\tcurrentPath={window.location.pathname}\n\t\t\t\t\tonClose={() => setSidebarOpen(false)}\n\t\t\t\t\tonSelect={scrollToComment}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => setPinMode((v) => !v)}\n\t\t\t\t\tstyle={pinMode ? toolbarButtonActiveStyle : toolbarButtonStyle}\n\t\t\t\t>\n\t\t\t\t\t{pinMode ? \"Cancel\" : \"Add comment\"}\n\t\t\t\t</button>\n\t\t\t\t<button type=\"button\" onClick={() => setSidebarOpen((v) => !v)} style={toolbarButtonGhostStyle}>\n\t\t\t\t\t{sidebarOpen ? \"Hide list\" : `List (${session.comments.length})`}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={finalizeSession}\n\t\t\t\t\tdisabled={finalizing}\n\t\t\t\t\tstyle={toolbarButtonFinalizeStyle}\n\t\t\t\t>\n\t\t\t\t\t{finalizing ? \"Finalizing…\" : \"Finalize\"}\n\t\t\t\t</button>\n\t\t\t\t<span style={toolbarHintStyle}>\n\t\t\t\t\t{pinMode ? \"Click any element to comment\" : `${visiblePins.length} on this page`}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SubmittedPanel({\n\tprogress,\n\teta: etaIso,\n\tstatus,\n}: {\n\tprogress: ReviewProgress;\n\teta: string;\n\tstatus: SessionStatus;\n}) {\n\t// Re-render every minute so the relative ETA label stays fresh between\n\t// the 10s data polls (which only re-render when the API payload changes).\n\tconst [, setTick] = useState(0);\n\tuseEffect(() => {\n\t\tconst id = window.setInterval(() => setTick((t) => t + 1), 60_000);\n\t\treturn () => window.clearInterval(id);\n\t}, []);\n\n\tconst eta = formatEta(etaIso);\n\tconst isInReview = status === \"in_review\";\n\tconst headline = isInReview ? \"Feedback under review\" : \"Applying feedback\";\n\n\treturn (\n\t\t<div style={submittedPanelStyle}>\n\t\t\t<style>{spinnerKeyframes}</style>\n\t\t\t<div style={submittedHeaderStyle}>\n\t\t\t\t<Spinner />\n\t\t\t\t<span style={submittedBadgeStyle}>Submitted</span>\n\t\t\t\t<strong style={{ fontSize: 13 }}>{headline}</strong>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressLabelStyle}>\n\t\t\t\t<span>Review progress</span>\n\t\t\t\t<span style={{ opacity: 0.7 }}>{progress.label}</span>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressTrackStyle}>\n\t\t\t\t<div style={{ ...submittedProgressFillStyle, width: `${progress.fillPct}%` }} />\n\t\t\t</div>\n\t\t\t<p style={submittedEtaStyle}>\n\t\t\t\tEstimated delivery: <span style={{ fontWeight: 600 }}>{eta}</span>\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n\n// Inline keyframes — toolbar avoids relying on a global stylesheet.\nconst spinnerKeyframes = `@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }`;\n\nfunction Spinner({ size = 14 }: { size?: number }) {\n\treturn (\n\t\t<span\n\t\t\taria-hidden\n\t\t\tstyle={{\n\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tborder: \"2px solid rgba(16, 185, 129, 0.25)\",\n\t\t\t\tborderTopColor: \"#10b981\",\n\t\t\t\tborderRadius: 999,\n\t\t\t\tanimation: \"yns-feedback-spin 0.9s linear infinite\",\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction PinOverlay({\n\tpin,\n\tnumber,\n\tediting,\n\tfeedbackSessionId,\n\tapiBase,\n\tonStartEdit,\n\tonCancelEdit,\n\tonSave,\n\tonRemove,\n}: {\n\tpin: FeedbackComment;\n\tnumber: number;\n\tediting: boolean;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonStartEdit: () => void;\n\tonCancelEdit: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove: () => Promise<void>;\n}) {\n\tconst target = useTargetPosition(pin.cssSelector, pin.offsetXRatio, pin.offsetYRatio);\n\tif (!target) return null;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\tposition: \"absolute\",\n\t\t\t\ttop: target.top,\n\t\t\t\tleft: target.left,\n\t\t\t\tzIndex: 2147483600,\n\t\t\t\tpointerEvents: \"auto\",\n\t\t\t}}\n\t\t>\n\t\t\t<button type=\"button\" onClick={onStartEdit} style={pinDotStyle} title={pin.content}>\n\t\t\t\t{number}\n\t\t\t</button>\n\t\t\t{editing && (\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial={pin.content}\n\t\t\t\t\tinitialAttachments={pin.attachments}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancelEdit}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t\tonRemove={onRemove}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction PendingCommentPopover({\n\tpending,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n}: {\n\tpending: PendingPin;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n}) {\n\treturn (\n\t\t<>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.rect.top + pending.rect.height * pending.offsetYRatio - 12,\n\t\t\t\t\tleft: pending.rect.left + pending.rect.width * pending.offsetXRatio - 12,\n\t\t\t\t\tzIndex: 2147483600,\n\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div style={pinDotStyle}>•</div>\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.clickY + 10,\n\t\t\t\t\tleft: pending.clickX + 10,\n\t\t\t\t\tzIndex: 2147483647,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial=\"\"\n\t\t\t\t\tinitialAttachments={[]}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancel}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n\nfunction CommentsSidebar({\n\tcomments,\n\tcurrentPath,\n\tonClose,\n\tonSelect,\n}: {\n\tcomments: FeedbackComment[];\n\tcurrentPath: string;\n\tonClose: () => void;\n\tonSelect: (comment: FeedbackComment) => void;\n}) {\n\tconst groups = new Map<string, FeedbackComment[]>();\n\tfor (const c of comments) {\n\t\tconst list = groups.get(c.pagePath) ?? [];\n\t\tlist.push(c);\n\t\tgroups.set(c.pagePath, list);\n\t}\n\tconst paths = Array.from(groups.keys()).sort((a, b) => {\n\t\tif (a === currentPath) return -1;\n\t\tif (b === currentPath) return 1;\n\t\treturn a.localeCompare(b);\n\t});\n\n\treturn (\n\t\t<div style={sidebarStyle}>\n\t\t\t<div style={sidebarHeaderStyle}>\n\t\t\t\t<strong style={{ fontSize: 14 }}>Comments ({comments.length})</strong>\n\t\t\t\t<button type=\"button\" onClick={onClose} style={ghostButtonStyle}>\n\t\t\t\t\tClose\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div style={sidebarScrollStyle}>\n\t\t\t\t{comments.length === 0 ? (\n\t\t\t\t\t<div style={sidebarEmptyStyle}>No comments yet. Click “Add comment” to leave one.</div>\n\t\t\t\t) : (\n\t\t\t\t\tpaths.map((path) => (\n\t\t\t\t\t\t<div key={path} style={{ marginBottom: 12 }}>\n\t\t\t\t\t\t\t<div style={sidebarPathStyle}>{path === currentPath ? `${path} · current` : path}</div>\n\t\t\t\t\t\t\t{(groups.get(path) ?? []).map((c, idx) => (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tkey={c.id}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => onSelect(c)}\n\t\t\t\t\t\t\t\t\tstyle={sidebarItemStyle}\n\t\t\t\t\t\t\t\t\tdisabled={c.status === \"done\" && false}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemIndexStyle}>{idx + 1}</span>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemTextStyle}>\n\t\t\t\t\t\t\t\t\t\t{c.content.length > 140 ? `${c.content.slice(0, 140)}…` : c.content}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{c.status === \"done\" && <span style={sidebarItemDoneStyle}>done</span>}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nconst MAX_ATTACHMENTS = 5;\nconst MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nconst readImageDimensions = (file: File): Promise<{ width: number; height: number } | null> =>\n\tnew Promise((resolve) => {\n\t\tconst url = URL.createObjectURL(file);\n\t\tconst img = new window.Image();\n\t\timg.onload = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve({ width: img.naturalWidth, height: img.naturalHeight });\n\t\t};\n\t\timg.onerror = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve(null);\n\t\t};\n\t\timg.src = url;\n\t});\n\nfunction EditPopover({\n\tinitial,\n\tinitialAttachments,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n\tonRemove,\n}: {\n\tinitial: string;\n\tinitialAttachments: FeedbackAttachment[];\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove?: () => Promise<void>;\n}) {\n\tconst [value, setValue] = useState(initial);\n\tconst [attachments, setAttachments] = useState<FeedbackAttachment[]>(initialAttachments);\n\tconst [saving, setSaving] = useState(false);\n\tconst [uploading, setUploading] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst textareaRef = useRef<HTMLTextAreaElement | null>(null);\n\tconst fileInputRef = useRef<HTMLInputElement | null>(null);\n\n\tuseEffect(() => {\n\t\tconst el = textareaRef.current;\n\t\tif (!el) return;\n\t\tel.focus();\n\t\tconst len = el.value.length;\n\t\tel.setSelectionRange(len, len);\n\t}, []);\n\n\tconst handleSubmit = async (e: FormEvent) => {\n\t\te.preventDefault();\n\t\tconst content = value.trim();\n\t\tif (!content) return;\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tawait onSave(content, attachments);\n\t\t} finally {\n\t\t\tsetSaving(false);\n\t\t}\n\t};\n\n\tconst handleFiles = async (files: FileList | null) => {\n\t\tif (!files || files.length === 0 || !apiBase) return;\n\t\tsetError(null);\n\t\tconst slots = MAX_ATTACHMENTS - attachments.length;\n\t\tif (slots <= 0) {\n\t\t\tsetError(`Max ${MAX_ATTACHMENTS} images per comment`);\n\t\t\treturn;\n\t\t}\n\t\tconst picked = Array.from(files).slice(0, slots);\n\t\tfor (const f of picked) {\n\t\t\tif (!f.type.startsWith(\"image/\")) {\n\t\t\t\tsetError(`\"${f.name}\" is not an image`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (f.size > MAX_ATTACHMENT_BYTES) {\n\t\t\t\tsetError(`\"${f.name}\" exceeds 5 MB`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tsetUploading(true);\n\t\ttry {\n\t\t\tconst dims = await Promise.all(picked.map(readImageDimensions));\n\t\t\tconst fd = new FormData();\n\t\t\tpicked.forEach((file, i) => {\n\t\t\t\tfd.append(\"file\", file);\n\t\t\t\tfd.append(\"width\", dims[i]?.width ? String(dims[i]?.width) : \"\");\n\t\t\t\tfd.append(\"height\", dims[i]?.height ? String(dims[i]?.height) : \"\");\n\t\t\t});\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(feedbackSessionId)}`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\", body: fd },\n\t\t\t);\n\t\t\tif (!res.ok) {\n\t\t\t\tconst body = (await res.json().catch(() => null)) as { error?: string } | null;\n\t\t\t\tsetError(body?.error ?? \"Upload failed\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst body = (await res.json()) as { uploads: FeedbackAttachment[] };\n\t\t\tsetAttachments((prev) => [...prev, ...body.uploads]);\n\t\t} catch {\n\t\t\tsetError(\"Upload failed\");\n\t\t} finally {\n\t\t\tsetUploading(false);\n\t\t\tif (fileInputRef.current) fileInputRef.current.value = \"\";\n\t\t}\n\t};\n\n\tconst removeAttachment = (url: string) => {\n\t\tsetAttachments((prev) => prev.filter((a) => a.url !== url));\n\t};\n\n\treturn (\n\t\t<form onSubmit={handleSubmit} style={popoverStyle}>\n\t\t\t<textarea\n\t\t\t\tref={textareaRef}\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => setValue(e.target.value)}\n\t\t\t\tplaceholder=\"Leave a comment…\"\n\t\t\t\tstyle={textareaStyle}\n\t\t\t\trows={3}\n\t\t\t/>\n\t\t\t{attachments.length > 0 && (\n\t\t\t\t<div style={attachmentRowStyle}>\n\t\t\t\t\t{attachments.map((att) => (\n\t\t\t\t\t\t<div key={att.url} style={attachmentThumbWrapStyle}>\n\t\t\t\t\t\t\t<img src={att.url} alt=\"\" style={attachmentThumbStyle} />\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => removeAttachment(att.url)}\n\t\t\t\t\t\t\t\tstyle={attachmentRemoveStyle}\n\t\t\t\t\t\t\t\tdisabled={saving || uploading}\n\t\t\t\t\t\t\t\taria-label=\"Remove attachment\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{error && <div style={errorTextStyle}>{error}</div>}\n\t\t\t<input\n\t\t\t\tref={fileInputRef}\n\t\t\t\ttype=\"file\"\n\t\t\t\taccept=\"image/*\"\n\t\t\t\tmultiple\n\t\t\t\tonChange={(e) => void handleFiles(e.target.files)}\n\t\t\t\tstyle={{ display: \"none\" }}\n\t\t\t/>\n\t\t\t<div style={popoverActionsStyle}>\n\t\t\t\t{onRemove && (\n\t\t\t\t\t<button type=\"button\" onClick={() => onRemove()} style={dangerButtonStyle} disabled={saving}>\n\t\t\t\t\t\tDelete\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => fileInputRef.current?.click()}\n\t\t\t\t\tstyle={attachIconButtonStyle}\n\t\t\t\t\tdisabled={saving || uploading || attachments.length >= MAX_ATTACHMENTS}\n\t\t\t\t\taria-label={uploading ? \"Uploading…\" : \"Attach image\"}\n\t\t\t\t\ttitle={uploading ? \"Uploading…\" : \"Attach image\"}\n\t\t\t\t>\n\t\t\t\t\t{uploading ? <SpinnerGlyph /> : <PaperclipGlyph />}\n\t\t\t\t</button>\n\t\t\t\t<div style={{ flex: 1 }} />\n\t\t\t\t<button type=\"button\" onClick={onCancel} style={ghostButtonStyle} disabled={saving}>\n\t\t\t\t\tCancel\n\t\t\t\t</button>\n\t\t\t\t<button type=\"submit\" style={primaryButtonStyle} disabled={saving || uploading || !value.trim()}>\n\t\t\t\t\t{saving ? \"Saving…\" : \"Save\"}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</form>\n\t);\n}\n\nfunction useTargetPosition(selector: string, offsetXRatio: number, offsetYRatio: number) {\n\tconst [pos, setPos] = useState<{ top: number; left: number } | null>(null);\n\n\tuseEffect(() => {\n\t\tconst update = () => {\n\t\t\tlet el: Element | null = null;\n\t\t\ttry {\n\t\t\t\tel = document.querySelector(selector);\n\t\t\t} catch {\n\t\t\t\tel = null;\n\t\t\t}\n\t\t\tif (!el) {\n\t\t\t\tsetPos(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst r = el.getBoundingClientRect();\n\t\t\tsetPos({\n\t\t\t\ttop: r.top + window.scrollY + r.height * offsetYRatio - 12,\n\t\t\t\tleft: r.left + window.scrollX + r.width * offsetXRatio - 12,\n\t\t\t});\n\t\t};\n\n\t\tupdate();\n\t\tconst ro = new ResizeObserver(update);\n\t\ttry {\n\t\t\tconst el = document.querySelector(selector);\n\t\t\tif (el) ro.observe(el);\n\t\t} catch {\n\t\t\t// swallow invalid selector\n\t\t}\n\t\twindow.addEventListener(\"scroll\", update, true);\n\t\twindow.addEventListener(\"resize\", update);\n\t\treturn () => {\n\t\t\tro.disconnect();\n\t\t\twindow.removeEventListener(\"scroll\", update, true);\n\t\t\twindow.removeEventListener(\"resize\", update);\n\t\t};\n\t}, [selector, offsetXRatio, offsetYRatio]);\n\n\treturn pos;\n}\n\n// Inline styles — toolbar is injected into arbitrary stores, so we avoid relying\n// on any CSS framework being present.\nconst toolbarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n\tpadding: \"8px 12px\",\n\tbackground: \"rgba(17, 17, 17, 0.92)\",\n\tcolor: \"white\",\n\tborderRadius: 999,\n\tboxShadow: \"0 8px 24px rgba(0,0,0,0.25)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tfontSize: 14,\n\tpointerEvents: \"auto\",\n};\n\nconst toolbarButtonStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"white\",\n\tcolor: \"#111\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarButtonActiveStyle: CSSProperties = {\n\t...toolbarButtonStyle,\n\tbackground: \"#ef4444\",\n\tcolor: \"white\",\n};\n\nconst toolbarButtonGhostStyle: CSSProperties = {\n\tborder: \"1px solid rgba(255,255,255,0.4)\",\n\tbackground: \"transparent\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n\tfontSize: 13,\n};\n\nconst toolbarButtonFinalizeStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarHintStyle: CSSProperties = {\n\topacity: 0.8,\n\tfontSize: 12,\n};\n\nconst pinDotStyle: CSSProperties = {\n\twidth: 24,\n\theight: 24,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tborder: \"2px solid white\",\n\tcursor: \"pointer\",\n\tfontSize: 12,\n\tfontWeight: 700,\n\tboxShadow: \"0 2px 6px rgba(0,0,0,0.3)\",\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tpadding: 0,\n};\n\nconst popoverStyle: CSSProperties = {\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 8,\n\tpadding: 12,\n\twidth: 280,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tcolor: \"#111\",\n};\n\nconst textareaStyle: CSSProperties = {\n\twidth: \"100%\",\n\tborder: \"1px solid #d1d5db\",\n\tborderRadius: 6,\n\tpadding: 8,\n\tfontSize: 14,\n\tresize: \"vertical\",\n\tfontFamily: \"inherit\",\n\tcolor: \"#111\",\n\tbackground: \"white\",\n\tboxSizing: \"border-box\",\n};\n\nconst popoverActionsStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 6,\n};\n\nconst baseButton: CSSProperties = {\n\tborder: \"1px solid transparent\",\n\tborderRadius: 6,\n\tpadding: \"6px 10px\",\n\tfontSize: 13,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n};\n\nconst primaryButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"#111\",\n\tcolor: \"white\",\n};\n\nconst ghostButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n};\n\nconst dangerButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#b91c1c\",\n\tborderColor: \"#fecaca\",\n};\n\nconst attachmentRowStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tflexWrap: \"wrap\",\n\tgap: 6,\n};\n\nconst attachmentThumbWrapStyle: CSSProperties = {\n\tposition: \"relative\",\n\twidth: 56,\n\theight: 56,\n\tborderRadius: 6,\n\toverflow: \"hidden\",\n\tborder: \"1px solid #e5e7eb\",\n\tbackground: \"#f9fafb\",\n};\n\nconst attachmentThumbStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: \"100%\",\n\tobjectFit: \"cover\",\n\tdisplay: \"block\",\n};\n\nconst attachmentRemoveStyle: CSSProperties = {\n\tposition: \"absolute\",\n\ttop: 2,\n\tright: 2,\n\twidth: 18,\n\theight: 18,\n\tborderRadius: 999,\n\tborder: \"none\",\n\tbackground: \"rgba(17, 17, 17, 0.85)\",\n\tcolor: \"white\",\n\tfontSize: 13,\n\tlineHeight: \"16px\",\n\tcursor: \"pointer\",\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst errorTextStyle: CSSProperties = {\n\tcolor: \"#b91c1c\",\n\tfontSize: 12,\n\tmargin: 0,\n};\n\nconst attachIconButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n\twidth: 30,\n\theight: 30,\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tflexShrink: 0,\n};\n\nfunction PaperclipGlyph() {\n\treturn (\n\t\t<svg\n\t\t\trole=\"img\"\n\t\t\taria-label=\"Attach image\"\n\t\t\twidth=\"14\"\n\t\t\theight=\"14\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t>\n\t\t\t<title>Attach image</title>\n\t\t\t<path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.93 8.83l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\" />\n\t\t</svg>\n\t);\n}\n\nfunction SpinnerGlyph() {\n\treturn (\n\t\t<>\n\t\t\t<style>{`@keyframes yns-attach-spin { to { transform: rotate(360deg); } }`}</style>\n\t\t\t<span\n\t\t\t\taria-hidden\n\t\t\t\tstyle={{\n\t\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\t\twidth: 12,\n\t\t\t\t\theight: 12,\n\t\t\t\t\tborder: \"2px solid rgba(55, 65, 81, 0.25)\",\n\t\t\t\t\tborderTopColor: \"#374151\",\n\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\tanimation: \"yns-attach-spin 0.9s linear infinite\",\n\t\t\t\t}}\n\t\t\t/>\n\t\t</>\n\t);\n}\n\nconst sidebarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\ttop: 0,\n\tright: 0,\n\tbottom: 0,\n\twidth: 360,\n\tmaxWidth: \"92vw\",\n\tbackground: \"white\",\n\tborderLeft: \"1px solid #e5e7eb\",\n\tboxShadow: \"-12px 0 32px rgba(0,0,0,0.12)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n};\n\nconst sidebarHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"space-between\",\n\tpadding: \"12px 16px\",\n\tborderBottom: \"1px solid #e5e7eb\",\n};\n\nconst sidebarScrollStyle: CSSProperties = {\n\tflex: 1,\n\toverflow: \"auto\",\n\tpadding: \"12px 12px 24px\",\n};\n\nconst sidebarEmptyStyle: CSSProperties = {\n\tcolor: \"#6b7280\",\n\tfontSize: 13,\n\tpadding: \"24px 8px\",\n\ttextAlign: \"center\",\n};\n\nconst sidebarPathStyle: CSSProperties = {\n\tfontSize: 11,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n\tcolor: \"#6b7280\",\n\tpadding: \"4px 4px 6px\",\n\twordBreak: \"break-all\",\n};\n\nconst sidebarItemStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"flex-start\",\n\tgap: 8,\n\twidth: \"100%\",\n\ttextAlign: \"left\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 6,\n\tpadding: \"8px 10px\",\n\tcursor: \"pointer\",\n\tfontSize: 13,\n\tmarginBottom: 6,\n\tcolor: \"#111\",\n};\n\nconst sidebarItemIndexStyle: CSSProperties = {\n\tflexShrink: 0,\n\twidth: 22,\n\theight: 22,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tfontWeight: 700,\n\tfontSize: 11,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst sidebarItemTextStyle: CSSProperties = {\n\tflex: 1,\n\twhiteSpace: \"pre-wrap\",\n\twordBreak: \"break-word\",\n};\n\nconst sidebarItemDoneStyle: CSSProperties = {\n\tflexShrink: 0,\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 600,\n\tpadding: \"2px 6px\",\n\tborderRadius: 4,\n\talignSelf: \"center\",\n};\n\nconst submittedPanelStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tpadding: \"12px 16px\",\n\twidth: \"min(420px, calc(100vw - 32px))\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 12,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n\tpointerEvents: \"auto\",\n};\n\nconst submittedHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n};\n\nconst submittedBadgeStyle: CSSProperties = {\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 700,\n\tpadding: \"2px 8px\",\n\tborderRadius: 999,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n};\n\nconst submittedProgressLabelStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tjustifyContent: \"space-between\",\n\tfontSize: 12,\n};\n\nconst submittedProgressTrackStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: 6,\n\tbackground: \"#f3f4f6\",\n\tborderRadius: 999,\n\toverflow: \"hidden\",\n};\n\nconst submittedProgressFillStyle: CSSProperties = {\n\theight: \"100%\",\n\tbackground: \"#10b981\",\n\tborderRadius: 999,\n\ttransition: \"width 500ms\",\n};\n\nconst submittedEtaStyle: CSSProperties = {\n\tmargin: 0,\n\tfontSize: 12,\n\tcolor: \"#6b7280\",\n};\n\nexport function mountFeedbackToolbar(): { unmount: () => void } | null {\n\tconsole.log(\"[YNS Feedback Toolbar] mountFeedbackToolbar() called\", {\n\t\tisWindow: typeof window !== \"undefined\",\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\talreadyMounted: typeof document !== \"undefined\" && Boolean(document.getElementById(MOUNT_NODE_ID)),\n\t});\n\tif (typeof window === \"undefined\") return null;\n\tif (process.env.NEXT_PUBLIC_VERCEL_ENV !== \"preview\") {\n\t\tconsole.log(\n\t\t\t\"[YNS Feedback Toolbar] gate failed — NEXT_PUBLIC_VERCEL_ENV is\",\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\t);\n\t\treturn null;\n\t}\n\tif (document.getElementById(MOUNT_NODE_ID)) return null;\n\n\tconst container = document.createElement(\"div\");\n\tcontainer.id = MOUNT_NODE_ID;\n\tcontainer.dataset.ynsFeedbackUi = \"true\";\n\tdocument.body.appendChild(container);\n\n\tconst root = createRoot(container);\n\troot.render(<FeedbackToolbar />);\n\n\treturn {\n\t\tunmount: () => {\n\t\t\troot.unmount();\n\t\t\tcontainer.remove();\n\t\t},\n\t};\n}\n\n// Auto-mount on import. Wait for DOM ready.\nif (typeof window !== \"undefined\") {\n\tconsole.log(\"[YNS Feedback Toolbar] module evaluated\", {\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\treadyState: document.readyState,\n\t\twillAutoMount: process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\",\n\t});\n}\nif (typeof window !== \"undefined\" && process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\") {\n\tif (document.readyState === \"loading\") {\n\t\tdocument.addEventListener(\"DOMContentLoaded\", () => {\n\t\t\tmountFeedbackToolbar();\n\t\t});\n\t} else {\n\t\tmountFeedbackToolbar();\n\t}\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","createRoot","Fragment","jsx","jsxs","MOUNT_NODE_ID","buildApiBase","override","host","protocol","computeCssSelector","el","path","node","part","className","classes","c","parent","tag","siblings","idx","ATTRS_OF_INTEREST","formatAttrs","parts","attr","v","trimmed","formatTextContent","text","buildSurroundingHtml","target","ancestors","cur","lines","depth","ancestor","indent","targetIndent","targetTag","targetText","childIndent","children","child","childText","i","isInsideToolbar","IGNORED_TAGS","POLL_INTERVAL_MS","formatEta","etaIso","eta","remainingMs","dateLabel","remainingMin","remainingHours","remainingDays","FeedbackToolbar","session","setSession","loading","setLoading","pinMode","setPinMode","pending","setPending","editingId","setEditingId","sidebarOpen","setSidebarOpen","finalizing","setFinalizing","apiBase","cancelled","controller","fetchOnce","res","data","interval","overlay","label","hovered","handleMove","e","rect","handleLeave","handleClick","event","offsetXRatio","offsetYRatio","refreshComments","submitNewComment","content","attachments","updateComment","id","removeComment","finalizeSession","nextStatus","prev","scrollToComment","comment","SubmittedPanel","visiblePins","pin","PinOverlay","PendingCommentPopover","CommentsSidebar","toolbarStyle","toolbarButtonActiveStyle","toolbarButtonStyle","toolbarButtonGhostStyle","toolbarButtonFinalizeStyle","toolbarHintStyle","progress","status","setTick","t","submittedPanelStyle","spinnerKeyframes","submittedHeaderStyle","Spinner","submittedBadgeStyle","submittedProgressLabelStyle","submittedProgressTrackStyle","submittedProgressFillStyle","submittedEtaStyle","size","number","editing","feedbackSessionId","onStartEdit","onCancelEdit","onSave","onRemove","useTargetPosition","pinDotStyle","EditPopover","onCancel","comments","currentPath","onClose","onSelect","groups","list","paths","a","b","sidebarStyle","sidebarHeaderStyle","ghostButtonStyle","sidebarScrollStyle","sidebarEmptyStyle","sidebarPathStyle","sidebarItemStyle","sidebarItemIndexStyle","sidebarItemTextStyle","sidebarItemDoneStyle","MAX_ATTACHMENTS","MAX_ATTACHMENT_BYTES","readImageDimensions","file","resolve","url","img","initial","initialAttachments","value","setValue","setAttachments","saving","setSaving","uploading","setUploading","error","setError","textareaRef","fileInputRef","len","handleSubmit","handleFiles","files","slots","picked","f","dims","fd","body","removeAttachment","popoverStyle","textareaStyle","attachmentRowStyle","att","attachmentThumbWrapStyle","attachmentThumbStyle","attachmentRemoveStyle","errorTextStyle","popoverActionsStyle","dangerButtonStyle","attachIconButtonStyle","SpinnerGlyph","PaperclipGlyph","primaryButtonStyle","selector","pos","setPos","update","r","ro","baseButton","mountFeedbackToolbar","container","root"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,21 @@ interface CommerceConfig {
|
|
|
8
8
|
version?: "v1";
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
type APICollectionImportMembershipsBody = {
|
|
12
|
+
file: File | Blob;
|
|
13
|
+
};
|
|
14
|
+
type APICollectionImportMembershipsResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
processed: number;
|
|
17
|
+
inserted: number;
|
|
18
|
+
skipped_existing: number;
|
|
19
|
+
errors: Array<{
|
|
20
|
+
row: number;
|
|
21
|
+
collection_slug?: string;
|
|
22
|
+
product_slug?: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
11
26
|
declare class YNSProvider {
|
|
12
27
|
#private;
|
|
13
28
|
constructor(config?: CommerceConfig);
|
|
@@ -27,6 +42,7 @@ declare class YNSProvider {
|
|
|
27
42
|
collectionGet(params: APICollectionGetByIdParams): Promise<APICollectionGetByIdResult | null>;
|
|
28
43
|
collectionBrowse(params: APICollectionsBrowseQueryParams): Promise<APICollectionsBrowseResult>;
|
|
29
44
|
collectionCreate(body: APICollectionCreateBody): Promise<APICollectionCreateResult>;
|
|
45
|
+
collectionImportMemberships(body: APICollectionImportMembershipsBody): Promise<APICollectionImportMembershipsResult>;
|
|
30
46
|
categoryGet(params: APICategoryGetByIdParams): Promise<APICategoryGetByIdResult | null>;
|
|
31
47
|
categoriesBrowse(params: APICategoriesBrowseQueryParams): Promise<APICategoriesBrowseResult>;
|
|
32
48
|
postBrowse(params?: APIPostsBrowseQueryParams): Promise<APIPostsBrowseResult>;
|
|
@@ -99,4 +115,4 @@ declare class YNSProvider {
|
|
|
99
115
|
|
|
100
116
|
declare function Commerce(config?: CommerceConfig): YNSProvider;
|
|
101
117
|
|
|
102
|
-
export { APIBlogCategoriesBrowseQueryParams, APIBlogCategoriesBrowseResult, APIBlogCategoryGetByIdParams, APIBlogCategoryGetByIdResult, APICartCreateBody, APICartCreateResult, APICartGetResult, APICategoriesBrowseQueryParams, APICategoriesBrowseResult, APICategoryCreateBody, APICategoryCreateResult, APICategoryGetByIdParams, APICategoryGetByIdResult, APICategoryUpdateBody, APICategoryUpdateResult, APICollectionCreateBody, APICollectionCreateResult, APICollectionGetByIdParams, APICollectionGetByIdResult, APICollectionsBrowseQueryParams, APICollectionsBrowseResult, APIContactMessageCreateBody, APIContactMessageCreateResult, APICustomerAddressCreateBody, APICustomerAddressCreateResult, APICustomerGetByIdParams, APICustomerGetByIdResult, APICustomerOrdersBrowseQueryParams, APICustomerOrdersBrowseResult, APICustomerUpdateBody, APICustomerUpdateResult, APICustomersBrowseQueryParams, APICustomersBrowseResult, APIInstaviewImagesBrowseParams, APIInstaviewImagesBrowseQueryParams, APIInstaviewImagesBrowseResult, APIInventoryAdjustBody, APIInventoryAdjustResult, APIInventoryBrowseQueryParams, APIInventoryBrowseResult, APILegalPageGetByPathResult, APILegalPagesBrowseResult, APIMeGetResult, APIOrderGetByIdParams, APIOrderGetByIdResult, APIOrderUpdateBody, APIOrderUpdateResult, APIOrdersBrowseQueryParams, APIOrdersBrowseResult, APIPostCommentCreateBody, APIPostCommentCreateResult, APIPostCommentsBrowseQueryParams, APIPostCommentsBrowseResult, APIPostCreateBody, APIPostCreateResult, APIPostDeleteResult, APIPostGetByIdParams, APIPostGetByIdResult, APIPostUpdateBody, APIPostUpdateResult, APIPostsBrowseQueryParams, APIPostsBrowseResult, APIProductCreateBody, APIProductCreateResult, APIProductDeleteResult, APIProductGetByIdParams, APIProductGetByIdResult, APIProductReviewCreateBody, APIProductReviewCreateResult, APIProductReviewsBrowseQueryParams, APIProductReviewsBrowseResult, APIProductUpdateBody, APIProductUpdateResult, APIProductsBrowseQueryParams, APIProductsBrowseResult, APISearchQueryParams, APISearchResult, APISubscriberCreateBody, APISubscriberCreateResult, APISubscriberDeleteResult, APIVariantCreateBody, APIVariantCreateResult, APIVariantGetByIdParams, APIVariantGetByIdResult, APIVariantUpdateBody, APIVariantUpdateResult, Commerce, type CommerceConfig, YNSProvider };
|
|
118
|
+
export { APIBlogCategoriesBrowseQueryParams, APIBlogCategoriesBrowseResult, APIBlogCategoryGetByIdParams, APIBlogCategoryGetByIdResult, APICartCreateBody, APICartCreateResult, APICartGetResult, APICategoriesBrowseQueryParams, APICategoriesBrowseResult, APICategoryCreateBody, APICategoryCreateResult, APICategoryGetByIdParams, APICategoryGetByIdResult, APICategoryUpdateBody, APICategoryUpdateResult, APICollectionCreateBody, APICollectionCreateResult, APICollectionGetByIdParams, APICollectionGetByIdResult, type APICollectionImportMembershipsBody, type APICollectionImportMembershipsResult, APICollectionsBrowseQueryParams, APICollectionsBrowseResult, APIContactMessageCreateBody, APIContactMessageCreateResult, APICustomerAddressCreateBody, APICustomerAddressCreateResult, APICustomerGetByIdParams, APICustomerGetByIdResult, APICustomerOrdersBrowseQueryParams, APICustomerOrdersBrowseResult, APICustomerUpdateBody, APICustomerUpdateResult, APICustomersBrowseQueryParams, APICustomersBrowseResult, APIInstaviewImagesBrowseParams, APIInstaviewImagesBrowseQueryParams, APIInstaviewImagesBrowseResult, APIInventoryAdjustBody, APIInventoryAdjustResult, APIInventoryBrowseQueryParams, APIInventoryBrowseResult, APILegalPageGetByPathResult, APILegalPagesBrowseResult, APIMeGetResult, APIOrderGetByIdParams, APIOrderGetByIdResult, APIOrderUpdateBody, APIOrderUpdateResult, APIOrdersBrowseQueryParams, APIOrdersBrowseResult, APIPostCommentCreateBody, APIPostCommentCreateResult, APIPostCommentsBrowseQueryParams, APIPostCommentsBrowseResult, APIPostCreateBody, APIPostCreateResult, APIPostDeleteResult, APIPostGetByIdParams, APIPostGetByIdResult, APIPostUpdateBody, APIPostUpdateResult, APIPostsBrowseQueryParams, APIPostsBrowseResult, APIProductCreateBody, APIProductCreateResult, APIProductDeleteResult, APIProductGetByIdParams, APIProductGetByIdResult, APIProductReviewCreateBody, APIProductReviewCreateResult, APIProductReviewsBrowseQueryParams, APIProductReviewsBrowseResult, APIProductUpdateBody, APIProductUpdateResult, APIProductsBrowseQueryParams, APIProductsBrowseResult, APISearchQueryParams, APISearchResult, APISubscriberCreateBody, APISubscriberCreateResult, APISubscriberDeleteResult, APIVariantCreateBody, APIVariantCreateResult, APIVariantGetByIdParams, APIVariantGetByIdResult, APIVariantUpdateBody, APIVariantUpdateResult, Commerce, type CommerceConfig, YNSProvider };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./chunk-PNKB6P4V.js";var B=process.env.YNS_API_KEY,m={YNS_API_KEY:B};var n={DEBUG:0,LOG:1,WARN:2,ERROR:3},R=process.env.NEXT_PUBLIC_LOG_LEVEL||"LOG",i=n[R],l="\x1B[0m",p="\x1B[34m",C="\x1B[32m",h="\x1B[33m",w="\x1B[31m",f="\u23F1\uFE0F",S="\u{1F41B}",G="\u2714\uFE0F",$="\u26A0\uFE0F",v="\u274C",A=`${f} `,O=`${p}${S}${l} `,U=`${C}${G}${l} `,E=`${h}${$}${l} `,L=`${w}${v}${l} `,I=P=>{let e=P?`[${P}] `:"";return{getLogger(t){return I([P,t].filter(Boolean).join(" > "))},time(t){i>n.DEBUG||console.time([A,e,t].filter(Boolean).join(" "))},timeEnd(t){i>n.DEBUG||console.timeEnd([A,e,t].filter(Boolean).join(" "))},debug(...t){i>n.DEBUG||console.log(...[O,e,...t].filter(Boolean))},log(...t){i>n.LOG||console.log(...[U,e,...t].filter(Boolean))},dir(t,s){i>n.LOG||console.dir(t,s)},warn(...t){i>n.WARN||console.warn(...[E,e,...t].filter(Boolean))},error(...t){i>n.ERROR||console.error(...[L,e,...t].filter(Boolean))}}},D=I();var u=class{#s;#t=I("YNSProvider");constructor(e={}){let t=e.token??m.YNS_API_KEY,s=m.YNS_API_KEY?.startsWith("sk-s-");if(!t)throw new Error("YNS API key is required. Set YNS_API_KEY environment variable or pass token in config.");let r=e.endpoint??(s?"https://yns.cx":"https://yns.store");this.#s={version:"v1",...e,token:t,endpoint:r},this.#t.debug("YNSProvider initialized",{endpoint:r,token:e.token?"from config":"from env"})}async#e(e,t="GET",s){let r=this.#t.getLogger("#restRequest"),o=`${this.#s.endpoint}/api/${this.#s.version}${e}`;r.debug(`Making ${t} request to YNS API: ${o}`);let a=await fetch(o,{method:t,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.#s.token}`},body:s?JSON.stringify(s):void 0});if(!a.ok){let g=a.headers.get("content-type"),d=`YNS REST request failed: ${t} ${o} ${a.status} ${a.statusText}`;if(g?.includes("application/json"))try{let c=await a.json();d=c.error||c.message||d}catch(c){r.error("Failed to parse YNS error response as JSON",c)}else{let c=await a.text();r.error(`YNS API request failed: ${a.status} ${a.statusText}`,d,c)}throw new Error(d)}let y=a.headers.get("content-type");if(!y?.includes("application/json"))throw new Error(`YNS API returned ${y} instead of JSON for ${e}`);return a.json()}async meGet(){let e=this.#t.getLogger("meGet");e.debug("Fetching my information");let s=await this.#e("/me");return e.debug("Received my information:",s),s}async productBrowse(e){let t=this.#t.getLogger("productBrowse");t.debug("Browsing products with params:",e);let s=new URLSearchParams;e.limit&&s.append("limit",e.limit.toString()),e.offset&&s.append("offset",e.offset.toString()),e.category&&s.append("category",e.category),e.query&&s.append("query",e.query),e.active!==void 0&&s.append("active",e.active.toString()),e.orderBy&&s.append("orderBy",e.orderBy),e.orderDirection&&s.append("orderDirection",e.orderDirection);let o=`/products${s.size?`?${s}`:""}`;t.debug("Constructed pathname:",o);let a=await this.#e(o);return t.debug("Received product browse result:",{meta:a.meta}),a}async productGet(e){let t=`/products/${e.idOrSlug}`,s=await this.#e(t);return s||null}async orderBrowse(e){let t=this.#t.getLogger("orderBrowse");t.debug("Browsing orders with params:",e);let s=new URLSearchParams;e.limit&&s.append("limit",e.limit.toString()),e.offset&&s.append("offset",e.offset.toString());let o=`/orders${s.size?`?${s}`:""}`;t.debug("Constructed pathname:",o);let a=await this.#e(o);return t.debug("Received orders browse result:",{meta:a.meta}),a}async orderGet(e){let t=`/orders/${e.id}`,s=await this.#e(t);return s||null}async cartUpsert(e){return await this.#e("/carts","POST",e)}async cartRemoveItem(e){let t=`/carts/${e.cartId}/line-items/${e.variantId}`,s=await this.#e(t,"DELETE");return null}async cartGet(e){let t=`/carts/${e.cartId}`;return await this.#e(t)}async collectionGet(e){let t=`/collections/${e.idOrSlug}`;return await this.#e(t)}async collectionBrowse(e){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.offset&&t.append("offset",e.offset.toString()),e.query&&t.append("query",e.query),e.active!==void 0&&t.append("active",e.active.toString());let r=`/collections${t.size?`?${t}`:""}`;return await this.#e(r)}async collectionCreate(e){return this.#e("/collections","POST",e)}async categoryGet(e){let t=`/categories/${e.idOrSlug}`;return await this.#e(t)}async categoriesBrowse(e){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.offset&&t.append("offset",e.offset.toString()),e.query&&t.append("query",e.query),e.active!==void 0&&t.append("active",e.active.toString());let r=`/categories${t.size?`?${t}`:""}`;return await this.#e(r)}async postBrowse(e={}){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.offset&&t.append("offset",e.offset.toString()),e.query&&t.append("query",e.query),e.active!==void 0&&t.append("active",e.active.toString()),e.tag&&t.append("tag",e.tag),e.categoryId&&t.append("categoryId",e.categoryId);let r=`/posts${t.size?`?${t}`:""}`;return this.#e(r)}async postGet(e){let t=`/posts/${e.idOrSlug}`;return this.#e(t)}async postCreate(e){return this.#e("/posts","POST",e)}async postUpdate(e,t){let s=`/posts/${e.idOrSlug}`;return this.#e(s,"PUT",t)}async postDelete(e){let t=`/posts/${e.idOrSlug}`;return this.#e(t,"DELETE")}async postCommentsBrowse(e,t={}){let s=new URLSearchParams;t.limit&&s.append("limit",t.limit.toString()),t.offset&&s.append("offset",t.offset.toString());let r=s.size?`?${s}`:"",o=`/posts/${e.idOrSlug}/comments${r}`;return this.#e(o)}async postCommentCreate(e,t){let s=`/posts/${e.idOrSlug}/comments`;return this.#e(s,"POST",t)}async blogCategoryBrowse(e={}){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.offset&&t.append("offset",e.offset.toString()),e.query&&t.append("query",e.query),e.active!==void 0&&t.append("active",e.active.toString());let r=`/blog-categories${t.size?`?${t}`:""}`;return this.#e(r)}async blogCategoryGet(e){let t=`/blog-categories/${e.idOrSlug}`;return this.#e(t)}async customerBrowse(e={}){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.offset&&t.append("offset",e.offset.toString()),e.search&&t.append("search",e.search);let r=`/customers${t.size?`?${t}`:""}`;return this.#e(r)}async customerGet(e){let t=`/customers/${e.id}`;return this.#e(t)}async customerUpdate(e,t){let s=`/customers/${e.id}`;return this.#e(s,"PUT",t)}async customerAddressCreate(e,t){let s=`/customers/${e.id}/addresses`;return this.#e(s,"POST",t)}async customerAddressDelete(e){let t=`/customers/${e.customerId}/addresses/${e.addressId}`;await this.#e(t,"DELETE")}async customerOrdersBrowse(e,t={}){let s=new URLSearchParams;t.limit&&s.append("limit",t.limit.toString()),t.offset&&s.append("offset",t.offset.toString());let r=s.size?`?${s}`:"",o=`/customers/${e.id}/orders${r}`;return this.#e(o)}async inventoryBrowse(e={}){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.cursor&&t.append("cursor",e.cursor),e.lowStock!==void 0&&t.append("lowStock",e.lowStock.toString());let r=`/inventory${t.size?`?${t}`:""}`;return this.#e(r)}async inventoryAdjust(e,t){let s=`/inventory/${encodeURIComponent(e)}/adjust`;return this.#e(s,"POST",t)}async variantGet(e){let t=`/variants/${e.idOrSku}`;return this.#e(t)}async variantUpdate(e,t){let s=`/variants/${e.idOrSku}`;return this.#e(s,"PUT",t)}async variantDelete(e){let t=`/variants/${e.idOrSku}`;await this.#e(t,"DELETE")}async variantCreate(e,t){let s=`/products/${e}/variants`;return this.#e(s,"POST",t)}async subscriberCreate(e){return this.#e("/subscribers","POST",e)}async subscriberDelete(e){let t=`/subscribers?email=${encodeURIComponent(e)}`;return this.#e(t,"DELETE")}async contactMessageCreate(e){return this.#e("/contact-messages","POST",e)}async productCreate(e){return this.#e("/products","POST",e)}async productUpdate(e,t){let s=`/products/${e.idOrSlug}`;return this.#e(s,"PUT",t)}async productDelete(e){let t=`/products/${e.idOrSlug}`;return this.#e(t,"DELETE")}async productReviewsBrowse(e,t={}){let s=new URLSearchParams;t.limit&&s.append("limit",t.limit.toString()),t.offset&&s.append("offset",t.offset.toString());let r=s.size?`?${s}`:"",o=`/products/${e.idOrSlug}/reviews${r}`;return this.#e(o)}async productReviewCreate(e,t){let s=`/products/${e.idOrSlug}/reviews`;return this.#e(s,"POST",t)}async categoryCreate(e){return this.#e("/categories","POST",e)}async categoryUpdate(e,t){let s=`/categories/${e.idOrSlug}`;return this.#e(s,"PUT",t)}async orderUpdate(e,t){let s=`/orders/${e.id}`;return this.#e(s,"PUT",t)}async legalPageBrowse(){return this.#e("/legal-pages")}async legalPageGet(e){let t=e.startsWith("/")?e.slice(1):e,s=`/legal-pages/${encodeURIComponent(t)}`;return this.#e(s)}async search(e){return this.request("/search",{query:e})}async instaviewImagesBrowse(e,t={}){let s=new URLSearchParams;t.limit&&s.append("limit",t.limit.toString()),t.cursor&&s.append("cursor",t.cursor);let r=s.size?`?${s}`:"",o=`/instaview/accounts/${e.handle}/images${r}`;return this.#e(o)}async request(e,t){let s=t?.query?`?${new URLSearchParams(Object.entries(t.query).map(([r,o])=>[r,String(o)]))}`:"";return this.#e(`${e}${s}`,t?.method??"GET",t?.body)}};function T(P={}){return new u(P)}export{T as Commerce,u as YNSProvider};
|
|
1
|
+
import"./chunk-PNKB6P4V.js";var p=process.env.YNS_API_KEY,A={YNS_API_KEY:p};var i={DEBUG:0,LOG:1,WARN:2,ERROR:3},B=process.env.NEXT_PUBLIC_LOG_LEVEL||"LOG",P=i[B],m="\x1B[0m",R="\x1B[34m",C="\x1B[32m",h="\x1B[33m",f="\x1B[31m",w="\u23F1\uFE0F",S="\u{1F41B}",$="\u2714\uFE0F",G="\u26A0\uFE0F",v="\u274C",g=`${w} `,O=`${R}${S}${m} `,E=`${C}${$}${m} `,U=`${h}${G}${m} `,b=`${f}${v}${m} `,y=c=>{let e=c?`[${c}] `:"";return{getLogger(t){return y([c,t].filter(Boolean).join(" > "))},time(t){P>i.DEBUG||console.time([g,e,t].filter(Boolean).join(" "))},timeEnd(t){P>i.DEBUG||console.timeEnd([g,e,t].filter(Boolean).join(" "))},debug(...t){P>i.DEBUG||console.log(...[O,e,...t].filter(Boolean))},log(...t){P>i.LOG||console.log(...[E,e,...t].filter(Boolean))},dir(t,s){P>i.LOG||console.dir(t,s)},warn(...t){P>i.WARN||console.warn(...[U,e,...t].filter(Boolean))},error(...t){P>i.ERROR||console.error(...[b,e,...t].filter(Boolean))}}},D=y();var d=class{#t;#s=y("YNSProvider");constructor(e={}){let t=e.token??A.YNS_API_KEY,s=A.YNS_API_KEY?.startsWith("sk-s-");if(!t)throw new Error("YNS API key is required. Set YNS_API_KEY environment variable or pass token in config.");let r=e.endpoint??(s?"https://yns.cx":"https://yns.store");this.#t={version:"v1",...e,token:t,endpoint:r},this.#s.debug("YNSProvider initialized",{endpoint:r,token:e.token?"from config":"from env"})}async#e(e,t="GET",s){let r=this.#s.getLogger("#restRequest"),o=`${this.#t.endpoint}/api/${this.#t.version}${e}`;r.debug(`Making ${t} request to YNS API: ${o}`);let a=await fetch(o,{method:t,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.#t.token}`},body:s?JSON.stringify(s):void 0});if(!a.ok){let u=a.headers.get("content-type"),n=`YNS REST request failed: ${t} ${o} ${a.status} ${a.statusText}`;if(u?.includes("application/json"))try{let l=await a.json();n=l.error||l.message||n}catch(l){r.error("Failed to parse YNS error response as JSON",l)}else{let l=await a.text();r.error(`YNS API request failed: ${a.status} ${a.statusText}`,n,l)}throw new Error(n)}let I=a.headers.get("content-type");if(!I?.includes("application/json"))throw new Error(`YNS API returned ${I} instead of JSON for ${e}`);return a.json()}async#r(e,t){let s=this.#s.getLogger("#multipartRequest"),r=`${this.#t.endpoint}/api/${this.#t.version}${e}`;s.debug(`Making multipart POST to YNS API: ${r}`);let o=await fetch(r,{method:"POST",headers:{Authorization:`Bearer ${this.#t.token}`},body:t});if(!o.ok){let I=o.headers.get("content-type"),u=`YNS REST request failed: POST ${r} ${o.status} ${o.statusText}`;if(I?.includes("application/json"))try{let n=await o.json();u=n.error||n.message||u}catch(n){s.error("Failed to parse YNS error response as JSON",n)}else{let n=await o.text();s.error(`YNS API request failed: ${o.status} ${o.statusText}`,u,n)}throw new Error(u)}let a=o.headers.get("content-type");if(!a?.includes("application/json"))throw new Error(`YNS API returned ${a} instead of JSON for ${e}`);return o.json()}async meGet(){let e=this.#s.getLogger("meGet");e.debug("Fetching my information");let s=await this.#e("/me");return e.debug("Received my information:",s),s}async productBrowse(e){let t=this.#s.getLogger("productBrowse");t.debug("Browsing products with params:",e);let s=new URLSearchParams;e.limit&&s.append("limit",e.limit.toString()),e.offset&&s.append("offset",e.offset.toString()),e.category&&s.append("category",e.category),e.query&&s.append("query",e.query),e.active!==void 0&&s.append("active",e.active.toString()),e.orderBy&&s.append("orderBy",e.orderBy),e.orderDirection&&s.append("orderDirection",e.orderDirection);let o=`/products${s.size?`?${s}`:""}`;t.debug("Constructed pathname:",o);let a=await this.#e(o);return t.debug("Received product browse result:",{meta:a.meta}),a}async productGet(e){let t=`/products/${e.idOrSlug}`,s=await this.#e(t);return s||null}async orderBrowse(e){let t=this.#s.getLogger("orderBrowse");t.debug("Browsing orders with params:",e);let s=new URLSearchParams;e.limit&&s.append("limit",e.limit.toString()),e.offset&&s.append("offset",e.offset.toString());let o=`/orders${s.size?`?${s}`:""}`;t.debug("Constructed pathname:",o);let a=await this.#e(o);return t.debug("Received orders browse result:",{meta:a.meta}),a}async orderGet(e){let t=`/orders/${e.id}`,s=await this.#e(t);return s||null}async cartUpsert(e){return await this.#e("/carts","POST",e)}async cartRemoveItem(e){let t=`/carts/${e.cartId}/line-items/${e.variantId}`,s=await this.#e(t,"DELETE");return null}async cartGet(e){let t=`/carts/${e.cartId}`;return await this.#e(t)}async collectionGet(e){let t=`/collections/${e.idOrSlug}`;return await this.#e(t)}async collectionBrowse(e){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.offset&&t.append("offset",e.offset.toString()),e.query&&t.append("query",e.query),e.active!==void 0&&t.append("active",e.active.toString());let r=`/collections${t.size?`?${t}`:""}`;return await this.#e(r)}async collectionCreate(e){return this.#e("/collections","POST",e)}async collectionImportMemberships(e){let t=new FormData;return t.append("file",e.file),this.#r("/collections/import-memberships",t)}async categoryGet(e){let t=`/categories/${e.idOrSlug}`;return await this.#e(t)}async categoriesBrowse(e){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.offset&&t.append("offset",e.offset.toString()),e.query&&t.append("query",e.query),e.active!==void 0&&t.append("active",e.active.toString());let r=`/categories${t.size?`?${t}`:""}`;return await this.#e(r)}async postBrowse(e={}){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.offset&&t.append("offset",e.offset.toString()),e.query&&t.append("query",e.query),e.active!==void 0&&t.append("active",e.active.toString()),e.tag&&t.append("tag",e.tag),e.categoryId&&t.append("categoryId",e.categoryId);let r=`/posts${t.size?`?${t}`:""}`;return this.#e(r)}async postGet(e){let t=`/posts/${e.idOrSlug}`;return this.#e(t)}async postCreate(e){return this.#e("/posts","POST",e)}async postUpdate(e,t){let s=`/posts/${e.idOrSlug}`;return this.#e(s,"PUT",t)}async postDelete(e){let t=`/posts/${e.idOrSlug}`;return this.#e(t,"DELETE")}async postCommentsBrowse(e,t={}){let s=new URLSearchParams;t.limit&&s.append("limit",t.limit.toString()),t.offset&&s.append("offset",t.offset.toString());let r=s.size?`?${s}`:"",o=`/posts/${e.idOrSlug}/comments${r}`;return this.#e(o)}async postCommentCreate(e,t){let s=`/posts/${e.idOrSlug}/comments`;return this.#e(s,"POST",t)}async blogCategoryBrowse(e={}){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.offset&&t.append("offset",e.offset.toString()),e.query&&t.append("query",e.query),e.active!==void 0&&t.append("active",e.active.toString());let r=`/blog-categories${t.size?`?${t}`:""}`;return this.#e(r)}async blogCategoryGet(e){let t=`/blog-categories/${e.idOrSlug}`;return this.#e(t)}async customerBrowse(e={}){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.offset&&t.append("offset",e.offset.toString()),e.search&&t.append("search",e.search);let r=`/customers${t.size?`?${t}`:""}`;return this.#e(r)}async customerGet(e){let t=`/customers/${e.id}`;return this.#e(t)}async customerUpdate(e,t){let s=`/customers/${e.id}`;return this.#e(s,"PUT",t)}async customerAddressCreate(e,t){let s=`/customers/${e.id}/addresses`;return this.#e(s,"POST",t)}async customerAddressDelete(e){let t=`/customers/${e.customerId}/addresses/${e.addressId}`;await this.#e(t,"DELETE")}async customerOrdersBrowse(e,t={}){let s=new URLSearchParams;t.limit&&s.append("limit",t.limit.toString()),t.offset&&s.append("offset",t.offset.toString());let r=s.size?`?${s}`:"",o=`/customers/${e.id}/orders${r}`;return this.#e(o)}async inventoryBrowse(e={}){let t=new URLSearchParams;e.limit&&t.append("limit",e.limit.toString()),e.cursor&&t.append("cursor",e.cursor),e.lowStock!==void 0&&t.append("lowStock",e.lowStock.toString());let r=`/inventory${t.size?`?${t}`:""}`;return this.#e(r)}async inventoryAdjust(e,t){let s=`/inventory/${encodeURIComponent(e)}/adjust`;return this.#e(s,"POST",t)}async variantGet(e){let t=`/variants/${e.idOrSku}`;return this.#e(t)}async variantUpdate(e,t){let s=`/variants/${e.idOrSku}`;return this.#e(s,"PUT",t)}async variantDelete(e){let t=`/variants/${e.idOrSku}`;await this.#e(t,"DELETE")}async variantCreate(e,t){let s=`/products/${e}/variants`;return this.#e(s,"POST",t)}async subscriberCreate(e){return this.#e("/subscribers","POST",e)}async subscriberDelete(e){let t=`/subscribers?email=${encodeURIComponent(e)}`;return this.#e(t,"DELETE")}async contactMessageCreate(e){return this.#e("/contact-messages","POST",e)}async productCreate(e){return this.#e("/products","POST",e)}async productUpdate(e,t){let s=`/products/${e.idOrSlug}`;return this.#e(s,"PUT",t)}async productDelete(e){let t=`/products/${e.idOrSlug}`;return this.#e(t,"DELETE")}async productReviewsBrowse(e,t={}){let s=new URLSearchParams;t.limit&&s.append("limit",t.limit.toString()),t.offset&&s.append("offset",t.offset.toString());let r=s.size?`?${s}`:"",o=`/products/${e.idOrSlug}/reviews${r}`;return this.#e(o)}async productReviewCreate(e,t){let s=`/products/${e.idOrSlug}/reviews`;return this.#e(s,"POST",t)}async categoryCreate(e){return this.#e("/categories","POST",e)}async categoryUpdate(e,t){let s=`/categories/${e.idOrSlug}`;return this.#e(s,"PUT",t)}async orderUpdate(e,t){let s=`/orders/${e.id}`;return this.#e(s,"PUT",t)}async legalPageBrowse(){return this.#e("/legal-pages")}async legalPageGet(e){let t=e.startsWith("/")?e.slice(1):e,s=`/legal-pages/${encodeURIComponent(t)}`;return this.#e(s)}async search(e){return this.request("/search",{query:e})}async instaviewImagesBrowse(e,t={}){let s=new URLSearchParams;t.limit&&s.append("limit",t.limit.toString()),t.cursor&&s.append("cursor",t.cursor);let r=s.size?`?${s}`:"",o=`/instaview/accounts/${e.handle}/images${r}`;return this.#e(o)}async request(e,t){let s=t?.query?`?${new URLSearchParams(Object.entries(t.query).map(([r,o])=>[r,String(o)]))}`:"";return this.#e(`${e}${s}`,t?.method??"GET",t?.body)}};function T(c={}){return new d(c)}export{T as Commerce,d as YNSProvider};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|