Skip to content

Commit fd61318

Browse files
authored
Merge branch 'main' into saminacodes-patch-1
2 parents 368c29d + f2e61b8 commit fd61318

File tree

70 files changed

+4518
-3380
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+4518
-3380
lines changed

.changeset/thin-lines-cover.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@thirdweb-dev/service-utils": patch
3+
"thirdweb": patch
4+
---
5+
6+
update dependencies

apps/dashboard/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,6 @@ ANALYTICS_SERVICE_URL=""
102102

103103
# Required for Nebula Chat
104104
NEXT_PUBLIC_NEBULA_URL=""
105+
106+
# required for billing parts of the dashboard (team -> settings -> billing / invoices)
107+
STRIPE_SECRET_KEY=""

apps/dashboard/.storybook/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,8 @@ const config: StorybookConfig = {
2727
},
2828
},
2929
staticDirs: ["../public"],
30+
features: {
31+
experimentalRSC: true,
32+
},
3033
};
3134
export default config;

apps/dashboard/package.json

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@
4545
"@radix-ui/react-slot": "^1.1.2",
4646
"@radix-ui/react-switch": "^1.1.3",
4747
"@radix-ui/react-tooltip": "1.1.8",
48-
"@sentry/nextjs": "9.3.0",
48+
"@sentry/nextjs": "9.5.0",
4949
"@shazow/whatsabi": "0.20.0",
50-
"@tanstack/react-query": "5.67.1",
50+
"@tanstack/react-query": "5.67.3",
5151
"@tanstack/react-table": "^8.21.2",
5252
"@thirdweb-dev/service-utils": "workspace:*",
5353
"@vercel/functions": "2.0.0",
@@ -61,18 +61,18 @@
6161
"date-fns": "4.1.0",
6262
"fetch-event-stream": "0.1.5",
6363
"flat": "^6.0.1",
64-
"framer-motion": "12.4.10",
64+
"framer-motion": "12.5.0",
6565
"fuse.js": "7.1.0",
6666
"idb-keyval": "^6.2.1",
6767
"input-otp": "^1.4.1",
68-
"ioredis": "^5.5.0",
68+
"ioredis": "^5.6.0",
6969
"ipaddr.js": "^2.2.0",
70-
"lucide-react": "0.477.0",
71-
"next": "15.2.0",
70+
"lucide-react": "0.479.0",
71+
"next": "15.2.2",
7272
"next-plausible": "^3.12.4",
73-
"next-themes": "^0.4.4",
73+
"next-themes": "^0.4.6",
7474
"nextjs-toploader": "^1.6.12",
75-
"nuqs": "^2.4.0",
75+
"nuqs": "^2.4.1",
7676
"p-limit": "^6.2.0",
7777
"papaparse": "^5.5.2",
7878
"pluralize": "^8.0.0",
@@ -94,7 +94,8 @@
9494
"shiki": "1.27.0",
9595
"sonner": "2.0.1",
9696
"spdx-correct": "^3.2.0",
97-
"swagger-ui-react": "^5.20.0",
97+
"stripe": "17.7.0",
98+
"swagger-ui-react": "^5.20.1",
9899
"tailwind-merge": "^2.6.0",
99100
"tailwindcss-animate": "^1.0.7",
100101
"thirdweb": "workspace:*",
@@ -105,20 +106,20 @@
105106
"devDependencies": {
106107
"@chakra-ui/cli": "^2.4.1",
107108
"@chromatic-com/storybook": "3.2.5",
108-
"@next/bundle-analyzer": "15.2.0",
109-
"@next/eslint-plugin-next": "15.2.0",
110-
"@playwright/test": "1.50.1",
111-
"@storybook/addon-essentials": "8.6.3",
112-
"@storybook/addon-interactions": "8.6.3",
113-
"@storybook/addon-links": "8.6.3",
114-
"@storybook/addon-onboarding": "8.6.3",
115-
"@storybook/addon-viewport": "8.6.3",
116-
"@storybook/blocks": "8.6.3",
117-
"@storybook/nextjs": "8.6.3",
118-
"@storybook/react": "8.6.3",
119-
"@storybook/test": "8.6.3",
109+
"@next/bundle-analyzer": "15.2.2",
110+
"@next/eslint-plugin-next": "15.2.2",
111+
"@playwright/test": "1.51.0",
112+
"@storybook/addon-essentials": "8.6.4",
113+
"@storybook/addon-interactions": "8.6.4",
114+
"@storybook/addon-links": "8.6.4",
115+
"@storybook/addon-onboarding": "8.6.4",
116+
"@storybook/addon-viewport": "8.6.4",
117+
"@storybook/blocks": "8.6.4",
118+
"@storybook/nextjs": "8.6.4",
119+
"@storybook/react": "8.6.4",
120+
"@storybook/test": "8.6.4",
120121
"@types/color": "4.2.0",
121-
"@types/node": "22.13.9",
122+
"@types/node": "22.13.10",
122123
"@types/papaparse": "^5.3.15",
123124
"@types/pluralize": "^0.0.33",
124125
"@types/qrcode": "^1.5.5",
@@ -129,16 +130,16 @@
129130
"@types/swagger-ui-react": "^4.19.0",
130131
"@typescript-eslint/eslint-plugin": "7.14.1",
131132
"@typescript-eslint/parser": "7.14.1",
132-
"autoprefixer": "^10.4.19",
133+
"autoprefixer": "^10.4.21",
133134
"checkly": "5.0.1",
134135
"eslint": "8.57.0",
135136
"eslint-config-biome": "1.9.4",
136137
"eslint-plugin-react-compiler": "19.0.0-beta-40c6c23-20250301",
137-
"eslint-plugin-storybook": "0.11.3",
138-
"knip": "5.45.0",
138+
"eslint-plugin-storybook": "0.11.4",
139+
"knip": "5.46.0",
139140
"next-sitemap": "^4.2.3",
140141
"postcss": "8.5.3",
141-
"storybook": "8.6.3",
142+
"storybook": "8.6.4",
142143
"tailwindcss": "3.4.17",
143144
"typescript": "5.8.2"
144145
}

