@crediball/elements 0.8.0 → 0.10.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.
@@ -285,6 +285,25 @@ var STYLE = `
285
285
  padding: 11px 22px;
286
286
  border-radius: var(--crediball-radius, 9999px);
287
287
  }
288
+ .promo-code { margin-top: 16px; }
289
+ .promo-code-toggle {
290
+ appearance: none; border: none; cursor: pointer; background: transparent; padding: 0;
291
+ font: inherit; font-size: 12px; color: var(--crediball-ink-muted, #7a7a7a); text-decoration: underline;
292
+ }
293
+ .promo-code-label { margin-bottom: 6px; font-size: 12px; font-weight: 600; color: var(--crediball-ink-muted, #7a7a7a); }
294
+ .promo-code-input {
295
+ width: 100%;
296
+ box-sizing: border-box;
297
+ appearance: none;
298
+ font: inherit;
299
+ font-size: 14px;
300
+ padding: 8px 12px;
301
+ border-radius: var(--crediball-radius, 9999px);
302
+ border: 1px solid var(--crediball-hairline, #e0e0e0);
303
+ background: var(--crediball-canvas, #ffffff);
304
+ color: var(--crediball-ink, #1d1d1f);
305
+ outline: none;
306
+ }
288
307
  .autotopup { margin-top: 16px; }
289
308
  .autotopup-toggle {
290
309
  appearance: none; border: none; cursor: pointer; background: transparent; padding: 0;
@@ -328,10 +347,19 @@ var CrediballPaywallElement = class extends CrediballElement {
328
347
  this.referral = null;
329
348
  this.referralCopied = false;
330
349
  this.autoTopupOpen = false;
350
+ this.promoCodeOpen = false;
331
351
  this.shadow = this.attachShadow({ mode: "open" });
332
352
  }
333
353
  static get observedAttributes() {
334
- return [...CrediballElement.observedAttributes, "open", "title", "description", "amounts"];
354
+ return [
355
+ ...CrediballElement.observedAttributes,
356
+ "open",
357
+ "title",
358
+ "description",
359
+ "amounts",
360
+ "allow-promo-code",
361
+ "promo-code-label"
362
+ ];
335
363
  }
336
364
  connectedCallback() {
337
365
  super.connectedCallback();
@@ -405,6 +433,7 @@ var CrediballPaywallElement = class extends CrediballElement {
405
433
  </button>`
406
434
  ).join("")}
407
435
  </div>` : ""}
436
+ ${this.renderPromoCode(Boolean(pkgs?.hasActiveCheckoutPromotion))}
408
437
  <div class="list">
409
438
  ${packages.length ? packages.map(
410
439
  (p) => `
@@ -454,8 +483,9 @@ var CrediballPaywallElement = class extends CrediballElement {
454
483
  const kind = btn.dataset.kind;
455
484
  if (kind === "package") {
456
485
  const pkg = packages.find((p) => p.id === btn.dataset.id);
457
- if (pkg && this.emit("crediball-select-package", pkg)) {
458
- void this.startCheckout({ packageId: pkg.id });
486
+ const code = this.currentPromoCode();
487
+ if (pkg && this.emit("crediball-select-package", { ...pkg, code })) {
488
+ void this.startCheckout({ packageId: pkg.id, code });
459
489
  }
460
490
  } else if (kind === "plan") {
461
491
  const plan = plans.find((p) => p.id === btn.dataset.id);
@@ -464,12 +494,17 @@ var CrediballPaywallElement = class extends CrediballElement {
464
494
  }
465
495
  } else if (kind === "amount") {
466
496
  const amount = Number(btn.dataset.amount);
467
- if (this.emit("crediball-topup", { amount })) {
468
- void this.startCheckout({ eurAmount: amount });
497
+ const code = this.currentPromoCode();
498
+ if (this.emit("crediball-topup", { amount, code })) {
499
+ void this.startCheckout({ eurAmount: amount, code });
469
500
  }
470
501
  }
471
502
  });
472
503
  });
