@queuezero/embed 0.1.9 → 0.1.11

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/README.md CHANGED
@@ -38,25 +38,27 @@ Mounts a complete waitlist widget (form + status + referral share) to the specif
38
38
 
39
39
  | Parameter | Type | Description |
40
40
  |-----------|------|-------------|
41
- | `elementId` | `string` | ID of the container element |
41
+ | `elementId` | `string \| null` | ID of the container element, or `null` for headless mode |
42
42
  | `campaignSlug` | `string` | Your campaign identifier |
43
43
  | `options.apiUrl` | `string` | API endpoint URL |
44
44
  | `options.displayMode` | `'inline' \| 'modal'` | Display as inline form or modal (default: `'inline'`) |
45
45
  | `options.modalOptions` | `object` | Modal configuration (only when `displayMode: 'modal'`) |
46
46
  | `options.modalOptions.triggerText` | `string` | Button text (default: `'Join Waitlist'`) |
47
+ | `options.modalOptions.triggerText` | `string` | Button text (default: `'Join Waitlist'`) |
48
+ | `options.modalOptions.attachToText` | `string` | Automatically attach modal to buttons with this text |
47
49
  | `options.modalOptions.size` | `'sm' \| 'md' \| 'lg'` | Modal width (default: `'md'`) |
48
50
 
49
51
  ### Modal Mode Example
50
52
 
