@zuplo/zudoku-plugin-monetization 0.0.13 → 0.0.15

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.
Files changed (3) hide show
  1. package/LICENSE.md +18 -0
  2. package/dist/index.mjs +79 -49
  3. package/package.json +22 -29
package/LICENSE.md ADDED
@@ -0,0 +1,18 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) Zuplo, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction,
7
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
8
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
17
+ OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/dist/index.mjs CHANGED
@@ -138,7 +138,7 @@ const categorizeRateCards = (rateCards, currency) => {
138
138
  if (!et) continue;
139
139
  if (et.type === "metered" && et.issueAfterReset != null) {
140
140
  let overagePrice;
141
- if (rc.price?.type === "tiered" && rc.price.tiers) {
141
+ if (et.isSoftLimit !== false && rc.price?.type === "tiered" && rc.price.tiers) {
142
142
  const overageTier = rc.price.tiers.find((t) => t.unitPrice?.amount);
143
143
  if (overageTier?.unitPrice) overagePrice = `${formatPrice(parseFloat(overageTier.unitPrice.amount), currency)}/unit`;
144
144
  }
@@ -184,7 +184,11 @@ const getPriceFromPlan = (plan) => {
184
184
 
185
185
  //#endregion
186
186
  //#region src/ZuploMonetizationWrapper.tsx
187
- const BASE_URL = "https://api.zuploedge.com";
187
+ const DEFAULT_GATEWAY_URL = "https://api.zuploedge.com";
188
+ const getBaseUrl = (context) => {
189
+ if (!context) return DEFAULT_GATEWAY_URL;
190
+ return context.env.ZUPLO_GATEWAY_SERVICE_URL || DEFAULT_GATEWAY_URL;
191
+ };
188
192
  const hasVariables = (value) => typeof value === "object" && value != null;
189
193
  const queryClient = new QueryClient({ defaultOptions: {
190
194
  queries: {
@@ -194,7 +198,7 @@ const queryClient = new QueryClient({ defaultOptions: {
194
198
  if (q.queryKey.length === 0) throw new Error("Query key must be a non-empty array");
195
199
  const url = q.queryKey[0];
196
200
  if (!url || typeof url !== "string") throw new Error("URL is required");
197
- const request = new Request(`${BASE_URL}${url}`, {
201
+ const request = new Request(joinUrl(getBaseUrl(q.meta?.context), url), {
198
202
  ...q.meta?.request,
199
203
  headers: {
200
204
  "Content-Type": "application/json",
@@ -221,7 +225,7 @@ const queryClient = new QueryClient({ defaultOptions: {
221
225
  const init = typeof m.meta?.request === "function" ? m.meta.request(variables) : m.meta?.request ?? {};
222
226
  const method = init.method || "POST";
223
227
  const body = init.body ?? (method !== "GET" && method !== "HEAD" ? JSON.stringify(variables) : void 0);
224
- const request = new Request(joinUrl(BASE_URL, url), {
228
+ const request = new Request(joinUrl(getBaseUrl(m.meta?.context), url), {
225
229
  ...init,
226
230
  method,
227
231
  body,
@@ -520,13 +524,18 @@ const CheckoutPage = () => {
520
524
  const ManagePaymentPage = () => {
521
525
  const zudoku = useZudoku();
522
526
  const { generateUrl } = useUrlUtils();
527
+ const deploymentName = useDeploymentName();
528
+ const auth = useAuth();
523
529
  const billingPortal = useQuery({
524
- queryKey: [`/v3/zudoku-metering/${useDeploymentName()}/stripe/portal`],
530
+ queryKey: [`/v3/zudoku-metering/${deploymentName}/stripe/portal`],
525
531
  meta: {
526
532
  context: zudoku,
527
533
  request: {
528
534
  method: "POST",
529
- body: JSON.stringify({ returnURL: generateUrl("/subscriptions") })
535
+ body: JSON.stringify({
536
+ email: auth.profile?.email,
537
+ returnURL: generateUrl("/subscriptions")
538
+ })
530
539
  }
531
540
  }
532
541
  });
@@ -561,7 +570,7 @@ const PricingCard = ({ plan, isPopular = false, isSubscribed = false }) => {
561
570
  const { quotas, features } = categorizeRateCards(defaultPhase.rateCards, plan.currency);
562
571
  const price = getPriceFromPlan(plan);
563
572
  const isFree = price.monthly === 0;
564
- const isCustom = plan.key === "enterprise";
573
+ const isCustom = plan.metadata?.isCustom === true;
565
574
  return /* @__PURE__ */ jsxs("div", {
566
575
  className: cn("relative rounded-lg border p-6 flex flex-col", isPopular && "border-primary border-2"),
567
576
  children: [
@@ -598,7 +607,7 @@ const PricingCard = ({ plan, isPopular = false, isSubscribed = false }) => {
598
607
  children: [formatPrice(price.yearly, plan.currency), "/year"]
599
608
  })] })] })
600
609
  }),
601
- isFree && /* @__PURE__ */ jsx("div", {
610
+ plan.paymentRequired === false && /* @__PURE__ */ jsx("div", {
602
611
  className: "text-sm text-muted-foreground mt-1",
603
612
  children: "No CC required"
604
613
  })
@@ -666,9 +675,9 @@ const PricingPage = ({ subtitle = "See our pricing options and choose the one th
666
675
  }),
667
676
  /* @__PURE__ */ jsx("div", {
668
677
  className: "w-full grid grid-cols-1 sm:grid-cols-[repeat(auto-fit,minmax(300px,max-content))] justify-center gap-6",
669
- children: pricingTable.items.slice(0, 4).map((plan) => /* @__PURE__ */ jsx(PricingCard, {
678
+ children: pricingTable.items.map((plan) => /* @__PURE__ */ jsx(PricingCard, {
670
679
  plan,
671
- isPopular: plan.key === "pro",
680
+ isPopular: plan.metadata?.isMostPopular === true,
672
681
  isSubscribed: subscriptions.items.some((subscription) => ["active", "canceled"].includes(subscription.status))
673
682
  }, plan.id))
674
683
  })
@@ -1541,34 +1550,59 @@ const isMeteredEntitlement = (entitlement) => {
1541
1550
  return "balance" in entitlement;
1542
1551
  };
1543
1552
  const UsageItem = ({ meter, item, subscription }) => {
1553
+ const isSoftLimit = item?.included?.entitlement?.isSoftLimit ?? true;
1544
1554
  const rate = (item?.price?.tiers?.find((t) => !t.upToAmount) ?? item?.price?.tiers?.at(-1))?.unitPrice?.amount;
1555
+ const hasOverage = meter.overage > 0;
1556
+ const limit = meter.balance + meter.usage - meter.overage;
1557
+ const isAtLimit = !isSoftLimit && meter.usage >= limit;
1558
+ const dangerZone = hasOverage || isAtLimit;
1545
1559
  return /* @__PURE__ */ jsxs(Card, {
1546
- className: cn(meter.overage > 0 && "border-destructive bg-destructive/5"),
1547
- children: [/* @__PURE__ */ jsxs(CardHeader, { children: [meter.overage > 0 && /* @__PURE__ */ jsxs(Alert, {
1548
- variant: "destructive",
1549
- className: "mb-4",
1550
- children: [
1551
- /* @__PURE__ */ jsx(AlertTriangleIcon, { className: "size-4 text-red-600 shrink-0" }),
1552
- /* @__PURE__ */ jsx(AlertTitle, { children: "You've exceeded your monthly quota" }),
1553
- /* @__PURE__ */ jsxs(AlertDescription, { children: [
1554
- "Additional usage is being charged at the overage rate",
1555
- rate ? ` ($${Number(rate).toFixed(2)}/call)` : "",
1556
- ". Upgrade to a higher plan for more usage."
1557
- ] }),
1558
- subscription && /* @__PURE__ */ jsx(AlertAction, { children: /* @__PURE__ */ jsx(SwitchPlanModal, {
1559
- subscription,
1560
- children: /* @__PURE__ */ jsxs(Button, {
1561
- variant: "destructive",
1562
- size: "xs",
1563
- children: [/* @__PURE__ */ jsx(ArrowUpIcon, {}), "Upgrade"]
1564
- })
1565
- }) })
1566
- ]
1567
- }), /* @__PURE__ */ jsxs(CardTitle, { children: [
1568
- item?.name ?? "Limit",
1569
- " ",
1570
- item?.price?.amount
1571
- ] })] }), /* @__PURE__ */ jsxs(CardContent, {
1560
+ className: cn(dangerZone && "border-destructive bg-destructive/5"),
1561
+ children: [/* @__PURE__ */ jsxs(CardHeader, { children: [
1562
+ hasOverage && isSoftLimit && /* @__PURE__ */ jsxs(Alert, {
1563
+ variant: "destructive",
1564
+ className: "mb-4",
1565
+ children: [
1566
+ /* @__PURE__ */ jsx(AlertTriangleIcon, { className: "size-4 text-red-600 shrink-0" }),
1567
+ /* @__PURE__ */ jsx(AlertTitle, { children: "You've exceeded your monthly quota" }),
1568
+ /* @__PURE__ */ jsxs(AlertDescription, { children: [
1569
+ "Additional usage is being charged at the overage rate",
1570
+ rate ? ` ($${Number(rate).toFixed(2)}/call)` : "",
1571
+ ". Upgrade to a higher plan for more usage."
1572
+ ] }),
1573
+ subscription && /* @__PURE__ */ jsx(AlertAction, { children: /* @__PURE__ */ jsx(SwitchPlanModal, {
1574
+ subscription,
1575
+ children: /* @__PURE__ */ jsxs(Button, {
1576
+ variant: "destructive",
1577
+ size: "xs",
1578
+ children: [/* @__PURE__ */ jsx(ArrowUpIcon, {}), "Upgrade"]
1579
+ })
1580
+ }) })
1581
+ ]
1582
+ }),
1583
+ isAtLimit && !isSoftLimit && /* @__PURE__ */ jsxs(Alert, {
1584
+ variant: "destructive",
1585
+ className: "mb-4",
1586
+ children: [
1587
+ /* @__PURE__ */ jsx(AlertTriangleIcon, { className: "size-4 text-red-600 shrink-0" }),
1588
+ /* @__PURE__ */ jsx(AlertTitle, { children: "You've reached your monthly limit" }),
1589
+ /* @__PURE__ */ jsx(AlertDescription, { children: "Requests beyond your quota are blocked. Upgrade to a higher plan for more usage." }),
1590
+ subscription && /* @__PURE__ */ jsx(AlertAction, { children: /* @__PURE__ */ jsx(SwitchPlanModal, {
1591
+ subscription,
1592
+ children: /* @__PURE__ */ jsxs(Button, {
1593
+ variant: "destructive",
1594
+ size: "xs",
1595
+ children: [/* @__PURE__ */ jsx(ArrowUpIcon, {}), "Upgrade"]
1596
+ })
1597
+ }) })
1598
+ ]
1599
+ }),
1600
+ /* @__PURE__ */ jsxs(CardTitle, { children: [
1601
+ item?.name ?? "Limit",
1602
+ " ",
1603
+ item?.price?.amount
1604
+ ] })
1605
+ ] }), /* @__PURE__ */ jsxs(CardContent, {
1572
1606
  className: "space-y-2",
1573
1607
  children: [
1574
1608
  /* @__PURE__ */ jsxs("div", {
@@ -1576,11 +1610,11 @@ const UsageItem = ({ meter, item, subscription }) => {
1576
1610
  children: [/* @__PURE__ */ jsx("div", {
1577
1611
  className: "flex flex-col gap-2 mb-2",
1578
1612
  children: /* @__PURE__ */ jsxs("span", {
1579
- className: cn(meter.overage > 0 && "text-red-600 font-medium"),
1613
+ className: cn(dangerZone && "text-red-600 font-medium"),
1580
1614
  children: [
1581
1615
  meter.usage.toLocaleString(),
1582
1616
  " used",
1583
- meter.overage > 0 && /* @__PURE__ */ jsxs("span", {
1617
+ hasOverage && isSoftLimit && /* @__PURE__ */ jsxs("span", {
1584
1618
  className: "ml-1 text-xs",
1585
1619
  children: [
1586
1620
  "(+",
@@ -1592,16 +1626,12 @@ const UsageItem = ({ meter, item, subscription }) => {
1592
1626
  })
1593
1627
  }), /* @__PURE__ */ jsxs("span", {
1594
1628
  className: "text-foreground font-medium",
1595
- children: [
1596
- (meter.balance + meter.usage - meter.overage).toLocaleString(),
1597
- " ",
1598
- "limit"
1599
- ]
1629
+ children: [limit.toLocaleString(), " limit"]
1600
1630
  })]
1601
1631
  }),
1602
1632
  /* @__PURE__ */ jsx(Progress, {
1603
- value: meter.usage / (meter.balance + meter.usage - meter.overage) * 100,
1604
- className: cn("mb-3 h-2", meter.overage > 0 && "bg-red-500")
1633
+ value: Math.min(100, limit > 0 ? meter.usage / limit * 100 : 100),
1634
+ className: cn("mb-3 h-2", dangerZone && "bg-destructive")
1605
1635
  }),
1606
1636
  /* @__PURE__ */ jsxs("p", {
1607
1637
  className: "text-xs text-muted-foreground",
@@ -1611,8 +1641,7 @@ const UsageItem = ({ meter, item, subscription }) => {
1611
1641
  })]
1612
1642
  });
1613
1643
  };
1614
- const Usage = ({ usageQuery, currentItems, subscription, isPendingFirstPayment }) => {
1615
- const usage = usageQuery.data;
1644
+ const Usage = ({ usage, isFetching, currentItems, subscription, isPendingFirstPayment }) => {
1616
1645
  const hasUsage = Object.values(usage.entitlements).some((value) => isMeteredEntitlement(value));
1617
1646
  return /* @__PURE__ */ jsxs("div", {
1618
1647
  className: "space-y-4",
@@ -1652,7 +1681,7 @@ const Usage = ({ usageQuery, currentItems, subscription, isPendingFirstPayment }
1652
1681
  meter: { ...value },
1653
1682
  subscription,
1654
1683
  item: currentItems?.find((item) => item.featureKey === key)
1655
- }, key) : []) : !usageQuery.isFetching && !subscription?.annotations?.["subscription.previous.id"] ? /* @__PURE__ */ jsxs(Alert, {
1684
+ }, key) : []) : !isFetching && !subscription?.annotations?.["subscription.previous.id"] ? /* @__PURE__ */ jsxs(Alert, {
1656
1685
  variant: "warning",
1657
1686
  children: [
1658
1687
  /* @__PURE__ */ jsx(Grid2x2XIcon, {}),
@@ -1693,7 +1722,8 @@ const ActiveSubscription = ({ subscription, deploymentName }) => {
1693
1722
  }),
1694
1723
  /* @__PURE__ */ jsx(Usage, {
1695
1724
  currentItems: activePhase?.items,
1696
- usageQuery,
1725
+ usage: usageQuery.data,
1726
+ isFetching: usageQuery.isFetching,
1697
1727
  subscription,
1698
1728
  isPendingFirstPayment
1699
1729
  }),
package/package.json CHANGED
@@ -1,53 +1,46 @@
1
1
  {
2
2
  "name": "@zuplo/zudoku-plugin-monetization",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/zuplo/zudoku",
7
7
  "directory": "packages/plugin-zuplo-monetization"
8
8
  },
9
9
  "type": "module",
10
- "main": "./src/index.ts",
11
- "types": "./src/index.ts",
10
+ "main": "./dist/index.mjs",
11
+ "types": "./dist/index.d.mts",
12
12
  "exports": {
13
- ".": "./src/index.ts"
13
+ ".": {
14
+ "import": "./dist/index.mjs",
15
+ "types": "./dist/index.d.mts"
16
+ }
14
17
  },
15
18
  "files": [
16
19
  "dist"
17
20
  ],
18
- "scripts": {
19
- "build": "tsdown",
20
- "dev": "tsdown --watch",
21
- "prepublishOnly": "pnpm build"
22
- },
23
- "publishConfig": {
24
- "main": "./dist/index.mjs",
25
- "types": "./dist/index.d.mts",
26
- "exports": {
27
- ".": {
28
- "import": "./dist/index.mjs",
29
- "types": "./dist/index.d.mts"
30
- }
31
- }
32
- },
33
21
  "dependencies": {
34
22
  "tinyduration": "3.4.1"
35
23
  },
36
24
  "devDependencies": {
37
- "@testing-library/dom": "catalog:",
38
- "@testing-library/jest-dom": "catalog:",
39
- "@testing-library/react": "catalog:",
40
- "@types/react": "catalog:",
41
- "@types/react-dom": "catalog:",
42
- "happy-dom": "catalog:",
43
- "react": "catalog:",
44
- "react-dom": "catalog:",
25
+ "@testing-library/dom": "10.4.1",
26
+ "@testing-library/jest-dom": "6.9.1",
27
+ "@testing-library/react": "16.3.2",
28
+ "@types/react": "19.2.14",
29
+ "@types/react-dom": "19.2.3",
30
+ "happy-dom": "20.6.1",
31
+ "react": "19.2.4",
32
+ "react-dom": "19.2.4",
45
33
  "tsdown": "0.20.3",
46
- "zudoku": "workspace:*"
34
+ "zudoku": "0.69.0"
47
35
  },
48
36
  "peerDependencies": {
49
37
  "react": ">=19.2.0",
50
38
  "react-dom": ">=19.2.0",
51
39
  "zudoku": "*"
40
+ },
41
+ "scripts": {
42
+ "build": "tsdown",
43
+ "dev": "tsdown --watch",
44
+ "typecheck": "tsc --project tsconfig.json"
52
45
  }
53
- }
46
+ }