@crediball/elements 0.6.0 → 0.7.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.
@@ -283,8 +283,29 @@ var STYLE = `
283
283
  padding: 11px 22px;
284
284
  border-radius: var(--crediball-radius, 9999px);
285
285
  }
286
- .referral { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--crediball-hairline, #e0e0e0); }
287
- .referral .eyebrow { margin-bottom: 8px; }
286
+ .autotopup { margin-top: 16px; }
287
+ .autotopup-toggle {
288
+ appearance: none; border: none; cursor: pointer; background: transparent; padding: 0;
289
+ font: inherit; font-size: 12px; color: var(--crediball-ink-muted, #7a7a7a); text-decoration: underline;
290
+ }
291
+ .autotopup-status { margin: 0; font-size: 12px; color: var(--crediball-ink-muted, #7a7a7a); }
292
+ .autotopup-warning { margin: 0; font-size: 12px; color: var(--crediball-accent, #0066cc); }
293
+ .autotopup-row { display: flex; gap: 8px; align-items: center; }
294
+ .autotopup-row select, .autotopup-row input {
295
+ font: inherit; font-size: 13px; padding: 6px 10px;
296
+ border-radius: var(--crediball-radius, 9999px); border: 1px solid var(--crediball-hairline, #e0e0e0);
297
+ background: var(--crediball-canvas, #ffffff); color: var(--crediball-ink, #1d1d1f);
298
+ }
299
+ .autotopup-row select { flex: 1; min-width: 0; }
300
+ .autotopup-row input { width: 72px; }
301
+ button.autotopup-submit {
302
+ appearance: none; border: none; cursor: pointer; flex: none;
303
+ background: var(--crediball-accent, #0066cc); color: var(--crediball-on-accent, #ffffff);
304
+ font: inherit; font-size: 13px; padding: 6px 14px; border-radius: var(--crediball-radius, 9999px);
305
+ }
306
+ .autotopup-hint { margin: 6px 0 0; font-size: 11px; color: var(--crediball-ink-muted, #7a7a7a); }
307
+ .referral { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--crediball-hairline, #e0e0e0); }
308
+ .referral-label { margin-bottom: 8px; font-size: 12px; font-weight: 600; color: var(--crediball-ink-muted, #7a7a7a); }
288
309
  .referral-row { display: flex; gap: 8px; align-items: center; }
289
310
  .referral-row .link {
290
311
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
@@ -304,6 +325,7 @@ var CrediballPaywallElement = class extends CrediballElement {
304
325
  super();
305
326
  this.referral = null;
306
327
  this.referralCopied = false;
328
+ this.autoTopupOpen = false;
307
329
  this.shadow = this.attachShadow({ mode: "open" });
308
330
  }
309
331
  static get observedAttributes() {
@@ -400,15 +422,21 @@ var CrediballPaywallElement = class extends CrediballElement {
400
422
  <input class="custom-input" inputmode="decimal" placeholder="${custom?.minEur ?? ""}" aria-label="Custom amount" />
401
423
  <button class="custom-add" type="button">${escapeHtml(addButtonText)}</button>
402
424
  </div>` : ""}
425
+ ${this.renderAutoTopup(packages, pkgs?.autoTopup, currency)}
403
426
  <p class="error" hidden></p>
427
+ <button class="close" part="close" type="button">Not now</button>
404
428
  ${this.referral?.link ? `<div class="referral">
405
- <div class="eyebrow">${this.referral.rewardCredits && this.referral.rewardCredits > 0 ? `Refer a friend \xB7 earn ${formatCredits(this.referral.rewardCredits)} credits` : "Refer a friend"}</div>
429
+ <div class="referral-label">${escapeHtml(
430
+ (this.getAttribute("referral-label") ?? "Refer a friend \xB7 earn {credits} credits").replace(
431
+ "{credits}",
432
+ formatCredits(this.referral.rewardCredits ?? 0)
433
+ )
434
+ )}</div>
406
435
  <div class="referral-row">
407
436
  <div class="link" title="${escapeHtml(this.referral.link)}">${escapeHtml(this.referral.link)}</div>
408
437
  <button class="referral-copy" type="button">${this.referralCopied ? "Copied!" : "Copy"}</button>
409
438
  </div>
410
439
  </div>` : ""}