apps/dashboard/src/@/actions/billing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use server";
2-
32
import "server-only";
3+
44
import { API_SERVER_URL } from "@/constants/env";
55
import { getAuthToken } from "../../app/api/lib/getAuthToken";
66
import type { ProductSKU } from "../lib/billing";
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import "server-only";
2+
3+
import Stripe from "stripe";
4+
import type { Team } from "../api/team";
5+
6+
let existingStripe: Stripe | undefined;
7+
8+
function getStripe() {
9+
if (!existingStripe) {
10+
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
11+
12+
if (!STRIPE_SECRET_KEY) {
13+
throw new Error("STRIPE_SECRET_KEY is not set");
14+
}
15+
16+
existingStripe = new Stripe(STRIPE_SECRET_KEY, {
17+
apiVersion: "2025-02-24.acacia",
18+
});
19+
}
20+
21+
return existingStripe;
22+
}
23+
24+
export async function getTeamInvoices(
25+
team: Team,
26+
options?: { cursor?: string },
27+
) {
28+
try {
29+
const customerId = team.stripeCustomerId;
30+
31+
if (!customerId) {
32+
throw new Error("No customer ID found");
33+
}
34+
35+
// Get the list of invoices for the customer
36+
const invoices = await getStripe().invoices.list({
37+
customer: customerId,
38+
limit: 10,
39+
starting_after: options?.cursor,
40+
});
41+
42+
return invoices;
43+
} catch (error) {
44+
console.error("Error fetching billing history:", error);
45+
46+
// If the error is that the customer doesn't exist, return an empty array
47+
// instead of throwing an error
48+
if (
49+
error instanceof Stripe.errors.StripeError &&
50+
error.message.includes("No such customer")
51+
) {
52+
return {
53+
data: [],
54+
has_more: false,
55+
};
56+
}
57+
58+
throw new Error("Failed to fetch billing history");
59+
}
60+
}

apps/dashboard/src/@/api/team.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { API_SERVER_URL, THIRDWEB_API_SECRET } from "@/constants/env";
33
import type { TeamResponse } from "@thirdweb-dev/service-utils";
44
import { getAuthToken } from "../../app/api/lib/getAuthToken";
55

6-
export type Team = TeamResponse;
6+
export type Team = TeamResponse & { stripeCustomerId: string | null };
7+
78
export async function getTeamBySlug(slug: string) {
89
const token = await getAuthToken();
910

apps/dashboard/src/@/components/blocks/code-segment.client.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ export const CodeSegment: React.FC<CodeSegmentProps> = ({
9696
onClick: () => setEnvironment(env.environment),
9797
isActive: activeEnvironment === env.environment,
9898
name: env.title,
99-
isEnabled: true,
10099
}))}
101100
tabClassName="text-sm gap-2 !text-sm"
102101
tabIconClassName="size-4"

apps/dashboard/src/@/components/ui/DatePickerWithRange.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,11 @@ export function DatePickerWithRange(props: {
9191
name: "From",
9292
onClick: () => setScreen("from"),
9393
isActive: screen === "from",
94-
isEnabled: true,
9594
},
9695
{
9796
name: "To",
9897
onClick: () => setScreen("to"),
9998
isActive: screen === "to",
100-
isEnabled: true,
10199
},
102100
]}
103101
/>

apps/dashboard/src/@/components/ui/tabs.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export function TabButtons(props: {
7979
name: React.ReactNode;
8080
onClick: () => void;
8181
isActive: boolean;
82-
isEnabled?: boolean;
82+
isDisabled?: boolean;
8383
icon?: React.FC<{ className?: string }>;
8484
}[];
8585
tabClassName?: string;
@@ -119,11 +119,11 @@ export function TabButtons(props: {
119119
"relative inline-flex h-auto items-center gap-1.5 rounded-lg px-2 font-medium text-sm hover:bg-accent lg:px-3 lg:text-base",
120120
!tab.isActive &&
121121
"text-muted-foreground hover:text-foreground",
122-
!tab.isEnabled && "cursor-not-allowed opacity-50",
122+
tab.isDisabled && "cursor-not-allowed opacity-50",
123123
props.tabClassName,
124124
tab.isActive && props.activeTabClassName,
125125
)}
126-
onClick={tab.isEnabled ? tab.onClick : undefined}
126+
onClick={!tab.isDisabled ? tab.onClick : undefined}
127127
>
128128
{tab.icon && (
129129
<tab.icon className={cn("size-6", props.tabIconClassName)} />

0 commit comments

Comments
 (0)