Skip to content

Commit 0fca96a

Browse files
committed
TOOL-3379
1 parent 815b9b1 commit 0fca96a

File tree

6 files changed

+161
-61
lines changed

6 files changed

+161
-61
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ export async function getTeamBySlug(slug: string) {
4747
return null;
4848
}
4949

50+
export function getTeamById(id: string) {
51+
return getTeamBySlug(id);
52+
}
53+
5054
export async function getTeams() {
5155
const token = await getAuthToken();
5256
if (!token) {

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

Lines changed: 37 additions & 34 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,32 +71,35 @@ 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]
101+
const customerId = isValidPlan(team.billingPlan)
102+
? planToCustomerId[team.billingPlan]
100103
: undefined;
101104

102105
const product = formData.get("product")?.toString() || "";
@@ -105,8 +108,8 @@ export async function createTicketAction(
105108
const title = prepareEmailTitle(
106109
product,
107110
problemArea,
108-
account.data.email,
109-
account.data.name,
111+
account.email || "",
112+
account.name || "",
110113
);
111114

112115
const keyVal: Record<string, string> = {};
@@ -117,10 +120,10 @@ export async function createTicketAction(
117120
const markdown = prepareEmailBody({
118121
product,
119122
markdownInput: keyVal.markdown || "",
120-
email: account.data.email,
121-
name: account.data.name,
123+
email: account.email || "",
124+
name: account.name || "",
122125
extraInfoInput: keyVal,
123-
walletAddress: activeAccount,
126+
walletAddress: walletAddress,
124127
});
125128

126129
const content = {
@@ -129,9 +132,9 @@ export async function createTicketAction(
129132
markdown,
130133
status: "open",
131134
onBehalfOf: {
132-
email: account.data.email,
133-
name: account.data.name,
134-
id: account.data.id,
135+
email: account.email,
136+
name: account.name,
137+
id: account.id,
135138
},
136139
customerId,
137140
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/components/help/contact-forms/shared/SupportForm_SelectInput.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type Props = {
1313
formLabel: string;
1414
name: string;
1515
required: boolean;
16-
value: string;
16+
value: string | undefined;
1717
onValueChange: (value: string) => void;
1818
};
1919

@@ -22,14 +22,6 @@ export const SupportForm_SelectInput = (props: Props) => {
2222

2323
return (
2424
<>
25-
<input
26-
hidden
27-
value={props.value}
28-
name={name}
29-
onChange={(e) => props.onValueChange(e.target.value)}
30-
required={required}
31-
/>
32-
3325
<div className="flex flex-col items-start gap-2">
3426
<Label htmlFor={name} className="relative">
3527
{formLabel}
@@ -41,6 +33,8 @@ export const SupportForm_SelectInput = (props: Props) => {
4133
</Label>
4234

4335
<Select
36+
name={name}
37+
required={required}
4438
value={props.value}
4539
onValueChange={(val) => {
4640
props.onValueChange(val);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Label } from "@/components/ui/label";
2+
import {
3+
Select,
4+
SelectContent,
5+
SelectItem,
6+
SelectTrigger,
7+
SelectValue,
8+
} from "@/components/ui/select";
9+
10+
type MinimalTeam = {
11+
name: string;
12+
id: string;
13+
};
14+
15+
type Props = {
16+
teams: MinimalTeam[];
17+
selectedTeamId: string | undefined;
18+
onChange: (teamId: string) => void;
19+
};
20+
21+
export const SupportForm_TeamSelection = (props: Props) => {
22+
const selectedTeamName = props.teams.find(
23+
(t) => t.id === props.selectedTeamId,
24+
)?.name;
25+
26+
return (
27+
<>
28+
<div className="flex flex-col items-start gap-2">
29+
<Label htmlFor="team" className="relative">
30+
Select Team
31+
<span className="-top-1.5 -right-2 absolute text-destructive"></span>
32+
</Label>
33+
34+
<Select
35+
name="teamId"
36+
value={props.selectedTeamId}
37+
onValueChange={(selectedId) => {
38+
props.onChange(selectedId);
39+
}}
40+
>
41+
<SelectTrigger id="team">
42+
<SelectValue placeholder="Select a Team">
43+
{selectedTeamName}
44+
</SelectValue>
45+
</SelectTrigger>
46+
<SelectContent>
47+
{props.teams.map((team) => (
48+
<SelectItem key={team.id} value={team.id}>
49+
{team.name}
50+
</SelectItem>
51+
))}
52+
</SelectContent>
53+
</Select>
54+
</div>
55+
</>
56+
);
57+
};

0 commit comments

Comments
 (0)