504
+ this.shadow.querySelector("[data-open-promo-code]")?.addEventListener("click", () => {
505
+ this.promoCodeOpen = true;
506
+ this.render();
507
+ });
473
508
  if (showCustom) {
474
509
  const input = this.shadow.querySelector(".custom-input");
475
510
  const addBtn = this.shadow.querySelector(".custom-add");
@@ -499,8 +534,9 @@ var CrediballPaywallElement = class extends CrediballElement {
499
534
  return showError(errEl, `Maximum is ${sym}${custom.maxEur}.`);
500
535
  }
501
536
  showError(errEl, null);
502
- if (this.emit("crediball-topup", { amount })) {
503
- void this.startCheckout({ eurAmount: amount });
537
+ const code = this.currentPromoCode();
538
+ if (this.emit("crediball-topup", { amount, code })) {
539
+ void this.startCheckout({ eurAmount: amount, code });
504
540
  }
505
541
  };
506
542
  addBtn?.addEventListener("click", submit);
@@ -532,6 +568,33 @@ var CrediballPaywallElement = class extends CrediballElement {
532
568
  }
533
569
  });
534
570
  }
571
+ /** HTML for the "Have a promo code?" toggle-open field. Shown automatically
572
+ * once the app has an active Checkout promotion (same as the custom-amount
573
+ * field auto-following the dashboard's custom-topup config) — the
574
+ * `allow-promo-code` attribute is only needed to force it on before you've
575
+ * set one up. Not validated client-side — an invalid code simply doesn't
576
+ * grant a bonus when the top-up is processed. */
577
+ renderPromoCode(hasActiveCheckoutPromotion) {
578
+ if (!this.hasAttribute("allow-promo-code") && !hasActiveCheckoutPromotion) return "";
579
+ const label = this.getAttribute("promo-code-label") ?? "Have a promo code?";
580
+ if (!this.promoCodeOpen) {
581
+ return `<div class="promo-code">
582
+ <button class="promo-code-toggle" type="button" data-open-promo-code>${escapeHtml(label)}</button>
583
+ </div>`;
584
+ }
585
+ return `<div class="promo-code">
586
+ <div class="promo-code-label">${escapeHtml(label)}</div>
587
+ <input class="promo-code-input" placeholder="PROMOCODE" aria-label="Promo code" />
588
+ </div>`;
589
+ }
590
+ /** The current promo code input value (uppercased, trimmed), or undefined
591
+ * when the field isn't shown/filled in. Read at click time — never re-render
592
+ * on keystroke, same reasoning as the custom-amount field. */
593
+ currentPromoCode() {
594
+ const input = this.shadow.querySelector(".promo-code-input");
595
+ const value = input?.value.trim().toUpperCase();
596
+ return value || void 0;
597
+ }
535
598
  /** HTML for the auto-topup opt-in row / status line / needs_auth-fix prompt. */
