@crediball/elements 0.4.0 → 0.5.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-KXVW7PIU.js → chunk-6CNCZGCF.js} +190 -39
- package/dist/crediball-elements.iife.js +299 -44
- package/dist/index.d.ts +36 -1
- package/dist/index.js +3 -1
- package/dist/register.js +3 -1
- package/package.json +2 -2
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
// src/base.ts
|
|
2
2
|
import {
|
|
3
|
-
getCrediballClient
|
|
3
|
+
getCrediballClient,
|
|
4
|
+
loadRemoteFont,
|
|
5
|
+
themeToCssVars
|
|
4
6
|
} from "@crediball/core";
|
|
5
|
-
var THEME_VARS = [
|
|
6
|
-
["accent", "--crediball-accent", String],
|
|
7
|
-
["onAccent", "--crediball-on-accent", String],
|
|
8
|
-
["ink", "--crediball-ink", String],
|
|
9
|
-
["canvas", "--crediball-canvas", String],
|
|
10
|
-
["hairline", "--crediball-hairline", String],
|
|
11
|
-
["radius", "--crediball-radius", (v) => `${v}px`],
|
|
12
|
-
["radiusCard", "--crediball-radius-card", (v) => `${v}px`],
|
|
13
|
-
["fontStack", "--crediball-font", String]
|
|
14
|
-
];
|
|
15
|
-
function loadRemoteFont(url) {
|
|
16
|
-
if (typeof document === "undefined") return;
|
|
17
|
-
const existing = document.querySelector("link[data-crediball-font]");
|
|
18
|
-
if (existing?.href === url) return;
|
|
19
|
-
if (existing) existing.href = url;
|
|
20
|
-
else {
|
|
21
|
-
const link = document.createElement("link");
|
|
22
|
-
link.rel = "stylesheet";
|
|
23
|
-
link.href = url;
|
|
24
|
-
link.setAttribute("data-crediball-font", "");
|
|
25
|
-
document.head.appendChild(link);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
7
|
var _CrediballElement = class _CrediballElement extends HTMLElement {
|
|
29
8
|
constructor() {
|
|
30
9
|
super(...arguments);
|
|
@@ -85,9 +64,8 @@ var _CrediballElement = class _CrediballElement extends HTMLElement {
|
|
|
85
64
|
if (this.getAttribute("apply-remote-theme") === "false") return;
|
|
86
65
|
const theme = this.getSnapshot()?.packages?.ui?.theme;
|
|
87
66
|
if (!theme) return;
|
|
88
|
-
for (const [
|
|
89
|
-
|
|
90
|
-
if (value != null && value !== "") this.style.setProperty(cssVar, fmt(value));
|
|
67
|
+
for (const [cssVar, value] of Object.entries(themeToCssVars(theme))) {
|
|
68
|
+
this.style.setProperty(cssVar, value);
|
|
91
69
|
}
|
|
92
70
|
if (theme.fontUrl) loadRemoteFont(theme.fontUrl);
|
|
93
71
|
}
|
|
@@ -110,9 +88,7 @@ _CrediballElement.CLIENT_ATTRS = /* @__PURE__ */ new Set([
|
|
|
110
88
|
var CrediballElement = _CrediballElement;
|
|
111
89
|
|
|
112
90
|
// src/format.ts
|
|
113
|
-
|
|
114
|
-
return new Intl.NumberFormat(void 0).format(Math.round(credits));
|
|
115
|
-
}
|
|
91
|
+
import { formatCredits } from "@crediball/core";
|
|
116
92
|
|
|
117
93
|
// src/balance.ts
|
|
118
94
|
var CrediballBalanceElement = class extends CrediballElement {
|
|
@@ -186,6 +162,7 @@ var CrediballTopUpButtonElement = class extends CrediballElement {
|
|
|
186
162
|
};
|
|
187
163
|
|
|
188
164
|
// src/low-credit-warning.ts
|
|
165
|
+
import { isBalanceLow } from "@crediball/core";
|
|
189
166
|
var CrediballLowCreditWarningElement = class extends CrediballElement {
|
|
190
167
|
static get observedAttributes() {
|
|
191
168
|
return [...CrediballElement.observedAttributes, "threshold"];
|
|
@@ -200,7 +177,7 @@ var CrediballLowCreditWarningElement = class extends CrediballElement {
|
|
|
200
177
|
const content = snapshot?.packages?.ui?.content;
|
|
201
178
|
const thresholdAttr = this.getAttribute("threshold");
|
|
202
179
|
const threshold = thresholdAttr != null ? Number(thresholdAttr) : content?.lowCreditThreshold;
|
|
203
|
-
const isLow =
|
|
180
|
+
const isLow = isBalanceLow(snapshot?.balance ?? null, threshold, !!snapshot?.isLow);
|
|
204
181
|
this.style.display = isLow ? "" : "none";
|
|
205
182
|
const slot = this.shadowRoot?.querySelector("slot");
|
|
206
183
|
if (slot) slot.textContent = content?.lowCreditMessage ?? "Low credit balance \u2014 top up to keep going.";
|
|
@@ -208,13 +185,7 @@ var CrediballLowCreditWarningElement = class extends CrediballElement {
|
|
|
208
185
|
};
|
|
209
186
|
|
|
210
187
|
// src/paywall.ts
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
return new Intl.NumberFormat(void 0, { style: "currency", currency }).format(amount);
|
|
214
|
-
} catch {
|
|
215
|
-
return `${amount.toFixed(2)} ${currency}`;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
188
|
+
import { formatMoney } from "@crediball/core";
|
|
218
189
|
var CURRENCY_SYMBOLS = { EUR: "\u20AC", USD: "$", GBP: "\xA3" };
|
|
219
190
|
function currencySymbolFor(currency) {
|
|
220
191
|
return CURRENCY_SYMBOLS[currency.toUpperCase()] ?? currency;
|
|
@@ -485,11 +456,191 @@ function showError(el, message) {
|
|
|
485
456
|
}
|
|
486
457
|
}
|
|
487
458
|
|
|
459
|
+
// src/referral-card.ts
|
|
460
|
+
import {
|
|
461
|
+
fetchReferral,
|
|
462
|
+
captureReferral,
|
|
463
|
+
completeStoredReferral
|
|
464
|
+
} from "@crediball/core";
|
|
465
|
+
var HTML_ESCAPES2 = {
|
|
466
|
+
"&": "&",
|
|
467
|
+
"<": "<",
|
|
468
|
+
">": ">",
|
|
469
|
+
'"': """,
|
|
470
|
+
"'": "'"
|
|
471
|
+
};
|
|
472
|
+
function escapeHtml2(s) {
|
|
473
|
+
return s.replace(/[&<>"']/g, (c) => HTML_ESCAPES2[c] ?? c);
|
|
474
|
+
}
|
|
475
|
+
var STYLE2 = `
|
|
476
|
+
:host { font: var(--crediball-font, system-ui, -apple-system, sans-serif); color: var(--crediball-ink, #1d1d1f); }
|
|
477
|
+
.card {
|
|
478
|
+
display: flex;
|
|
479
|
+
flex-direction: column;
|
|
480
|
+
gap: 14px;
|
|
481
|
+
padding: 24px;
|
|
482
|
+
border: 1px solid var(--crediball-hairline, #e0e0e0);
|
|
483
|
+
border-radius: var(--crediball-radius-card, 18px);
|
|
484
|
+
background: var(--crediball-canvas, #ffffff);
|
|
485
|
+
}
|
|
486
|
+
.title { font-size: 16px; font-weight: 600; }
|
|
487
|
+
.desc { font-size: 13px; color: var(--crediball-ink-muted, #7a7a7a); margin-top: 2px; }
|
|
488
|
+
.row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
489
|
+
.link {
|
|
490
|
+
flex: 1 1 180px;
|
|
491
|
+
min-width: 0;
|
|
492
|
+
overflow: hidden;
|
|
493
|
+
text-overflow: ellipsis;
|
|
494
|
+
white-space: nowrap;
|
|
495
|
+
font-size: 14px;
|
|
496
|
+
border: 1px solid var(--crediball-hairline, #e0e0e0);
|
|
497
|
+
border-radius: var(--crediball-radius, 9999px);
|
|
498
|
+
padding: 8px 14px;
|
|
499
|
+
}
|
|
500
|
+
button {
|
|
501
|
+
appearance: none;
|
|
502
|
+
cursor: pointer;
|
|
503
|
+
font: inherit;
|
|
504
|
+
font-size: 14px;
|
|
505
|
+
padding: 8px 16px;
|
|
506
|
+
border-radius: var(--crediball-radius, 9999px);
|
|
507
|
+
white-space: nowrap;
|
|
508
|
+
}
|
|
509
|
+
button.copy { border: 1px solid var(--crediball-hairline, #e0e0e0); background: transparent; color: var(--crediball-ink, #1d1d1f); }
|
|
510
|
+
button.share { border: none; background: var(--crediball-accent, #0066cc); color: var(--crediball-on-accent, #ffffff); }
|
|
511
|
+
.stats { display: flex; gap: 16px; font-size: 13px; color: var(--crediball-ink-muted, #7a7a7a); }
|
|
512
|
+
.stats strong { color: var(--crediball-ink, #1d1d1f); font-weight: 600; }
|
|
513
|
+
`;
|
|
514
|
+
var CrediballReferralCardElement = class extends CrediballElement {
|
|
515
|
+
constructor() {
|
|
516
|
+
super();
|
|
517
|
+
this.data = null;
|
|
518
|
+
this.copied = false;
|
|
519
|
+
this.copiedTimer = null;
|
|
520
|
+
this.shadow = this.attachShadow({ mode: "open" });
|
|
521
|
+
}
|
|
522
|
+
static get observedAttributes() {
|
|
523
|
+
return [
|
|
524
|
+
...CrediballElement.observedAttributes,
|
|
525
|
+
"title",
|
|
526
|
+
"description",
|
|
527
|
+
"copy-label",
|
|
528
|
+
"share-label",
|
|
529
|
+
"hide-stats"
|
|
530
|
+
];
|
|
531
|
+
}
|
|
532
|
+
connectedCallback() {
|
|
533
|
+
super.connectedCallback();
|
|
534
|
+
void this.loadReferral();
|
|
535
|
+
}
|
|
536
|
+
disconnectedCallback() {
|
|
537
|
+
super.disconnectedCallback();
|
|
538
|
+
if (this.copiedTimer) clearTimeout(this.copiedTimer);
|
|
539
|
+
}
|
|
540
|
+
attributeChangedCallback(name) {
|
|
541
|
+
super.attributeChangedCallback(name);
|
|
542
|
+
if (!this.isConnected) return;
|
|
543
|
+
if (name === "publishable-key" || name === "user-id" || name === "api-url") {
|
|
544
|
+
void this.loadReferral();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/** Auto-capture ?ref=, auto-complete once a user is known, then fetch the card data. */
|
|
548
|
+
async loadReferral() {
|
|
549
|
+
const publishableKey = this.getAttribute("publishable-key");
|
|
550
|
+
const userId = this.getAttribute("user-id");
|
|
551
|
+
const apiUrl = this.getAttribute("api-url") ?? void 0;
|
|
552
|
+
if (!publishableKey) return;
|
|
553
|
+
if (this.getAttribute("capture-referrals") !== "false") {
|
|
554
|
+
captureReferral({ publishableKey, apiUrl });
|
|
555
|
+
if (userId) {
|
|
556
|
+
void completeStoredReferral({ publishableKey, userId, apiUrl }).catch(() => {
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (!userId) {
|
|
561
|
+
this.data = null;
|
|
562
|
+
this.render();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
try {
|
|
566
|
+
this.data = await fetchReferral({ publishableKey, userId, apiUrl });
|
|
567
|
+
} catch {
|
|
568
|
+
this.data = null;
|
|
569
|
+
}
|
|
570
|
+
this.render();
|
|
571
|
+
}
|
|
572
|
+
render() {
|
|
573
|
+
const d = this.data;
|
|
574
|
+
if (!d || !d.enabled || !d.code) {
|
|
575
|
+
this.shadow.innerHTML = "";
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const title = this.getAttribute("title") ?? "Invite friends";
|
|
579
|
+
const copyLabel = this.getAttribute("copy-label") ?? "Copy";
|
|
580
|
+
const shareLabel = this.getAttribute("share-label") ?? "Share";
|
|
581
|
+
const hideStats = this.getAttribute("hide-stats") != null;
|
|
582
|
+
const reward = d.rewardCredits ?? 0;
|
|
583
|
+
const referredReward = d.referredRewardCredits ?? 0;
|
|
584
|
+
const description = this.getAttribute("description") ?? (reward > 0 ? referredReward > 0 ? `Earn ${formatCredits(reward)} credits for every friend who joins \u2014 they get ${formatCredits(referredReward)} credits too.` : `Earn ${formatCredits(reward)} credits for every friend who joins.` : null);
|
|
585
|
+
const shown = d.link ?? d.code;
|
|
586
|
+
const rewards = d.rewards ?? { pending: 0, converted: 0, creditsEarned: 0 };
|
|
587
|
+
const canShare = typeof navigator !== "undefined" && typeof navigator.share === "function";
|
|
588
|
+
this.shadow.innerHTML = `
|
|
589
|
+
<style>${STYLE2}</style>
|
|
590
|
+
<div class="card" part="card">
|
|
591
|
+
<div>
|
|
592
|
+
<div class="title">${escapeHtml2(title)}</div>
|
|
593
|
+
${description ? `<div class="desc">${escapeHtml2(description)}</div>` : ""}
|
|
594
|
+
</div>
|
|
595
|
+
<div class="row">
|
|
596
|
+
<div class="link" part="link" title="${escapeHtml2(shown)}">${escapeHtml2(shown)}</div>
|
|
597
|
+
<button class="copy" part="copy-button" type="button">${escapeHtml2(this.copied ? "Copied!" : copyLabel)}</button>
|
|
598
|
+
${canShare ? `<button class="share" part="share-button" type="button">${escapeHtml2(shareLabel)}</button>` : ""}
|
|
599
|
+
</div>
|
|
600
|
+
${hideStats ? "" : `<div class="stats">
|
|
601
|
+
<span><strong>${d.clicks ?? 0}</strong> clicks</span>
|
|
602
|
+
<span><strong>${rewards.converted}</strong> joined</span>
|
|
603
|
+
${rewards.creditsEarned > 0 ? `<span><strong>${formatCredits(rewards.creditsEarned)}</strong> credits earned</span>` : ""}
|
|
604
|
+
</div>`}
|
|
605
|
+
</div>
|
|
606
|
+
`;
|
|
607
|
+
this.shadow.querySelector("button.copy")?.addEventListener("click", () => void this.copy());
|
|
608
|
+
this.shadow.querySelector("button.share")?.addEventListener("click", () => void this.share());
|
|
609
|
+
}
|
|
610
|
+
linkOrCode() {
|
|
611
|
+
return this.data?.link ?? this.data?.code ?? null;
|
|
612
|
+
}
|
|
613
|
+
async copy() {
|
|
614
|
+
const text = this.linkOrCode();
|
|
615
|
+
if (!text || typeof navigator === "undefined" || !navigator.clipboard) return;
|
|
616
|
+
await navigator.clipboard.writeText(text);
|
|
617
|
+
this.copied = true;
|
|
618
|
+
this.render();
|
|
619
|
+
if (this.copiedTimer) clearTimeout(this.copiedTimer);
|
|
620
|
+
this.copiedTimer = setTimeout(() => {
|
|
621
|
+
this.copied = false;
|
|
622
|
+
this.render();
|
|
623
|
+
}, 2e3);
|
|
624
|
+
}
|
|
625
|
+
async share() {
|
|
626
|
+
const url = this.data?.link ?? void 0;
|
|
627
|
+
if (url && typeof navigator !== "undefined" && typeof navigator.share === "function") {
|
|
628
|
+
try {
|
|
629
|
+
await navigator.share({ url });
|
|
630
|
+
return;
|
|
631
|
+
} catch {
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
await this.copy();
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
|
|
488
638
|
export {
|
|
489
639
|
CrediballElement,
|
|
490
640
|
CrediballBalanceElement,
|
|
491
641
|
CrediballUsageElement,
|
|
492
642
|
CrediballTopUpButtonElement,
|
|
493
643
|
CrediballLowCreditWarningElement,
|
|
494
|
-
CrediballPaywallElement
|
|
644
|
+
CrediballPaywallElement,
|
|
645
|
+
CrediballReferralCardElement
|
|
495
646
|
};
|
|
@@ -25,6 +25,7 @@ var Crediball = (() => {
|
|
|
25
25
|
CrediballElement: () => CrediballElement,
|
|
26
26
|
CrediballLowCreditWarningElement: () => CrediballLowCreditWarningElement,
|
|
27
27
|
CrediballPaywallElement: () => CrediballPaywallElement,
|
|
28
|
+
CrediballReferralCardElement: () => CrediballReferralCardElement,
|
|
28
29
|
CrediballTopUpButtonElement: () => CrediballTopUpButtonElement,
|
|
29
30
|
CrediballUsageElement: () => CrediballUsageElement
|
|
30
31
|
});
|
|
@@ -68,8 +69,21 @@ var Crediball = (() => {
|
|
|
68
69
|
return () => window.removeEventListener(BALANCE_CHANGED_EVENT, listener);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
// ../core/dist/
|
|
72
|
+
// ../core/dist/http.js
|
|
72
73
|
var DEFAULT_API_URL = "https://www.crediball.ai/api";
|
|
74
|
+
async function errorMessage(res, fallback) {
|
|
75
|
+
const body = await res.json().catch(() => null);
|
|
76
|
+
return body && typeof body === "object" && "error" in body && String(body.error) || fallback;
|
|
77
|
+
}
|
|
78
|
+
async function fetchJson(url, init, failureLabel = `Request to ${url}`) {
|
|
79
|
+
const res = await fetch(url, init);
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
throw new Error(await errorMessage(res, `${failureLabel} failed with ${res.status}`));
|
|
82
|
+
}
|
|
83
|
+
return res.json();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ../core/dist/client.js
|
|
73
87
|
var DEFAULT_POLL_MS = 3e4;
|
|
74
88
|
var SSE_HEALTHY_POLL_MULTIPLIER = 2;
|
|
75
89
|
var MAX_SSE_FAILURES = 2;
|
|
@@ -85,15 +99,6 @@ var Crediball = (() => {
|
|
|
85
99
|
error: null
|
|
86
100
|
};
|
|
87
101
|
}
|
|
88
|
-
async function fetchJson(url, headers) {
|
|
89
|
-
const res = await fetch(url, { headers });
|
|
90
|
-
if (!res.ok) {
|
|
91
|
-
const body = await res.json().catch(() => null);
|
|
92
|
-
const message = body && typeof body === "object" && "error" in body && String(body.error) || `Request to ${url} failed with ${res.status}`;
|
|
93
|
-
throw new Error(message);
|
|
94
|
-
}
|
|
95
|
-
return res.json();
|
|
96
|
-
}
|
|
97
102
|
function computeIsLow(balance, costs, explicitThreshold) {
|
|
98
103
|
if (balance == null)
|
|
99
104
|
return false;
|
|
@@ -134,10 +139,10 @@ var Crediball = (() => {
|
|
|
134
139
|
const qs = `userId=${encodeURIComponent(userId)}`;
|
|
135
140
|
try {
|
|
136
141
|
const [balanceRes, usageRes, costsRes, packagesRes] = await Promise.all([
|
|
137
|
-
fetchJson(`${this.apiUrl}/public/balance?${qs}`, headers),
|
|
138
|
-
fetchJson(`${this.apiUrl}/public/usage?${qs}`, headers),
|
|
139
|
-
fetchJson(`${this.apiUrl}/public/costs`, headers),
|
|
140
|
-
fetchJson(`${this.apiUrl}/public/packages?${qs}`, headers)
|
|
142
|
+
fetchJson(`${this.apiUrl}/public/balance?${qs}`, { headers }),
|
|
143
|
+
fetchJson(`${this.apiUrl}/public/usage?${qs}`, { headers }),
|
|
144
|
+
fetchJson(`${this.apiUrl}/public/costs`, { headers }),
|
|
145
|
+
fetchJson(`${this.apiUrl}/public/packages?${qs}`, { headers })
|
|
141
146
|
]);
|
|
142
147
|
const costs = {};
|
|
143
148
|
for (const e of costsRes.events)
|
|
@@ -188,22 +193,16 @@ var Crediball = (() => {
|
|
|
188
193
|
this.apiUrl = (config.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
189
194
|
this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
190
195
|
}
|
|
191
|
-
|
|
196
|
+
postCheckout(path, input) {
|
|
192
197
|
const { publishableKey, userId } = this.config;
|
|
193
|
-
|
|
198
|
+
return fetchJson(`${this.apiUrl}${path}`, {
|
|
194
199
|
method: "POST",
|
|
195
200
|
headers: {
|
|
196
201
|
"Content-Type": "application/json",
|
|
197
202
|
Authorization: `Bearer ${publishableKey}`
|
|
198
203
|
},
|
|
199
204
|
body: JSON.stringify({ userId, ...input })
|
|
200
|
-
});
|
|
201
|
-
if (!res.ok) {
|
|
202
|
-
const body = await res.json().catch(() => null);
|
|
203
|
-
const message = body && typeof body === "object" && "error" in body && String(body.error) || `Checkout request failed with ${res.status}`;
|
|
204
|
-
throw new Error(message);
|
|
205
|
-
}
|
|
206
|
-
return res.json();
|
|
205
|
+
}, "Checkout request");
|
|
207
206
|
}
|
|
208
207
|
pause() {
|
|
209
208
|
this.stopPolling();
|
|
@@ -303,8 +302,86 @@ var Crediball = (() => {
|
|
|
303
302
|
return client;
|
|
304
303
|
}
|
|
305
304
|
|
|
306
|
-
//
|
|
307
|
-
var
|
|
305
|
+
// ../core/dist/referral.js
|
|
306
|
+
var storageKey = (publishableKey) => `crediball_ref_${publishableKey}`;
|
|
307
|
+
function apiBase(config) {
|
|
308
|
+
return (config.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
309
|
+
}
|
|
310
|
+
function authHeaders(config) {
|
|
311
|
+
return { Authorization: `Bearer ${config.publishableKey}` };
|
|
312
|
+
}
|
|
313
|
+
function getStoredReferralCode(publishableKey) {
|
|
314
|
+
try {
|
|
315
|
+
return window.localStorage.getItem(storageKey(publishableKey));
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function clearStoredReferralCode(publishableKey) {
|
|
321
|
+
try {
|
|
322
|
+
window.localStorage.removeItem(storageKey(publishableKey));
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function captureReferral(config, code) {
|
|
327
|
+
let captured = code?.trim();
|
|
328
|
+
if (!captured && typeof window !== "undefined") {
|
|
329
|
+
captured = new URLSearchParams(window.location.search).get("ref")?.trim() ?? void 0;
|
|
330
|
+
}
|
|
331
|
+
if (!captured)
|
|
332
|
+
return;
|
|
333
|
+
captured = captured.toUpperCase();
|
|
334
|
+
const already = getStoredReferralCode(config.publishableKey);
|
|
335
|
+
try {
|
|
336
|
+
window.localStorage.setItem(storageKey(config.publishableKey), captured);
|
|
337
|
+
} catch {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (already === captured)
|
|
341
|
+
return;
|
|
342
|
+
void fetchJson(`${apiBase(config)}/public/referral/click`, {
|
|
343
|
+
method: "POST",
|
|
344
|
+
headers: { "Content-Type": "application/json", ...authHeaders(config) },
|
|
345
|
+
body: JSON.stringify({ code: captured })
|
|
346
|
+
}, "Referral click").catch(() => {
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
async function completeStoredReferral(config) {
|
|
350
|
+
const code = getStoredReferralCode(config.publishableKey);
|
|
351
|
+
if (!code || !config.userId)
|
|
352
|
+
return;
|
|
353
|
+
await fetchJson(`${apiBase(config)}/public/referral/complete`, {
|
|
354
|
+
method: "POST",
|
|
355
|
+
headers: { "Content-Type": "application/json", ...authHeaders(config) },
|
|
356
|
+
body: JSON.stringify({ code, userId: config.userId })
|
|
357
|
+
}, "Referral completion");
|
|
358
|
+
clearStoredReferralCode(config.publishableKey);
|
|
359
|
+
}
|
|
360
|
+
async function fetchReferral(config) {
|
|
361
|
+
const data = await fetchJson(`${apiBase(config)}/public/referral?userId=${encodeURIComponent(config.userId)}`, { headers: authHeaders(config) }, "Referral fetch");
|
|
362
|
+
if (data.enabled && data.code && !data.link && typeof window !== "undefined") {
|
|
363
|
+
data.link = `${window.location.origin}/?ref=${data.code}`;
|
|
364
|
+
}
|
|
365
|
+
return data;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ../core/dist/format.js
|
|
369
|
+
function formatCredits(credits) {
|
|
370
|
+
return new Intl.NumberFormat(void 0).format(Math.round(credits));
|
|
371
|
+
}
|
|
372
|
+
function formatMoney(amount, currency) {
|
|
373
|
+
try {
|
|
374
|
+
return new Intl.NumberFormat(void 0, { style: "currency", currency }).format(amount);
|
|
375
|
+
} catch {
|
|
376
|
+
return `${amount.toFixed(2)} ${currency}`;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function isBalanceLow(balance, threshold, fallback) {
|
|
380
|
+
return threshold != null && balance != null ? balance < threshold : fallback;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ../core/dist/theme.js
|
|
384
|
+
var THEME_CSS_VAR_ENTRIES = [
|
|
308
385
|
["accent", "--crediball-accent", String],
|
|
309
386
|
["onAccent", "--crediball-on-accent", String],
|
|
310
387
|
["ink", "--crediball-ink", String],
|
|
@@ -314,11 +391,25 @@ var Crediball = (() => {
|
|
|
314
391
|
["radiusCard", "--crediball-radius-card", (v) => `${v}px`],
|
|
315
392
|
["fontStack", "--crediball-font", String]
|
|
316
393
|
];
|
|
394
|
+
function themeToCssVars(theme) {
|
|
395
|
+
const vars = {};
|
|
396
|
+
if (!theme)
|
|
397
|
+
return vars;
|
|
398
|
+
for (const [key, cssVar, fmt] of THEME_CSS_VAR_ENTRIES) {
|
|
399
|
+
const value = theme[key];
|
|
400
|
+
if (value != null && value !== "")
|
|
401
|
+
vars[cssVar] = fmt(value);
|
|
402
|
+
}
|
|
403
|
+
return vars;
|
|
404
|
+
}
|
|
317
405
|
function loadRemoteFont(url) {
|
|
318
|
-
if (typeof document === "undefined")
|
|
406
|
+
if (typeof document === "undefined")
|
|
407
|
+
return;
|
|
319
408
|
const existing = document.querySelector("link[data-crediball-font]");
|
|
320
|
-
if (existing?.href === url)
|
|
321
|
-
|
|
409
|
+
if (existing?.href === url)
|
|
410
|
+
return;
|
|
411
|
+
if (existing)
|
|
412
|
+
existing.href = url;
|
|
322
413
|
else {
|
|
323
414
|
const link = document.createElement("link");
|
|
324
415
|
link.rel = "stylesheet";
|
|
@@ -327,6 +418,8 @@ var Crediball = (() => {
|
|
|
327
418
|
document.head.appendChild(link);
|
|
328
419
|
}
|
|
329
420
|
}
|
|
421
|
+
|
|
422
|
+
// src/base.ts
|
|
330
423
|
var _CrediballElement = class _CrediballElement extends HTMLElement {
|
|
331
424
|
constructor() {
|
|
332
425
|
super(...arguments);
|
|
@@ -387,9 +480,8 @@ var Crediball = (() => {
|
|
|
387
480
|
if (this.getAttribute("apply-remote-theme") === "false") return;
|
|
388
481
|
const theme = this.getSnapshot()?.packages?.ui?.theme;
|
|
389
482
|
if (!theme) return;
|
|
390
|
-
for (const [
|
|
391
|
-
|
|
392
|
-
if (value != null && value !== "") this.style.setProperty(cssVar, fmt(value));
|
|
483
|
+
for (const [cssVar, value] of Object.entries(themeToCssVars(theme))) {
|
|
484
|
+
this.style.setProperty(cssVar, value);
|
|
393
485
|
}
|
|
394
486
|
if (theme.fontUrl) loadRemoteFont(theme.fontUrl);
|
|
395
487
|
}
|
|
@@ -411,11 +503,6 @@ var Crediball = (() => {
|
|
|
411
503
|
]);
|
|
412
504
|
var CrediballElement = _CrediballElement;
|
|
413
505
|
|
|
414
|
-
// src/format.ts
|
|
415
|
-
function formatCredits(credits) {
|
|
416
|
-
return new Intl.NumberFormat(void 0).format(Math.round(credits));
|
|
417
|
-
}
|
|
418
|
-
|
|
419
506
|
// src/balance.ts
|
|
420
507
|
var CrediballBalanceElement = class extends CrediballElement {
|
|
421
508
|
constructor() {
|
|
@@ -502,7 +589,7 @@ var Crediball = (() => {
|
|
|
502
589
|
const content = snapshot?.packages?.ui?.content;
|
|
503
590
|
const thresholdAttr = this.getAttribute("threshold");
|
|
504
591
|
const threshold = thresholdAttr != null ? Number(thresholdAttr) : content?.lowCreditThreshold;
|
|
505
|
-
const isLow =
|
|
592
|
+
const isLow = isBalanceLow(snapshot?.balance ?? null, threshold, !!snapshot?.isLow);
|
|
506
593
|
this.style.display = isLow ? "" : "none";
|
|
507
594
|
const slot = this.shadowRoot?.querySelector("slot");
|
|
508
595
|
if (slot) slot.textContent = content?.lowCreditMessage ?? "Low credit balance \u2014 top up to keep going.";
|
|
@@ -510,13 +597,6 @@ var Crediball = (() => {
|
|
|
510
597
|
};
|
|
511
598
|
|
|
512
599
|
// src/paywall.ts
|
|
513
|
-
function formatMoney(amount, currency) {
|
|
514
|
-
try {
|
|
515
|
-
return new Intl.NumberFormat(void 0, { style: "currency", currency }).format(amount);
|
|
516
|
-
} catch {
|
|
517
|
-
return `${amount.toFixed(2)} ${currency}`;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
600
|
var CURRENCY_SYMBOLS = { EUR: "\u20AC", USD: "$", GBP: "\xA3" };
|
|
521
601
|
function currencySymbolFor(currency) {
|
|
522
602
|
return CURRENCY_SYMBOLS[currency.toUpperCase()] ?? currency;
|
|
@@ -787,6 +867,180 @@ var Crediball = (() => {
|
|
|
787
867
|
}
|
|
788
868
|
}
|
|
789
869
|
|
|
870
|
+
// src/referral-card.ts
|
|
871
|
+
var HTML_ESCAPES2 = {
|
|
872
|
+
"&": "&",
|
|
873
|
+
"<": "<",
|
|
874
|
+
">": ">",
|
|
875
|
+
'"': """,
|
|
876
|
+
"'": "'"
|
|
877
|
+
};
|
|
878
|
+
function escapeHtml2(s) {
|
|
879
|
+
return s.replace(/[&<>"']/g, (c) => HTML_ESCAPES2[c] ?? c);
|
|
880
|
+
}
|
|
881
|
+
var STYLE2 = `
|
|
882
|
+
:host { font: var(--crediball-font, system-ui, -apple-system, sans-serif); color: var(--crediball-ink, #1d1d1f); }
|
|
883
|
+
.card {
|
|
884
|
+
display: flex;
|
|
885
|
+
flex-direction: column;
|
|
886
|
+
gap: 14px;
|
|
887
|
+
padding: 24px;
|
|
888
|
+
border: 1px solid var(--crediball-hairline, #e0e0e0);
|
|
889
|
+
border-radius: var(--crediball-radius-card, 18px);
|
|
890
|
+
background: var(--crediball-canvas, #ffffff);
|
|
891
|
+
}
|
|
892
|
+
.title { font-size: 16px; font-weight: 600; }
|
|
893
|
+
.desc { font-size: 13px; color: var(--crediball-ink-muted, #7a7a7a); margin-top: 2px; }
|
|
894
|
+
.row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
895
|
+
.link {
|
|
896
|
+
flex: 1 1 180px;
|
|
897
|
+
min-width: 0;
|
|
898
|
+
overflow: hidden;
|
|
899
|
+
text-overflow: ellipsis;
|
|
900
|
+
white-space: nowrap;
|
|
901
|
+
font-size: 14px;
|
|
902
|
+
border: 1px solid var(--crediball-hairline, #e0e0e0);
|
|
903
|
+
border-radius: var(--crediball-radius, 9999px);
|
|
904
|
+
padding: 8px 14px;
|
|
905
|
+
}
|
|
906
|
+
button {
|
|
907
|
+
appearance: none;
|
|
908
|
+
cursor: pointer;
|
|
909
|
+
font: inherit;
|
|
910
|
+
font-size: 14px;
|
|
911
|
+
padding: 8px 16px;
|
|
912
|
+
border-radius: var(--crediball-radius, 9999px);
|
|
913
|
+
white-space: nowrap;
|
|
914
|
+
}
|
|
915
|
+
button.copy { border: 1px solid var(--crediball-hairline, #e0e0e0); background: transparent; color: var(--crediball-ink, #1d1d1f); }
|
|
916
|
+
button.share { border: none; background: var(--crediball-accent, #0066cc); color: var(--crediball-on-accent, #ffffff); }
|
|
917
|
+
.stats { display: flex; gap: 16px; font-size: 13px; color: var(--crediball-ink-muted, #7a7a7a); }
|
|
918
|
+
.stats strong { color: var(--crediball-ink, #1d1d1f); font-weight: 600; }
|
|
919
|
+
`;
|
|
920
|
+
var CrediballReferralCardElement = class extends CrediballElement {
|
|
921
|
+
constructor() {
|
|
922
|
+
super();
|
|
923
|
+
this.data = null;
|
|
924
|
+
this.copied = false;
|
|
925
|
+
this.copiedTimer = null;
|
|
926
|
+
this.shadow = this.attachShadow({ mode: "open" });
|
|
927
|
+
}
|
|
928
|
+
static get observedAttributes() {
|
|
929
|
+
return [
|
|
930
|
+
...CrediballElement.observedAttributes,
|
|
931
|
+
"title",
|
|
932
|
+
"description",
|
|
933
|
+
"copy-label",
|
|
934
|
+
"share-label",
|
|
935
|
+
"hide-stats"
|
|
936
|
+
];
|
|
937
|
+
}
|
|
938
|
+
connectedCallback() {
|
|
939
|
+
super.connectedCallback();
|
|
940
|
+
void this.loadReferral();
|
|
941
|
+
}
|
|
942
|
+
disconnectedCallback() {
|
|
943
|
+
super.disconnectedCallback();
|
|
944
|
+
if (this.copiedTimer) clearTimeout(this.copiedTimer);
|
|
945
|
+
}
|
|
946
|
+
attributeChangedCallback(name) {
|
|
947
|
+
super.attributeChangedCallback(name);
|
|
948
|
+
if (!this.isConnected) return;
|
|
949
|
+
if (name === "publishable-key" || name === "user-id" || name === "api-url") {
|
|
950
|
+
void this.loadReferral();
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/** Auto-capture ?ref=, auto-complete once a user is known, then fetch the card data. */
|
|
954
|
+
async loadReferral() {
|
|
955
|
+
const publishableKey = this.getAttribute("publishable-key");
|
|
956
|
+
const userId = this.getAttribute("user-id");
|
|
957
|
+
const apiUrl = this.getAttribute("api-url") ?? void 0;
|
|
958
|
+
if (!publishableKey) return;
|
|
959
|
+
if (this.getAttribute("capture-referrals") !== "false") {
|
|
960
|
+
captureReferral({ publishableKey, apiUrl });
|
|
961
|
+
if (userId) {
|
|
962
|
+
void completeStoredReferral({ publishableKey, userId, apiUrl }).catch(() => {
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (!userId) {
|
|
967
|
+
this.data = null;
|
|
968
|
+
this.render();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
this.data = await fetchReferral({ publishableKey, userId, apiUrl });
|
|
973
|
+
} catch {
|
|
974
|
+
this.data = null;
|
|
975
|
+
}
|
|
976
|
+
this.render();
|
|
977
|
+
}
|
|
978
|
+
render() {
|
|
979
|
+
const d = this.data;
|
|
980
|
+
if (!d || !d.enabled || !d.code) {
|
|
981
|
+
this.shadow.innerHTML = "";
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
const title = this.getAttribute("title") ?? "Invite friends";
|
|
985
|
+
const copyLabel = this.getAttribute("copy-label") ?? "Copy";
|
|
986
|
+
const shareLabel = this.getAttribute("share-label") ?? "Share";
|
|
987
|
+
const hideStats = this.getAttribute("hide-stats") != null;
|
|
988
|
+
const reward = d.rewardCredits ?? 0;
|
|
989
|
+
const referredReward = d.referredRewardCredits ?? 0;
|
|
990
|
+
const description = this.getAttribute("description") ?? (reward > 0 ? referredReward > 0 ? `Earn ${formatCredits(reward)} credits for every friend who joins \u2014 they get ${formatCredits(referredReward)} credits too.` : `Earn ${formatCredits(reward)} credits for every friend who joins.` : null);
|
|
991
|
+
const shown = d.link ?? d.code;
|
|
992
|
+
const rewards = d.rewards ?? { pending: 0, converted: 0, creditsEarned: 0 };
|
|
993
|
+
const canShare = typeof navigator !== "undefined" && typeof navigator.share === "function";
|
|
994
|
+
this.shadow.innerHTML = `
|
|
995
|
+
<style>${STYLE2}</style>
|
|
996
|
+
<div class="card" part="card">
|
|
997
|
+
<div>
|
|
998
|
+
<div class="title">${escapeHtml2(title)}</div>
|
|
999
|
+
${description ? `<div class="desc">${escapeHtml2(description)}</div>` : ""}
|
|
1000
|
+
</div>
|
|
1001
|
+
<div class="row">
|
|
1002
|
+
<div class="link" part="link" title="${escapeHtml2(shown)}">${escapeHtml2(shown)}</div>
|
|
1003
|
+
<button class="copy" part="copy-button" type="button">${escapeHtml2(this.copied ? "Copied!" : copyLabel)}</button>
|
|
1004
|
+
${canShare ? `<button class="share" part="share-button" type="button">${escapeHtml2(shareLabel)}</button>` : ""}
|
|
1005
|
+
</div>
|
|
1006
|
+
${hideStats ? "" : `<div class="stats">
|
|
1007
|
+
<span><strong>${d.clicks ?? 0}</strong> clicks</span>
|
|
1008
|
+
<span><strong>${rewards.converted}</strong> joined</span>
|
|
1009
|
+
${rewards.creditsEarned > 0 ? `<span><strong>${formatCredits(rewards.creditsEarned)}</strong> credits earned</span>` : ""}
|
|
1010
|
+
</div>`}
|
|
1011
|
+
</div>
|
|
1012
|
+
`;
|
|
1013
|
+
this.shadow.querySelector("button.copy")?.addEventListener("click", () => void this.copy());
|
|
1014
|
+
this.shadow.querySelector("button.share")?.addEventListener("click", () => void this.share());
|
|
1015
|
+
}
|
|
1016
|
+
linkOrCode() {
|
|
1017
|
+
return this.data?.link ?? this.data?.code ?? null;
|
|
1018
|
+
}
|
|
1019
|
+
async copy() {
|
|
1020
|
+
const text = this.linkOrCode();
|
|
1021
|
+
if (!text || typeof navigator === "undefined" || !navigator.clipboard) return;
|
|
1022
|
+
await navigator.clipboard.writeText(text);
|
|
1023
|
+
this.copied = true;
|
|
1024
|
+
this.render();
|
|
1025
|
+
if (this.copiedTimer) clearTimeout(this.copiedTimer);
|
|
1026
|
+
this.copiedTimer = setTimeout(() => {
|
|
1027
|
+
this.copied = false;
|
|
1028
|
+
this.render();
|
|
1029
|
+
}, 2e3);
|
|
1030
|
+
}
|
|
1031
|
+
async share() {
|
|
1032
|
+
const url = this.data?.link ?? void 0;
|
|
1033
|
+
if (url && typeof navigator !== "undefined" && typeof navigator.share === "function") {
|
|
1034
|
+
try {
|
|
1035
|
+
await navigator.share({ url });
|
|
1036
|
+
return;
|
|
1037
|
+
} catch {
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
await this.copy();
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
|
|
790
1044
|
// src/register.ts
|
|
791
1045
|
function define(name, ctor) {
|
|
792
1046
|
if (!customElements.get(name)) customElements.define(name, ctor);
|
|
@@ -796,5 +1050,6 @@ var Crediball = (() => {
|
|
|
796
1050
|
define("crediball-topup-button", CrediballTopUpButtonElement);
|
|
797
1051
|
define("crediball-low-credit-warning", CrediballLowCreditWarningElement);
|
|
798
1052
|
define("crediball-paywall", CrediballPaywallElement);
|
|
1053
|
+
define("crediball-referral-card", CrediballReferralCardElement);
|
|
799
1054
|
return __toCommonJS(iife_exports);
|
|
800
1055
|
})();
|
package/dist/index.d.ts
CHANGED
|
@@ -125,4 +125,39 @@ declare class CrediballPaywallElement extends CrediballElement {
|
|
|
125
125
|
private redirect;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
/**
|
|
129
|
+
* <crediball-referral-card publishable-key="cb_pub_..." user-id="u123"></crediball-referral-card>
|
|
130
|
+
*
|
|
131
|
+
* Drop-in invite card — the web-component sibling of @crediball/react's
|
|
132
|
+
* <ReferralCard/>, wrapping the same @crediball/core referral functions:
|
|
133
|
+
* - lazily creates + fetches the user's referral code/link/stats,
|
|
134
|
+
* - AUTO-CAPTURES a `?ref=CODE` visit into localStorage and, once a user-id is
|
|
135
|
+
* present, attaches it as a pending referral (set `capture-referrals="false"`
|
|
136
|
+
* to opt out and drive capture/completion yourself),
|
|
137
|
+
* - renders nothing while loading or when the referral program is disabled in
|
|
138
|
+
* the dashboard.
|
|
139
|
+
*
|
|
140
|
+
* Rewards are only ever granted server-side, when the dashboard-configured
|
|
141
|
+
* conversion condition is observed on a secret-key call — a browser can't
|
|
142
|
+
* trigger a payout. Optional attributes: title, description, copy-label,
|
|
143
|
+
* share-label, hide-stats.
|
|
144
|
+
*/
|
|
145
|
+
declare class CrediballReferralCardElement extends CrediballElement {
|
|
146
|
+
private shadow;
|
|
147
|
+
private data;
|
|
148
|
+
private copied;
|
|
149
|
+
private copiedTimer;
|
|
150
|
+
static get observedAttributes(): string[];
|
|
151
|
+
constructor();
|
|
152
|
+
connectedCallback(): void;
|
|
153
|
+
disconnectedCallback(): void;
|
|
154
|
+
attributeChangedCallback(name: string): void;
|
|
155
|
+
/** Auto-capture ?ref=, auto-complete once a user is known, then fetch the card data. */
|
|
156
|
+
private loadReferral;
|
|
157
|
+
protected render(): void;
|
|
158
|
+
private linkOrCode;
|
|
159
|
+
private copy;
|
|
160
|
+
private share;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export { CrediballBalanceElement, CrediballElement, CrediballLowCreditWarningElement, CrediballPaywallElement, CrediballReferralCardElement, CrediballTopUpButtonElement, CrediballUsageElement };
|
package/dist/index.js
CHANGED
|
@@ -3,14 +3,16 @@ import {
|
|
|
3
3
|
CrediballElement,
|
|
4
4
|
CrediballLowCreditWarningElement,
|
|
5
5
|
CrediballPaywallElement,
|
|
6
|
+
CrediballReferralCardElement,
|
|
6
7
|
CrediballTopUpButtonElement,
|
|
7
8
|
CrediballUsageElement
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6CNCZGCF.js";
|
|
9
10
|
export {
|
|
10
11
|
CrediballBalanceElement,
|
|
11
12
|
CrediballElement,
|
|
12
13
|
CrediballLowCreditWarningElement,
|
|
13
14
|
CrediballPaywallElement,
|
|
15
|
+
CrediballReferralCardElement,
|
|
14
16
|
CrediballTopUpButtonElement,
|
|
15
17
|
CrediballUsageElement
|
|
16
18
|
};
|
package/dist/register.js
CHANGED
|
@@ -2,9 +2,10 @@ import {
|
|
|
2
2
|
CrediballBalanceElement,
|
|
3
3
|
CrediballLowCreditWarningElement,
|
|
4
4
|
CrediballPaywallElement,
|
|
5
|
+
CrediballReferralCardElement,
|
|
5
6
|
CrediballTopUpButtonElement,
|
|
6
7
|
CrediballUsageElement
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-6CNCZGCF.js";
|
|
8
9
|
|
|
9
10
|
// src/register.ts
|
|
10
11
|
function define(name, ctor) {
|
|
@@ -15,3 +16,4 @@ define("crediball-usage", CrediballUsageElement);
|
|
|
15
16
|
define("crediball-topup-button", CrediballTopUpButtonElement);
|
|
16
17
|
define("crediball-low-credit-warning", CrediballLowCreditWarningElement);
|
|
17
18
|
define("crediball-paywall", CrediballPaywallElement);
|
|
19
|
+
define("crediball-referral-card", CrediballReferralCardElement);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crediball/elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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.4.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"tsup": "^8.3.5",
|