@queuezero/embed 0.1.9 → 0.1.10

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 b=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var q=(n,e,t)=>e in n?h(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var z=(n,e)=>{for(var t in e)h(n,t,{get:e[t],enumerable:!0})},x=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of b(e))!v.call(n,o)&&o!==t&&h(n,o,{get:()=>e[o],enumerable:!(r=y(e,o))||r.enumerable});return n};var $=n=>x(h({},"__esModule",{value:!0}),n);var d=(n,e,t)=>q(n,typeof e!="symbol"?e+"":e,t);var E={};z(E,{init:()=>k});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,7 +217,7 @@ 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=`
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=e?document.getElementById(e):null;if(!o)if(r.displayMode==="modal")o=document.createElement("div");else{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})`);if(this.configData=await e.json(),this.applyBranding(),this.options.displayMode==="modal"){let{triggerSelector:t,attachToText:r}=this.options.modalOptions||{};if(t||r){if(this.createModalDOM(),t&&document.querySelectorAll(t).forEach(o=>{o.addEventListener("click",a=>{a.preventDefault(),this.openModal()})}),r){let o=r.toLowerCase();document.querySelectorAll("a, button").forEach(a=>{(a.textContent||"").toLowerCase().includes(o)&&a.addEventListener("click",i=>{i.preventDefault(),this.openModal()})})}}else this.renderTrigger()}else 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
221
  <button class="qz-trigger ${r}" id="qz-trigger-${this.campaign}">
222
222
  ${t}
223
223
  </button>
@@ -227,21 +227,21 @@ var QueueZero=(()=>{var h=Object.defineProperty;var y=Object.getOwnPropertyDescr
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=`
230
+ `;let a="";e.coverImageUrl&&(a=`<img src="${e.coverImageUrl}" alt="" class="qz-modal-cover" />`),this.modalOverlay.innerHTML=`
231
231
  <div class="qz-modal ${r}">
232
232
  <div class="qz-modal-header">
233
233
  ${o}
234
234
  </div>
235
- ${s}
235
+ ${a}
236
236
  <div class="qz-modal-body">
237
237
  <h2 class="qz-modal-title">${this.configData.name}</h2>
238
238
  <div id="qz-modal-form-container-${this.campaign}"></div>
239
239
  </div>
240
240
  </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`
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>`,s=i.required?"required":"",c=i.placeholder||"";switch(i.type){case"select":let p=(i.options||[]).map(f=>`<option value="${f}">${f}</option>`).join("");return`
242
242
  <div class="qz-form-group">
243
243
  ${g}
244
- <select class="qz-input qz-select" id="${m}" name="${i.key}" ${n}>
244
+ <select class="qz-input qz-select" id="${m}" name="${i.key}" ${s}>
245
245
  <option value="">Select an option</option>
246
246
  ${p}
247
247
  </select>
@@ -249,7 +249,7 @@ var QueueZero=(()=>{var h=Object.defineProperty;var y=Object.getOwnPropertyDescr
249
249
  `;case"checkbox":return`
250
250
  <div class="qz-form-group qz-checkbox-group">
251
251
  <label class="qz-checkbox-label">
252
- <input type="checkbox" class="qz-checkbox" id="${m}" name="${i.key}" ${n} />
252
+ <input type="checkbox" class="qz-checkbox" id="${m}" name="${i.key}" ${s} />
253
253
  <span>${i.label}${i.required?" *":""}</span>
254
254
  </label>
255
255
  </div>
@@ -260,9 +260,9 @@ var QueueZero=(()=>{var h=Object.defineProperty;var y=Object.getOwnPropertyDescr
260
260
  class="qz-input qz-textarea"
261
261
  id="${m}"
262
262
  name="${i.key}"
263
- placeholder="${l}"
263
+ placeholder="${c}"
264
264
  rows="3"
265
- ${n}
265
+ ${s}
266
266
  ></textarea>
267
267
  </div>
268
268
  `;case"number":return`
@@ -273,8 +273,8 @@ var QueueZero=(()=>{var h=Object.defineProperty;var y=Object.getOwnPropertyDescr
273
273
  id="${m}"
274
274
  name="${i.key}"
275
275
  type="number"
276
- placeholder="${l}"
277
- ${n}
276
+ placeholder="${c}"
277
+ ${s}
278
278
  />
279
279
  </div>
280
280
  `;default:return`
@@ -285,22 +285,22 @@ var QueueZero=(()=>{var h=Object.defineProperty;var y=Object.getOwnPropertyDescr
285
285
  id="${m}"
286
286
  name="${i.key}"
287
287
  type="${i.type==="email"?"email":"text"}"
288
- placeholder="${l}"
289
- ${n}
288
+ placeholder="${c}"
289
+ ${s}
290
290
  />
291
291
  </div>
292
- `}},s=r.map(o).join("");e.innerHTML=`
292
+ `}},a=r.map(o).join("");e.innerHTML=`
293
293
  <form class="qz-form" id="qz-form-${this.campaign}">
294
- ${s}
294
+ ${a}
295
295
  <button type="submit" class="qz-button">Join Waitlist</button>
296
296
  </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=`
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),a={},l="";if(o.forEach((s,c)=>{let p=s.toString();c==="email"&&(l=p),a[c]=p}),!l){this.showError(t,"Email is required");return}let m=new URLSearchParams(window.location.search).get("ref"),g={campaign:this.campaign,email:l,metadata:a,referrer_code:m||null};r&&(r.disabled=!0,r.textContent="Joining...");try{let s=await fetch(`${this.options.apiUrl}/v1/submit`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)});if(!s.ok){let p=await s.json();throw new Error(p.message||p.error||"Failed to join")}let c=await s.json();this.submitted=!0,this.submittedData=c,this.renderSuccess(),this.options.onSuccess?.(c)}catch(s){this.showError(t,s.message||"Something went wrong"),r&&(r.disabled=!1,r.textContent="Join Waitlist"),this.options.onError?.(s)}}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,a=`
298
298
  <div class="qz-success">
299
299
  <h3>You're on the list!</h3>
300
300
  <p>You're #${e} in line.</p>
301
- `;if(o&&(s+=`
301
+ `;if(o&&(a+=`
302
302
  <div style="margin-top: 20px;">
303
303
  <p style="margin-bottom: 8px; color: #a6e3a1;">Refer friends to move up:</p>
304
304
  <input class="qz-input" readonly value="${o}" onclick="this.select()" style="text-align: center; cursor: pointer;">
305
305
  </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);})();
306
+ `),a+="</div>",this.options.displayMode==="modal"){let l=document.getElementById(`qz-modal-form-container-${this.campaign}`);if(l){l.innerHTML=a;return}}this.container.innerHTML=a}};function k(n,e,t){return new u(n,e,t)}return $(E);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@queuezero/embed",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
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",