536
599
  renderAutoTopup(packages, autoTopup, currency) {
537
600
  if (!packages.length) return "";
@@ -699,6 +699,25 @@ var Crediball = (() => {
699
699
  padding: 11px 22px;
700
700
  border-radius: var(--crediball-radius, 9999px);
701
701
  }
702
+ .promo-code { margin-top: 16px; }
703
+ .promo-code-toggle {
704
+ appearance: none; border: none; cursor: pointer; background: transparent; padding: 0;
705
+ font: inherit; font-size: 12px; color: var(--crediball-ink-muted, #7a7a7a); text-decoration: underline;
706
+ }
707
+ .promo-code-label { margin-bottom: 6px; font-size: 12px; font-weight: 600; color: var(--crediball-ink-muted, #7a7a7a); }
708
+ .promo-code-input {
709
+ width: 100%;
710
+ box-sizing: border-box;
711
+ appearance: none;
712
+ font: inherit;
713
+ font-size: 14px;
714
+ padding: 8px 12px;
715
+ border-radius: var(--crediball-radius, 9999px);
716
+ border: 1px solid var(--crediball-hairline, #e0e0e0);
717
+ background: var(--crediball-canvas, #ffffff);
718
+ color: var(--crediball-ink, #1d1d1f);
719
+ outline: none;
720
+ }
702
721
  .autotopup { margin-top: 16px; }
703
722
  .autotopup-toggle {
704
723
  appearance: none; border: none; cursor: pointer; background: transparent; padding: 0;
@@ -742,10 +761,19 @@ var Crediball = (() => {
742
761
  this.referral = null;
743
762
  this.referralCopied = false;
744
763
  this.autoTopupOpen = false;
764
+ this.promoCodeOpen = false;
745
765
  this.shadow = this.attachShadow({ mode: "open" });
746
766
  }
747
767
  static get observedAttributes() {
748
- return [...CrediballElement.observedAttributes, "open", "title", "description", "amounts"];
768
+ return [
769
+ ...CrediballElement.observedAttributes,
770
+ "open",
771
+ "title",
772
+ "description",
773
+ "amounts",
774
+ "allow-promo-code",
775
+ "promo-code-label"
776
+ ];
749
777
  }
750
778
  connectedCallback() {
751
779
  super.connectedCallback();
@@ -819,6 +847,7 @@ var Crediball = (() => {
819
847
  </button>`
820
848
  ).join("")}
821
849
  </div>` : ""}
850
+ ${this.renderPromoCode(Boolean(pkgs?.hasActiveCheckoutPromotion))}
822
851
  <div class="list">
823
852
  ${packages.length ? packages.map(
824
853
  (p) => `
@@ -868,8 +897,9 @@ var Crediball = (() => {
868
897
  const kind = btn.dataset.kind;
869
898
  if (kind === "package") {
870
899
  const pkg = packages.find((p) => p.id === btn.dataset.id);
871
- if (pkg && this.emit("crediball-select-package", pkg)) {
872
- void this.startCheckout({ packageId: pkg.id });
900
+ const code = this.currentPromoCode();
901
+ if (pkg && this.emit("crediball-select-package", { ...pkg, code })) {
902
+ void this.startCheckout({ packageId: pkg.id, code });
873
903
  }
874
904
  } else if (kind === "plan") {
875
905
  const plan = plans.find((p) => p.id === btn.dataset.id);
@@ -878,12 +908,17 @@ var Crediball = (() => {
878
908
  }
879
909
  } else if (kind === "amount") {
880
910
  const amount = Number(btn.dataset.amount);
881
- if (this.emit("crediball-topup", { amount })) {
882
- void this.startCheckout({ eurAmount: amount });
911
+ const code = this.currentPromoCode();
912
+ if (this.emit("crediball-topup", { amount, code })) {
913
+ void this.startCheckout({ eurAmount: amount, code });
883
914
  }
884
915
  }
885
916
  });
886
917
  });
918
+ this.shadow.querySelector("[data-open-promo-code]")?.addEventListener("click", () => {
919
+ this.promoCodeOpen = true;
920
+ this.render();
921
+ });
887
922
  if (showCustom) {
888
923
  const input = this.shadow.querySelector(".custom-input");
889
924
  const addBtn = this.shadow.querySelector(".custom-add");
@@ -913,8 +948,9 @@ var Crediball = (() => {
913
948
  return showError(errEl, `Maximum is ${sym}${custom.maxEur}.`);
914
949
  }
915
950
  showError(errEl, null);
916
- if (this.emit("crediball-topup", { amount })) {
917
- void this.startCheckout({ eurAmount: amount });
951
+ const code = this.currentPromoCode();
952
+ if (this.emit("crediball-topup", { amount, code })) {
953
+ void this.startCheckout({ eurAmount: amount, code });
918
954
  }
919
955
  };
920
956
  addBtn?.addEventListener("click", submit);
@@ -946,6 +982,33 @@ var Crediball = (() => {
946
982
  }
947
983
  });
948
984
  }
985
+ /** HTML for the "Have a promo code?" toggle-open field. Shown automatically
986
+ * once the app has an active Checkout promotion (same as the custom-amount
987
+ * field auto-following the dashboard's custom-topup config) — the
988
+ * `allow-promo-code` attribute is only needed to force it on before you've
989
+ * set one up. Not validated client-side — an invalid code simply doesn't
990
+ * grant a bonus when the top-up is processed. */
991
+ renderPromoCode(hasActiveCheckoutPromotion) {
992
+ if (!this.hasAttribute("allow-promo-code") && !hasActiveCheckoutPromotion) return "";
993
+ const label = this.getAttribute("promo-code-label") ?? "Have a promo code?";
994
+ if (!this.promoCodeOpen) {
995
+ return `<div class="promo-code">
996
+ <button class="promo-code-toggle" type="button" data-open-promo-code>${escapeHtml(label)}</button>
997
+ </div>`;
998
+ }
999
+ return `<div class="promo-code">
1000
+ <div class="promo-code-label">${escapeHtml(label)}</div>
1001
+ <input class="promo-code-input" placeholder="PROMOCODE" aria-label="Promo code" />
1002
+ </div>`;
1003
+ }
1004
+ /** The current promo code input value (uppercased, trimmed), or undefined
1005
+ * when the field isn't shown/filled in. Read at click time — never re-render
1006
+ * on keystroke, same reasoning as the custom-amount field. */
1007
+ currentPromoCode() {
1008
+ const input = this.shadow.querySelector(".promo-code-input");
1009
+ const value = input?.value.trim().toUpperCase();
1010
+ return value || void 0;
1011
+ }
949
1012
  /** HTML for the auto-topup opt-in row / status line / needs_auth-fix prompt. */
950
1013
  renderAutoTopup(packages, autoTopup, currency) {
951
1014
  if (!packages.length) return "";
package/dist/index.d.ts CHANGED
@@ -113,6 +113,7 @@ declare class CrediballPaywallElement extends CrediballElement {
113
113
  private referral;
114
114
  private referralCopied;
115
115
  private autoTopupOpen;
116
+ private promoCodeOpen;
116
117
  static get observedAttributes(): string[];
117
118
  constructor();
118
119
  connectedCallback(): void;
@@ -122,6 +123,17 @@ declare class CrediballPaywallElement extends CrediballElement {
122
123
  open(): void;
123
124
  close(): void;
124
125
  protected render(): void;
126
+ /** HTML for the "Have a promo code?" toggle-open field. Shown automatically
127
+ * once the app has an active Checkout promotion (same as the custom-amount
128
+ * field auto-following the dashboard's custom-topup config) — the
129
+ * `allow-promo-code` attribute is only needed to force it on before you've
130
+ * set one up. Not validated client-side — an invalid code simply doesn't
131
+ * grant a bonus when the top-up is processed. */
132
+ private renderPromoCode;
133
+ /** The current promo code input value (uppercased, trimmed), or undefined
134
+ * when the field isn't shown/filled in. Read at click time — never re-render
135
+ * on keystroke, same reasoning as the custom-amount field. */
136
+ private currentPromoCode;
125
137
  /** HTML for the auto-topup opt-in row / status line / needs_auth-fix prompt. */
126
138
  private renderAutoTopup;
127
139
  /** Copy the invite link to the clipboard, with a 2s "Copied!" confirmation. */
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  CrediballReferralCardElement,
7
7
  CrediballTopUpButtonElement,
8
8
  CrediballUsageElement
9
- } from "./chunk-YCLIZG7D.js";
9
+ } from "./chunk-YP5JNDTD.js";
10
10
  export {
11
11
  CrediballBalanceElement,
12
12
  CrediballElement,
package/dist/register.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  CrediballReferralCardElement,
6
6
  CrediballTopUpButtonElement,
7
7
  CrediballUsageElement
8
- } from "./chunk-YCLIZG7D.js";
8
+ } from "./chunk-YP5JNDTD.js";
9
9
 
10
10
  // src/register.ts
11
11
  function define(name, ctor) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crediball/elements",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Framework-agnostic web components for showing Crediball credits inside your AI app.",
5
5
  "license": "MIT",
6
6
  "author": "Filippo Rezzadore <filipporezzadore@gmail.com>",
@@ -56,7 +56,7 @@
56
56
  "prepublishOnly": "npm run build"
57
57
  },
58
58
  "dependencies": {
59
- "@crediball/core": "^0.7.0"
59
+ "@crediball/core": "^0.9.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "tsup": "^8.3.5",