@queuezero/vue 0.1.7 → 0.1.9

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
@@ -77,9 +77,26 @@ Ready-to-use signup form.
77
77
  <input :value="email" @input="setEmail($event.target.value)" />
78
78
  <button @click="submit">{{ loading ? '...' : 'Join' }}</button>
79
79
  </WaitlistForm>
80
+
81
+ <!-- Modal mode -->
82
+ <WaitlistForm
83
+ display-mode="modal"
84
+ trigger-text="Join Our Waitlist"
85
+ modal-size="md"
86
+ />
80
87
  </template>
81
88
  ```
82
89
 
90
+ #### Modal Mode Props
91
+
92
+ | Prop | Type | Default | Description |
93
+ |------|------|---------|-------------|
94
+ | `display-mode` | `'inline' \| 'modal'` | `'inline'` | Display as inline form or modal |
95
+ | `trigger-text` | `string` | `'Join Waitlist'` | Button text (modal mode only) |
96
+ | `modal-size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Modal width (modal mode only) |
97
+
98
+ The modal automatically uses your campaign's branding (logo, cover image, theme color) from the API.
99
+
83
100
  ### WaitlistStatus
84
101
 
85
102
  Displays position, score, and referral count.
package/dist/index.d.mts CHANGED
@@ -56,6 +56,12 @@ interface WaitlistFormProps {
56
56
  showLabel?: boolean;
57
57
  /** Label text */
58
58
  labelText?: string;
59
+ /** Display mode: 'inline' renders form directly, 'modal' renders trigger button */
60
+ displayMode?: 'inline' | 'modal';
61
+ /** Text for modal trigger button (only used when displayMode is 'modal') */
62
+ triggerText?: string;
63
+ /** Modal size (only used when displayMode is 'modal') */
64
+ modalSize?: 'sm' | 'md' | 'lg';
59
65
  }
60
66
  /**
61
67
  * Props for WaitlistStatus component
@@ -200,6 +206,18 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
200
206
  type: StringConstructor;
201
207
  default: string;
202
208
  };
209
+ displayMode: {
210
+ type: PropType<"inline" | "modal">;
211
+ default: string;
212
+ };
213
+ triggerText: {
214
+ type: StringConstructor;
215
+ default: string;
216
+ };
217
+ modalSize: {
218
+ type: PropType<"sm" | "md" | "lg">;
219
+ default: string;
220
+ };
203
221
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
204
222
  [key: string]: any;
205
223
  }> | VNode<vue.RendererNode, vue.RendererElement, {
@@ -236,6 +254,18 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
236
254
  type: StringConstructor;
237
255
  default: string;
238
256
  };
257
+ displayMode: {
258
+ type: PropType<"inline" | "modal">;
259
+ default: string;
260
+ };
261
+ triggerText: {
262
+ type: StringConstructor;
263
+ default: string;
264
+ };
265
+ modalSize: {
266
+ type: PropType<"sm" | "md" | "lg">;
267
+ default: string;
268
+ };
239
269
  }>> & Readonly<{
240
270
  onSuccess?: ((response: SubmitResponse) => any) | undefined;
241
271
  onError?: ((error: Error) => any) | undefined;
@@ -247,6 +277,9 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
247
277
  loadingText: string;
248
278
  showLabel: boolean;
249
279
  labelText: string;
280
+ displayMode: "inline" | "modal";
281
+ triggerText: string;
282
+ modalSize: "sm" | "md" | "lg";
250
283
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
251
284
 
252
285
  /**
package/dist/index.d.ts CHANGED
@@ -56,6 +56,12 @@ interface WaitlistFormProps {
56
56
  showLabel?: boolean;
57
57
  /** Label text */
58
58
  labelText?: string;
59
+ /** Display mode: 'inline' renders form directly, 'modal' renders trigger button */
60
+ displayMode?: 'inline' | 'modal';
61
+ /** Text for modal trigger button (only used when displayMode is 'modal') */
62
+ triggerText?: string;
63
+ /** Modal size (only used when displayMode is 'modal') */
64
+ modalSize?: 'sm' | 'md' | 'lg';
59
65
  }
