Skip to content

Commit 6d9ab20

Browse files
arcoravenjnsdls
authored andcommitted
feat: dedicated support self-serve
1 parent 9667c74 commit 6d9ab20

File tree

3 files changed

+486
-280
lines changed

3 files changed

+486
-280
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use server";
2+
import "server-only";
3+
4+
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
5+
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs";
6+
7+
export async function createDedicatedSupportChannel(
8+
teamIdOrSlug: string,
9+
channelType: "slack" | "telegram",
10+
): Promise<{ error: string | null }> {
11+
const token = await getAuthToken();
12+
if (!token) {
13+
return { error: "Unauthorized" };
14+
}
15+
16+
const res = await fetch(
17+
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamIdOrSlug}/dedicated-support-channel`,
18+
{
19+
body: JSON.stringify({
20+
type: channelType,
21+
}),
22+
headers: {
23+
Authorization: `Bearer ${token}`,
24+
"Content-Type": "application/json",
25+
},
26+
method: "POST",
27+
},
28+
);
29+
if (!res.ok) {
30+
const json = await res.json();
31+
return {
32+
error:
33+
json.error?.message ?? "Failed to create dedicated support channel.",
34+
};
35+
}
36+
return { error: null };
37+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"use client";
2+
import { useMutation } from "@tanstack/react-query";
3+
import { useState } from "react";
4+
import { toast } from "sonner";
5+
import { createDedicatedSupportChannel } from "@/api/dedicated-support";
6+
import type { Team } from "@/api/team";
7+
import { SettingsCard } from "@/components/blocks/SettingsCard";
8+
import {
9+
Select,
10+
SelectContent,
11+
SelectItem,
12+
SelectTrigger,
13+
SelectValue,
14+
} from "@/components/ui/select";
15+
import { useDashboardRouter } from "@/lib/DashboardRouter";
16+
17+
const CHANNEL_TYPES = [
18+
{ name: "Slack", value: "slack" },
19+
{ name: "Telegram", value: "telegram" },
20+
] as const;
21+
type ChannelType = (typeof CHANNEL_TYPES)[number]["value"];
22+
23+
interface DedicatedSupportFormProps {
24+
teamId: string;
25+
teamSlug: string;
26+
billingPlan: Team["billingPlan"];
27+
channelType?: ChannelType;
28+
channelName?: string;
29+
isOwnerAccount: boolean;
30+
}
31+
32+
export function TeamDedicatedSupportCard({
33+
teamId,
34+
teamSlug,
35+
billingPlan,
36+
channelType,
37+
channelName,
38+
isOwnerAccount,
39+
}: DedicatedSupportFormProps) {
40+
const router = useDashboardRouter();
41+
const [selectedChannelType, setSelectedChannelType] = useState<ChannelType>(
42+
CHANNEL_TYPES[0].value,
43+
);
44+
45+
const isFeatureEnabled = billingPlan === "scale" || billingPlan === "pro";
46+
47+
const createMutation = useMutation({
48+
mutationFn: async (params: {
49+
teamId: string;
50+
channelType: "slack" | "telegram";
51+
}) => {
52+
const res = await createDedicatedSupportChannel(
53+
params.teamId,
54+
params.channelType,
55+
);
56+
if (res.error) {
57+
throw new Error(res.error);
58+
}
59+
},
60+
onError: (error) => {
61+
toast.error(error.message);
62+
},
63+
onSuccess: () => {
64+
toast.success(
65+
"Dedicated support channel requested. Please check your email for an invite link shortly.",
66+
);
67+
},
68+
});
69+
70+
// Already set up.
71+
if (channelType && channelName) {
72+
return (
73+
<SettingsCard
74+
bottomText={undefined}
75+
errorText={undefined}
76+
header={{
77+
description:
78+
"Get a dedicated support channel with the thirdweb team.",
79+
title: "Dedicated Support",
80+
}}
81+
noPermissionText={undefined}
82+
>
83+
<div className="md:w-[450px]">
84+
<p className="text-muted-foreground text-sm">
85+
Your dedicated support channel: #<strong>{channelName}</strong> on{" "}
86+
{CHANNEL_TYPES.find((c) => c.value === channelType)?.name}
87+
</p>
88+
</div>
89+
</SettingsCard>
90+
);
91+
}
92+
93+
return (
94+
<SettingsCard
95+
bottomText={
96+
!isFeatureEnabled ? (
97+
<>
98+
Upgrade to the <b>Scale</b> or <b>Pro</b> plan to unlock this
99+
feature.
100+
</>
101+
) : undefined
102+
}
103+
errorText={undefined}
104+
header={{
105+
description: "Get a dedicated support channel with the thirdweb team.",
106+
title: "Dedicated Support",
107+
}}
108+
noPermissionText={
109+
!isOwnerAccount
110+
? "Only team owners can request a dedicated support channel."
111+
: undefined
112+
}
113+
saveButton={
114+
isFeatureEnabled
115+
? {
116+
disabled: createMutation.isPending,
117+
isPending: createMutation.isPending,
118+
label: "Create Support Channel",
119+
onClick: () =>
120+
createMutation.mutate({
121+
channelType: selectedChannelType,
122+
teamId,
123+
}),
124+
}
125+
: {
126+
disabled: false,
127+
isPending: false,
128+
label: "Upgrade Plan",
129+
onClick: () =>
130+
router.push(
131+
`/team/${teamSlug}/~/settings/billing?showPlans=true&highlight=scale`,
132+
),
133+
}
134+
}
135+
>
136+
<div className="md:w-[450px]">
137+
<Select
138+
disabled={!isFeatureEnabled}
139+
onValueChange={(val) => setSelectedChannelType(val as ChannelType)}
140+
value={selectedChannelType}
141+
>
142+
<SelectTrigger>
143+
<SelectValue placeholder="Select Channel Type" />
144+
</SelectTrigger>
145+
<SelectContent>
146+
{CHANNEL_TYPES.map(({ name, value }) => (
147+
<SelectItem key={value} value={value}>
148+
{name}
149+
</SelectItem>
150+
))}
151+
</SelectContent>
152+
</Select>
153+
</div>
154+
<p className="mt-2 text-muted-foreground text-sm">
155+
All current members of this team will be sent an invite link to their
156+
email. You can invite other members later.
157+
</p>
158+
</SettingsCard>
159+
);
160+
}

0 commit comments

Comments
 (0)