@crediball/elements 0.5.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.
@@ -185,7 +185,7 @@ var CrediballLowCreditWarningElement = class extends CrediballElement {
185
185
  };
186
186
 
187
187
  // src/paywall.ts
188
- import { formatMoney } from "@crediball/core";
188
+ import { formatMoney, fetchReferral } from "@crediball/core";
189
189
  var CURRENCY_SYMBOLS = { EUR: "\u20AC", USD: "$", GBP: "\xA3" };
190
190
  function currencySymbolFor(currency) {
191
191
  return CURRENCY_SYMBOLS[currency.toUpperCase()] ?? currency;
@@ -283,15 +283,81 @@ var STYLE = `
283
283
  padding: 11px 22px;
284
284
  border-radius: var(--crediball-radius, 9999px);
285
285
  }
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); }
309
+ .referral-row { display: flex; gap: 8px; align-items: center; }
310
+ .referral-row .link {
311
+ flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
312
+ font-size: 13px; color: var(--crediball-ink, #1d1d1f);
313
+ border: 1px solid var(--crediball-hairline, #e0e0e0);
314
+ border-radius: var(--crediball-radius, 9999px); padding: 8px 14px;
315
+ }
316
+ button.referral-copy {
317
+ flex: none; appearance: none; cursor: pointer; background: transparent;
318
+ color: var(--crediball-ink, #1d1d1f); border: 1px solid var(--crediball-hairline, #e0e0e0);
319
+ font: inherit; font-size: 14px; padding: 8px 16px; border-radius: var(--crediball-radius, 9999px);
320
+ white-space: nowrap;
321
+ }
286
322
  `;
287
323
  var CrediballPaywallElement = class extends CrediballElement {
288
- static get observedAttributes() {
289
- return [...CrediballElement.observedAttributes, "open", "title", "description", "amounts"];
290
- }
291
324
  constructor() {
292
325
  super();
326
+ this.referral = null;
327
+ this.referralCopied = false;
328
+ this.autoTopupOpen = false;
293
329
  this.shadow = this.attachShadow({ mode: "open" });
294
330
  }
331
+ static get observedAttributes() {
332
+ return [...CrediballElement.observedAttributes, "open", "title", "description", "amounts"];
333
+ }
334
+ connectedCallback() {
335
+ super.connectedCallback();
336
+ void this.loadReferral();
337
+ }
338
+ attributeChangedCallback(name) {
339
+ super.attributeChangedCallback(name);
340
+ if (this.isConnected && (name === "publishable-key" || name === "user-id" || name === "api-url")) {
341
+ void this.loadReferral();
342
+ }
343
+ }
344
+ /** Fetch the user's invite link for the "Refer a friend" row (best-effort). */
345
+ async loadReferral() {
346
+ const publishableKey = this.getAttribute("publishable-key");
347
+ const userId = this.getAttribute("user-id");
348
+ const apiUrl = this.getAttribute("api-url") ?? void 0;
349
+ if (!publishableKey || !userId) {
350
+ this.referral = null;
351
+ return;
352
+ }
353
+ try {
354
+ const data = await fetchReferral({ publishableKey, userId, apiUrl });
355
+ this.referral = data.enabled ? data : null;
356
+ } catch {
357
+ this.referral = null;
358
+ }
359
+ if (this.hasAttribute("open")) this.render();
360
+ }
295
361
  open() {
296
362
  this.setAttribute("open", "");
297
363
  }
@@ -356,8 +422,21 @@ var CrediballPaywallElement = class extends CrediballElement {
356
422
  <input class="custom-input" inputmode="decimal" placeholder="${custom?.minEur ?? ""}" aria-label="Custom amount" />
357
423
  <button class="custom-add" type="button">${escapeHtml(addButtonText)}</button>
358
424
  </div>` : ""}
425
+ ${this.renderAutoTopup(packages, pkgs?.autoTopup, currency)}
359
426
  <p class="error" hidden></p>
360
427
  <button class="close" part="close" type="button">Not now</button>
428
+ ${this.referral?.link ? `<div class="referral">
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>
435
+ <div class="referral-row">
436
+ <div class="link" title="${escapeHtml(this.referral.link)}">${escapeHtml(this.referral.link)}</div>
437
+ <button class="referral-copy" type="button">${this.referralCopied ? "Copied!" : "Copy"}</button>
438
+ </div>
439
+ </div>` : ""}
361
440
  </div>
362
441
  </div>
363
442
  `;
@@ -365,6 +444,7 @@ var CrediballPaywallElement = class extends CrediballElement {
365
444
  if (e.target === e.currentTarget) this.close();
366
445
  });
367
446
  this.shadow.querySelector(".close")?.addEventListener("click", () => this.close());
447
+ this.shadow.querySelector(".referral-copy")?.addEventListener("click", () => void this.copyReferral());
368
448
  this.shadow.querySelectorAll("button.option").forEach((el) => {
369
449
  const btn = el;
370
450
  btn.addEventListener("click", () => {
@@ -413,6 +493,78 @@ var CrediballPaywallElement = class extends CrediballElement {
413
493
  if (e.key === "Enter") submit();
414
494
  });
415
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>`;
556
+ }
557
+ /** Copy the invite link to the clipboard, with a 2s "Copied!" confirmation. */
558
+ async copyReferral() {
559
+ const link = this.referral?.link;
560
+ if (!link || typeof navigator === "undefined" || !navigator.clipboard) return;
561
+ await navigator.clipboard.writeText(link);
562
+ this.referralCopied = true;
563
+ this.render();
564
+ setTimeout(() => {
565
+ this.referralCopied = false;
566
+ if (this.hasAttribute("open")) this.render();
567
+ }, 2e3);
416
568
  }
417
569
  /** Dispatch a cancelable option event; returns true when the host did NOT preventDefault. */
418
570
  emit(name, detail) {
@@ -435,6 +587,17 @@ var CrediballPaywallElement = class extends CrediballElement {
435
587
  this.client.createSubscriptionCheckout({ planId, returnPath: this.returnPath() })
436
588
  );
437
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
+ }
438
601
  async redirect(session) {
439
602
  try {
440
603
  const { url } = await session;
@@ -458,7 +621,7 @@ function showError(el, message) {
458
621
 
459
622
  // src/referral-card.ts
460
623
  import {
461
- fetchReferral,
624
+ fetchReferral as fetchReferral2,
462
625
  captureReferral,
463
626
  completeStoredReferral
464
627
  } from "@crediball/core";
@@ -563,7 +726,7 @@ var CrediballReferralCardElement = class extends CrediballElement {
563
726
  return;
564
727
  }
565
728
  try {
566
- this.data = await fetchReferral({ publishableKey, userId, apiUrl });
729
+ this.data = await fetchReferral2({ publishableKey, userId, apiUrl });
567
730
  } catch {
568
731
  this.data = null;
569
732
  }
@@ -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,15 +697,81 @@ var Crediball = (() => {
694
697
  padding: 11px 22px;
695
698
  border-radius: var(--crediball-radius, 9999px);
696
699
  }
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); }
723
+ .referral-row { display: flex; gap: 8px; align-items: center; }
724
+ .referral-row .link {
725
+ flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
726
+ font-size: 13px; color: var(--crediball-ink, #1d1d1f);
727
+ border: 1px solid var(--crediball-hairline, #e0e0e0);
728
+ border-radius: var(--crediball-radius, 9999px); padding: 8px 14px;
729
+ }
730
+ button.referral-copy {
731
+ flex: none; appearance: none; cursor: pointer; background: transparent;
732
+ color: var(--crediball-ink, #1d1d1f); border: 1px solid var(--crediball-hairline, #e0e0e0);
733
+ font: inherit; font-size: 14px; padding: 8px 16px; border-radius: var(--crediball-radius, 9999px);
734
+ white-space: nowrap;
735
+ }
697
736
  `;
698
737
  var CrediballPaywallElement = class extends CrediballElement {
699
- static get observedAttributes() {
700
- return [...CrediballElement.observedAttributes, "open", "title", "description", "amounts"];
701
- }
702
738
  constructor() {
703
739
  super();
740
+ this.referral = null;
741
+ this.referralCopied = false;
742
+ this.autoTopupOpen = false;
704
743
  this.shadow = this.attachShadow({ mode: "open" });
705
744
  }
745
+ static get observedAttributes() {
746
+ return [...CrediballElement.observedAttributes, "open", "title", "description", "amounts"];
747
+ }
748
+ connectedCallback() {
749
+ super.connectedCallback();
750
+ void this.loadReferral();
751
+ }
752
+ attributeChangedCallback(name) {
753
+ super.attributeChangedCallback(name);
754
+ if (this.isConnected && (name === "publishable-key" || name === "user-id" || name === "api-url")) {
755
+ void this.loadReferral();
756
+ }
757
+ }
758
+ /** Fetch the user's invite link for the "Refer a friend" row (best-effort). */
759
+ async loadReferral() {
760
+ const publishableKey = this.getAttribute("publishable-key");
761
+ const userId = this.getAttribute("user-id");
762
+ const apiUrl = this.getAttribute("api-url") ?? void 0;
763
+ if (!publishableKey || !userId) {
764
+ this.referral = null;
765
+ return;
766
+ }
767
+ try {
768
+ const data = await fetchReferral({ publishableKey, userId, apiUrl });
769
+ this.referral = data.enabled ? data : null;
770
+ } catch {
771
+ this.referral = null;
772
+ }
773
+ if (this.hasAttribute("open")) this.render();
774
+ }
706
775
  open() {
707
776
  this.setAttribute("open", "");
708
777
  }
@@ -767,8 +836,21 @@ var Crediball = (() => {
767
836
  <input class="custom-input" inputmode="decimal" placeholder="${custom?.minEur ?? ""}" aria-label="Custom amount" />
768
837
  <button class="custom-add" type="button">${escapeHtml(addButtonText)}</button>
769
838
  </div>` : ""}
839
+ ${this.renderAutoTopup(packages, pkgs?.autoTopup, currency)}
770
840
  <p class="error" hidden></p>
771
841
  <button class="close" part="close" type="button">Not now</button>
842
+ ${this.referral?.link ? `<div class="referral">
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>
849
+ <div class="referral-row">
850
+ <div class="link" title="${escapeHtml(this.referral.link)}">${escapeHtml(this.referral.link)}</div>
851
+ <button class="referral-copy" type="button">${this.referralCopied ? "Copied!" : "Copy"}</button>
852
+ </div>
853
+ </div>` : ""}
772
854
  </div>
773
855
  </div>
774
856
  `;
@@ -776,6 +858,7 @@ var Crediball = (() => {
776
858
  if (e.target === e.currentTarget) this.close();
777
859
  });
778
860
  this.shadow.querySelector(".close")?.addEventListener("click", () => this.close());
861
+ this.shadow.querySelector(".referral-copy")?.addEventListener("click", () => void this.copyReferral());
779
862
  this.shadow.querySelectorAll("button.option").forEach((el) => {
780
863
  const btn = el;
781
864
  btn.addEventListener("click", () => {
@@ -824,6 +907,78 @@ var Crediball = (() => {
824
907
  if (e.key === "Enter") submit();
825
908
  });
826
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>`;
970
+ }
971
+ /** Copy the invite link to the clipboard, with a 2s "Copied!" confirmation. */
972
+ async copyReferral() {
973
+ const link = this.referral?.link;
974
+ if (!link || typeof navigator === "undefined" || !navigator.clipboard) return;
975
+ await navigator.clipboard.writeText(link);
976
+ this.referralCopied = true;
977
+ this.render();
978
+ setTimeout(() => {
979
+ this.referralCopied = false;
980
+ if (this.hasAttribute("open")) this.render();
981
+ }, 2e3);
827
982
  }
828
983
  /** Dispatch a cancelable option event; returns true when the host did NOT preventDefault. */
829
984
  emit(name, detail) {
@@ -846,6 +1001,17 @@ var Crediball = (() => {
846
1001
  this.client.createSubscriptionCheckout({ planId, returnPath: this.returnPath() })
847
1002
  );
848
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
+ }
849
1015
  async redirect(session) {
850
1016
  try {
851
1017
  const { url } = await session;
package/dist/index.d.ts CHANGED
@@ -110,11 +110,22 @@ declare class CrediballLowCreditWarningElement extends CrediballElement {
110
110
  */
111
111
  declare class CrediballPaywallElement extends CrediballElement {
112
112
  private shadow;
113
+ private referral;
114
+ private referralCopied;
115
+ private autoTopupOpen;
113
116
  static get observedAttributes(): string[];
114
117
  constructor();
118
+ connectedCallback(): void;
119
+ attributeChangedCallback(name: string): void;
120
+ /** Fetch the user's invite link for the "Refer a friend" row (best-effort). */
121
+ private loadReferral;
115
122
  open(): void;
116
123
  close(): void;
117
124
  protected render(): void;
125
+ /** HTML for the auto-topup opt-in row / status line / needs_auth-fix prompt. */
126
+ private renderAutoTopup;
127
+ /** Copy the invite link to the clipboard, with a 2s "Copied!" confirmation. */
128
+ private copyReferral;
118
129
  /** Dispatch a cancelable option event; returns true when the host did NOT preventDefault. */
119
130
  private emit;
120
131
  private returnPath;
@@ -122,6 +133,12 @@ declare class CrediballPaywallElement extends CrediballElement {
122
133
  private startCheckout;
123
134
  /** Start the built-in recurring subscription Checkout and redirect. */
124
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;
125
142
  private redirect;
126
143
  }
127
144
 
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  CrediballReferralCardElement,
7
7
  CrediballTopUpButtonElement,
8
8
  CrediballUsageElement
9
- } from "./chunk-6CNCZGCF.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-6CNCZGCF.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.5.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",