Skip to content

Commit 532f4d7

Browse files
jnsdlsMananTank
authored andcommitted
add /subscriptions endpoint
1 parent 614649a commit 532f4d7

File tree

7 files changed

+440
-66
lines changed

7 files changed

+440
-66
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { getAuthToken } from "../../app/api/lib/getAuthToken";
2+
import { API_SERVER_URL } from "../constants/env";
3+
4+
// keep in line with product SKUs in the backend
5+
type ProductSKU =
6+
| "plan:starter"
7+
| "plan:growth"
8+
| "plan:custom"
9+
| "product:ecosystem_wallets"
10+
| "product:engine_standard"
11+
| "product:engine_premium"
12+
| "usage:storage"
13+
| "usage:in_app_wallet"
14+
| "usage:aa_sponsorship"
15+
| "usage:aa_sponsorship_op_grant"
16+
| null;
17+
18+
type InvoiceLine = {
19+
// amount for this line item
20+
amount: number;
21+
// statement descriptor
22+
description: string | null;
23+
// the thirdweb product sku or null if it is not recognized
24+
thirdwebSku: ProductSKU | null;
25+
};
26+
27+
type Invoice = {
28+
// total amount excluding tax
29+
amount: number | null;
30+
// the ISO currency code (e.g. USD)
31+
currency: string;
32+
// the line items on the invoice
33+
lines: InvoiceLine[];
34+
};
35+
36+
export type TeamSubscription = {
37+
id: string;
38+
type: "PLAN" | "USAGE" | "PLAN_ADD_ON" | "PRODUCT";
39+
status:
40+
| "incomplete"
41+
| "incomplete_expired"
42+
| "trialing"
43+
| "active"
44+
| "past_due"
45+
| "canceled"
46+
| "unpaid"
47+
| "paused";
48+
currentPeriodStart: string;
49+
currentPeriodEnd: string;
50+
trialStart: string | null;
51+
trialEnd: string | null;
52+
upcomingInvoice: Invoice;
53+
};
54+
55+
export async function getTeamSubscriptions(slug: string) {
56+
const token = await getAuthToken();
57+
58+
if (!token) {
59+
return null;
60+
}
61+
62+
const teamRes = await fetch(
63+
`${API_SERVER_URL}/v1/teams/${slug}/subscriptions`,
64+
{
65+
headers: {
66+
Authorization: `Bearer ${token}`,
67+
},
68+
},
69+
);
70+
if (teamRes.ok) {
71+
return (await teamRes.json())?.result as TeamSubscription[];
72+
}
73+
return null;
74+
}
75+
76+
// util fn:
77+
78+
export function parseThirdwebSKU(sku: ProductSKU) {
79+
if (!sku) {
80+
return null;
81+
}
82+
switch (sku) {
83+
case "plan:starter":
84+
return "Starter Plan";
85+
case "plan:growth":
86+
return "Growth Plan";
87+
case "plan:custom":
88+
return "Custom Plan";
89+
case "product:ecosystem_wallets":
90+
return "Ecosystem Wallets";
91+
case "product:engine_standard":
92+
return "Engine Standard";
93+
case "product:engine_premium":
94+
return "Engine Premium";
95+
case "usage:storage":
96+
return "Storage";
97+
case "usage:in_app_wallet":
98+
return "In-App Wallet";
99+
case "usage:aa_sponsorship":
100+
return "AA Sponsorship";
101+
case "usage:aa_sponsorship_op_grant":
102+
return "AA Sponsorship Op Grant";
103+
default:
104+
return null;
105+
}
106+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import {
3+
createDashboardAccountStub,
4+
teamStub,
5+
teamSubscriptionsStub,
6+
} from "stories/stubs";
7+
import {
8+
BadgeContainer,
9+
mobileViewport,
10+
} from "../../../../../../../../stories/utils";
11+
import { PlanInfoCard } from "./PlanInfoCard";
12+
13+
const meta = {
14+
title: "Billing/PlanInfoCard",
15+
component: Story,
16+
parameters: {
17+
nextjs: {
18+
appDirectory: true,
19+
},
20+
},
21+
} satisfies Meta<typeof Story>;
22+
23+
export default meta;
24+
type Story = StoryObj<typeof meta>;
25+
26+
export const Desktop: Story = {
27+
args: {},
28+
};
29+
30+
export const Mobile: Story = {
31+
args: {},
32+
parameters: {
33+
viewport: mobileViewport("iphone14"),
34+
},
35+
};
36+
37+
function Story() {
38+
const team = teamStub("foo", "growth");
39+
const zeroUsageOnDemandSubs = teamSubscriptionsStub("plan:growth");
40+
const subsWith1Usage = teamSubscriptionsStub("plan:growth", {
41+
storage: {
42+
amount: 10000,
43+
quantity: 4,
44+
},
45+
});
46+
47+
const subsWith4Usage = teamSubscriptionsStub("plan:growth", {
48+
storage: {
49+
amount: 10000,
50+
quantity: 4,
51+
},
52+
aaSponsorshipAmount: {
53+
amount: 7500,
54+
quantity: 4,
55+
},
56+
aaSponsorshipOpGrantAmount: {
57+
amount: 2500,
58+
quantity: 4,
59+
},
60+
inAppWalletAmount: {
61+
amount: 40000,
62+
quantity: 100,
63+
},
64+
});
65+
66+
const account = createDashboardAccountStub("foo");
67+
68+
return (
69+
<div className="container flex max-w-[1130px] flex-col gap-12 lg:p-10">
70+
<BadgeContainer label="On-demand Subscriptions with 0 usage">
71+
<PlanInfoCard
72+
team={team}
73+
subscriptions={zeroUsageOnDemandSubs}
74+
account={account}
75+
/>
76+
</BadgeContainer>
77+
78+
<BadgeContainer label="On-demand Subscriptions with 1 usage">
79+
<PlanInfoCard
80+
team={team}
81+
subscriptions={subsWith1Usage}
82+
account={account}
83+
/>
84+
</BadgeContainer>
85+
86+
<BadgeContainer label="On-demand Subscriptions with 4 usage">
87+
<PlanInfoCard
88+
team={team}
89+
subscriptions={subsWith4Usage}
90+
account={account}
91+
/>
92+
</BadgeContainer>
93+
</div>
94+
);
95+
}

0 commit comments

Comments
 (0)