@zuplo/zudoku-plugin-monetization 0.0.44 → 0.0.46
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/index.mjs +251 -60
- package/dist/pricing-ui.d.mts +7 -7
- package/package.json +8 -7
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { D as formatDurationAdjective, E as formatDuration, O as formatDurationInterval, T as formatPrice, _ as formatPlanPrice, a as planHasDefaultTaxBehavior, b as sameEntitlementSet, c as getPlanPriceSchedule, d as PlanEntitlements, f as PlanPhaseHeader, l as PlanPriceTag, o as subscriptionTaxLegendSentence, p as EntitlementList, r as isCustomPlan, t as PricingTable, u as PlanPriceSchedule, w as formatMinorCurrencyAmount, x as categorizeRateCards, y as comparePlanEntitlements } from "./PricingTable-WkG2n7V-.mjs";
|
|
2
2
|
import { cn, createPlugin, joinUrl, throwIfProblemJson } from "zudoku";
|
|
3
|
-
import { AlertTriangleIcon, ArrowDownIcon, ArrowLeftRightIcon, ArrowUpIcon, CalendarIcon, CheckCheckIcon, CheckIcon, ChevronDownIcon, CircleSlashIcon, ClockIcon, CreditCardIcon, Grid2x2XIcon, InfoIcon, Loader2Icon, LockIcon, MoreVerticalIcon, RefreshCcw, RefreshCwIcon, Settings, ShieldIcon, StarsIcon, Trash2Icon, XIcon } from "zudoku/icons";
|
|
4
|
-
import { Link, Outlet, useLocation, useNavigate, useSearchParams } from "zudoku/router";
|
|
3
|
+
import { AlertTriangleIcon, ArrowDownIcon, ArrowLeftRightIcon, ArrowUpIcon, BadgePercentIcon, CalendarIcon, CheckCheckIcon, CheckIcon, ChevronDownIcon, CircleSlashIcon, ClockIcon, CreditCardIcon, Grid2x2XIcon, HistoryIcon, InfoIcon, Loader2Icon, LockIcon, MoreVerticalIcon, RefreshCcw, RefreshCwIcon, Settings, ShieldIcon, StarsIcon, Trash2Icon, XIcon } from "zudoku/icons";
|
|
4
|
+
import { Link, Navigate, Outlet, useLocation, useNavigate, useSearchParams } from "zudoku/router";
|
|
5
5
|
import { useAuth, useZudoku } from "zudoku/hooks";
|
|
6
6
|
import { QueryClient, QueryClientProvider, queryOptions, useMutation, useQuery, useQueryClient, useSuspenseQuery } from "zudoku/react-query";
|
|
7
7
|
import { Button, ClientOnly, Head, Heading, Link as Link$1, Slot } from "zudoku/components";
|
|
@@ -98,7 +98,8 @@ const queryClient = new QueryClient({ defaultOptions: {
|
|
|
98
98
|
...q.meta?.request?.headers
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
|
-
const
|
|
101
|
+
const queryContext = q.meta?.context;
|
|
102
|
+
const response = await fetch(queryContext?.getAuthState().isAuthenticated ? await queryContext.signRequest(request) : request);
|
|
102
103
|
await throwIfProblemJson(response);
|
|
103
104
|
if (!response.ok) throw new Error("Failed to fetch request");
|
|
104
105
|
return response.json();
|
|
@@ -128,7 +129,8 @@ const queryClient = new QueryClient({ defaultOptions: {
|
|
|
128
129
|
...init.headers
|
|
129
130
|
}
|
|
130
131
|
});
|
|
131
|
-
const
|
|
132
|
+
const mutationContext = m.meta?.context;
|
|
133
|
+
const response = await fetch(mutationContext?.getAuthState().isAuthenticated ? await mutationContext.signRequest(request) : request);
|
|
132
134
|
await throwIfProblemJson(response);
|
|
133
135
|
if (!response.ok) {
|
|
134
136
|
const errorText = await response.text();
|
|
@@ -451,17 +453,13 @@ const useUrlUtils = () => {
|
|
|
451
453
|
};
|
|
452
454
|
//#endregion
|
|
453
455
|
//#region src/pages/CheckoutPage.tsx
|
|
454
|
-
const
|
|
455
|
-
const [searchParams] = useSearchParams();
|
|
456
|
-
const planId = searchParams.get("planId");
|
|
456
|
+
const CheckoutRedirect = ({ planId }) => {
|
|
457
457
|
const zudoku = useZudoku();
|
|
458
458
|
const auth = useAuth();
|
|
459
459
|
const { generateUrl } = useUrlUtils();
|
|
460
|
-
const deploymentName = useDeploymentName();
|
|
461
|
-
if (!planId) throw new Error(`missing planId in URL`);
|
|
462
460
|
const checkoutLink = useQuery({
|
|
463
461
|
queryKey: [
|
|
464
|
-
`/v3/zudoku-metering/${
|
|
462
|
+
`/v3/zudoku-metering/${useDeploymentName()}/stripe/checkout`,
|
|
465
463
|
planId,
|
|
466
464
|
auth.profile?.sub
|
|
467
465
|
],
|
|
@@ -499,6 +497,15 @@ const CheckoutPage = () => {
|
|
|
499
497
|
})
|
|
500
498
|
});
|
|
501
499
|
};
|
|
500
|
+
const CheckoutPage = () => {
|
|
501
|
+
const [searchParams] = useSearchParams();
|
|
502
|
+
const planId = searchParams.get("planId");
|
|
503
|
+
if (!planId) return /* @__PURE__ */ jsx(Navigate, {
|
|
504
|
+
to: "/pricing",
|
|
505
|
+
replace: true
|
|
506
|
+
});
|
|
507
|
+
return /* @__PURE__ */ jsx(CheckoutRedirect, { planId });
|
|
508
|
+
};
|
|
502
509
|
//#endregion
|
|
503
510
|
//#region src/pages/ManagePaymentPage.tsx
|
|
504
511
|
const ManagePaymentPage = () => {
|
|
@@ -1034,6 +1041,24 @@ const useSubscriptions = () => {
|
|
|
1034
1041
|
});
|
|
1035
1042
|
};
|
|
1036
1043
|
//#endregion
|
|
1044
|
+
//#region src/hooks/usePendingCredits.ts
|
|
1045
|
+
/**
|
|
1046
|
+
* Fetch the operator-applied usage credits for a subscription, via the Zuplo
|
|
1047
|
+
* metering `.../pending-credits` endpoint. Failures are swallowed (`retry: false`,
|
|
1048
|
+
* `throwOnError: false`) so the usage page never breaks when credits are
|
|
1049
|
+
* unavailable — the credit banner simply isn't shown.
|
|
1050
|
+
*/
|
|
1051
|
+
const usePendingCredits = (deploymentName, subscriptionId) => {
|
|
1052
|
+
const zudoku = useZudoku();
|
|
1053
|
+
return useQuery({
|
|
1054
|
+
queryKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions/${subscriptionId}/pending-credits`],
|
|
1055
|
+
meta: { context: zudoku },
|
|
1056
|
+
refetchOnWindowFocus: true,
|
|
1057
|
+
retry: false,
|
|
1058
|
+
throwOnError: false
|
|
1059
|
+
});
|
|
1060
|
+
};
|
|
1061
|
+
//#endregion
|
|
1037
1062
|
//#region src/pages/subscriptions/ConfirmDeleteKeyAlert.tsx
|
|
1038
1063
|
const ConfirmDeleteKeyAlert = ({ children, onDelete }) => {
|
|
1039
1064
|
return /* @__PURE__ */ jsxs(AlertDialog, { children: [/* @__PURE__ */ jsx(AlertDialogTrigger, {
|
|
@@ -2142,6 +2167,7 @@ const sectionLabelClassName = "text-base font-semibold tracking-wide mb-3 mt-2";
|
|
|
2142
2167
|
const formatDateTimeRange = (from, to) => `${formatDateTime(from)} – ${formatDateTime(to)}`;
|
|
2143
2168
|
const SubscriptionPlanDetails = ({ subscription }) => {
|
|
2144
2169
|
const { pricing } = useMonetizationConfig();
|
|
2170
|
+
const hasEnded = !!subscription.activeTo && new Date(subscription.activeTo).getTime() < Date.now();
|
|
2145
2171
|
const plan = subscription.plan;
|
|
2146
2172
|
const view = getSubscriptionPlanView(subscription, { units: pricing?.units });
|
|
2147
2173
|
const { priceLabel } = view;
|
|
@@ -2195,10 +2221,10 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2195
2221
|
}) : null] })] }),
|
|
2196
2222
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
|
|
2197
2223
|
className: detailLabelClassName,
|
|
2198
|
-
children: "Current period"
|
|
2224
|
+
children: hasEnded ? "Ended" : "Current period"
|
|
2199
2225
|
}), /* @__PURE__ */ jsx("dd", {
|
|
2200
2226
|
className: "text-foreground",
|
|
2201
|
-
children: subscription.alignment?.currentAlignedBillingPeriod ? formatDateTimeRange(subscription.alignment.currentAlignedBillingPeriod.from, subscription.alignment.currentAlignedBillingPeriod.to) : "—"
|
|
2227
|
+
children: hasEnded ? formatDateTime(subscription.activeTo ?? "") : subscription.alignment?.currentAlignedBillingPeriod ? formatDateTimeRange(subscription.alignment.currentAlignedBillingPeriod.from, subscription.alignment.currentAlignedBillingPeriod.to) : "—"
|
|
2202
2228
|
})] })
|
|
2203
2229
|
]
|
|
2204
2230
|
}),
|
|
@@ -2236,48 +2262,185 @@ const SubscriptionPlanDetails = ({ subscription }) => {
|
|
|
2236
2262
|
});
|
|
2237
2263
|
};
|
|
2238
2264
|
//#endregion
|
|
2265
|
+
//#region src/utils/priceIncludedUnits.ts
|
|
2266
|
+
const partAmount = (part) => {
|
|
2267
|
+
if (!part || part.amount === void 0) return 0;
|
|
2268
|
+
const amount = parseFloat(part.amount);
|
|
2269
|
+
return Number.isFinite(amount) ? amount : Number.POSITIVE_INFINITY;
|
|
2270
|
+
};
|
|
2271
|
+
const priceIncludedUnits = (price) => {
|
|
2272
|
+
if (!price) return 0;
|
|
2273
|
+
if (price.type === "flat" || price.type === "package" || price.type === "dynamic") return 0;
|
|
2274
|
+
if (price.tiers?.length) {
|
|
2275
|
+
if ((price.mode ?? "graduated") !== "graduated") return 0;
|
|
2276
|
+
const sorted = [...price.tiers].sort((a, b) => {
|
|
2277
|
+
if (a.upToAmount === void 0) return 1;
|
|
2278
|
+
if (b.upToAmount === void 0) return -1;
|
|
2279
|
+
return Number(a.upToAmount) - Number(b.upToAmount);
|
|
2280
|
+
});
|
|
2281
|
+
let free = 0;
|
|
2282
|
+
for (const [index, tier] of sorted.entries()) {
|
|
2283
|
+
if (partAmount(tier.unitPrice) > 0) break;
|
|
2284
|
+
if (index > 0 && partAmount(tier.flatPrice) > 0) break;
|
|
2285
|
+
if (tier.upToAmount === void 0) return Number.POSITIVE_INFINITY;
|
|
2286
|
+
const bound = Number(tier.upToAmount);
|
|
2287
|
+
if (!Number.isFinite(bound) || bound <= free) break;
|
|
2288
|
+
free = bound;
|
|
2289
|
+
}
|
|
2290
|
+
return free;
|
|
2291
|
+
}
|
|
2292
|
+
if (price.type === "unit") {
|
|
2293
|
+
const amount = parseFloat(price.amount ?? "");
|
|
2294
|
+
return Number.isFinite(amount) && amount === 0 ? Number.POSITIVE_INFINITY : 0;
|
|
2295
|
+
}
|
|
2296
|
+
return 0;
|
|
2297
|
+
};
|
|
2298
|
+
//#endregion
|
|
2299
|
+
//#region src/pages/subscriptions/deriveUsageView.ts
|
|
2300
|
+
const formatAmount = (amount) => {
|
|
2301
|
+
const value = parseFloat(amount ?? "");
|
|
2302
|
+
return Number.isFinite(value) ? `$${value.toFixed(2)}` : void 0;
|
|
2303
|
+
};
|
|
2304
|
+
const pluralizeUnit = (unitName) => unitName.endsWith("s") ? unitName : `${unitName}s`;
|
|
2305
|
+
/** The label for what additional usage costs, when the shape has one number. */
|
|
2306
|
+
const rateLabelFor = (price, unitName) => {
|
|
2307
|
+
if (!price) return void 0;
|
|
2308
|
+
if (price.type === "unit") {
|
|
2309
|
+
const amount = formatAmount(price.amount);
|
|
2310
|
+
return amount ? `${amount}/${unitName}` : void 0;
|
|
2311
|
+
}
|
|
2312
|
+
if (price.type === "tiered") {
|
|
2313
|
+
const amount = formatAmount((price.tiers?.find((t) => !t.upToAmount) ?? price.tiers?.at(-1))?.unitPrice?.amount);
|
|
2314
|
+
return amount ? `${amount}/${unitName}` : void 0;
|
|
2315
|
+
}
|
|
2316
|
+
if (price.type === "package") {
|
|
2317
|
+
const amount = formatAmount(price.amount);
|
|
2318
|
+
if (!amount) return void 0;
|
|
2319
|
+
const size = parseFloat(price.quantityPerUnit ?? "");
|
|
2320
|
+
return Number.isFinite(size) && size > 0 ? `${amount} per ${size.toLocaleString()} ${pluralizeUnit(unitName)}` : amount;
|
|
2321
|
+
}
|
|
2322
|
+
};
|
|
2323
|
+
const NO_CAP = "There is no usage cap.";
|
|
2324
|
+
const deriveUsageView = (meter, item, unitName = "unit") => {
|
|
2325
|
+
const quota = meter.balance + meter.usage - meter.overage;
|
|
2326
|
+
const isSoftLimit = item?.included?.entitlement?.isSoftLimit ?? true;
|
|
2327
|
+
const rateLabel = rateLabelFor(item?.price, unitName);
|
|
2328
|
+
if (!isSoftLimit) return {
|
|
2329
|
+
kind: "capped",
|
|
2330
|
+
usage: meter.usage,
|
|
2331
|
+
quota,
|
|
2332
|
+
remaining: meter.balance,
|
|
2333
|
+
atLimit: meter.usage >= quota,
|
|
2334
|
+
rateLabel
|
|
2335
|
+
};
|
|
2336
|
+
if (!item) return {
|
|
2337
|
+
kind: "meteredGeneric",
|
|
2338
|
+
usage: meter.usage,
|
|
2339
|
+
quota: quota > 0 ? quota : void 0,
|
|
2340
|
+
caption: "Usage is billed per your plan's pricing."
|
|
2341
|
+
};
|
|
2342
|
+
if (!item.price || item.price.type === "flat") return {
|
|
2343
|
+
kind: "meteredGeneric",
|
|
2344
|
+
usage: meter.usage,
|
|
2345
|
+
quota: quota > 0 ? quota : void 0,
|
|
2346
|
+
caption: `Usage doesn't change your bill. ${NO_CAP}`
|
|
2347
|
+
};
|
|
2348
|
+
const isGraduated = item.price.type === "tiered" && (item.price.mode ?? "graduated") === "graduated";
|
|
2349
|
+
const derivable = item.price.type === "unit" || isGraduated;
|
|
2350
|
+
const freeUnits = priceIncludedUnits(item.price);
|
|
2351
|
+
if (derivable) {
|
|
2352
|
+
if (freeUnits === 0) return {
|
|
2353
|
+
kind: "payAsYouGo",
|
|
2354
|
+
usage: meter.usage,
|
|
2355
|
+
caption: `Pay as you go — every ${unitName} is billed; there is no usage cap.`,
|
|
2356
|
+
rateLabel
|
|
2357
|
+
};
|
|
2358
|
+
if (freeUnits === Number.POSITIVE_INFINITY) return {
|
|
2359
|
+
kind: "meteredGeneric",
|
|
2360
|
+
usage: meter.usage,
|
|
2361
|
+
quota: quota > 0 ? quota : void 0,
|
|
2362
|
+
caption: `Included with your plan. ${NO_CAP}`,
|
|
2363
|
+
rateLabel
|
|
2364
|
+
};
|
|
2365
|
+
if (quota > 0) return {
|
|
2366
|
+
kind: "included",
|
|
2367
|
+
usage: meter.usage,
|
|
2368
|
+
included: quota,
|
|
2369
|
+
remaining: meter.balance,
|
|
2370
|
+
overage: meter.overage,
|
|
2371
|
+
rateLabel
|
|
2372
|
+
};
|
|
2373
|
+
return {
|
|
2374
|
+
kind: "meteredGeneric",
|
|
2375
|
+
usage: meter.usage,
|
|
2376
|
+
caption: `The first ${freeUnits.toLocaleString()} ${pluralizeUnit(unitName)} are included; additional usage is billed. ${NO_CAP}`,
|
|
2377
|
+
rateLabel
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
return {
|
|
2381
|
+
kind: "meteredGeneric",
|
|
2382
|
+
usage: meter.usage,
|
|
2383
|
+
quota: quota > 0 ? quota : void 0,
|
|
2384
|
+
caption: `Usage is billed per your plan's pricing. ${NO_CAP}`,
|
|
2385
|
+
rateLabel
|
|
2386
|
+
};
|
|
2387
|
+
};
|
|
2388
|
+
//#endregion
|
|
2239
2389
|
//#region src/pages/subscriptions/Usage.tsx
|
|
2240
2390
|
const isMeteredEntitlement = (entitlement) => {
|
|
2241
2391
|
return "balance" in entitlement;
|
|
2242
2392
|
};
|
|
2243
|
-
const UsageItem = ({ meter, item, subscription, featureKey }) => {
|
|
2393
|
+
const UsageItem = ({ meter, item, subscription, featureKey, pendingCredit }) => {
|
|
2394
|
+
const { pricing } = useMonetizationConfig();
|
|
2244
2395
|
const cadence = item?.billingCadence ?? subscription?.billingCadence;
|
|
2245
2396
|
const billingPeriod = cadence ? formatDurationAdjective(cadence) : "monthly";
|
|
2246
|
-
const
|
|
2247
|
-
const
|
|
2248
|
-
const
|
|
2249
|
-
const
|
|
2250
|
-
|
|
2251
|
-
|
|
2397
|
+
const view = deriveUsageView(meter, item, pricing?.units?.[item?.key ?? ""] ?? pricing?.units?.[featureKey] ?? "unit");
|
|
2398
|
+
const atHardLimit = view.kind === "capped" && view.atLimit;
|
|
2399
|
+
const overIncluded = view.kind === "included" && view.overage > 0;
|
|
2400
|
+
const upgradeAction = (variant) => subscription && /* @__PURE__ */ jsx(AlertAction, { children: /* @__PURE__ */ jsx(SwitchPlanModal, {
|
|
2401
|
+
subscription,
|
|
2402
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
2403
|
+
variant,
|
|
2404
|
+
size: "xs",
|
|
2405
|
+
children: [/* @__PURE__ */ jsx(ArrowUpIcon, {}), "Upgrade"]
|
|
2406
|
+
})
|
|
2407
|
+
}) });
|
|
2252
2408
|
return /* @__PURE__ */ jsxs(Card, {
|
|
2253
|
-
className: cn(
|
|
2409
|
+
className: cn(atHardLimit && "border-destructive bg-destructive/5"),
|
|
2254
2410
|
children: [/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2255
|
-
|
|
2256
|
-
variant: "destructive",
|
|
2411
|
+
pendingCredit && /* @__PURE__ */ jsxs(Alert, {
|
|
2257
2412
|
className: "mb-4",
|
|
2258
2413
|
children: [
|
|
2259
|
-
/* @__PURE__ */ jsx(
|
|
2414
|
+
/* @__PURE__ */ jsx(BadgePercentIcon, { className: "size-4 text-green-600 shrink-0" }),
|
|
2415
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "Usage credit applied" }),
|
|
2416
|
+
/* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
2417
|
+
"A credit of ",
|
|
2418
|
+
pendingCredit.units.toLocaleString(),
|
|
2419
|
+
" ",
|
|
2420
|
+
pendingCredit.units === 1 ? "unit" : "units",
|
|
2421
|
+
" applies to this billing period and will be deducted from your next invoice automatically."
|
|
2422
|
+
] })
|
|
2423
|
+
]
|
|
2424
|
+
}),
|
|
2425
|
+
overIncluded && /* @__PURE__ */ jsxs(Alert, {
|
|
2426
|
+
variant: "warning",
|
|
2427
|
+
className: "mb-4",
|
|
2428
|
+
children: [
|
|
2429
|
+
/* @__PURE__ */ jsx(AlertTriangleIcon, { className: "size-4 shrink-0" }),
|
|
2260
2430
|
/* @__PURE__ */ jsxs(AlertTitle, { children: [
|
|
2261
|
-
"You've
|
|
2431
|
+
"You've used your included ",
|
|
2262
2432
|
billingPeriod,
|
|
2263
|
-
"
|
|
2433
|
+
" usage"
|
|
2264
2434
|
] }),
|
|
2265
2435
|
/* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
2266
|
-
"Additional usage is
|
|
2267
|
-
|
|
2268
|
-
". Upgrade to a higher plan for more usage."
|
|
2436
|
+
"Additional usage is billed",
|
|
2437
|
+
view.rateLabel ? ` at ${view.rateLabel}` : "",
|
|
2438
|
+
". Upgrade to a higher plan for more included usage."
|
|
2269
2439
|
] }),
|
|
2270
|
-
|
|
2271
|
-
subscription,
|
|
2272
|
-
children: /* @__PURE__ */ jsxs(Button, {
|
|
2273
|
-
variant: "destructive",
|
|
2274
|
-
size: "xs",
|
|
2275
|
-
children: [/* @__PURE__ */ jsx(ArrowUpIcon, {}), "Upgrade"]
|
|
2276
|
-
})
|
|
2277
|
-
}) })
|
|
2440
|
+
upgradeAction("outline")
|
|
2278
2441
|
]
|
|
2279
2442
|
}),
|
|
2280
|
-
|
|
2443
|
+
atHardLimit && /* @__PURE__ */ jsxs(Alert, {
|
|
2281
2444
|
variant: "destructive",
|
|
2282
2445
|
className: "mb-4",
|
|
2283
2446
|
children: [
|
|
@@ -2288,58 +2451,79 @@ const UsageItem = ({ meter, item, subscription, featureKey }) => {
|
|
|
2288
2451
|
" limit"
|
|
2289
2452
|
] }),
|
|
2290
2453
|
/* @__PURE__ */ jsx(AlertDescription, { children: "Requests beyond your quota are blocked. Upgrade to a higher plan for more usage." }),
|
|
2291
|
-
|
|
2292
|
-
subscription,
|
|
2293
|
-
children: /* @__PURE__ */ jsxs(Button, {
|
|
2294
|
-
variant: "destructive",
|
|
2295
|
-
size: "xs",
|
|
2296
|
-
children: [/* @__PURE__ */ jsx(ArrowUpIcon, {}), "Upgrade"]
|
|
2297
|
-
})
|
|
2298
|
-
}) })
|
|
2454
|
+
upgradeAction("destructive")
|
|
2299
2455
|
]
|
|
2300
2456
|
}),
|
|
2301
2457
|
/* @__PURE__ */ jsx(CardTitle, { children: item?.name ?? featureKey })
|
|
2302
|
-
] }), /* @__PURE__ */
|
|
2458
|
+
] }), /* @__PURE__ */ jsx(CardContent, {
|
|
2303
2459
|
className: "space-y-2",
|
|
2304
|
-
children: [
|
|
2460
|
+
children: view.kind === "capped" || view.kind === "included" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2305
2461
|
/* @__PURE__ */ jsxs("div", {
|
|
2306
2462
|
className: "flex items-center justify-between text-sm",
|
|
2307
2463
|
children: [/* @__PURE__ */ jsx("div", {
|
|
2308
2464
|
className: "flex flex-col gap-2 mb-2",
|
|
2309
2465
|
children: /* @__PURE__ */ jsxs("span", {
|
|
2310
|
-
className: cn(
|
|
2466
|
+
className: cn(atHardLimit && "text-red-600 font-medium"),
|
|
2311
2467
|
children: [
|
|
2312
|
-
|
|
2468
|
+
view.usage.toLocaleString(),
|
|
2313
2469
|
" used",
|
|
2314
|
-
|
|
2470
|
+
view.kind === "included" && view.overage > 0 && /* @__PURE__ */ jsxs("span", {
|
|
2315
2471
|
className: "ml-1 text-xs",
|
|
2316
2472
|
children: [
|
|
2317
2473
|
"(+",
|
|
2318
|
-
|
|
2474
|
+
view.overage.toLocaleString(),
|
|
2319
2475
|
" overage)"
|
|
2320
2476
|
]
|
|
2321
2477
|
})
|
|
2322
2478
|
]
|
|
2323
2479
|
})
|
|
2324
|
-
}), /* @__PURE__ */
|
|
2480
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2325
2481
|
className: "text-foreground font-medium",
|
|
2326
|
-
children:
|
|
2482
|
+
children: view.kind === "capped" ? `${view.quota.toLocaleString()} limit` : `${view.included.toLocaleString()} included`
|
|
2327
2483
|
})]
|
|
2328
2484
|
}),
|
|
2329
2485
|
/* @__PURE__ */ jsx(Progress, {
|
|
2330
|
-
value: Math.min(100,
|
|
2331
|
-
className: cn("mb-3 h-2",
|
|
2486
|
+
value: Math.min(100, (view.kind === "capped" ? view.quota : view.included) > 0 ? view.usage / (view.kind === "capped" ? view.quota : view.included) * 100 : 100),
|
|
2487
|
+
className: cn("mb-3 h-2", atHardLimit && "bg-destructive")
|
|
2332
2488
|
}),
|
|
2333
2489
|
/* @__PURE__ */ jsxs("p", {
|
|
2334
2490
|
className: "text-xs text-muted-foreground",
|
|
2335
|
-
children: [
|
|
2491
|
+
children: [
|
|
2492
|
+
view.remaining.toLocaleString(),
|
|
2493
|
+
view.kind === "included" ? " included" : "",
|
|
2494
|
+
" remaining this billing period"
|
|
2495
|
+
]
|
|
2336
2496
|
})
|
|
2337
|
-
]
|
|
2497
|
+
] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
2498
|
+
className: "flex items-center justify-between text-sm",
|
|
2499
|
+
children: [/* @__PURE__ */ jsxs("span", { children: [view.usage.toLocaleString(), " used this billing period"] }), view.kind === "meteredGeneric" && view.quota !== void 0 ? /* @__PURE__ */ jsxs("span", {
|
|
2500
|
+
className: "text-foreground font-medium",
|
|
2501
|
+
children: [view.quota.toLocaleString(), " quota"]
|
|
2502
|
+
}) : view.rateLabel && /* @__PURE__ */ jsx("span", {
|
|
2503
|
+
className: "text-muted-foreground",
|
|
2504
|
+
children: view.rateLabel
|
|
2505
|
+
})]
|
|
2506
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
2507
|
+
className: "text-xs text-muted-foreground",
|
|
2508
|
+
children: view.caption
|
|
2509
|
+
})] })
|
|
2338
2510
|
})]
|
|
2339
2511
|
});
|
|
2340
2512
|
};
|
|
2341
|
-
const Usage = ({ usage, isFetching, currentItems, subscription, isPendingFirstPayment }) => {
|
|
2513
|
+
const Usage = ({ usage, isFetching, currentItems, subscription, isPendingFirstPayment, pendingCredits }) => {
|
|
2342
2514
|
const hasUsage = Object.values(usage.entitlements).some((value) => isMeteredEntitlement(value));
|
|
2515
|
+
if (!!subscription?.activeTo && new Date(subscription.activeTo).getTime() < Date.now()) return /* @__PURE__ */ jsxs("div", {
|
|
2516
|
+
className: "space-y-4",
|
|
2517
|
+
children: [/* @__PURE__ */ jsx(Heading, {
|
|
2518
|
+
level: 3,
|
|
2519
|
+
children: "Usage"
|
|
2520
|
+
}), /* @__PURE__ */ jsxs(Alert, { children: [
|
|
2521
|
+
/* @__PURE__ */ jsx(HistoryIcon, { className: "size-4 shrink-0" }),
|
|
2522
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "This subscription has ended" }),
|
|
2523
|
+
/* @__PURE__ */ jsx(AlertDescription, { children: "Usage history isn't available yet." })
|
|
2524
|
+
] })]
|
|
2525
|
+
});
|
|
2526
|
+
const creditByFeature = new Map((pendingCredits ?? []).map((credit) => [credit.featureKey, credit]));
|
|
2343
2527
|
return /* @__PURE__ */ jsxs("div", {
|
|
2344
2528
|
className: "space-y-4",
|
|
2345
2529
|
children: [
|
|
@@ -2378,7 +2562,8 @@ const Usage = ({ usage, isFetching, currentItems, subscription, isPendingFirstPa
|
|
|
2378
2562
|
featureKey: key,
|
|
2379
2563
|
meter: { ...value },
|
|
2380
2564
|
subscription,
|
|
2381
|
-
item: currentItems?.find((item) => item.featureKey === key)
|
|
2565
|
+
item: currentItems?.find((item) => item.featureKey === key),
|
|
2566
|
+
pendingCredit: creditByFeature.get(key)
|
|
2382
2567
|
}, key) : []) : !isFetching && !subscription?.annotations?.["subscription.previous.id"] ? /* @__PURE__ */ jsxs(Alert, {
|
|
2383
2568
|
variant: "warning",
|
|
2384
2569
|
children: [
|
|
@@ -2401,6 +2586,7 @@ const ActiveSubscription = ({ subscription, deploymentName }) => {
|
|
|
2401
2586
|
refetchOnWindowFocus: true,
|
|
2402
2587
|
meta: { context: zudoku }
|
|
2403
2588
|
});
|
|
2589
|
+
const pendingCreditsQuery = usePendingCredits(deploymentName, subscription.id);
|
|
2404
2590
|
const isPendingFirstPayment = usageQuery.data.paymentStatus.isFirstPayment === true && usageQuery.data.paymentStatus.status !== "paid" && usageQuery.data.paymentStatus.status !== "not_required";
|
|
2405
2591
|
const activePhase = getActivePhase(subscription);
|
|
2406
2592
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
@@ -2419,7 +2605,8 @@ const ActiveSubscription = ({ subscription, deploymentName }) => {
|
|
|
2419
2605
|
usage: usageQuery.data,
|
|
2420
2606
|
isFetching: usageQuery.isFetching,
|
|
2421
2607
|
subscription,
|
|
2422
|
-
isPendingFirstPayment
|
|
2608
|
+
isPendingFirstPayment,
|
|
2609
|
+
pendingCredits: pendingCreditsQuery.data?.pendingCredits
|
|
2423
2610
|
}),
|
|
2424
2611
|
subscription?.consumer?.apiKeys && /* @__PURE__ */ jsx(ApiKeysList, {
|
|
2425
2612
|
isPendingFirstPayment,
|
|
@@ -2565,7 +2752,11 @@ const zuploMonetizationPlugin = createPlugin((options = {}) => ({
|
|
|
2565
2752
|
queryClient.prefetchQuery(pricingPageQuery(context));
|
|
2566
2753
|
if (context.getAuthState().isAuthenticated) queryClient.prefetchQuery(subscriptionsQuery(context));
|
|
2567
2754
|
},
|
|
2755
|
+
events: { auth: ({ prev, next }) => {
|
|
2756
|
+
if (prev.isAuthenticated && !next.isAuthenticated) queryClient.removeQueries();
|
|
2757
|
+
} },
|
|
2568
2758
|
getIdentities: async (context) => {
|
|
2759
|
+
if (!context.getAuthState().isAuthenticated) return [];
|
|
2569
2760
|
return (await queryClient.fetchQuery(subscriptionsQuery(context))).items.flatMap((sub) => sub.status !== "active" ? [] : sub.consumer.apiKeys.flatMap((apiKey) => apiKey.expiresOn && new Date(apiKey.expiresOn) < /* @__PURE__ */ new Date() ? [] : {
|
|
2570
2761
|
label: `${sub.name} (****${apiKey.key.slice(-5)})`,
|
|
2571
2762
|
id: apiKey.id,
|
package/dist/pricing-ui.d.mts
CHANGED
|
@@ -139,7 +139,7 @@ declare const FeatureItem: ({
|
|
|
139
139
|
}: {
|
|
140
140
|
feature: Feature;
|
|
141
141
|
className?: string;
|
|
142
|
-
}) => import("react
|
|
142
|
+
}) => import("react").JSX.Element;
|
|
143
143
|
//#endregion
|
|
144
144
|
//#region src/pricing-ui/PlanEntitlements.d.ts
|
|
145
145
|
/**
|
|
@@ -161,7 +161,7 @@ declare const PlanEntitlements: ({
|
|
|
161
161
|
billingCadence?: string;
|
|
162
162
|
units?: Record<string, string>;
|
|
163
163
|
itemClassName?: string;
|
|
164
|
-
}) => import("react
|
|
164
|
+
}) => import("react").JSX.Element;
|
|
165
165
|
//#endregion
|
|
166
166
|
//#region src/utils/formatPlanPrice.d.ts
|
|
167
167
|
type PlanPriceLabel = {
|
|
@@ -225,7 +225,7 @@ declare const PlanPriceSchedule: ({
|
|
|
225
225
|
billingCadence?: string;
|
|
226
226
|
size?: "sm" | "lg";
|
|
227
227
|
className?: string;
|
|
228
|
-
}) => import("react
|
|
228
|
+
}) => import("react").JSX.Element;
|
|
229
229
|
//#endregion
|
|
230
230
|
//#region src/pricing-ui/PlanPriceTag.d.ts
|
|
231
231
|
/**
|
|
@@ -253,7 +253,7 @@ declare const PlanPriceTag: ({
|
|
|
253
253
|
billingCadence?: string;
|
|
254
254
|
description?: boolean;
|
|
255
255
|
size?: "inline" | "lg";
|
|
256
|
-
}) => import("react
|
|
256
|
+
}) => import("react").JSX.Element;
|
|
257
257
|
//#endregion
|
|
258
258
|
//#region src/pricing-ui/PricingCard.d.ts
|
|
259
259
|
type PricingCardProps = {
|
|
@@ -269,7 +269,7 @@ declare const PricingCard: ({
|
|
|
269
269
|
units,
|
|
270
270
|
action,
|
|
271
271
|
className
|
|
272
|
-
}: PricingCardProps) => import("react
|
|
272
|
+
}: PricingCardProps) => import("react").JSX.Element | null;
|
|
273
273
|
//#endregion
|
|
274
274
|
//#region src/pricing-ui/PricingTable.d.ts
|
|
275
275
|
type PricingTableProps = {
|
|
@@ -319,7 +319,7 @@ declare const PricingTable: ({
|
|
|
319
319
|
showTaxLegend,
|
|
320
320
|
className,
|
|
321
321
|
cardClassName
|
|
322
|
-
}: PricingTableProps) => import("react
|
|
322
|
+
}: PricingTableProps) => import("react").JSX.Element;
|
|
323
323
|
//#endregion
|
|
324
324
|
//#region src/pricing-ui/QuotaItem.d.ts
|
|
325
325
|
declare const QuotaItem: ({
|
|
@@ -328,7 +328,7 @@ declare const QuotaItem: ({
|
|
|
328
328
|
}: {
|
|
329
329
|
quota: Quota;
|
|
330
330
|
className?: string;
|
|
331
|
-
}) => import("react
|
|
331
|
+
}) => import("react").JSX.Element;
|
|
332
332
|
//#endregion
|
|
333
333
|
//#region src/utils/categorizeRateCards.d.ts
|
|
334
334
|
declare const categorizeRateCards: (rateCards: RateCard[], options?: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zuplo/zudoku-plugin-monetization",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.46",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zuplo/zudoku",
|
|
@@ -31,13 +31,14 @@
|
|
|
31
31
|
"@testing-library/dom": "10.4.1",
|
|
32
32
|
"@testing-library/jest-dom": "6.9.1",
|
|
33
33
|
"@testing-library/react": "16.3.2",
|
|
34
|
-
"@types/react": "19.2.
|
|
34
|
+
"@types/react": "19.2.17",
|
|
35
35
|
"@types/react-dom": "19.2.3",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"react
|
|
36
|
+
"@typescript/native-preview": "7.0.0-dev.20260624.1",
|
|
37
|
+
"happy-dom": "20.10.2",
|
|
38
|
+
"react": "19.2.7",
|
|
39
|
+
"react-dom": "19.2.7",
|
|
39
40
|
"tsdown": "0.22.0",
|
|
40
|
-
"zudoku": "0.
|
|
41
|
+
"zudoku": "0.82.3"
|
|
41
42
|
},
|
|
42
43
|
"peerDependencies": {
|
|
43
44
|
"react": ">=19.2.0",
|
|
@@ -52,6 +53,6 @@
|
|
|
52
53
|
"scripts": {
|
|
53
54
|
"build": "tsdown",
|
|
54
55
|
"dev": "tsdown --watch",
|
|
55
|
-
"typecheck": "
|
|
56
|
+
"typecheck": "tsgo --project tsconfig.json"
|
|
56
57
|
}
|
|
57
58
|
}
|