Skip to content

Commit 4d40679

Browse files
authored
Merge branch 'main' into cursor/deprecate-modular-contracts-from-explore-d408
2 parents b7142f2 + 8d3b9c6 commit 4d40679

File tree

551 files changed

+13651
-9468
lines changed

Some content is hidden

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

551 files changed

+13651
-9468
lines changed

.changeset/eighty-rings-think.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Fix native token fallback when insight query fails

.changeset/sad-vans-wait.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Fix Various alignment issues in React Components

apps/dashboard/framer-rewrites.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module.exports = [
99
// -- build category
1010
"/wallets",
1111
"/account-abstraction",
12-
"/universal-bridge",
12+
"/payments",
1313
"/auth",
1414
"/in-app-wallets",
1515
"/transactions",
@@ -62,4 +62,6 @@ module.exports = [
6262
// -- universal bridge landing pages --
6363
"/universal-bridge-regions/:region_slug",
6464
"/enterprise",
65+
"/token",
66+
"/vault",
6567
];

apps/dashboard/redirects.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ async function redirects() {
416416
source: "/connect/account-abstraction",
417417
},
418418
{
419-
destination: "/universal-bridge",
419+
destination: "/payments",
420420
permanent: false,
421421
source: "/connect/universal-bridge",
422422
},
@@ -440,9 +440,19 @@ async function redirects() {
440440
permanent: false,
441441
source: "/rpc-edge",
442442
},
443+
{
444+
destination: "/payments",
445+
permanent: false,
446+
source: "/universal-bridge",
447+
},
443448
...legacyDashboardToTeamRedirects,
444449
...projectPageRedirects,
445450
...teamPageRedirects,
451+
{
452+
source: "/support/:path*",
453+
destination: "/team/~/~/support",
454+
permanent: false,
455+
},
446456
];
447457
}
448458

apps/dashboard/src/app/(app)/login/auth-actions.ts renamed to apps/dashboard/src/@/actions/auth-actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
1212
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
1313
import { API_SERVER_SECRET } from "@/constants/server-envs";
1414
import { isVercel } from "@/utils/vercel";
15-
import { verifyTurnstileToken } from "./verifyTurnstileToken";
15+
import { verifyTurnstileToken } from "../../app/login/verifyTurnstileToken";
1616