60
66
  /**
61
67
  * Props for WaitlistStatus component
@@ -200,6 +206,18 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
200
206
  type: StringConstructor;
201
207
  default: string;
202
208
  };
209
+ displayMode: {
210
+ type: PropType<"inline" | "modal">;
211
+ default: string;
212
+ };
213
+ triggerText: {
214
+ type: StringConstructor;
215
+ default: string;
216
+ };
217
+ modalSize: {
218
+ type: PropType<"sm" | "md" | "lg">;
219
+ default: string;
220
+ };
203
221
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
204
222
  [key: string]: any;
205
223
  }> | VNode<vue.RendererNode, vue.RendererElement, {
@@ -236,6 +254,18 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
236
254
  type: StringConstructor;
237
255
  default: string;
238
256
  };
257
+ displayMode: {
258
+ type: PropType<"inline" | "modal">;
259
+ default: string;
260
+ };
261
+ triggerText: {
262
+ type: StringConstructor;
263
+ default: string;
264
+ };
265
+ modalSize: {
266
+ type: PropType<"sm" | "md" | "lg">;
267
+ default: string;
268
+ };
239
269
  }>> & Readonly<{
240
270
  onSuccess?: ((response: SubmitResponse) => any) | undefined;
241
271
  onError?: ((error: Error) => any) | undefined;
@@ -247,6 +277,9 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
247
277
  loadingText: string;
248
278
  showLabel: boolean;
249
279
  labelText: string;
280
+ displayMode: "inline" | "modal";
281
+ triggerText: string;
282
+ modalSize: "sm" | "md" | "lg";
250
283
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
251
284
 
252
285
  /**
package/dist/index.js CHANGED
@@ -76,7 +76,13 @@ function useWaitlist(campaign, config) {
76
76
  join: stateManager.join.bind(stateManager),
77
77
  refresh: stateManager.refresh.bind(stateManager),
78
78
  fetchPublicConfig: stateManager.fetchPublicConfig.bind(stateManager),
79
- getReferralLink: stateManager.getReferralLink.bind(stateManager),
79
+ getReferralLink: () => {
80
+ const code = stateManager.getReferralCode();
81
+ if (code && typeof window !== "undefined") {
82
+ return `${window.location.origin}${window.location.pathname}?ref=${code}`;
83
+ }
84
+ return stateManager.getReferralLink();
85
+ },
80
86
  getReferralCode: stateManager.getReferralCode.bind(stateManager),
81
87
  reset: stateManager.reset.bind(stateManager)
82
88
  };
@@ -135,6 +141,18 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
135
141
  labelText: {
136
142
  type: String,
137
143
  default: "Email"
144
+ },
145
+ displayMode: {
146
+ type: String,
147
+ default: "inline"
148
+ },
149
+ triggerText: {
150
+ type: String,
151
+ default: "Join Waitlist"
152
+ },
153
+ modalSize: {
154
+ type: String,
155
+ default: "md"
138
156
  }
139
157
  },
140
158
  emits: {
@@ -145,8 +163,13 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
145
163
  const { join, loading, error: contextError, isJoined, publicConfig } = useWaitlistContext();
146
164
  const email = (0, import_vue2.ref)("");
147
165
  const localError = (0, import_vue2.ref)(null);
166
+ const isModalOpen = (0, import_vue2.ref)(false);
167
+ const successData = (0, import_vue2.ref)(null);
148
168
  const formData = (0, import_vue2.reactive)({});
149
169
  const displayError = (0, import_vue2.computed)(() => localError.value || contextError.value);
170
+ const branding = (0, import_vue2.computed)(() => publicConfig.value?.branding || {});
171
+ const themeColor = (0, import_vue2.computed)(() => branding.value.themeColor || "#10B981");
172
+ const sizeClasses = { sm: "qz-modal-sm", md: "qz-modal-md", lg: "qz-modal-lg" };
150
173
  const formFields = (0, import_vue2.computed)(() => {
151
174
  return publicConfig.value?.formFields?.filter((f) => f.enabled) ?? [];
152
175
  });
@@ -161,6 +184,7 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
161
184
  if (result) {
162
185
  email.value = "";
163
186
  Object.keys(formData).forEach((key) => delete formData[key]);
187
+ successData.value = result;
164
188
  emit("success", result);
165
189
  } else if (contextError.value) {
166
190
  emit("error", contextError.value);
@@ -194,7 +218,7 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
194
218
  formData[field.key] = e.target.value;
195
219
  }
196
220
  }, [
197
- (0, import_vue2.h)("option", { value: "" }, field.placeholder || `Select ${field.label}`),
221
+ (0, import_vue2.h)("option", { value: "" }, "Select an option"),
198
222
  ...(field.options || []).map(
199
223
  (opt) => (0, import_vue2.h)("option", { value: opt }, opt)
200
224
  )
@@ -298,7 +322,7 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
298
322
  formFields.value.forEach((field) => {
299
323
  fieldNodes.push(renderField(field));
300
324
  });
301
- return (0, import_vue2.h)(
325
+ const formContent = (0, import_vue2.h)(
302
326
  "form",
303
327
  {
304
328
  class: "qz-form",
@@ -324,6 +348,74 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
324
348
  )
325
349
  ]
326
350
  );
351
+ if (props.displayMode === "inline") {
352
+ return formContent;
353
+ }
354
+ return (0, import_vue2.h)("div", { class: "qz-waitlist-modal-wrapper" }, [
355
+ // Trigger button
356
+ (0, import_vue2.h)(
357
+ "button",
358
+ {
359
+ type: "button",
360
+ class: "qz-modal-trigger",
361
+ style: { "--qz-theme-color": themeColor.value },
362
+ onClick: () => {
363
+ isModalOpen.value = true;
364
+ }
365
+ },
366
+ props.triggerText
367
+ ),
368
+ // Modal overlay (only when open)
369
+ isModalOpen.value && (0, import_vue2.h)(
370
+ "div",
371
+ {
372
+ class: "qz-modal-overlay",
373
+ onClick: (e) => {
374
+ if (e.target === e.currentTarget) isModalOpen.value = false;
375
+ }
376
+ },
377
+ [
378
+ (0, import_vue2.h)("div", { class: `qz-modal ${sizeClasses[props.modalSize]}`, style: { "--qz-theme-color": themeColor.value } }, [
379
+ // Header
380
+ (0, import_vue2.h)("div", { class: "qz-modal-header" }, [
381
+ branding.value.logoUrl ? (0, import_vue2.h)("img", { src: branding.value.logoUrl, alt: "Logo", class: "qz-modal-logo" }) : (0, import_vue2.h)("span"),
382
+ (0, import_vue2.h)("button", {
383
+ type: "button",
384
+ class: "qz-modal-close",
385
+ "aria-label": "Close",
386
+ onClick: () => {
387
+ isModalOpen.value = false;
388
+ }
389
+ }, [
390
+ (0, import_vue2.h)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, [
391
+ (0, import_vue2.h)("path", { d: "M18 6L6 18M6 6l12 12" })
392
+ ])
393
+ ])
394
+ ]),
395
+ // Cover image
396
+ branding.value.coverImageUrl && (0, import_vue2.h)("img", { src: branding.value.coverImageUrl, alt: "", class: "qz-modal-cover" }),
397
+ // Body
398
+ (0, import_vue2.h)("div", { class: "qz-modal-body" }, [
399
+ (0, import_vue2.h)("h2", { class: "qz-modal-title" }, publicConfig.value?.name || "Join Waitlist"),
400
+ successData.value ? (0, import_vue2.h)("div", { class: "qz-success" }, [
401
+ (0, import_vue2.h)("h3", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "0.5rem", textAlign: "center" } }, "You're on the list!"),
402
+ (0, import_vue2.h)("p", { style: { textAlign: "center", marginBottom: "1.5rem", opacity: "0.9" } }, `You're #${successData.value.position || "?"} in line.`),
403
+ successData.value.referralLink && (0, import_vue2.h)("div", { style: { marginTop: "1.5rem" } }, [
404
+ (0, import_vue2.h)("p", { style: { marginBottom: "0.5rem", fontSize: "0.875rem", opacity: "0.9", textAlign: "center" } }, "Refer friends to move up:"),
405
+ (0, import_vue2.h)("input", {
406
+ class: "qz-form-input",
407
+ style: { textAlign: "center", cursor: "pointer" },
408
+ readonly: true,
409
+ value: window.location.origin + window.location.pathname + "?ref=" + successData.value.referralCode,
410
+ onClick: (e) => e.target.select()
411
+ })
412
+ ])
413
+ ]) : formContent
414
+ ])
415
+ ])
416
+ ]
417
+ )
418
+ ]);
327
419
  };
328
420
  }
329
421
  });
package/dist/index.mjs CHANGED
@@ -40,7 +40,13 @@ function useWaitlist(campaign, config) {
40
40
  join: stateManager.join.bind(stateManager),
41
41
  refresh: stateManager.refresh.bind(stateManager),
42
42
  fetchPublicConfig: stateManager.fetchPublicConfig.bind(stateManager),
43
- getReferralLink: stateManager.getReferralLink.bind(stateManager),
43
+ getReferralLink: () => {
44
+ const code = stateManager.getReferralCode();
45
+ if (code && typeof window !== "undefined") {
46
+ return `${window.location.origin}${window.location.pathname}?ref=${code}`;
47
+ }
48
+ return stateManager.getReferralLink();
49
+ },
44
50
  getReferralCode: stateManager.getReferralCode.bind(stateManager),
45
51
  reset: stateManager.reset.bind(stateManager)
46
52
  };
@@ -99,6 +105,18 @@ var WaitlistForm = defineComponent({
99
105
  labelText: {
100
106
  type: String,
101
107
  default: "Email"
108
+ },
109
+ displayMode: {
110
+ type: String,
111
+ default: "inline"
112
+ },
113
+ triggerText: {
114
+ type: String,
115
+ default: "Join Waitlist"
116
+ },
117
+ modalSize: {
118
+ type: String,
119
+ default: "md"
102
120
  }
103
121
  },
104
122
  emits: {
@@ -109,8 +127,13 @@ var WaitlistForm = defineComponent({
109
127
  const { join, loading, error: contextError, isJoined, publicConfig } = useWaitlistContext();
110
128
  const email = ref2("");
111
129
  const localError = ref2(null);
130
+ const isModalOpen = ref2(false);
131
+ const successData = ref2(null);
112
132
  const formData = reactive({});
113
133
  const displayError = computed2(() => localError.value || contextError.value);
134
+ const branding = computed2(() => publicConfig.value?.branding || {});
135
+ const themeColor = computed2(() => branding.value.themeColor || "#10B981");
136
+ const sizeClasses = { sm: "qz-modal-sm", md: "qz-modal-md", lg: "qz-modal-lg" };
114
137
  const formFields = computed2(() => {
115
138
  return publicConfig.value?.formFields?.filter((f) => f.enabled) ?? [];
116
139
  });
@@ -125,6 +148,7 @@ var WaitlistForm = defineComponent({
125
148
  if (result) {
126
149
  email.value = "";
127
150
  Object.keys(formData).forEach((key) => delete formData[key]);
151
+ successData.value = result;
128
152
  emit("success", result);
129
153
  } else if (contextError.value) {
130
154
  emit("error", contextError.value);
@@ -158,7 +182,7 @@ var WaitlistForm = defineComponent({
158
182
  formData[field.key] = e.target.value;
159
183
  }
160
184
  }, [
161
- h("option", { value: "" }, field.placeholder || `Select ${field.label}`),
185
+ h("option", { value: "" }, "Select an option"),
162
186
  ...(field.options || []).map(
163
187
  (opt) => h("option", { value: opt }, opt)
164
188
  )
@@ -262,7 +286,7 @@ var WaitlistForm = defineComponent({
262
286
  formFields.value.forEach((field) => {
263
287
  fieldNodes.push(renderField(field));
264
288
  });
265
- return h(
289
+ const formContent = h(
266
290
  "form",
267
291
  {
268
292
  class: "qz-form",
@@ -288,6 +312,74 @@ var WaitlistForm = defineComponent({
288
312
  )
289
313
  ]
290
314
  );
315
+ if (props.displayMode === "inline") {
316
+ return formContent;
317
+ }
318
+ return h("div", { class: "qz-waitlist-modal-wrapper" }, [
319
+ // Trigger button
320
+ h(
321
+ "button",
322
+ {
323
+ type: "button",
324
+ class: "qz-modal-trigger",
325
+ style: { "--qz-theme-color": themeColor.value },
326
+ onClick: () => {
327
+ isModalOpen.value = true;
328
+ }
329
+ },
330
+ props.triggerText
331
+ ),
332
+ // Modal overlay (only when open)
333
+ isModalOpen.value && h(
334
+ "div",
335
+ {
336
+ class: "qz-modal-overlay",
337
+ onClick: (e) => {
338
+ if (e.target === e.currentTarget) isModalOpen.value = false;
339
+ }
340
+ },
341
+ [
342
+ h("div", { class: `qz-modal ${sizeClasses[props.modalSize]}`, style: { "--qz-theme-color": themeColor.value } }, [
343
+ // Header
344
+ h("div", { class: "qz-modal-header" }, [
345
+ branding.value.logoUrl ? h("img", { src: branding.value.logoUrl, alt: "Logo", class: "qz-modal-logo" }) : h("span"),
346
+ h("button", {
347
+ type: "button",
348
+ class: "qz-modal-close",
349
+ "aria-label": "Close",
350
+ onClick: () => {
351
+ isModalOpen.value = false;
352
+ }
353
+ }, [
354
+ h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, [
355
+ h("path", { d: "M18 6L6 18M6 6l12 12" })
356
+ ])
357
+ ])
358
+ ]),
359
+ // Cover image
360
+ branding.value.coverImageUrl && h("img", { src: branding.value.coverImageUrl, alt: "", class: "qz-modal-cover" }),
361
+ // Body
362
+ h("div", { class: "qz-modal-body" }, [
363
+ h("h2", { class: "qz-modal-title" }, publicConfig.value?.name || "Join Waitlist"),
364
+ successData.value ? h("div", { class: "qz-success" }, [
365
+ h("h3", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "0.5rem", textAlign: "center" } }, "You're on the list!"),
366
+ h("p", { style: { textAlign: "center", marginBottom: "1.5rem", opacity: "0.9" } }, `You're #${successData.value.position || "?"} in line.`),
367
+ successData.value.referralLink && h("div", { style: { marginTop: "1.5rem" } }, [
368
+ h("p", { style: { marginBottom: "0.5rem", fontSize: "0.875rem", opacity: "0.9", textAlign: "center" } }, "Refer friends to move up:"),
369
+ h("input", {
370
+ class: "qz-form-input",
371
+ style: { textAlign: "center", cursor: "pointer" },
372
+ readonly: true,
373
+ value: window.location.origin + window.location.pathname + "?ref=" + successData.value.referralCode,
374
+ onClick: (e) => e.target.select()
375
+ })
376
+ ])
377
+ ]) : formContent
378
+ ])
379
+ ])
380
+ ]
381
+ )
382
+ ]);
291
383
  };
292
384
  }
293
385
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@queuezero/vue",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Vue components and composables for QueueZero viral waitlists",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -34,7 +34,7 @@
34
34
  "vue": ">=3.3.0"
35
35
  },
36
36
  "dependencies": {
37
- "queuezero": "^0.1.5"
37
+ "queuezero": "^0.1.6"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.13.11",