51
53
  ```html
52
- <div id="queuezero-waitlist"></div>
53
54
  <script src="https://cdn.jsdelivr.net/npm/@queuezero/embed@latest/dist/index.global.js"></script>
54
55
  <script>
55
- QueueZero.init('queuezero-waitlist', 'your-campaign-slug', {
56
+ // Headless mode: Pass null as container ID and use attachToText
57
+ QueueZero.init(null, 'your-campaign-slug', {
56
58
  apiUrl: 'https://api.queuezero.io',
57
59
  displayMode: 'modal',
58
60
  modalOptions: {
59
- triggerText: 'Join Our Waitlist',
61
+ attachToText: 'Join Waitlist', // Will attach to any button with this text
60
62
  size: 'md'
61
63
  }
62
64
  });
@@ -1,4 +1,4 @@
1
- var QueueZero=(()=>{var h=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var z=(a,e,t)=>e in a?h(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var q=(a,e)=>{for(var t in e)h(a,t,{get:e[t],enumerable:!0})},x=(a,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of b(e))!v.call(a,o)&&o!==t&&h(a,o,{get:()=>e[o],enumerable:!(r=y(e,o))||r.enumerable});return a};var $=a=>x(h({},"__esModule",{value:!0}),a);var d=(a,e,t)=>z(a,typeof e!="symbol"?e+"":e,t);var C={};q(C,{init:()=>k});var w=`
1
+ var QueueZero=(()=>{var h=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var q=(a,e,t)=>e in a?h(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var z=(a,e)=>{for(var t in e)h(a,t,{get:e[t],enumerable:!0})},x=(a,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of v(e))!b.call(a,r)&&r!==t&&h(a,r,{get:()=>e[r],enumerable:!(o=y(e,r))||o.enumerable});return a};var $=a=>x(h({},"__esModule",{value:!0}),a);var p=(a,e,t)=>q(a,typeof e!="symbol"?e+"":e,t);var M={};z(M,{init:()=>E});var w=`
2
2
  /* Base Form Styles */
3
3
  .qz-form {
4
4
  font-family: var(--qz-font-family, system-ui, -apple-system, sans-serif);
@@ -217,90 +217,89 @@ var QueueZero=(()=>{var h=Object.defineProperty;var y=Object.getOwnPropertyDescr
217
217
  margin: 0 0 20px 0;
218
218
  text-align: center;
219
219
  }
220
- `,u=class{constructor(e,t,r){d(this,"container");d(this,"campaign","");d(this,"options",{apiUrl:""});d(this,"configData",null);d(this,"submitted",!1);d(this,"submittedData",null);d(this,"modalElement",null);d(this,"modalOverlay",null);let o=document.getElementById(e);if(!o){console.error(`QueueZero: Element with ID "${e}" not found.`),this.container=document.createElement("div");return}this.container=o,this.campaign=t,this.options=r,this.injectStyles(),this.render(),this.loadCampaign()}injectStyles(){if(document.getElementById("qz-styles"))return;let e=document.createElement("style");e.id="qz-styles",e.textContent=w,document.head.appendChild(e)}render(){this.container.innerHTML='<div class="qz-loading">Loading waitlist...</div>'}async loadCampaign(){try{let e=await fetch(`${this.options.apiUrl}/v1/config/${this.campaign}`);if(!e.ok)throw new Error(`Waitlist not found (${e.status})`);this.configData=await e.json(),this.applyBranding(),this.options.displayMode==="modal"?this.renderTrigger():this.renderInlineForm()}catch(e){this.container.innerHTML=`<div class="qz-error">Failed to load waitlist: ${e.message}</div>`}}applyBranding(){if(!this.configData)return;let e=this.configData.branding||{},t=document.documentElement;e.themeColor&&(t.style.setProperty("--qz-theme-color",e.themeColor),this.container.style.setProperty("--qz-theme-color",e.themeColor)),e.backgroundColor&&t.style.setProperty("--qz-bg-color",e.backgroundColor),e.fontFamily&&t.style.setProperty("--qz-font-family",e.fontFamily)}renderTrigger(){if(!this.configData)return;let e=this.options.modalOptions||{},t=e.triggerText||"Join Waitlist",r=e.triggerClass||"";this.container.innerHTML=`
221
- <button class="qz-trigger ${r}" id="qz-trigger-${this.campaign}">
220
+ `,u=class{constructor(e,t,o){p(this,"container");p(this,"campaign","");p(this,"options",{apiUrl:""});p(this,"configData",null);p(this,"submitted",!1);p(this,"submittedData",null);p(this,"modalElement",null);p(this,"modalOverlay",null);let r=e?document.getElementById(e):null;if(!r)if(o.displayMode==="modal")r=document.createElement("div");else{console.error(`QueueZero: Element with ID "${e}" not found.`),this.container=document.createElement("div");return}this.container=r,this.campaign=t,this.options=o,this.injectStyles(),this.render(),o.displayMode==="modal"&&this.attachHeadlessTriggers(),this.loadCampaign()}injectStyles(){if(document.getElementById("qz-styles"))return;let e=document.createElement("style");e.id="qz-styles",e.textContent=w,document.head.appendChild(e)}render(){this.container.innerHTML='<div class="qz-loading">Loading waitlist...</div>'}async loadCampaign(){try{let e=await fetch(`${this.options.apiUrl}/v1/config/${this.campaign}`);if(!e.ok)throw new Error(`Waitlist not found (${e.status})`);if(this.configData=await e.json(),this.applyBranding(),this.options.displayMode==="modal"){let{triggerSelector:t,attachToText:o}=this.options.modalOptions||{};if(t||o){let r=this.modalOverlay?.classList.contains("qz-modal-open");this.createModalDOM(),r&&this.openModal()}else this.renderTrigger()}else this.renderInlineForm()}catch(e){this.container&&(this.container.innerHTML=`<div class="qz-error">Error loading waitlist: ${e.message}</div>`),console.error(e)}}attachHeadlessTriggers(){let{triggerSelector:e,attachToText:t}=this.options.modalOptions||{};if(e)try{document.querySelectorAll(e).forEach(o=>{o.addEventListener("click",r=>{r.preventDefault(),r.stopImmediatePropagation(),this.openModal()})})}catch(o){console.warn("QueueZero: Invalid triggerSelector",o)}if(t){let o=t.toLowerCase();document.querySelectorAll("a, button").forEach(r=>{(r.textContent||"").toLowerCase().includes(o)&&r.addEventListener("click",l=>{l.preventDefault(),l.stopImmediatePropagation(),this.openModal()})})}}applyBranding(){if(!this.configData)return;let e=this.configData.branding||{},t=document.documentElement;e.themeColor&&(t.style.setProperty("--qz-theme-color",e.themeColor),this.container.style.setProperty("--qz-theme-color",e.themeColor)),e.backgroundColor&&t.style.setProperty("--qz-bg-color",e.backgroundColor),e.fontFamily&&t.style.setProperty("--qz-font-family",e.fontFamily)}renderTrigger(){if(!this.configData)return;let e=this.options.modalOptions||{},t=e.triggerText||"Join Waitlist",o=e.triggerClass||"";this.container.innerHTML=`
221
+ <button class="qz-trigger ${o}" id="qz-trigger-${this.campaign}">
222
222
  ${t}
223
223
  </button>
224
- `,this.container.querySelector(".qz-trigger")?.addEventListener("click",()=>this.openModal()),this.createModalDOM()}createModalDOM(){if(!this.configData)return;let e=this.configData.branding||{},r=`qz-modal-${(this.options.modalOptions||{}).size||"md"}`;this.modalOverlay=document.createElement("div"),this.modalOverlay.className="qz-modal-overlay",this.modalOverlay.id=`qz-modal-overlay-${this.campaign}`;let o="";e.logoUrl?o+=`<img src="${e.logoUrl}" alt="Logo" class="qz-modal-logo" />`:o+="<span></span>",o+=`
224
+ `,this.container.querySelector(".qz-trigger")?.addEventListener("click",()=>this.openModal()),this.createModalDOM()}createModalDOM(){this.modalOverlay&&(this.modalOverlay.remove(),this.modalOverlay=null);let e=this.configData?.branding||{},o=`qz-modal-${(this.options.modalOptions||{}).size||"md"}`,r=this.configData?.name||"Loading...";this.modalOverlay=document.createElement("div"),this.modalOverlay.className="qz-modal-overlay",this.modalOverlay.id=`qz-modal-overlay-${this.campaign}`;let n="";e.logoUrl?n+=`<img src="${e.logoUrl}" alt="Logo" class="qz-modal-logo" />`:n+="<span></span>",n+=`
225
225
  <button class="qz-modal-close" aria-label="Close">
226
226
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
227
227
  <path d="M18 6L6 18M6 6l12 12" />
228
228
  </svg>
229
229
  </button>
230
- `;let s="";e.coverImageUrl&&(s=`<img src="${e.coverImageUrl}" alt="" class="qz-modal-cover" />`),this.modalOverlay.innerHTML=`
231
- <div class="qz-modal ${r}">
230
+ `;let l="";e.coverImageUrl&&(l=`<img src="${e.coverImageUrl}" alt="" class="qz-modal-cover" />`);let i=`<h2 class="qz-modal-title">${r}</h2>`;i+=`<div id="qz-modal-form-container-${this.campaign}">`,this.configData||(i+='<div class="qz-loading" style="text-align:center; padding: 20px;">Loading...</div>'),i+="</div>",this.modalOverlay.innerHTML=`
231
+ <div class="qz-modal ${o}">
232
232
  <div class="qz-modal-header">
233
- ${o}
233
+ ${n}
234
234
  </div>
235
- ${s}
235
+ ${l}
236
236
  <div class="qz-modal-body">
237
- <h2 class="qz-modal-title">${this.configData.name}</h2>
238
- <div id="qz-modal-form-container-${this.campaign}"></div>
237
+ ${i}
239
238
  </div>
240
239
  </div>
241
- `,document.body.appendChild(this.modalOverlay),this.modalOverlay.querySelector(".qz-modal-close")?.addEventListener("click",()=>this.closeModal()),this.modalOverlay.addEventListener("click",i=>{i.target===this.modalOverlay&&this.closeModal()}),document.addEventListener("keydown",i=>{i.key==="Escape"&&this.modalOverlay?.classList.contains("qz-modal-open")&&this.closeModal()})}openModal(){if(!this.modalOverlay)return;let e=document.getElementById(`qz-modal-form-container-${this.campaign}`);e&&!e.hasChildNodes()&&this.renderFormInto(e),this.modalOverlay.classList.add("qz-modal-open"),document.body.style.overflow="hidden"}closeModal(){this.modalOverlay?.classList.remove("qz-modal-open"),document.body.style.overflow=""}renderInlineForm(){this.renderFormInto(this.container)}renderFormInto(e){if(!this.configData)return;let t=this.configData.branding?.themeColor||"#10B981";e.style.setProperty("--qz-theme-color",t);let r=(this.configData.formFields||[]).filter(i=>i.enabled);r.length===0&&r.push({key:"email",label:"Email Address",type:"email",enabled:!0,required:!0});let o=i=>{let m=`qz-${i.key}`,g=`<label class="qz-label" for="${m}">${i.label}${i.required?" *":""}</label>`,n=i.required?"required":"",l=i.placeholder||"";switch(i.type){case"select":let p=(i.options||[]).map(f=>`<option value="${f}">${f}</option>`).join("");return`
240
+ `,document.body.appendChild(this.modalOverlay),this.modalOverlay.querySelector(".qz-modal-close")?.addEventListener("click",()=>this.closeModal()),this.modalOverlay.addEventListener("click",d=>{d.target===this.modalOverlay&&this.closeModal()}),document.addEventListener("keydown",d=>{d.key==="Escape"&&this.modalOverlay?.classList.contains("qz-modal-open")&&this.closeModal()})}openModal(){if(this.modalOverlay||this.createModalDOM(),!this.modalOverlay)return;let e=document.getElementById(`qz-modal-form-container-${this.campaign}`);e&&!e.hasChildNodes()&&this.renderFormInto(e),this.modalOverlay.classList.add("qz-modal-open"),document.body.style.overflow="hidden"}closeModal(){this.modalOverlay?.classList.remove("qz-modal-open"),document.body.style.overflow=""}renderInlineForm(){this.renderFormInto(this.container)}renderFormInto(e){if(!this.configData)return;let t=this.configData.branding?.themeColor||"#10B981";e.style.setProperty("--qz-theme-color",t);let o=(this.configData.formFields||[]).filter(i=>i.enabled);o.length===0&&o.push({key:"email",label:"Email Address",type:"email",enabled:!0,required:!0});let r=i=>{let c=`qz-${i.key}`,d=`<label class="qz-label" for="${c}">${i.label}${i.required?" *":""}</label>`,s=i.required?"required":"",m=i.placeholder||"";switch(i.type){case"select":let g=(i.options||[]).map(f=>`<option value="${f}">${f}</option>`).join("");return`
242
241
  <div class="qz-form-group">
243
- ${g}
244
- <select class="qz-input qz-select" id="${m}" name="${i.key}" ${n}>
242
+ ${d}
243
+ <select class="qz-input qz-select" id="${c}" name="${i.key}" ${s}>
245
244
  <option value="">Select an option</option>
246
- ${p}
245
+ ${g}
247
246
  </select>
248
247
  </div>
249
248
  `;case"checkbox":return`
250
249
  <div class="qz-form-group qz-checkbox-group">
251
250
  <label class="qz-checkbox-label">
252
- <input type="checkbox" class="qz-checkbox" id="${m}" name="${i.key}" ${n} />
251
+ <input type="checkbox" class="qz-checkbox" id="${c}" name="${i.key}" ${s} />
253
252
  <span>${i.label}${i.required?" *":""}</span>
254
253
  </label>
255
254
  </div>
256
255
  `;case"textarea":return`
257
256
  <div class="qz-form-group">
258
- ${g}
257
+ ${d}
259
258
  <textarea
260
259
  class="qz-input qz-textarea"
261
- id="${m}"
260
+ id="${c}"
262
261
  name="${i.key}"
263
- placeholder="${l}"
262
+ placeholder="${m}"
264
263
  rows="3"
265
- ${n}
264
+ ${s}
266
265
  ></textarea>
267
266
  </div>
268
267
  `;case"number":return`
269
268
  <div class="qz-form-group">
270
- ${g}
269
+ ${d}
271
270
  <input
272
271
  class="qz-input"
273
- id="${m}"
272
+ id="${c}"
274
273
  name="${i.key}"
275
274
  type="number"
276
- placeholder="${l}"
277
- ${n}
275
+ placeholder="${m}"
276
+ ${s}
278
277
  />
279
278
  </div>
280
279
  `;default:return`
281
280
  <div class="qz-form-group">
282
- ${g}
281
+ ${d}
283
282
  <input
284
283
  class="qz-input"
285
- id="${m}"
284
+ id="${c}"
286
285
  name="${i.key}"
287
286
  type="${i.type==="email"?"email":"text"}"
288
- placeholder="${l}"
289
- ${n}
287
+ placeholder="${m}"
288
+ ${s}
290
289
  />
291
290
  </div>
292
- `}},s=r.map(o).join("");e.innerHTML=`
291
+ `}},n=o.map(r).join("");e.innerHTML=`
293
292
  <form class="qz-form" id="qz-form-${this.campaign}">
294
- ${s}
293
+ ${n}
295
294
  <button type="submit" class="qz-button">Join Waitlist</button>
296
295
  </form>
297
- `,e.querySelector("form")?.addEventListener("submit",i=>this.handleSubmit(i))}async handleSubmit(e){e.preventDefault();let t=e.target,r=t.querySelector("button"),o=new FormData(t),s={},c="";if(o.forEach((n,l)=>{let p=n.toString();l==="email"&&(c=p),s[l]=p}),!c){this.showError(t,"Email is required");return}let m=new URLSearchParams(window.location.search).get("ref"),g={campaign:this.campaign,email:c,metadata:s,referrer_code:m||null};r&&(r.disabled=!0,r.textContent="Joining...");try{let n=await fetch(`${this.options.apiUrl}/v1/submit`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)});if(!n.ok){let p=await n.json();throw new Error(p.message||p.error||"Failed to join")}let l=await n.json();this.submitted=!0,this.submittedData=l,this.renderSuccess(),this.options.onSuccess?.(l)}catch(n){this.showError(t,n.message||"Something went wrong"),r&&(r.disabled=!1,r.textContent="Join Waitlist"),this.options.onError?.(n)}}showError(e,t){let r=e.querySelector(".qz-error");r&&r.remove();let o=document.createElement("div");o.className="qz-error",o.textContent=t,e.insertBefore(o,e.firstChild)}renderSuccess(){let e=this.submittedData?.position||"?",t=this.submittedData?.referralCode,r=window.location.origin+window.location.pathname,o=t?`${r}?ref=${t}`:null,s=`
296
+ `,e.querySelector("form")?.addEventListener("submit",i=>this.handleSubmit(i))}async handleSubmit(e){e.preventDefault();let t=e.target,o=t.querySelector("button"),r=new FormData(t),n={},l="";if(r.forEach((s,m)=>{let g=s.toString();m==="email"&&(l=g),n[m]=g}),!l){this.showError(t,"Email is required");return}let c=new URLSearchParams(window.location.search).get("ref"),d={campaign:this.campaign,email:l,metadata:n,referrer_code:c||null};o&&(o.disabled=!0,o.textContent="Joining...");try{let s=await fetch(`${this.options.apiUrl}/v1/submit`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d)});if(!s.ok){let g=await s.json();throw new Error(g.message||g.error||"Failed to join")}let m=await s.json();this.submitted=!0,this.submittedData=m,this.renderSuccess(),this.options.onSuccess?.(m)}catch(s){this.showError(t,s.message||"Something went wrong"),o&&(o.disabled=!1,o.textContent="Join Waitlist"),this.options.onError?.(s)}}showError(e,t){let o=e.querySelector(".qz-error");o&&o.remove();let r=document.createElement("div");r.className="qz-error",r.textContent=t,e.insertBefore(r,e.firstChild)}renderSuccess(){let e=this.submittedData?.position||"?",t=this.submittedData?.referralCode,o=window.location.origin+window.location.pathname,r=t?`${o}?ref=${t}`:null,n=`
298
297
  <div class="qz-success">
299
298
  <h3>You're on the list!</h3>
300
299
  <p>You're #${e} in line.</p>
301
- `;if(o&&(s+=`
300
+ `;if(r&&(n+=`
302
301
  <div style="margin-top: 20px;">
303
302
  <p style="margin-bottom: 8px; color: #a6e3a1;">Refer friends to move up:</p>
304
- <input class="qz-input" readonly value="${o}" onclick="this.select()" style="text-align: center; cursor: pointer;">
303
+ <input class="qz-input" readonly value="${r}" onclick="this.select()" style="text-align: center; cursor: pointer;">
305
304
  </div>
306
- `),s+="</div>",this.options.displayMode==="modal"){let c=document.getElementById(`qz-modal-form-container-${this.campaign}`);if(c){c.innerHTML=s;return}}this.container.innerHTML=s}};function k(a,e,t){return new u(a,e,t)}return $(C);})();
305
+ `),n+="</div>",this.options.displayMode==="modal"){let l=document.getElementById(`qz-modal-form-container-${this.campaign}`);if(l){l.innerHTML=n;return}}this.container.innerHTML=n}};function E(a,e,t){return new u(a,e,t)}return $(M);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@queuezero/embed",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Embeddable vanilla JS widget for QueueZero viral waitlists",
5
5
  "main": "./dist/index.global.js",
6
6
  "browser": "./dist/index.global.js",