Skip to content

Commit 99a326a

Browse files
authored
Merge branch 'main' into crosschain-ui
2 parents a3ca046 + dceb47d commit 99a326a

File tree

46 files changed

+1004
-219
lines changed

Some content is hidden

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

46 files changed

+1004
-219
lines changed

.changeset/cool-pumpkins-punch.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+
Dont retry on quote errors, show fiat value in every step

.changeset/good-carpets-dream.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+
Handle updating session keys with new params and expose `shouldUpdateSessionKey` from `extensions/erc4337`

.changeset/many-cameras-cover.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+
Add treasure and treasureTopaz chain definitions

apps/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"react-table": "^7.8.0",
9090
"recharts": "2.15.1",
9191
"remark-gfm": "^4.0.0",
92+
"responsive-rsc": "0.0.7",
9293
"server-only": "^0.0.1",
9394
"shiki": "1.27.0",
9495
"sonner": "^1.7.4",

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export type Team = {
2424
image?: string;
2525
billingPlan: "pro" | "growth" | "free" | "starter";
2626
billingStatus: "validPayment" | (string & {}) | null;
27+
supportPlan: "pro" | "growth" | "free" | "starter";
2728
billingEmail: string | null;
2829
growthTrialEligible: false;
2930
enabledScopes: EnabledTeamScope[];
@@ -47,6 +48,10 @@ export async function getTeamBySlug(slug: string) {
4748
return null;
4849
}
4950

51+
export function getTeamById(id: string) {
52+
return getTeamBySlug(id);
53+
}
54+
5055
export async function getTeams() {
5156
const token = await getAuthToken();
5257
if (!token) {

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export function DatePickerWithRange(props: {
2727
header?: React.ReactNode;
2828
footer?: React.ReactNode;
2929
labelOverride?: string;
30+
popoverAlign?: "start" | "end" | "center";
3031
}) {
3132
const [screen, setScreen] = React.useState<"from" | "to">("from");
3233
const { from, to, setFrom, setTo } = props;
@@ -65,7 +66,11 @@ export function DatePickerWithRange(props: {
6566
</PopoverTrigger>
6667

6768
{/* Popover */}
68-
<PopoverContent className="w-auto p-0" align="start">
69+
<PopoverContent
70+
className="w-auto p-0"
71+
align={props.popoverAlign || "start"}
72+
sideOffset={10}
73+
>
6974
<DynamicHeight>
7075
<div>
7176
{!isValid && (

apps/dashboard/src/app/(dashboard)/support/create-ticket/components/create-ticket.action.ts

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"use server";
22
import "server-only";
33

4-
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
5-
import { API_SERVER_URL } from "@/constants/env";
6-
import { cookies } from "next/headers";
7-
import { redirect } from "next/navigation";
4+
import { getTeamById } from "@/api/team";
5+
import { getRawAccount } from "../../../../account/settings/getAccount";
6+
import { getAuthTokenWalletAddress } from "../../../../api/lib/getAuthToken";
7+
import { loginRedirect } from "../../../../login/loginRedirect";
88

99
type State = {
1010
success: boolean;
@@ -71,42 +71,46 @@ export async function createTicketAction(
7171
_previousState: State,
7272
formData: FormData,
7373
) {
74-
const cookieManager = await cookies();
75-
const activeAccount = cookieManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
76-
const token = activeAccount
77-
? cookieManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
78-
: null;
79-
if (!activeAccount || !token) {
80-
// user is not logged in, make them log in
81-
redirect(`/login?next=${encodeURIComponent("/support")}`);
74+
const teamId = formData.get("teamId")?.toString();
75+
76+
if (!teamId) {
77+
return {
78+
success: false,
79+
message: "teamId is required",
80+
};
8281
}
83-
const accountRes = await fetch(`${API_SERVER_URL}/v1/account/me`, {
84-
method: "GET",
85-
headers: {
86-
Authorization: `Bearer ${token}`,
87-
},
88-
});
89-
if (accountRes.status !== 200) {
90-
// user is not logged in, make them log in
91-
redirect(`/login?next=${encodeURIComponent("/support")}`);
82+
83+
const team = await getTeamById(teamId);
84+
85+
if (!team) {
86+
return {
87+
success: false,
88+
message: `Team with id "${teamId}" not found`,
89+
};
9290
}
9391

94-
const account = (await accountRes.json()) as {
95-
data: { name: string; email: string; plan: string; id: string };
96-
};
92+
const [walletAddress, account] = await Promise.all([
93+
getAuthTokenWalletAddress(),
94+
getRawAccount(),
95+
]);
96+
97+
if (!walletAddress || !account) {
98+
loginRedirect("/support");
99+
}
97100

98-
const customerId = isValidPlan(account.data.plan)
99-
? planToCustomerId[account.data.plan]
100-
: undefined;
101+
const customerId = isValidPlan(team.supportPlan)
102+
? planToCustomerId[team.supportPlan]
103+
: // fallback to "free" tier
104+
planToCustomerId.free;
101105

102106
const product = formData.get("product")?.toString() || "";
103107
const problemArea = formData.get("extraInfo_Problem_Area")?.toString() || "";
104108

105109
const title = prepareEmailTitle(
106110
product,
107111
problemArea,
108-
account.data.email,
109-
account.data.name,
112+
account.email || "",
113+
account.name || "",
110114
);
111115

112116
const keyVal: Record<string, string> = {};
@@ -117,10 +121,10 @@ export async function createTicketAction(
117121
const markdown = prepareEmailBody({
118122
product,
119123
markdownInput: keyVal.markdown || "",
120-
email: account.data.email,
121-
name: account.data.name,
124+
email: account.email || "",
125+
name: account.name || "",
122126
extraInfoInput: keyVal,
123-
walletAddress: activeAccount,
127+
walletAddress: walletAddress,
124128
});
125129

126130
const content = {
@@ -129,9 +133,9 @@ export async function createTicketAction(
129133
markdown,
130134
status: "open",
131135
onBehalfOf: {
132-
email: account.data.email,
133-
name: account.data.name,
134-
id: account.data.id,
136+
email: account.email,
137+
name: account.name,
138+
id: account.id,
135139
},
136140
customerId,
137141
emailInboxId: process.env.UNTHREAD_EMAIL_INBOX_ID,

apps/dashboard/src/app/(dashboard)/support/create-ticket/components/create-ticket.client.tsx

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { Spinner } from "@/components/ui/Spinner/Spinner";
44
import { Button } from "@/components/ui/button";
55
import { Skeleton } from "@/components/ui/skeleton";
6+
import { cn } from "@/lib/utils";
67
import { SupportForm_SelectInput } from "components/help/contact-forms/shared/SupportForm_SelectInput";
78
import dynamic from "next/dynamic";
89
import {
@@ -14,6 +15,7 @@ import {
1415
} from "react";
1516
import { useFormStatus } from "react-dom";
1617
import { toast } from "sonner";
18+
import { SupportForm_TeamSelection } from "../../../../../components/help/contact-forms/shared/SupportForm_TeamSelection";
1719
import { createTicketAction } from "./create-ticket.action";
1820

1921
const ConnectSupportForm = dynamic(
@@ -75,14 +77,46 @@ const productOptions: { label: string; component: ReactElement }[] = [
7577
},
7678
];
7779

78-
export function CreateTicket() {
80+
function ProductAreaSelection(props: {
81+
productLabel: string;
82+
setProductLabel: (val: string) => void;
83+
}) {
84+
const { productLabel, setProductLabel } = props;
85+
86+
return (
87+
<div className="flex flex-col gap-6">
88+
<SupportForm_SelectInput
89+
formLabel="What do you need help with?"
90+
name="product"
91+
options={productOptions.map((o) => o.label)}
92+
promptText="Select a product"
93+
onValueChange={setProductLabel}
94+
value={productLabel}
95+
required={true}
96+
/>
97+
{productOptions.find((o) => o.label === productLabel)?.component}
98+
</div>
99+
);
100+
}
101+
102+
export function CreateTicket(props: {
103+
teams: {
104+
name: string;
105+
id: string;
106+
}[];
107+
}) {
79108
const formRef = useRef<HTMLFormElement>(null);
109+
const [selectedTeamId, setSelectedTeamId] = useState<string | undefined>(
110+
props.teams[0]?.id,
111+
);
112+
80113
const [productLabel, setProductLabel] = useState("");
81114

82115
const [state, formAction] = useActionState(createTicketAction, {
83116
message: "",
84117
success: false,
85118
});
119+
86120
// needed here
87121
// eslint-disable-next-line no-restricted-syntax
88122
useEffect(() => {
@@ -112,16 +146,19 @@ export function CreateTicket() {
112146
<div className="h-7" />
113147

114148
<div className="flex flex-col gap-6">
115-
<SupportForm_SelectInput
116-
formLabel="What do you need help with?"
117-
name="product"
118-
options={productOptions.map((o) => o.label)}
119-
promptText="Select a product"
120-
onValueChange={setProductLabel}
121-
value={productLabel}
122-
required={true}
149+
{/* Don't conditionally render this - it has be rendered to submit the input values */}
150+
<div className={cn(props.teams.length === 1 && "hidden")}>
151+
<SupportForm_TeamSelection
152+
selectedTeamId={selectedTeamId}
153+
onChange={(teamId) => setSelectedTeamId(teamId)}
154+
teams={props.teams}
155+
/>
156+
</div>
157+
158+
<ProductAreaSelection
159+
productLabel={productLabel}
160+
setProductLabel={setProductLabel}
123161
/>
124-
{productOptions.find((o) => o.label === productLabel)?.component}
125162
</div>
126163
</div>
127164

apps/dashboard/src/app/(dashboard)/support/create-ticket/page.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getTeams } from "@/api/team";
12
import {
23
Breadcrumb,
34
BreadcrumbItem,
@@ -7,17 +8,16 @@ import {
78
BreadcrumbSeparator,
89
} from "@/components/ui/breadcrumb";
910
import Link from "next/link";
10-
import { redirect } from "next/navigation";
11-
import { getAuthToken } from "../../../api/lib/getAuthToken";
11+
import { loginRedirect } from "../../../login/loginRedirect";
1212
import { CreateTicket } from "./components/create-ticket.client";
1313

1414
export default async function Page() {
15-
const authToken = await getAuthToken();
15+
const teams = await getTeams();
1616

17-
if (!authToken) {
18-
return redirect(
19-
`/login?next=${encodeURIComponent("/support/create-ticket")}`,
20-
);
17+
const pagePath = "/support/create-ticket";
18+
19+
if (!teams || teams.length === 0) {
20+
loginRedirect(pagePath);
2121
}
2222

2323
return (
@@ -36,7 +36,12 @@ export default async function Page() {
3636
</BreadcrumbList>
3737
</Breadcrumb>
3838
<div className="container max-w-[750px] py-10">
39-
<CreateTicket />
39+
<CreateTicket
40+
teams={teams.map((t) => ({
41+
name: t.name,
42+
id: t.id,
43+
}))}
44+
/>
4045
</div>
4146
</div>
4247
);

apps/dashboard/src/app/(landing)/in-app-wallets/page.tsx

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import analyticsImage from "../../../../public/assets/landingpage/desktop/analyt
1414
import authDesktopImage from "../../../../public/assets/landingpage/desktop/auth.png";
1515
import crossPlatformDesktopImage from "../../../../public/assets/landingpage/desktop/cross-platform.png";
1616
import enterpriseSecurityImage from "../../../../public/assets/landingpage/desktop/enterprise-security.png";
17-
import guestImage from "../../../../public/assets/landingpage/desktop/guest.png";
1817
import magicImage from "../../../../public/assets/landingpage/desktop/magic.png";
1918
import onboardImage from "../../../../public/assets/landingpage/desktop/onboard.png";
2019
import powerfulImage from "../../../../public/assets/landingpage/desktop/powerful.png";
@@ -24,7 +23,6 @@ import analyticsMobileImage from "../../../../public/assets/landingpage/mobile/a
2423
import authMobileImage from "../../../../public/assets/landingpage/mobile/auth.png";
2524
import crossPlatformMobileImage from "../../../../public/assets/landingpage/mobile/cross-platform.png";
2625
import enterpriseSecurityMobileImage from "../../../../public/assets/landingpage/mobile/enterprise-security.png";
27-
import guestMobileImage from "../../../../public/assets/landingpage/mobile/guest.png";
2826
import magicMobileImage from "../../../../public/assets/landingpage/mobile/magic.png";
2927
import mobileOnboardImage from "../../../../public/assets/landingpage/mobile/onboard.png";
3028
import powerfulMobileImage from "../../../../public/assets/landingpage/mobile/powerful.png";
@@ -190,16 +188,6 @@ export default function Page() {
190188
href="https://portal.thirdweb.com/connect/in-app-wallet/how-to/enable-gasless"
191189
/>
192190

193-
<LandingCardWithImage
194-
title="Guest mode"
195-
description={`Allow anyone to use your app in seconds — with a wallet that's generated automatically when they press "Continue as guest."`}
196-
image={guestImage}
197-
mobileImage={guestMobileImage}
198-
TRACKING_CATEGORY={TRACKING_CATEGORY}
199-
href="https://portal.thirdweb.com/references/wallets/latest/LocalWallet"
200-
colSpan={1}
201-
/>
202-
203191
<LandingCardWithImage
204192
title="Bring your own auth"
205193
description="Integrate your authentication system and spin up in-app wallets for your users — new and existing."

0 commit comments

Comments
 (0)