Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/dashboard/src/@/api/team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export async function getTeamBySlug(slug: string) {
return null;
}

export function getTeamById(id: string) {
return getTeamBySlug(id);
}

export async function getTeams() {
const token = await getAuthToken();
if (!token) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use server";
import "server-only";

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

type State = {
success: boolean;
Expand Down Expand Up @@ -71,32 +71,35 @@ export async function createTicketAction(
_previousState: State,
formData: FormData,
) {
const cookieManager = await cookies();
const activeAccount = cookieManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
const token = activeAccount
? cookieManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
: null;
if (!activeAccount || !token) {
// user is not logged in, make them log in
redirect(`/login?next=${encodeURIComponent("/support")}`);
const teamId = formData.get("teamId")?.toString();

if (!teamId) {
return {
success: false,
message: "teamId is required",
};
}
const accountRes = await fetch(`${API_SERVER_URL}/v1/account/me`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
if (accountRes.status !== 200) {
// user is not logged in, make them log in
redirect(`/login?next=${encodeURIComponent("/support")}`);

const team = await getTeamById(teamId);

if (!team) {
return {
success: false,
message: `Team with id "${teamId}" not found`,
};
}

const account = (await accountRes.json()) as {
data: { name: string; email: string; plan: string; id: string };
};
const [walletAddress, account] = await Promise.all([
getAuthTokenWalletAddress(),
getRawAccount(),
]);

if (!walletAddress || !account) {
loginRedirect("/support");
}

const customerId = isValidPlan(account.data.plan)
? planToCustomerId[account.data.plan]
const customerId = isValidPlan(team.billingPlan)
? planToCustomerId[team.billingPlan]
: undefined;

const product = formData.get("product")?.toString() || "";
Expand All @@ -105,8 +108,8 @@ export async function createTicketAction(
const title = prepareEmailTitle(
product,
problemArea,
account.data.email,
account.data.name,
account.email || "",
account.name || "",
);

const keyVal: Record<string, string> = {};
Expand All @@ -117,10 +120,10 @@ export async function createTicketAction(
const markdown = prepareEmailBody({
product,
markdownInput: keyVal.markdown || "",
email: account.data.email,
name: account.data.name,
email: account.email || "",
name: account.name || "",
extraInfoInput: keyVal,
walletAddress: activeAccount,
walletAddress: walletAddress,
});

const content = {
Expand All @@ -129,9 +132,9 @@ export async function createTicketAction(
markdown,
status: "open",
onBehalfOf: {
email: account.data.email,
name: account.data.name,
id: account.data.id,
email: account.email,
name: account.name,
id: account.id,
},
customerId,
emailInboxId: process.env.UNTHREAD_EMAIL_INBOX_ID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { SupportForm_SelectInput } from "components/help/contact-forms/shared/SupportForm_SelectInput";
import dynamic from "next/dynamic";
import {
Expand All @@ -14,6 +15,7 @@ import {
} from "react";
import { useFormStatus } from "react-dom";
import { toast } from "sonner";
import { SupportForm_TeamSelection } from "../../../../../components/help/contact-forms/shared/SupportForm_TeamSelection";
import { createTicketAction } from "./create-ticket.action";

const ConnectSupportForm = dynamic(
Expand Down Expand Up @@ -75,14 +77,46 @@ const productOptions: { label: string; component: ReactElement }[] = [
},
];

export function CreateTicket() {
function ProductAreaSelection(props: {
productLabel: string;
setProductLabel: (val: string) => void;
}) {
const { productLabel, setProductLabel } = props;

return (
<div className="flex flex-col gap-6">
<SupportForm_SelectInput
formLabel="What do you need help with?"
name="product"
options={productOptions.map((o) => o.label)}
promptText="Select a product"
onValueChange={setProductLabel}
value={productLabel}
required={true}
/>
{productOptions.find((o) => o.label === productLabel)?.component}
</div>
);
}

export function CreateTicket(props: {
teams: {
name: string;
id: string;
}[];
}) {
const formRef = useRef<HTMLFormElement>(null);
const [selectedTeamId, setSelectedTeamId] = useState<string | undefined>(
props.teams[0]?.id,
);

const [productLabel, setProductLabel] = useState("");

const [state, formAction] = useActionState(createTicketAction, {
message: "",
success: false,
});

// needed here
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
Expand Down Expand Up @@ -112,16 +146,19 @@ export function CreateTicket() {
<div className="h-7" />

<div className="flex flex-col gap-6">
<SupportForm_SelectInput
formLabel="What do you need help with?"
name="product"
options={productOptions.map((o) => o.label)}
promptText="Select a product"
onValueChange={setProductLabel}
value={productLabel}
required={true}
{/* Don't conditionally render this - it has be rendered to submit the input values */}
<div className={cn(props.teams.length === 1 && "hidden")}>
<SupportForm_TeamSelection
selectedTeamId={selectedTeamId}
onChange={(teamId) => setSelectedTeamId(teamId)}
teams={props.teams}
/>
</div>

<ProductAreaSelection
productLabel={productLabel}
setProductLabel={setProductLabel}
/>
{productOptions.find((o) => o.label === productLabel)?.component}
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getTeams } from "@/api/team";
import {
Breadcrumb,
BreadcrumbItem,
Expand All @@ -7,17 +8,16 @@ import {
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import Link from "next/link";
import { redirect } from "next/navigation";
import { getAuthToken } from "../../../api/lib/getAuthToken";
import { loginRedirect } from "../../../login/loginRedirect";
import { CreateTicket } from "./components/create-ticket.client";

export default async function Page() {
const authToken = await getAuthToken();
const teams = await getTeams();

if (!authToken) {
return redirect(
`/login?next=${encodeURIComponent("/support/create-ticket")}`,
);
const pagePath = "/support/create-ticket";

if (!teams || teams.length === 0) {
loginRedirect(pagePath);
}

return (
Expand All @@ -36,7 +36,12 @@ export default async function Page() {
</BreadcrumbList>
</Breadcrumb>
<div className="container max-w-[750px] py-10">
<CreateTicket />
<CreateTicket
teams={teams.map((t) => ({
name: t.name,
id: t.id,
}))}
/>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type Props = {
formLabel: string;
name: string;
required: boolean;
value: string;
value: string | undefined;
onValueChange: (value: string) => void;
};

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

return (
<>
<input
hidden
value={props.value}
name={name}
onChange={(e) => props.onValueChange(e.target.value)}
required={required}
/>

<div className="flex flex-col items-start gap-2">
<Label htmlFor={name} className="relative">
{formLabel}
Expand All @@ -41,6 +33,8 @@ export const SupportForm_SelectInput = (props: Props) => {
</Label>

<Select
name={name}
required={required}
value={props.value}
onValueChange={(val) => {
props.onValueChange(val);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";

type MinimalTeam = {
name: string;
id: string;
};

type Props = {
teams: MinimalTeam[];
selectedTeamId: string | undefined;
onChange: (teamId: string) => void;
};

export const SupportForm_TeamSelection = (props: Props) => {
const selectedTeamName = props.teams.find(
(t) => t.id === props.selectedTeamId,
)?.name;

return (
<>
<div className="flex flex-col items-start gap-2">
<Label htmlFor="team" className="relative">
Select Team
<span className="-top-1.5 -right-2 absolute text-destructive">•</span>
</Label>

<Select
name="teamId"
value={props.selectedTeamId}
onValueChange={(selectedId) => {
props.onChange(selectedId);
}}
>
<SelectTrigger id="team">
<SelectValue placeholder="Select a Team">
{selectedTeamName}
</SelectValue>
</SelectTrigger>
<SelectContent>
{props.teams.map((team) => (
<SelectItem key={team.id} value={team.id}>
{team.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</>
);
};
Loading