@queuezero/react 0.1.8 → 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
@@ -78,8 +78,31 @@ A ready-to-use signup form.
78
78
  </div>
79
79
  )}
80
80
  </WaitlistForm>
81
+
82
+ // Modal mode
83
+ <WaitlistForm
84
+ displayMode="modal"
85
+ triggerText="Join Our Waitlist"
86
+ modalSize="md"
87
+ // Headless modal mode (attaches to existing buttons)
88
+ <WaitlistForm
89
+ displayMode="modal"
90
+ attachToText="Join Waitlist"
91
+ modalSize="md"
92
+ />
81
93
  ```
82
94
 
95
+ #### Modal Mode Props
96
+
97
+ | Prop | Type | Default | Description |
98
+ |------|------|---------|-------------|
99
+ | `displayMode` | `'inline' \| 'modal'` | `'inline'` | Display as inline form or modal |
100
+ | `triggerText` | `string` | `'Join Waitlist'` | Button text (modal mode only) |
101
+ | `attachToText` | `string` | - | Text to match for auto-attaching modal (headless mode) |
102
+ | `modalSize` | `'sm' \| 'md' \| 'lg'` | `'md'` | Modal width (modal mode only) |
103
+
104
+ The modal automatically uses your campaign's branding (logo, cover image, theme color) from the API.
105
+
83
106
  ### WaitlistStatus
84
107
 
85
108
  Displays position, score, and referral count.
package/dist/index.d.mts CHANGED
@@ -80,6 +80,10 @@ interface WaitlistFormProps {
80
80
  triggerText?: string;
81
81
  /** Modal size (only used when displayMode is 'modal') */
82
82
  modalSize?: 'sm' | 'md' | 'lg';
83
+ /** CSS selector to attach modal trigger to existing elements */
84
+ triggerSelector?: string;
85
+ /** Text content to match for attaching modal trigger to <a> and <button> elements */
86
+ attachToText?: string;
83
87
  /** Children for custom rendering */
84
88
  children?: React.ReactNode;
85
89
  }
@@ -203,7 +207,7 @@ declare function useWaitlist(campaign: string, config?: WaitlistProviderProps['c
203
207
  * <WaitlistForm displayMode="modal" triggerText="Join Our Waitlist" />
204
208
  * ```
205
209
  */
206
- declare function WaitlistForm({ onSuccess, onError, referrerCode, metadata, className, placeholder, buttonText, loadingText, showLabel, labelText, displayMode, triggerText, modalSize, children, }: WaitlistFormProps): any;
210
+ declare function WaitlistForm({ onSuccess, onError, referrerCode, metadata, className, placeholder, buttonText, loadingText, showLabel, labelText, displayMode, triggerText, modalSize, triggerSelector, attachToText, children, }: WaitlistFormProps): any;
207
211
 
208
212
  /**
209
213
  * WaitlistStatus - Shows current position, score, and referral count
package/dist/index.d.ts CHANGED
@@ -80,6 +80,10 @@ interface WaitlistFormProps {
80
80
  triggerText?: string;
81
81
  /** Modal size (only used when displayMode is 'modal') */
82
82
  modalSize?: 'sm' | 'md' | 'lg';
83
+ /** CSS selector to attach modal trigger to existing elements */
84
+ triggerSelector?: string;
85
+ /** Text content to match for attaching modal trigger to <a> and <button> elements */
86
+ attachToText?: string;
83
87
  /** Children for custom rendering */
84
88
  children?: React.ReactNode;
85
89
  }
