@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.
- package/dist/{chunk-6CNCZGCF.js → chunk-KRSZ3FQQ.js} +169 -6
- package/dist/crediball-elements.iife.js +169 -3
- package/dist/index.d.ts +17 -0
- package/dist/index.js +1 -1
- package/dist/register.js +1 -1
- package/package.json +2 -2
|
@@ -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
|
|
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
package/dist/register.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crediball/elements",
|
|
3
|
-
"version": "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.
|
|
59
|
+
"@crediball/core": "^0.6.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"tsup": "^8.3.5",
|