411
- <button class="close" part="close" type="button">Not now</button>
412
440
  </div>
413
441
  </div>
414
442
  `;
@@ -465,6 +493,66 @@ var CrediballPaywallElement = class extends CrediballElement {
465
493
  if (e.key === "Enter") submit();
466
494
  });
467
495
  }
496
+ this.shadow.querySelector("[data-open-autotopup]")?.addEventListener("click", () => {
497
+ this.autoTopupOpen = true;
498
+ this.render();
499
+ });
500
+ this.shadow.querySelector(".autotopup-submit")?.addEventListener("click", () => {
501
+ const packageId = this.shadow.querySelector(".autotopup-package")?.value;
502
+ const thresholdCredits = Number(
503
+ this.shadow.querySelector(".autotopup-threshold")?.value
504
+ );
505
+ if (!packageId || !Number.isFinite(thresholdCredits) || thresholdCredits < 0) return;
506
+ if (this.emit("crediball-enable-autotopup", { packageId, thresholdCredits })) {
507
+ void this.startAutoTopupCheckout(packageId, thresholdCredits);
508
+ }
509
+ });
510
+ const fixBtn = this.shadow.querySelector("[data-fix-package]");
511
+ fixBtn?.addEventListener("click", () => {
512
+ const packageId = fixBtn.dataset.fixPackage;
513
+ const thresholdCredits = Number(fixBtn.dataset.fixThreshold);
514
+ if (!packageId || !Number.isFinite(thresholdCredits)) return;
515
+ if (this.emit("crediball-enable-autotopup", { packageId, thresholdCredits })) {
516
+ void this.startAutoTopupCheckout(packageId, thresholdCredits);
517
+ }
518
+ });
519
+ }
520
+ /** HTML for the auto-topup opt-in row / status line / needs_auth-fix prompt. */
521
+ renderAutoTopup(packages, autoTopup, currency) {
522
+ if (!packages.length) return "";
523
+ if (autoTopup?.enabled && autoTopup.status === "active") {
524
+ const pkg = packages.find((p) => p.id === autoTopup.packageId);
525
+ return `<div class="autotopup"><p class="autotopup-status">Auto top-up is on \u2014 buying ${escapeHtml(
526
+ pkg?.name ?? "your package"
527
+ )} whenever your balance drops below ${formatCredits(autoTopup.thresholdCredits)} credits.</p></div>`;
528
+ }
529
+ if (autoTopup?.enabled && (autoTopup.status === "needs_auth" || autoTopup.status === "failed")) {
530
+ const message = autoTopup.status === "needs_auth" ? "Auto top-up needs you to re-confirm your payment method." : "Auto top-up's last charge failed.";
531
+ return `<div class="autotopup">
532
+ <p class="autotopup-warning">${escapeHtml(message)}</p>
533
+ <button class="autotopup-toggle" type="button" data-fix-package="${escapeHtml(
534
+ autoTopup.packageId
535
+ )}" data-fix-threshold="${autoTopup.thresholdCredits}">Fix auto top-up</button>
536
+ </div>`;
537
+ }
538
+ if (this.autoTopupOpen) {
539
+ const cheapest = packages.reduce((min, p) => p.price < min.price ? p : min, packages[0]);
540
+ return `<div class="autotopup">
541
+ <div class="autotopup-row">
542
+ <select class="autotopup-package" aria-label="Package">
543
+ ${packages.map(
544
+ (p) => `<option value="${escapeHtml(p.id)}">${escapeHtml(p.name)} \xB7 ${formatMoney(p.price, currency)}</option>`
545
+ ).join("")}
546
+ </select>
547
+ <input class="autotopup-threshold" inputmode="numeric" aria-label="Threshold credits" value="${cheapest.credits}" />
548
+ <button class="autotopup-submit" type="button">Turn on</button>
549
+ </div>
550
+ <p class="autotopup-hint">Buy that package automatically whenever your balance drops below the credit amount above.</p>
551
+ </div>`;
552
+ }
553
+ return `<div class="autotopup">
554
+ <button class="autotopup-toggle" type="button" data-open-autotopup>Keep me topped up automatically</button>
555
+ </div>`;
468
556
  }
469
557
  /** Copy the invite link to the clipboard, with a 2s "Copied!" confirmation. */
470
558
  async copyReferral() {
@@ -499,6 +587,17 @@ var CrediballPaywallElement = class extends CrediballElement {
499
587
  this.client.createSubscriptionCheckout({ planId, returnPath: this.returnPath() })
500
588
  );
501
589
  }
590
+ /**
591
+ * Start the built-in auto-topup setup Checkout and redirect. No money changes
592
+ * hands here — it only saves a payment method Crediball charges later,
593
+ * off-session, whenever the balance drops below `thresholdCredits`.
594
+ */
595
+ startAutoTopupCheckout(packageId, thresholdCredits) {
596
+ if (!this.client) return Promise.resolve();
597
+ return this.redirect(
598
+ this.client.createAutoTopupCheckout({ packageId, thresholdCredits, returnPath: this.returnPath() })
599
+ );
600
+ }
502
601
  async redirect(session) {
503
602
  try {
504
603
  const { url } = await session;
@@ -133,6 +133,9 @@ var Crediball = (() => {
133
133
  this.createSubscriptionCheckout = (input) => {
134
134
  return this.postCheckout("/public/subscriptions/checkout", input);
135
135
  };
136
+ this.createAutoTopupCheckout = (input) => {
137
+ return this.postCheckout("/public/autotopup/setup", input);
138
+ };
136
139
  this.refresh = async () => {
137
140
  const { publishableKey, userId } = this.config;
138
141
  const headers = { Authorization: `Bearer ${publishableKey}` };
@@ -694,8 +697,29 @@ var Crediball = (() => {
694
697
  padding: 11px 22px;
695
698
  border-radius: var(--crediball-radius, 9999px);
696
699
  }
697
- .referral { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--crediball-hairline, #e0e0e0); }
698
- .referral .eyebrow { margin-bottom: 8px; }
700
+ .autotopup { margin-top: 16px; }
701
+ .autotopup-toggle {
702
+ appearance: none; border: none; cursor: pointer; background: transparent; padding: 0;
703
+ font: inherit; font-size: 12px; color: var(--crediball-ink-muted, #7a7a7a); text-decoration: underline;
704
+ }
705
+ .autotopup-status { margin: 0; font-size: 12px; color: var(--crediball-ink-muted, #7a7a7a); }
706
+ .autotopup-warning { margin: 0; font-size: 12px; color: var(--crediball-accent, #0066cc); }
707
+ .autotopup-row { display: flex; gap: 8px; align-items: center; }
708
+ .autotopup-row select, .autotopup-row input {
709
+ font: inherit; font-size: 13px; padding: 6px 10px;
710
+ border-radius: var(--crediball-radius, 9999px); border: 1px solid var(--crediball-hairline, #e0e0e0);
711
+ background: var(--crediball-canvas, #ffffff); color: var(--crediball-ink, #1d1d1f);
712
+ }
713
+ .autotopup-row select { flex: 1; min-width: 0; }
714
+ .autotopup-row input { width: 72px; }
715
+ button.autotopup-submit {
716
+ appearance: none; border: none; cursor: pointer; flex: none;
717
+ background: var(--crediball-accent, #0066cc); color: var(--crediball-on-accent, #ffffff);
718
+ font: inherit; font-size: 13px; padding: 6px 14px; border-radius: var(--crediball-radius, 9999px);
719
+ }
720
+ .autotopup-hint { margin: 6px 0 0; font-size: 11px; color: var(--crediball-ink-muted, #7a7a7a); }
721
+ .referral { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--crediball-hairline, #e0e0e0); }
722
+ .referral-label { margin-bottom: 8px; font-size: 12px; font-weight: 600; color: var(--crediball-ink-muted, #7a7a7a); }
699
723
  .referral-row { display: flex; gap: 8px; align-items: center; }
700
724
  .referral-row .link {
701
725
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
@@ -715,6 +739,7 @@ var Crediball = (() => {
715
739
  super();
716
740
  this.referral = null;
717
741
  this.referralCopied = false;
742
+ this.autoTopupOpen = false;
718
743
  this.shadow = this.attachShadow({ mode: "open" });
719
744
  }
720
745
  static get observedAttributes() {
@@ -811,15 +836,21 @@ var Crediball = (() => {
811
836
  <input class="custom-input" inputmode="decimal" placeholder="${custom?.minEur ?? ""}" aria-label="Custom amount" />
812
837
  <button class="custom-add" type="button">${escapeHtml(addButtonText)}</button>
813
838
  </div>` : ""}