1717
export async function getLoginPayload(
1818
params: GenerateLoginPayloadParams,

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

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"use server";
2+
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
3+
import type { SupportTicket } from "../../app/(app)/team/[team_slug]/(team)/~/support/types/tickets";
4+
import { getAuthToken, getAuthTokenWalletAddress } from "./auth-token";
5+
6+
const ESCALATION_FEEDBACK_RATING = 9999;
7+
8+
export async function createSupportTicket(params: {
9+
message: string;
10+
teamSlug: string;
11+
teamId: string;
12+
title: string;
13+
conversationId?: string;
14+
}): Promise<{ data: SupportTicket } | { error: string }> {
15+
const token = await getAuthToken();
16+
if (!token) {
17+
return { error: "No auth token available" };
18+
}
19+
20+
try {
21+
const walletAddress = await getAuthTokenWalletAddress();
22+
23+
const encodedTeamSlug = encodeURIComponent(params.teamSlug);
24+
const apiUrl = `${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${encodedTeamSlug}/support-conversations`;
25+
26+
// Build the payload for creating a conversation
27+
// If the message does not already include wallet address, prepend it
28+
let message = params.message;
29+
if (!message.includes("Wallet address:")) {
30+
message = `Wallet address: ${String(walletAddress || "-")}\n${message}`;
31+
}
32+
33+
const payload = {
34+
markdown: message.trim(),
35+
title: params.title,
36+
};
37+
38+
const body = JSON.stringify(payload);
39+
const headers: Record<string, string> = {
40+
Accept: "application/json",
41+
Authorization: `Bearer ${token}`,
42+
"Content-Type": "application/json",
43+
"Accept-Encoding": "identity",
44+
};
45+
46+
const response = await fetch(apiUrl, {
47+
body,
48+
headers,
49+
method: "POST",
50+
});
51+
52+
if (!response.ok) {
53+
const errorText = await response.text();
54+
return { error: `API Server error: ${response.status} - ${errorText}` };
55+
}
56+
57+
const createdConversation: SupportTicket = await response.json();
58+
59+
// Escalate to SIWA feedback endpoint if conversationId is provided
60+
if (params.conversationId) {
61+
try {
62+
const siwaUrl = process.env.NEXT_PUBLIC_SIWA_URL;
63+
if (siwaUrl) {
64+
await fetch(`${siwaUrl}/v1/chat/feedback`, {
65+
method: "POST",
66+
headers: {
67+
"Content-Type": "application/json",
68+
Authorization: `Bearer ${token}`,
69+
...(params.teamId ? { "x-team-id": params.teamId } : {}),
70+
},
71+
body: JSON.stringify({
72+
conversationId: params.conversationId,
73+
feedbackRating: ESCALATION_FEEDBACK_RATING,
74+
}),
75+
});
76+
}
77+
} catch (error) {
78+
// Log error but don't fail the ticket creation
79+
console.error("Failed to escalate to SIWA feedback:", error);
80+
}
81+
}
82+
83+
return { data: createdConversation };
84+
} catch (error) {
85+
return {
86+
error: `Failed to create support ticket: ${error instanceof Error ? error.message : "Unknown error"}`,
87+
};
88+
}
89+
}
90+
91+
export async function sendMessageToTicket(request: {
92+
ticketId: string;
93+
teamSlug: string;
94+
teamId: string;
95+
message: string;
96+
}): Promise<{ success: true } | { error: string }> {
97+
if (!request.ticketId || !request.teamSlug) {
98+
return { error: "Ticket ID and team slug are required" };
99+
}
100+
101+
const token = await getAuthToken();
102+
if (!token) {
103+
return { error: "No auth token available" };
104+
}
105+
106+
try {
107+
const encodedTeamSlug = encodeURIComponent(request.teamSlug);
108+
const encodedTicketId = encodeURIComponent(request.ticketId);
109+
const apiUrl = `${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${encodedTeamSlug}/support-conversations/${encodedTicketId}/messages`;
110+
111+
// Append /unthread send for customer messages to ensure proper routing
112+
const messageWithUnthread = `${request.message.trim()}\n/unthread send`;
113+
const payload = {
114+
markdown: messageWithUnthread,
115+
};
116+
117+
const body = JSON.stringify(payload);
118+
const headers: Record<string, string> = {
119+
Accept: "application/json",
120+
Authorization: `Bearer ${token}`,
121+
"Content-Type": "application/json",
122+
"Accept-Encoding": "identity",
123+
...(request.teamId ? { "x-team-id": request.teamId } : {}),
124+
};
125+
126+
const response = await fetch(apiUrl, {
127+
body,
128+
headers,
129+
method: "POST",
130+
});
131+
132+
if (!response.ok) {
133+
const errorText = await response.text();
134+
return { error: `API Server error: ${response.status} - ${errorText}` };
135+
}
136+
137+
return { success: true };
138+
} catch (error) {
139+
return {
140+
error: `Failed to send message: ${error instanceof Error ? error.message : "Unknown error"}`,
141+
};
142+
}
143+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export async function service_getTeamBySlug(slug: string) {
4848
return null;
4949
}
5050

51-
export function getTeamById(id: string) {
51+
function getTeamById(id: string) {
5252
return getTeamBySlug(id);
5353
}
5454

apps/dashboard/src/@/api/webhook-configs.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type WebhookConfigsResponse =
3737
};
3838

3939
interface CreateWebhookConfigRequest {
40-
topics: { id: string; filters: object | null }[];
40+
topicIdsWithFilters: { id: string; filters: object | null }[];
4141
destinationUrl: string;
4242
description: string;
4343
isPaused?: boolean;
@@ -76,7 +76,7 @@ type TopicsResponse =
7676

7777
interface UpdateWebhookConfigRequest {
7878
destinationUrl?: string;
79-
topics?: { id: string; filters: object | null }[];
79+
topicIdsWithFilters?: { id: string; filters: object | null }[];
8080
description?: string;
8181
isPaused?: boolean;
8282
}
@@ -310,3 +310,53 @@ export async function deleteWebhookConfig(props: {
310310
status: "success",
311311
};
312312
}
313+
314+
type TestDestinationUrlResponse =
315+
| {
316+
result: {
317+
httpStatusCode: number;
318+
httpResponseBody: string;
319+
};
320+
status: "success";
321+
}
322+
| {
323+
body: string;
324+
reason: string;
325+
status: "error";
326+
};
327+
328+
export async function testDestinationUrl(props: {
329+
teamIdOrSlug: string;
330+
projectIdOrSlug: string;
331+
destinationUrl: string;
332+
}): Promise<TestDestinationUrlResponse> {
333+
const authToken = await getAuthToken();
334+
335+
if (!authToken) {
336+
return {
337+
body: "Authentication required",
338+
reason: "no_auth_token",
339+
status: "error",
340+
};
341+
}
342+
343+
const resp = await fetch(
344+
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${props.teamIdOrSlug}/projects/${props.projectIdOrSlug}/webhook-configs/test-destination-url`,
345+
{
346+
body: JSON.stringify({ destinationUrl: props.destinationUrl }),
347+
headers: {
348+
Authorization: `Bearer ${authToken}`,
349+
"Content-Type": "application/json",
350+
},
351+
method: "POST",
352+
},
353+
);
354+
if (!resp.ok) {
355+
return {
356+
body: await resp.text(),
357+
reason: "unknown",
358+
status: "error",
359+
};
360+
}
361+
return await resp.json();
362+
}

apps/dashboard/src/@/components/blocks/charts/area-chart.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
2929
title: string;
3030
description?: string;
3131
titleClassName?: string;
32+
headerClassName?: string;
3233
};
3334
customHeader?: React.ReactNode;
3435
// chart config
@@ -52,6 +53,12 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
5253
toolTipLabelFormatter?: (label: string, payload: unknown) => React.ReactNode;
5354
toolTipValueFormatter?: (value: unknown) => React.ReactNode;
5455
emptyChartState?: React.ReactElement;
56+
margin?: {
57+
top?: number;
58+
right?: number;
59+
bottom?: number;
60+
left?: number;
61+
};
5562
};
5663

5764
export function ThirdwebAreaChart<TConfig extends ChartConfig>(
@@ -62,7 +69,7 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
6269
return (
6370
<Card className={props.className}>
6471
{props.header && (
65-
<CardHeader>
72+
<CardHeader className={props.header.headerClassName}>
6673
<CardTitle className={cn("mb-2", props.header.titleClassName)}>
6774
{props.header.title}
6875
</CardTitle>
@@ -85,7 +92,16 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
8592
{props.emptyChartState}
8693
</EmptyChartState>
8794
) : (
88-
<AreaChart accessibilityLayer data={props.data}>
95+
<AreaChart
96+
accessibilityLayer
97+
data={props.data}
98+
margin={{
99+
right: props.margin?.right ?? 0,
100+
left: props.margin?.left ?? 0,
101+
bottom: props.margin?.bottom ?? 10,
102+
top: props.margin?.top ?? 0,
103+
}}
104+
>
89105
<CartesianGrid vertical={false} />
90106
{props.yAxis && <YAxis axisLine={false} tickLine={false} />}
91107
<XAxis

apps/dashboard/src/@/components/blocks/upsell-wrapper.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { CrownIcon, LockIcon, SparklesIcon } from "lucide-react";
3+
import { CrownIcon, LockIcon } from "lucide-react";
44
import Link from "next/link";
55
import type React from "react";
66
import type { Team } from "@/api/team";
@@ -111,18 +111,15 @@ export function UpsellContent(props: {
111111
<CardContent className="space-y-6">
112112
{props.benefits && props.benefits.length > 0 && (
113113
<div className="space-y-3">
114-
<h4 className="font-semibold text-muted-foreground text-sm uppercase tracking-wide">
115-
What you'll get:
114+
<h4 className="font-semibold text-foreground text-sm capitalize text-center">
115+
What you'll get
116116
</h4>
117-
<div className="grid gap-2">
117+
<div className="grid gap-1.5">
118118
{props.benefits.map((benefit) => (
119119
<div
120-
className="flex items-center gap-3"
120+
className="flex items-center justify-center gap-3 text-center text-balance"
121121
key={benefit.description}
122122
>
123-
<div className="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-accent">
124-
<SparklesIcon className="h-3 w-3 text-success-text" />
125-
</div>
126123
<span className="text-sm">{benefit.description}</span>
127124
{benefit.status === "soon" && (
128125
<Badge className="text-xs" variant="secondary">

0 commit comments

Comments
 (0)