@@ -203,7 +207,7 @@ declare function useWaitlist(campaign: string, config?: WaitlistProviderProps['c
203
207
  * <WaitlistForm displayMode="modal" triggerText="Join Our Waitlist" />
204
208
  * ```
205
209
  */
206
- declare function WaitlistForm({ onSuccess, onError, referrerCode, metadata, className, placeholder, buttonText, loadingText, showLabel, labelText, displayMode, triggerText, modalSize, children, }: WaitlistFormProps): any;
210
+ declare function WaitlistForm({ onSuccess, onError, referrerCode, metadata, className, placeholder, buttonText, loadingText, showLabel, labelText, displayMode, triggerText, modalSize, triggerSelector, attachToText, children, }: WaitlistFormProps): any;
207
211
 
208
212
  /**
209
213
  * WaitlistStatus - Shows current position, score, and referral count
package/dist/index.js CHANGED
@@ -81,7 +81,13 @@ function WaitlistProvider({ campaign, config, children }) {
81
81
  campaign,
82
82
  join: stateManager.join.bind(stateManager),
83
83
  refresh: stateManager.refresh.bind(stateManager),
84
- getReferralLink: stateManager.getReferralLink.bind(stateManager),
84
+ getReferralLink: () => {
85
+ const code = stateManager.getReferralCode();
86
+ if (code && typeof window !== "undefined") {
87
+ return `${window.location.origin}${window.location.pathname}?ref=${code}`;
88
+ }
89
+ return stateManager.getReferralLink();
90
+ },
85
91
  getReferralCode: stateManager.getReferralCode.bind(stateManager),
86
92
  reset: stateManager.reset.bind(stateManager),
87
93
  config: publicConfig,
@@ -176,6 +182,8 @@ function WaitlistForm({
176
182
  displayMode = "inline",
177
183
  triggerText = "Join Waitlist",
178
184
  modalSize = "md",
185
+ triggerSelector,
186
+ attachToText,
179
187
  children
180
188
  }) {
181
189
  const { join, loading, error, isJoined, config, configLoading } = useWaitlistContext();
@@ -183,6 +191,7 @@ function WaitlistForm({
183
191
  const [formData, setFormData] = (0, import_react2.useState)({});
184
192
  const [localError, setLocalError] = (0, import_react2.useState)(null);
185
193
  const [isModalOpen, setIsModalOpen] = (0, import_react2.useState)(false);
194
+ const [successData, setSuccessData] = (0, import_react2.useState)(null);
186
195
  (0, import_react2.useEffect)(() => {
187
196
  const handleEsc = (e) => {
188
197
  if (e.key === "Escape" && isModalOpen) setIsModalOpen(false);
@@ -213,6 +222,7 @@ function WaitlistForm({
213
222
  if (result) {
214
223
  setEmail("");
215
224
  setFormData({});
225
+ setSuccessData(result);
216
226
  onSuccess?.(result);
217
227
  } else if (error) {
218
228
  onError?.(error);
@@ -275,7 +285,7 @@ function WaitlistForm({
275
285
  required: field.required,
276
286
  className: "qz-form-select",
277
287
  children: [
278
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", children: field.placeholder || `Select ${field.label}` }),
288
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", children: "Select an option" }),
279
289
  field.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: opt, children: opt }, opt))
280
290
  ]
281
291
  }
@@ -340,8 +350,33 @@ function WaitlistForm({
340
350
  if (displayMode === "inline") {
341
351
  return formContent;
342
352
  }
353
+ const isHeadless = !!(triggerSelector || attachToText);
354
+ (0, import_react2.useEffect)(() => {
355
+ if (displayMode !== "modal") return;
356
+ const cleanupFns = [];
357
+ const attach = (element) => {
358
+ const handler = (e) => {
359
+ e.preventDefault();
360
+ setIsModalOpen(true);
361
+ };
362
+ element.addEventListener("click", handler);
363
+ cleanupFns.push(() => element.removeEventListener("click", handler));
364
+ };
365
+ if (triggerSelector) {
366
+ document.querySelectorAll(triggerSelector).forEach(attach);
367
+ }
368
+ if (attachToText) {
369
+ const lowerText = attachToText.toLowerCase();
370
+ document.querySelectorAll("a, button").forEach((el) => {
371
+ if (el.textContent?.toLowerCase().includes(lowerText)) {
372
+ attach(el);
373
+ }
374
+ });
375
+ }
376
+ return () => cleanupFns.forEach((fn) => fn());
377
+ }, [displayMode, triggerSelector, attachToText]);
343
378
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
344
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
379
+ !isHeadless && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
345
380
  "button",
346
381
  {
347
382
  type: "button",
@@ -373,7 +408,27 @@ function WaitlistForm({
373
408
  branding.coverImageUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: branding.coverImageUrl, alt: "", className: "qz-modal-cover" }),
374
409
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "qz-modal-body", children: [
375
410
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "qz-modal-title", children: config?.name || "Join Waitlist" }),
376
- formContent
411
+ successData ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "qz-success", children: [
412
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { style: { fontSize: "1.25rem", fontWeight: 600, marginBottom: "0.5rem", textAlign: "center" }, children: "You're on the list!" }),
413
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: { textAlign: "center", marginBottom: "1.5rem", opacity: 0.9 }, children: [
414
+ "You're #",
415
+ successData.position || "?",
416
+ " in line."
417
+ ] }),
418
+ successData.referralLink && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "1.5rem" }, children: [
419
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { marginBottom: "0.5rem", fontSize: "0.875rem", opacity: 0.9, textAlign: "center" }, children: "Refer friends to move up:" }),
420
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
421
+ "input",
422
+ {
423
+ readOnly: true,
424
+ value: window.location.origin + window.location.pathname + "?ref=" + successData.referralCode,
425
+ onClick: (e) => e.currentTarget.select(),
426
+ className: "qz-form-input",
427
+ style: { textAlign: "center", cursor: "pointer" }
428
+ }
429
+ )
430
+ ] })
431
+ ] }) : formContent
377
432
  ] })
378
433
  ] })
379
434
  }
package/dist/index.mjs CHANGED
@@ -47,7 +47,13 @@ function WaitlistProvider({ campaign, config, children }) {
47
47
  campaign,
48
48
  join: stateManager.join.bind(stateManager),
49
49
  refresh: stateManager.refresh.bind(stateManager),
50
- getReferralLink: stateManager.getReferralLink.bind(stateManager),
50
+ getReferralLink: () => {
51
+ const code = stateManager.getReferralCode();
52
+ if (code && typeof window !== "undefined") {
53
+ return `${window.location.origin}${window.location.pathname}?ref=${code}`;
54
+ }
55
+ return stateManager.getReferralLink();
56
+ },
51
57
  getReferralCode: stateManager.getReferralCode.bind(stateManager),
52
58
  reset: stateManager.reset.bind(stateManager),
53
59
  config: publicConfig,
@@ -142,6 +148,8 @@ function WaitlistForm({
142
148
  displayMode = "inline",
143
149
  triggerText = "Join Waitlist",
144
150
  modalSize = "md",
151
+ triggerSelector,
152
+ attachToText,
145
153
  children
146
154
  }) {
147
155
  const { join, loading, error, isJoined, config, configLoading } = useWaitlistContext();
@@ -149,6 +157,7 @@ function WaitlistForm({
149
157
  const [formData, setFormData] = useState2({});
150
158
  const [localError, setLocalError] = useState2(null);
151
159
  const [isModalOpen, setIsModalOpen] = useState2(false);
160
+ const [successData, setSuccessData] = useState2(null);
152
161
  useEffect2(() => {
153
162
  const handleEsc = (e) => {
154
163
  if (e.key === "Escape" && isModalOpen) setIsModalOpen(false);
@@ -179,6 +188,7 @@ function WaitlistForm({
179
188
  if (result) {
180
189
  setEmail("");
181
190
  setFormData({});
191
+ setSuccessData(result);
182
192
  onSuccess?.(result);
183
193
  } else if (error) {
184
194
  onError?.(error);
@@ -241,7 +251,7 @@ function WaitlistForm({
241
251
  required: field.required,
242
252
  className: "qz-form-select",
243
253
  children: [
244
- /* @__PURE__ */ jsx2("option", { value: "", children: field.placeholder || `Select ${field.label}` }),
254
+ /* @__PURE__ */ jsx2("option", { value: "", children: "Select an option" }),
245
255
  field.options?.map((opt) => /* @__PURE__ */ jsx2("option", { value: opt, children: opt }, opt))
246
256
  ]
247
257
  }
@@ -306,8 +316,33 @@ function WaitlistForm({
306
316
  if (displayMode === "inline") {
307
317
  return formContent;
308
318
  }
319
+ const isHeadless = !!(triggerSelector || attachToText);
320
+ useEffect2(() => {
321
+ if (displayMode !== "modal") return;
322
+ const cleanupFns = [];
323
+ const attach = (element) => {
324
+ const handler = (e) => {
325
+ e.preventDefault();
326
+ setIsModalOpen(true);
327
+ };
328
+ element.addEventListener("click", handler);
329
+ cleanupFns.push(() => element.removeEventListener("click", handler));
330
+ };
331
+ if (triggerSelector) {
332
+ document.querySelectorAll(triggerSelector).forEach(attach);
333
+ }
334
+ if (attachToText) {
335
+ const lowerText = attachToText.toLowerCase();
336
+ document.querySelectorAll("a, button").forEach((el) => {
337
+ if (el.textContent?.toLowerCase().includes(lowerText)) {
338
+ attach(el);
339
+ }
340
+ });
341
+ }
342
+ return () => cleanupFns.forEach((fn) => fn());
343
+ }, [displayMode, triggerSelector, attachToText]);
309
344
  return /* @__PURE__ */ jsxs(Fragment, { children: [
310
- /* @__PURE__ */ jsx2(
345
+ !isHeadless && /* @__PURE__ */ jsx2(
311
346
  "button",
312
347
  {
313
348
  type: "button",
@@ -339,7 +374,27 @@ function WaitlistForm({
339
374
  branding.coverImageUrl && /* @__PURE__ */ jsx2("img", { src: branding.coverImageUrl, alt: "", className: "qz-modal-cover" }),
340
375
  /* @__PURE__ */ jsxs("div", { className: "qz-modal-body", children: [
341
376
  /* @__PURE__ */ jsx2("h2", { className: "qz-modal-title", children: config?.name || "Join Waitlist" }),
342
- formContent
377
+ successData ? /* @__PURE__ */ jsxs("div", { className: "qz-success", children: [
378
+ /* @__PURE__ */ jsx2("h3", { style: { fontSize: "1.25rem", fontWeight: 600, marginBottom: "0.5rem", textAlign: "center" }, children: "You're on the list!" }),
379
+ /* @__PURE__ */ jsxs("p", { style: { textAlign: "center", marginBottom: "1.5rem", opacity: 0.9 }, children: [
380
+ "You're #",
381
+ successData.position || "?",
382
+ " in line."
383
+ ] }),
384
+ successData.referralLink && /* @__PURE__ */ jsxs("div", { style: { marginTop: "1.5rem" }, children: [
385
+ /* @__PURE__ */ jsx2("p", { style: { marginBottom: "0.5rem", fontSize: "0.875rem", opacity: 0.9, textAlign: "center" }, children: "Refer friends to move up:" }),
386
+ /* @__PURE__ */ jsx2(
387
+ "input",
388
+ {
389
+ readOnly: true,
390
+ value: window.location.origin + window.location.pathname + "?ref=" + successData.referralCode,
391
+ onClick: (e) => e.currentTarget.select(),
392
+ className: "qz-form-input",
393
+ style: { textAlign: "center", cursor: "pointer" }
394
+ }
395
+ )
396
+ ] })
397
+ ] }) : formContent
343
398
  ] })
344
399
  ] })
345
400
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@queuezero/react",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "React components and hooks for QueueZero viral waitlists",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",