@crediball/elements 0.2.0 → 0.4.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.
@@ -1,5 +1,30 @@
1
1
  // src/base.ts
2
- import { getCrediballClient } from "@crediball/core";
2
+ import {
3
+ getCrediballClient
4
+ } 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
+ }
3
28
  var _CrediballElement = class _CrediballElement extends HTMLElement {
4
29
  constructor() {
5
30
  super(...arguments);
@@ -42,9 +67,30 @@ var _CrediballElement = class _CrediballElement extends HTMLElement {
42
67
  const lowCreditThreshold = thresholdAttr != null ? Number(thresholdAttr) : void 0;
43
68
  this.client = getCrediballClient({ publishableKey, userId, apiUrl, lowCreditThreshold });
44
69
  this.client.start();
45
- this.unsubscribe = this.client.subscribe(() => this.render());
70
+ this.unsubscribe = this.client.subscribe(() => {
71
+ this.applyRemoteTheme();
72
+ this.render();
73
+ });
74
+ this.applyRemoteTheme();
46
75
  this.render();
47
76
  }
77
+ /**
78
+ * Apply the developer-published theme (served alongside packages) as
79
+ * `--crediball-*` custom properties on this element — custom properties pierce
80
+ * the shadow boundary, so the shadow-DOM styles pick them up. On by default;
81
+ * set `apply-remote-theme="false"` to theme from your own CSS instead. The
82
+ * published theme takes precedence over host `--crediball-*` vars (dashboard wins).
83
+ */
84
+ applyRemoteTheme() {
85
+ if (this.getAttribute("apply-remote-theme") === "false") return;
86
+ const theme = this.getSnapshot()?.packages?.ui?.theme;
87
+ if (!theme) return;
88
+ for (const [key, cssVar, fmt] of THEME_VARS) {
89
+ const value = theme[key];
90
+ if (value != null && value !== "") this.style.setProperty(cssVar, fmt(value));
91
+ }
92
+ if (theme.fontUrl) loadRemoteFont(theme.fontUrl);
93
+ }
48
94
  teardownClient() {
49
95
  this.client?.stop();
50
96
  this.unsubscribe?.();
@@ -126,6 +172,8 @@ var CrediballTopUpButtonElement = class extends CrediballElement {
126
172
  this.button.addEventListener("click", () => this.handleClick());
127
173
  }
128
174
  render() {
175
+ const slot = this.button.querySelector("slot");
176
+ if (slot) slot.textContent = this.getSnapshot()?.packages?.ui?.content?.topUpButtonText ?? "Add credits";
129
177
  }
130
178
  handleClick() {
131
179
  const paywall = document.querySelector("crediball-paywall");
@@ -149,10 +197,13 @@ var CrediballLowCreditWarningElement = class extends CrediballElement {
149
197
  }
150
198
  render() {
151
199
  const snapshot = this.getSnapshot();
200
+ const content = snapshot?.packages?.ui?.content;
152
201
  const thresholdAttr = this.getAttribute("threshold");
153
- const threshold = thresholdAttr != null ? Number(thresholdAttr) : void 0;
202
+ const threshold = thresholdAttr != null ? Number(thresholdAttr) : content?.lowCreditThreshold;
154
203
  const isLow = threshold != null && snapshot?.balance != null ? snapshot.balance < threshold : !!snapshot?.isLow;
155
204
  this.style.display = isLow ? "" : "none";
205
+ const slot = this.shadowRoot?.querySelector("slot");
206
+ if (slot) slot.textContent = content?.lowCreditMessage ?? "Low credit balance \u2014 top up to keep going.";
156
207
  }
157
208
  };
158
209
 
@@ -285,9 +336,11 @@ var CrediballPaywallElement = class extends CrediballElement {
285
336
  }
286
337
  const snapshot = this.getSnapshot();
287
338
  const pkgs = snapshot?.packages;
339
+ const uiContent = pkgs?.ui?.content;
288
340
  const currency = pkgs?.currency ?? "EUR";
289
- const title = this.getAttribute("title") ?? "You need more credits to continue.";
290
- const description = this.getAttribute("description") ?? "Top up to keep using this feature.";
341
+ const title = this.getAttribute("title") ?? uiContent?.paywallTitle ?? "You need more credits to continue.";
342
+ const description = this.getAttribute("description") ?? uiContent?.paywallDescription ?? "Top up to keep using this feature.";
343
+ const addButtonText = uiContent?.paywallButtonText ?? "Add";
291
344
  const amountsAttr = this.getAttribute("amounts");
292
345
  const legacyAmounts = amountsAttr ? amountsAttr.split(",").map((s) => Number(s.trim())).filter((n) => Number.isFinite(n)) : [5, 10];
293
346
  const packages = pkgs?.packages ?? [];
@@ -330,9 +383,9 @@ var CrediballPaywallElement = class extends CrediballElement {
330
383
  ${showCustom ? `<div class="custom-row">
331
384
  <span class="sym">${escapeHtml(currencySymbolFor(currency))}</span>
332
385
  <input class="custom-input" inputmode="decimal" placeholder="${custom?.minEur ?? ""}" aria-label="Custom amount" />
333
- <button class="custom-add" type="button">Add</button>
334
- </div>
335
- <p class="error" hidden></p>` : ""}
386
+ <button class="custom-add" type="button">${escapeHtml(addButtonText)}</button>
387
+ </div>` : ""}
388
+ <p class="error" hidden></p>
336
389
  <button class="close" part="close" type="button">Not now</button>
337
390
  </div>
338
391
  </div>
@@ -352,7 +405,9 @@ var CrediballPaywallElement = class extends CrediballElement {
352
405
  }
353
406
  } else if (kind === "plan") {
354
407
  const plan = plans.find((p) => p.id === btn.dataset.id);
355
- if (plan) this.emit("crediball-select-plan", plan);
408
+ if (plan && this.emit("crediball-select-plan", plan)) {
409
+ void this.startSubscriptionCheckout(plan.id);
410
+ }
356
411
  } else if (kind === "amount") {
357
412
  const amount = Number(btn.dataset.amount);
358
413
  if (this.emit("crediball-topup", { amount })) {
@@ -394,12 +449,24 @@ var CrediballPaywallElement = class extends CrediballElement {
394
449
  this.dispatchEvent(ev);
395
450
  return !ev.defaultPrevented;
396
451
  }
397
- /** Start the built-in Stripe Checkout and redirect. Errors are surfaced in the dialog. */
398
- async startCheckout(input) {
399
- if (!this.client) return;
452
+ returnPath() {
453
+ return typeof window !== "undefined" ? window.location.pathname + window.location.search : void 0;
454
+ }
455
+ /** Start the built-in top-up Checkout and redirect. Errors are surfaced in the dialog. */
456
+ startCheckout(input) {
457
+ if (!this.client) return Promise.resolve();
458
+ return this.redirect(this.client.createCheckout({ ...input, returnPath: this.returnPath() }));
459
+ }
460
+ /** Start the built-in recurring subscription Checkout and redirect. */
461
+ startSubscriptionCheckout(planId) {
462
+ if (!this.client) return Promise.resolve();
463
+ return this.redirect(
464
+ this.client.createSubscriptionCheckout({ planId, returnPath: this.returnPath() })
465
+ );
466
+ }
467
+ async redirect(session) {
400
468
  try {
401
- const returnPath = typeof window !== "undefined" ? window.location.pathname + window.location.search : void 0;
402
- const { url } = await this.client.createCheckout({ ...input, returnPath });
469
+ const { url } = await session;
403
470
  if (typeof window !== "undefined") window.location.assign(url);
404
471
  } catch (err) {
405
472
  const errEl = this.shadow.querySelector(".error");
@@ -69,7 +69,7 @@ var Crediball = (() => {
69
69
  }
70
70
 
71
71
  // ../core/dist/client.js
72
- var DEFAULT_API_URL = "https://crediball.app/api";
72
+ var DEFAULT_API_URL = "https://www.crediball.ai/api";
73
73
  var DEFAULT_POLL_MS = 3e4;
74
74
  var SSE_HEALTHY_POLL_MULTIPLIER = 2;
75
75
  var MAX_SSE_FAILURES = 2;
@@ -122,22 +122,11 @@ var Crediball = (() => {
122
122
  this.costOf = (event) => {
123
123
  return this.store.get().costs[event]?.costCredits;
124
124
  };
125
- this.createCheckout = async (input) => {
126
- const { publishableKey, userId } = this.config;
127
- const res = await fetch(`${this.apiUrl}/public/topup/checkout`, {
128
- method: "POST",
129
- headers: {
130
- "Content-Type": "application/json",
131
- Authorization: `Bearer ${publishableKey}`
132
- },
133
- body: JSON.stringify({ userId, ...input })
134
- });
135
- if (!res.ok) {
136
- const body = await res.json().catch(() => null);
137
- const message = body && typeof body === "object" && "error" in body && String(body.error) || `Checkout request failed with ${res.status}`;
138
- throw new Error(message);
139
- }
140
- return res.json();
125
+ this.createCheckout = (input) => {
126
+ return this.postCheckout("/public/topup/checkout", input);
127
+ };
128
+ this.createSubscriptionCheckout = (input) => {
129
+ return this.postCheckout("/public/subscriptions/checkout", input);
141
130
  };
142
131
  this.refresh = async () => {
143
132
  const { publishableKey, userId } = this.config;
@@ -199,6 +188,23 @@ var Crediball = (() => {
199
188
  this.apiUrl = (config.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
200
189
  this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_MS;
201
190
  }
191
+ async postCheckout(path, input) {
192
+ const { publishableKey, userId } = this.config;
193
+ const res = await fetch(`${this.apiUrl}${path}`, {
194
+ method: "POST",
195
+ headers: {
196
+ "Content-Type": "application/json",
197
+ Authorization: `Bearer ${publishableKey}`
198
+ },
199
+ 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();
207
+ }
202
208
  pause() {
203
209
  this.stopPolling();
204
210
  this.disconnectSse();
@@ -298,6 +304,29 @@ var Crediball = (() => {
298
304
  }
299
305
 
300
306
  // src/base.ts
307
+ var THEME_VARS = [
308
+ ["accent", "--crediball-accent", String],
309
+ ["onAccent", "--crediball-on-accent", String],
310
+ ["ink", "--crediball-ink", String],
311
+ ["canvas", "--crediball-canvas", String],
312
+ ["hairline", "--crediball-hairline", String],
313
+ ["radius", "--crediball-radius", (v) => `${v}px`],
314
+ ["radiusCard", "--crediball-radius-card", (v) => `${v}px`],
315
+ ["fontStack", "--crediball-font", String]
316
+ ];
317
+ function loadRemoteFont(url) {
318
+ if (typeof document === "undefined") return;
319
+ const existing = document.querySelector("link[data-crediball-font]");
320
+ if (existing?.href === url) return;
321
+ if (existing) existing.href = url;
322
+ else {
323
+ const link = document.createElement("link");
324
+ link.rel = "stylesheet";
325
+ link.href = url;
326
+ link.setAttribute("data-crediball-font", "");
327
+ document.head.appendChild(link);
328
+ }
329
+ }
301
330
  var _CrediballElement = class _CrediballElement extends HTMLElement {
302
331
  constructor() {
303
332
  super(...arguments);
@@ -340,9 +369,30 @@ var Crediball = (() => {
340
369
  const lowCreditThreshold = thresholdAttr != null ? Number(thresholdAttr) : void 0;
341
370
  this.client = getCrediballClient({ publishableKey, userId, apiUrl, lowCreditThreshold });
342
371
  this.client.start();
343
- this.unsubscribe = this.client.subscribe(() => this.render());
372
+ this.unsubscribe = this.client.subscribe(() => {
373
+ this.applyRemoteTheme();
374
+ this.render();
375
+ });
376
+ this.applyRemoteTheme();
344
377
  this.render();
345
378
  }
379
+ /**
380
+ * Apply the developer-published theme (served alongside packages) as
381
+ * `--crediball-*` custom properties on this element — custom properties pierce
382
+ * the shadow boundary, so the shadow-DOM styles pick them up. On by default;
383
+ * set `apply-remote-theme="false"` to theme from your own CSS instead. The
384
+ * published theme takes precedence over host `--crediball-*` vars (dashboard wins).
385
+ */
386
+ applyRemoteTheme() {
387
+ if (this.getAttribute("apply-remote-theme") === "false") return;
388
+ const theme = this.getSnapshot()?.packages?.ui?.theme;
389
+ if (!theme) return;
390
+ for (const [key, cssVar, fmt] of THEME_VARS) {
391
+ const value = theme[key];
392
+ if (value != null && value !== "") this.style.setProperty(cssVar, fmt(value));
393
+ }
394
+ if (theme.fontUrl) loadRemoteFont(theme.fontUrl);
395
+ }
346
396
  teardownClient() {
347
397
  this.client?.stop();
348
398
  this.unsubscribe?.();
@@ -424,6 +474,8 @@ var Crediball = (() => {
424
474
  this.button.addEventListener("click", () => this.handleClick());
425
475
  }
426
476
  render() {
477
+ const slot = this.button.querySelector("slot");
478
+ if (slot) slot.textContent = this.getSnapshot()?.packages?.ui?.content?.topUpButtonText ?? "Add credits";
427
479
  }
428
480
  handleClick() {
429
481
  const paywall = document.querySelector("crediball-paywall");
@@ -447,10 +499,13 @@ var Crediball = (() => {
447
499
  }
448
500
  render() {
449
501
  const snapshot = this.getSnapshot();
502
+ const content = snapshot?.packages?.ui?.content;
450
503
  const thresholdAttr = this.getAttribute("threshold");
451
- const threshold = thresholdAttr != null ? Number(thresholdAttr) : void 0;
504
+ const threshold = thresholdAttr != null ? Number(thresholdAttr) : content?.lowCreditThreshold;
452
505
  const isLow = threshold != null && snapshot?.balance != null ? snapshot.balance < threshold : !!snapshot?.isLow;
453
506
  this.style.display = isLow ? "" : "none";
507
+ const slot = this.shadowRoot?.querySelector("slot");
508
+ if (slot) slot.textContent = content?.lowCreditMessage ?? "Low credit balance \u2014 top up to keep going.";
454
509
  }
455
510
  };
456
511
 
@@ -583,9 +638,11 @@ var Crediball = (() => {
583
638
  }
584
639
  const snapshot = this.getSnapshot();
585
640
  const pkgs = snapshot?.packages;
641
+ const uiContent = pkgs?.ui?.content;
586
642
  const currency = pkgs?.currency ?? "EUR";
587
- const title = this.getAttribute("title") ?? "You need more credits to continue.";
588
- const description = this.getAttribute("description") ?? "Top up to keep using this feature.";
643
+ const title = this.getAttribute("title") ?? uiContent?.paywallTitle ?? "You need more credits to continue.";
644
+ const description = this.getAttribute("description") ?? uiContent?.paywallDescription ?? "Top up to keep using this feature.";
645
+ const addButtonText = uiContent?.paywallButtonText ?? "Add";
589
646
  const amountsAttr = this.getAttribute("amounts");
590
647
  const legacyAmounts = amountsAttr ? amountsAttr.split(",").map((s) => Number(s.trim())).filter((n) => Number.isFinite(n)) : [5, 10];
591
648
  const packages = pkgs?.packages ?? [];
@@ -628,9 +685,9 @@ var Crediball = (() => {
628
685
  ${showCustom ? `<div class="custom-row">
629
686
  <span class="sym">${escapeHtml(currencySymbolFor(currency))}</span>
630
687
  <input class="custom-input" inputmode="decimal" placeholder="${custom?.minEur ?? ""}" aria-label="Custom amount" />
631
- <button class="custom-add" type="button">Add</button>
632
- </div>
633
- <p class="error" hidden></p>` : ""}
688
+ <button class="custom-add" type="button">${escapeHtml(addButtonText)}</button>
689
+ </div>` : ""}
690
+ <p class="error" hidden></p>
634
691
  <button class="close" part="close" type="button">Not now</button>
635
692
  </div>
636
693
  </div>
@@ -650,7 +707,9 @@ var Crediball = (() => {
650
707
  }
651
708
  } else if (kind === "plan") {
652
709
  const plan = plans.find((p) => p.id === btn.dataset.id);
653
- if (plan) this.emit("crediball-select-plan", plan);
710
+ if (plan && this.emit("crediball-select-plan", plan)) {
711
+ void this.startSubscriptionCheckout(plan.id);
712
+ }
654
713
  } else if (kind === "amount") {
655
714
  const amount = Number(btn.dataset.amount);
656
715
  if (this.emit("crediball-topup", { amount })) {
@@ -692,12 +751,24 @@ var Crediball = (() => {
692
751
  this.dispatchEvent(ev);
693
752
  return !ev.defaultPrevented;
694
753
  }
695
- /** Start the built-in Stripe Checkout and redirect. Errors are surfaced in the dialog. */
696
- async startCheckout(input) {
697
- if (!this.client) return;
754
+ returnPath() {
755
+ return typeof window !== "undefined" ? window.location.pathname + window.location.search : void 0;
756
+ }
757
+ /** Start the built-in top-up Checkout and redirect. Errors are surfaced in the dialog. */
758
+ startCheckout(input) {
759
+ if (!this.client) return Promise.resolve();
760
+ return this.redirect(this.client.createCheckout({ ...input, returnPath: this.returnPath() }));
761
+ }
762
+ /** Start the built-in recurring subscription Checkout and redirect. */
763
+ startSubscriptionCheckout(planId) {
764
+ if (!this.client) return Promise.resolve();
765
+ return this.redirect(
766
+ this.client.createSubscriptionCheckout({ planId, returnPath: this.returnPath() })
767
+ );
768
+ }
769
+ async redirect(session) {
698
770
  try {
699
- const returnPath = typeof window !== "undefined" ? window.location.pathname + window.location.search : void 0;
700
- const { url } = await this.client.createCheckout({ ...input, returnPath });
771
+ const { url } = await session;
701
772
  if (typeof window !== "undefined") window.location.assign(url);
702
773
  } catch (err) {
703
774
  const errEl = this.shadow.querySelector(".error");
package/dist/index.d.ts CHANGED
@@ -24,6 +24,14 @@ declare abstract class CrediballElement extends HTMLElement {
24
24
  */
25
25
  attributeChangedCallback(name: string): void;
26
26
  private connectClient;
27
+ /**
28
+ * Apply the developer-published theme (served alongside packages) as
29
+ * `--crediball-*` custom properties on this element — custom properties pierce
30
+ * the shadow boundary, so the shadow-DOM styles pick them up. On by default;
31
+ * set `apply-remote-theme="false"` to theme from your own CSS instead. The
32
+ * published theme takes precedence over host `--crediball-*` vars (dashboard wins).
33
+ */
34
+ private applyRemoteTheme;
27
35
  private teardownClient;
28
36
  protected getSnapshot(): CrediballSnapshot | null;
29
37
  /** Called on connect, on relevant attribute changes, and on every data update. */
@@ -92,16 +100,13 @@ declare class CrediballLowCreditWarningElement extends CrediballElement {
92
100
  * subscription-plans catalog. Call `.open()` / `.close()`, or toggle the `open`
93
101
  * attribute.
94
102
  *
95
- * Top-ups pay out of the box: picking a package or entering a custom amount
96
- * starts a Crediball-hosted Stripe Checkout (using the publishable key) and
97
- * redirects — the developer only has to connect payouts in the dashboard. Each
98
- * choice still first dispatches a bubbling, CANCELABLE event
99
- * (`crediball-select-package` / `crediball-topup`); call `event.preventDefault()`
100
- * in a listener to suppress the built-in checkout and run your own flow instead.
101
- *
102
- * Subscription plans have no built-in checkout (they need recurring billing your
103
- * backend sets up): picking one only dispatches `crediball-select-plan` for the
104
- * host to fulfil server-side.
103
+ * Everything pays out of the box: picking a package, entering a custom amount, or
104
+ * choosing a subscription plan starts a Crediball-hosted Stripe Checkout (using the
105
+ * publishable key) and redirects — the developer only has to connect payouts in the
106
+ * dashboard. Subscriptions bill recurringly and grant credits each period. Each
107
+ * choice first dispatches a bubbling, CANCELABLE event (`crediball-select-package` /
108
+ * `crediball-topup` / `crediball-select-plan`); call `event.preventDefault()` in a
109
+ * listener to suppress the built-in checkout and run your own flow instead.
105
110
  */
106
111
  declare class CrediballPaywallElement extends CrediballElement {
107
112
  private shadow;
@@ -112,8 +117,12 @@ declare class CrediballPaywallElement extends CrediballElement {
112
117
  protected render(): void;
113
118
  /** Dispatch a cancelable option event; returns true when the host did NOT preventDefault. */
114
119
  private emit;
115
- /** Start the built-in Stripe Checkout and redirect. Errors are surfaced in the dialog. */
120
+ private returnPath;
121
+ /** Start the built-in top-up Checkout and redirect. Errors are surfaced in the dialog. */
116
122
  private startCheckout;
123
+ /** Start the built-in recurring subscription Checkout and redirect. */
124
+ private startSubscriptionCheckout;
125
+ private redirect;
117
126
  }
118
127
 
119
128
  export { CrediballBalanceElement, CrediballElement, CrediballLowCreditWarningElement, CrediballPaywallElement, CrediballTopUpButtonElement, CrediballUsageElement };
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  CrediballPaywallElement,
6
6
  CrediballTopUpButtonElement,
7
7
  CrediballUsageElement
8
- } from "./chunk-VW5SGSLN.js";
8
+ } from "./chunk-KXVW7PIU.js";
9
9
  export {
10
10
  CrediballBalanceElement,
11
11
  CrediballElement,
package/dist/register.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  CrediballPaywallElement,
5
5
  CrediballTopUpButtonElement,
6
6
  CrediballUsageElement
7
- } from "./chunk-VW5SGSLN.js";
7
+ } from "./chunk-KXVW7PIU.js";
8
8
 
9
9
  // src/register.ts
10
10
  function define(name, ctor) {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@crediball/elements",
3
- "version": "0.2.0",
3
+ "version": "0.4.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>",
7
- "homepage": "https://crediball.app",
7
+ "homepage": "https://crediball.ai",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/filipporezzadore/Crediball.git",
@@ -56,7 +56,7 @@
56
56
  "prepublishOnly": "npm run build"
57
57
  },
58
58
  "dependencies": {
59
- "@crediball/core": "^0.1.1"
59
+ "@crediball/core": "^0.3.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "tsup": "^8.3.5",