@nebulr-group/bridge-svelte 0.4.0-beta.6 → 0.4.0-beta.7

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.
@@ -96,12 +96,28 @@
96
96
  });
97
97
 
98
98
  const warningLevel = $derived(snapshot?.warningLevel ?? null);
99
+ // TBP-275 — `metered` quotas bill overage instead of blocking. Prefer the
100
+ // server-authoritative `overcap` flag (handles limit === 0 pure-per-unit)
101
+ // over a UI-derived `used > limit`.
102
+ const isMetered = $derived(snapshot?.policy === 'metered');
99
103
  const overCap = $derived(
100
- snapshot ? snapshot.used > snapshot.limit : false,
104
+ snapshot
105
+ ? (snapshot.overcap ?? snapshot.used > snapshot.limit)
106
+ : false,
101
107
  );
102
- const visible = $derived(snapshot !== undefined && warningLevel !== null);
108
+ // Hard caps show only at the warning thresholds. Metered quotas also show
109
+ // once billing has engaged (overCap), even with no warningLevel — that's the
110
+ // live "you're now being billed for overage" state.
111
+ const visible = $derived(
112
+ snapshot !== undefined && (warningLevel !== null || (isMetered && overCap)),
113
+ );
114
+ // Metered never blocks, so it never renders as 'critical' — it's informational.
103
115
  const severity = $derived<Severity>(
104
- warningLevel === 'critical' || overCap ? 'critical' : 'warn',
116
+ isMetered
117
+ ? 'warn'
118
+ : warningLevel === 'critical' || overCap
119
+ ? 'critical'
120
+ : 'warn',
105
121
  );
106
122
  const displayLabel = $derived(label ?? snapshot?.label ?? metric);
107
123
  const percent = $derived(
@@ -109,13 +125,58 @@
109
125
  ? Math.min(100, Math.round((snapshot.used / snapshot.limit) * 100))
110
126
  : 0,
111
127
  );
128
+ // Meter bar is meaningless for pure per-unit metered (limit 0) — hide it there.
129
+ const showMeter = $derived(!!snapshot && snapshot.limit > 0);
130
+
131
+ /** Format an estimated cost like "$1.00" / "1.00 SEK". */
132
+ function formatCost(amount: number | undefined, currency: string | undefined): string {
133
+ if (amount === undefined) return '';
134
+ const cur = (currency ?? '').toUpperCase();
135
+ try {
136
+ return new Intl.NumberFormat(undefined, {
137
+ style: 'currency',
138
+ currency: cur || 'USD',
139
+ }).format(amount);
140
+ } catch {
141
+ // Unknown currency code → fall back to "<amount> <CUR>".
142
+ return `${amount.toFixed(2)}${cur ? ` ${cur}` : ''}`;
143
+ }
144
+ }
112
145
 
113
146
  function getCopy(
114
147
  snap: QuotaSnapshot | undefined,
115
148
  admin: boolean,
116
149
  ): { title: string; body: string; cta?: string } {
117
150
  if (!snap) return { title: '', body: '' };
118
- const over = snap.used > snap.limit;
151
+
152
+ // TBP-275 — metered: live usage + projected cost, never a blocking message.
153
+ if (snap.policy === 'metered') {
154
+ const overUnits = snap.limit > 0 ? Math.max(0, snap.used - snap.limit) : snap.used;
155
+ const cost = formatCost(snap.overageEstimate, snap.currency);
156
+ const costSuffix = cost ? ` · ~${cost} estimated this period` : '';
157
+ if (snap.limit > 0) {
158
+ // included allotment + overage
159
+ if (overUnits > 0) {
160
+ return {
161
+ title: `${displayLabel} overage`,
162
+ body: `${overUnits.toLocaleString()} over your ${snap.limit.toLocaleString()} included${costSuffix}.`,
163
+ };
164
+ }
165
+ // approaching the included allotment (warningLevel drove visibility)
166
+ const unit = formatCost(snap.unitAmount, snap.currency);
167
+ return {
168
+ title: `${displayLabel} approaching included limit`,
169
+ body: `You've used ${snap.used.toLocaleString()} of ${snap.limit.toLocaleString()} included${unit ? ` — extra usage is billed at ${unit}/unit` : ''}.`,
170
+ };
171
+ }
172
+ // pure per-unit (limit 0) — billed from unit 1
173
+ return {
174
+ title: `${displayLabel} usage`,
175
+ body: `${snap.used.toLocaleString()} ${displayLabel}${costSuffix}.`,
176
+ };
177
+ }
178
+
179
+ const over = snap.overcap ?? snap.used > snap.limit;
119
180
  const remaining = Math.max(0, snap.remaining);
120
181
  if (over) {
121
182
  return admin
@@ -178,9 +239,11 @@
178
239
  <div class="bqb-content">
179
240
  <strong class="bqb-title">{copy.title}</strong>
180
241
  <span class="bqb-body">{copy.body}</span>
181
- <div class="bqb-meter" aria-hidden="true">
182
- <div class="bqb-meter-fill" style={`width: ${Math.min(100, percent)}%`}></div>
183
- </div>
242
+ {#if showMeter}
243
+ <div class="bqb-meter" aria-hidden="true">
244
+ <div class="bqb-meter-fill" style={`width: ${Math.min(100, percent)}%`}></div>
245
+ </div>
246
+ {/if}
184
247
  </div>
185
248
  {#if copy.cta && isBillingAdmin}
186
249
  <button type="button" class="bqb-cta" onclick={handleAction}>{copy.cta}</button>
@@ -121,8 +121,13 @@
121
121
  picking = true;
122
122
  pickError = null;
123
123
  try {
124
- if (price.amount === 0) {
125
- // Free plan — select directly
124
+ if (price.amount === 0 && !plan.hasCost) {
125
+ // Free plan — select directly. TBP-275: guard on `!plan.hasCost` so a
126
+ // $0-base plan that carries METERED pricing is NOT treated as free —
127
+ // it falls through to checkout/changePlan below, capturing a payment
128
+ // method so per-unit overage can be billed (US-C). Without this guard a
129
+ // metered $0-base plan would hit selectFreePlan, which the backend now
130
+ // rejects ("has a cost — use the checkout endpoint instead").
126
131
  await getBridgeAuth().selectFreePlan(plan.key);
127
132
  await loadSubscription();
128
133
  onSelect?.({ plan, price });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebulr-group/bridge-svelte",
3
- "version": "0.4.0-beta.6",
3
+ "version": "0.4.0-beta.7",
4
4
  "description": "Bridge Svelte library, This library helps you to add bridge authentication and feature flags, and payments to your svelte application.",
5
5
  "author": "Iman Pouya",
6
6
  "license": "MIT",
@@ -67,7 +67,7 @@
67
67
  }
68
68
  },
69
69
  "dependencies": {
70
- "@nebulr-group/bridge-auth-core": "0.4.0-beta.7"
70
+ "@nebulr-group/bridge-auth-core": "0.4.0-beta.8"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@sveltejs/kit": "^2.16.0",