@crediball/elements 0.8.0 → 0.9.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()}
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,30 @@ var CrediballPaywallElement = class extends CrediballElement {
532
568
  }
533
569
  });
534
570
  }
571
+ /** HTML for the "Have a promo code?" toggle-open field. Gated on the
572
+ * `allow-promo-code` attribute (off by default). Not validated client-side —
573
+ * an invalid code simply doesn't grant a bonus when the top-up is processed. */
574
+ renderPromoCode() {
575
+ if (!this.hasAttribute("allow-promo-code")) return "";
576
+ const label = this.getAttribute("promo-code-label") ?? "Have a promo code?";
577
+ if (!this.promoCodeOpen) {
578
+ return `<div class="promo-code">
579
+ <button class="promo-code-toggle" type="button" data-open-promo-code>${escapeHtml(label)}</button>
580
+ </div>`;
581
+ }
582
+ return `<div class="promo-code">
583
+ <div class="promo-code-label">${escapeHtml(label)}</div>
584
+ <input class="promo-code-input" placeholder="PROMOCODE" aria-label="Promo code" />
585
+ </div>`;
586
+ }
587
+ /** The current promo code input value (uppercased, trimmed), or undefined
588
+ * when the field isn't shown/filled in. Read at click time — never re-render
589
+ * on keystroke, same reasoning as the custom-amount field. */
590
+ currentPromoCode() {
591
+ const input = this.shadow.querySelector(".promo-code-input");
592
+ const value = input?.value.trim().toUpperCase();
593
+ return value || void 0;
594
+ }
535
595
  /** HTML for the auto-topup opt-in row / status line / needs_auth-fix prompt. */
536
596
  renderAutoTopup(packages, autoTopup, currency) {
537
597
  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()}
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,30 @@ var Crediball = (() => {
946
982
  }
947
983
  });
948
984
  }
985
+ /** HTML for the "Have a promo code?" toggle-open field. Gated on the
986
+ * `allow-promo-code` attribute (off by default). Not validated client-side —
987
+ * an invalid code simply doesn't grant a bonus when the top-up is processed. */
988
+ renderPromoCode() {
989
+ if (!this.hasAttribute("allow-promo-code")) return "";
990
+ const label = this.getAttribute("promo-code-label") ?? "Have a promo code?";
991
+ if (!this.promoCodeOpen) {
992
+ return `<div class="promo-code">
993
+ <button class="promo-code-toggle" type="button" data-open-promo-code>${escapeHtml(label)}</button>
994
+ </div>`;
995
+ }
996
+ return `<div class="promo-code">
997
+ <div class="promo-code-label">${escapeHtml(label)}</div>
998
+ <input class="promo-code-input" placeholder="PROMOCODE" aria-label="Promo code" />
999
+ </div>`;
1000
+ }
1001
+ /** The current promo code input value (uppercased, trimmed), or undefined
1002
+ * when the field isn't shown/filled in. Read at click time — never re-render
1003
+ * on keystroke, same reasoning as the custom-amount field. */
1004
+ currentPromoCode() {
1005
+ const input = this.shadow.querySelector(".promo-code-input");
1006
+ const value = input?.value.trim().toUpperCase();
1007
+ return value || void 0;
1008
+ }
949
1009
  /** HTML for the auto-topup opt-in row / status line / needs_auth-fix prompt. */
950
1010
  renderAutoTopup(packages, autoTopup, currency) {
951
1011
  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,14 @@ 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. Gated on the
127
+ * `allow-promo-code` attribute (off by default). Not validated client-side —
128
+ * an invalid code simply doesn't grant a bonus when the top-up is processed. */
129
+ private renderPromoCode;
130
+ /** The current promo code input value (uppercased, trimmed), or undefined
131
+ * when the field isn't shown/filled in. Read at click time — never re-render
132
+ * on keystroke, same reasoning as the custom-amount field. */
133
+ private currentPromoCode;
125
134
  /** HTML for the auto-topup opt-in row / status line / needs_auth-fix prompt. */
126
135
  private renderAutoTopup;
127
136
  /** 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-MLNG4RJB.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-MLNG4RJB.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.9.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.8.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "tsup": "^8.3.5",