839
+ ${this.renderAutoTopup(packages, pkgs?.autoTopup, currency)}
814
840
  <p class="error" hidden></p>
841
+ <button class="close" part="close" type="button">Not now</button>
815
842
  ${this.referral?.link ? `<div class="referral">
816
- <div class="eyebrow">${this.referral.rewardCredits && this.referral.rewardCredits > 0 ? `Refer a friend \xB7 earn ${formatCredits(this.referral.rewardCredits)} credits` : "Refer a friend"}</div>
843
+ <div class="referral-label">${escapeHtml(
844
+ (this.getAttribute("referral-label") ?? "Refer a friend \xB7 earn {credits} credits").replace(
845
+ "{credits}",
846
+ formatCredits(this.referral.rewardCredits ?? 0)
847
+ )
848
+ )}</div>
817
849
  <div class="referral-row">
818
850
  <div class="link" title="${escapeHtml(this.referral.link)}">${escapeHtml(this.referral.link)}</div>
819
851
  <button class="referral-copy" type="button">${this.referralCopied ? "Copied!" : "Copy"}</button>
820
852
  </div>
821
853
  </div>` : ""}
822
- <button class="close" part="close" type="button">Not now</button>
823
854
  </div>
824
855
  </div>
825
856
  `;
@@ -876,6 +907,66 @@ var Crediball = (() => {
876
907
  if (e.key === "Enter") submit();
877
908
  });
878
909
  }
910
+ this.shadow.querySelector("[data-open-autotopup]")?.addEventListener("click", () => {
911
+ this.autoTopupOpen = true;
912
+ this.render();
913
+ });
914
+ this.shadow.querySelector(".autotopup-submit")?.addEventListener("click", () => {
915
+ const packageId = this.shadow.querySelector(".autotopup-package")?.value;
916
+ const thresholdCredits = Number(
917
+ this.shadow.querySelector(".autotopup-threshold")?.value
918
+ );
919
+ if (!packageId || !Number.isFinite(thresholdCredits) || thresholdCredits < 0) return;
920
+ if (this.emit("crediball-enable-autotopup", { packageId, thresholdCredits })) {
921
+ void this.startAutoTopupCheckout(packageId, thresholdCredits);
922
+ }
923
+ });
924
+ const fixBtn = this.shadow.querySelector("[data-fix-package]");
925
+ fixBtn?.addEventListener("click", () => {
926
+ const packageId = fixBtn.dataset.fixPackage;
927
+ const thresholdCredits = Number(fixBtn.dataset.fixThreshold);
928
+ if (!packageId || !Number.isFinite(thresholdCredits)) return;
929
+ if (this.emit("crediball-enable-autotopup", { packageId, thresholdCredits })) {
930
+ void this.startAutoTopupCheckout(packageId, thresholdCredits);
931
+ }
932
+ });
933
+ }
934
+ /** HTML for the auto-topup opt-in row / status line / needs_auth-fix prompt. */
935
+ renderAutoTopup(packages, autoTopup, currency) {
936
+ if (!packages.length) return "";
937
+ if (autoTopup?.enabled && autoTopup.status === "active") {
938
+ const pkg = packages.find((p) => p.id === autoTopup.packageId);
939
+ return `<div class="autotopup"><p class="autotopup-status">Auto top-up is on \u2014 buying ${escapeHtml(
940
+ pkg?.name ?? "your package"
941
+ )} whenever your balance drops below ${formatCredits(autoTopup.thresholdCredits)} credits.</p></div>`;
942
+ }
943
+ if (autoTopup?.enabled && (autoTopup.status === "needs_auth" || autoTopup.status === "failed")) {
944
+ const message = autoTopup.status === "needs_auth" ? "Auto top-up needs you to re-confirm your payment method." : "Auto top-up's last charge failed.";
945
+ return `<div class="autotopup">
946
+ <p class="autotopup-warning">${escapeHtml(message)}</p>
947
+ <button class="autotopup-toggle" type="button" data-fix-package="${escapeHtml(
948
+ autoTopup.packageId
949
+ )}" data-fix-threshold="${autoTopup.thresholdCredits}">Fix auto top-up</button>
950
+ </div>`;
951
+ }
952
+ if (this.autoTopupOpen) {
953
+ const cheapest = packages.reduce((min, p) => p.price < min.price ? p : min, packages[0]);
954
+ return `<div class="autotopup">
955
+ <div class="autotopup-row">
956
+ <select class="autotopup-package" aria-label="Package">
957
+ ${packages.map(
958
+ (p) => `<option value="${escapeHtml(p.id)}">${escapeHtml(p.name)} \xB7 ${formatMoney(p.price, currency)}</option>`
959
+ ).join("")}
960
+ </select>
961
+ <input class="autotopup-threshold" inputmode="numeric" aria-label="Threshold credits" value="${cheapest.credits}" />
962
+ <button class="autotopup-submit" type="button">Turn on</button>
963
+ </div>
964
+ <p class="autotopup-hint">Buy that package automatically whenever your balance drops below the credit amount above.</p>
965
+ </div>`;
966
+ }
967
+ return `<div class="autotopup">
968
+ <button class="autotopup-toggle" type="button" data-open-autotopup>Keep me topped up automatically</button>
969
+ </div>`;
879
970
  }
880
971
  /** Copy the invite link to the clipboard, with a 2s "Copied!" confirmation. */
881
972
  async copyReferral() {
@@ -910,6 +1001,17 @@ var Crediball = (() => {
910
1001
  this.client.createSubscriptionCheckout({ planId, returnPath: this.returnPath() })
911
1002
  );
912
1003
  }
1004
+ /**
1005
+ * Start the built-in auto-topup setup Checkout and redirect. No money changes
1006
+ * hands here — it only saves a payment method Crediball charges later,
1007
+ * off-session, whenever the balance drops below `thresholdCredits`.
1008
+ */
1009
+ startAutoTopupCheckout(packageId, thresholdCredits) {
1010
+ if (!this.client) return Promise.resolve();
1011
+ return this.redirect(
1012
+ this.client.createAutoTopupCheckout({ packageId, thresholdCredits, returnPath: this.returnPath() })
1013
+ );
1014
+ }
913
1015
  async redirect(session) {
914
1016
  try {
915
1017
  const { url } = await session;
package/dist/index.d.ts CHANGED
@@ -112,6 +112,7 @@ declare class CrediballPaywallElement extends CrediballElement {
112
112
  private shadow;
113
113
  private referral;
114
114
  private referralCopied;
115
+ private autoTopupOpen;
115
116
  static get observedAttributes(): string[];
116
117
  constructor();
117
118
  connectedCallback(): void;
@@ -121,6 +122,8 @@ declare class CrediballPaywallElement extends CrediballElement {
121
122
  open(): void;
122
123
  close(): void;
123
124
  protected render(): void;
125
+ /** HTML for the auto-topup opt-in row / status line / needs_auth-fix prompt. */
126
+ private renderAutoTopup;
124
127
  /** Copy the invite link to the clipboard, with a 2s "Copied!" confirmation. */
125
128
  private copyReferral;
126
129
  /** Dispatch a cancelable option event; returns true when the host did NOT preventDefault. */
@@ -130,6 +133,12 @@ declare class CrediballPaywallElement extends CrediballElement {
130
133
  private startCheckout;
131
134
  /** Start the built-in recurring subscription Checkout and redirect. */
132
135
  private startSubscriptionCheckout;
136
+ /**
137
+ * Start the built-in auto-topup setup Checkout and redirect. No money changes
138
+ * hands here — it only saves a payment method Crediball charges later,
139
+ * off-session, whenever the balance drops below `thresholdCredits`.
140
+ */
141
+ private startAutoTopupCheckout;
133
142
  private redirect;
134
143
  }
135
144
 
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  CrediballReferralCardElement,
7
7
  CrediballTopUpButtonElement,
8
8
  CrediballUsageElement
9
- } from "./chunk-MWB56UEW.js";
9
+ } from "./chunk-KRSZ3FQQ.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-MWB56UEW.js";
8
+ } from "./chunk-KRSZ3FQQ.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.6.0",
3
+ "version": "0.7.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.4.0"
59
+ "@crediball/core": "^0.6.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "tsup": "^8.3.5",