Skip to content

Commit 2abfc64

Browse files
committed
chore: Add support CRUD endpoints
1 parent 7ab76ac commit 2abfc64

File tree

6 files changed

+631
-1
lines changed

6 files changed

+631
-1
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"use server";
2+
import "server-only";
3+
4+
import { getTeamById } from "@/api/team";
5+
import { getUnthreadConversation } from "lib/unthread/get-conversation";
6+
import { getRawAccount } from "../../../../account/settings/getAccount";
7+
import { loginRedirect } from "../../../../login/loginRedirect";
8+
9+
type AddMessageToTicketResponse =
10+
| {
11+
success: false;
12+
message: string;
13+
}
14+
| {
15+
success: true;
16+
};
17+
18+
export async function addMessageToTicketAction(args: {
19+
teamId: string;
20+
conversationId: string;
21+
messageMarkdown: string;
22+
}): Promise<AddMessageToTicketResponse> {
23+
const { teamId, conversationId, messageMarkdown } = args;
24+
if (!teamId || !conversationId || !messageMarkdown) {
25+
return {
26+
success: false,
27+
message: "Missing required arguments.",
28+
};
29+
}
30+
31+
const account = await getRawAccount();
32+
if (!account) {
33+
// User is not logged in.
34+
loginRedirect("/support");
35+
}
36+
37+
const team = await getTeamById(teamId);
38+
const customerId = team?.unthreadCustomerId;
39+
if (!customerId) {
40+
return {
41+
success: false,
42+
message: `Support customer for team ${teamId} not found.`,
43+
};
44+
}
45+
46+
if (
47+
[
48+
process.env.UNTHREAD_FREE_TIER_ID,
49+
process.env.UNTHREAD_GROWTH_TIER_ID,
50+
process.env.UNTHREAD_PRO_TIER_ID,
51+
].includes(customerId)
52+
) {
53+
// Disallow "shared" legacy customer IDs because this endpoint returns customer-specific data.
54+
return {
55+
success: false,
56+
message: `This endpoint is not supported for legacy customer ID ${customerId}`,
57+
};
58+
}
59+
60+
// Get the conversation first to confirm the user has permissions to update this ticket.
61+
const conversation = await getUnthreadConversation(conversationId);
62+
if (!conversation || conversation.customerId !== customerId) {
63+
return {
64+
success: false,
65+
message: "Ticket not found.",
66+
};
67+
}
68+
69+
// Get the conversation and messages.
70+
const res = await fetch(
71+
`https://api.unthread.io/api/conversations/${conversationId}/messages`,
72+
{
73+
method: "POST",
74+
headers: {
75+
"Content-Type": "application/json",
76+
"X-Api-Key": process.env.UNTHREAD_API_KEY ?? "",
77+
},
78+
body: JSON.stringify({
79+
body: {
80+
type: "markdown",
81+
value: messageMarkdown,
82+
},
83+
onBehalfOf: {
84+
email: account.email,
85+
name: account.name,
86+
id: customerId,
87+
},
88+
}),
89+
},
90+
);
91+
if (!res.ok) {
92+
console.error(
93+
"Error adding message to the ticket:",
94+
res.status,
95+
res.statusText,
96+
await res.text(),
97+
);
98+
return {
99+
success: false,
100+
message: "Error adding message to the ticket. Please try again later.",
101+
};
102+
}
103+
104+
return { success: true };
105+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,15 @@ export async function createTicketAction(
9898
loginRedirect("/support");
9999
}
100100

101-
const customerId = isValidPlan(team.supportPlan)
101+
// @TODO: This needs to be updated to use team.unthreadCustomerId after all users are migrated.
102+
let customerId = isValidPlan(team.supportPlan)
102103
? planToCustomerId[team.supportPlan]
103104
: // fallback to "free" tier
104105
planToCustomerId.free;
106+
// @TODO: Test accounts. Remove when released.
107+
if (teamId === "clku79c5k0188520uyn729f7v") {
108+
customerId = team.unthreadCustomerId;
109+
}
105110

106111
const product = formData.get("product")?.toString() || "";
107112
const problemArea = formData.get("extraInfo_Problem_Area")?.toString() || "";
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"use server";
2+
import "server-only";
3+
4+
import { getTeamById } from "@/api/team";
5+
import { getUnthreadConversation } from "lib/unthread/get-conversation";
6+
import { getRawAccount } from "../../../../account/settings/getAccount";
7+
import { loginRedirect } from "../../../../login/loginRedirect";
8+
9+
type GetTicketResponse =
10+
| {
11+
success: false;
12+
message: string;
13+
}
14+
| {
15+
success: true;
16+
result: UnthreadMessagesResponse;
17+
};
18+
19+
interface UnthreadMessage {
20+
ts: string;
21+
botId: string;
22+
botName: string;
23+
timestamp: string;
24+
threadTs: string;
25+
id: string;
26+
user: {
27+
id: string;
28+
name: string;
29+
email: string;
30+
photo?: string;
31+
} | null;
32+
conversation: {
33+
customerId: string;
34+
};
35+
resolvedContent: (
36+
| string
37+
| {
38+
type: "link";
39+
id: string;
40+
name: string;
41+
}
42+
)[];
43+
text: string;
44+
htmlContent: string;
45+
}
46+
47+
interface UnthreadMessagesResponse {
48+
data: UnthreadMessage[];
49+
totalCount: number;
50+
cursors: {
51+
hasNext: boolean;
52+
hasPrevious: boolean;
53+
next?: string;
54+
previous?: string;
55+
};
56+
}
57+
58+
export async function getTicketAction(args: {
59+
teamId: string;
60+
conversationId: string;
61+
cursor?: string;
62+
}): Promise<GetTicketResponse> {
63+
const { teamId, conversationId, cursor = "" } = args;
64+
if (!teamId || !conversationId) {
65+
return {
66+
success: false,
67+
message: "Missing required arguments.",
68+
};
69+
}
70+
71+
const account = await getRawAccount();
72+
if (!account) {
73+
// User is not logged in.
74+
loginRedirect("/support");
75+
}
76+
77+
const team = await getTeamById(teamId);
78+
const customerId = team?.unthreadCustomerId;
79+
if (!customerId) {
80+
return {
81+
success: false,
82+
message: `Support customer for team ${teamId} not found.`,
83+
};
84+
}
85+
86+
if (
87+
[
88+
process.env.UNTHREAD_FREE_TIER_ID,
89+
process.env.UNTHREAD_GROWTH_TIER_ID,
90+
process.env.UNTHREAD_PRO_TIER_ID,
91+
].includes(customerId)
92+
) {
93+
// Disallow "shared" legacy customer IDs because this endpoint returns customer-specific data.
94+
return {
95+
success: false,
96+
message: `This endpoint is not supported for legacy customer ID ${customerId}`,
97+
};
98+
}
99+
100+
// Get the conversation first to confirm the user has permissions to update this ticket.
101+
const conversation = await getUnthreadConversation(conversationId);
102+
if (!conversation || conversation.customerId !== customerId) {
103+
return {
104+
success: false,
105+
message: "Ticket not found.",
106+
};
107+
}
108+
109+
// Get the conversation and messages.
110+
const res = await fetch(
111+
`https://api.unthread.io/api/conversations/${conversationId}/messages/list`,
112+
{
113+
method: "POST",
114+
headers: {
115+
"Content-Type": "application/json",
116+
"X-Api-Key": process.env.UNTHREAD_API_KEY ?? "",
117+
},
118+
body: JSON.stringify({
119+
select: [
120+
"ts",
121+
"botId",
122+
"botName",
123+
"text",
124+
"timestamp",
125+
"threadTs",
126+
"metadata",
127+
"user.id",
128+
"user.name",
129+
"user.email",
130+
"user.slackId",
131+
"user.photo",
132+
],
133+
order: ["ts"],
134+
descending: true,
135+
limit: 100,
136+
cursor,
137+
}),
138+
},
139+
);
140+
if (!res.ok) {
141+
console.error(
142+
"Error retrieving ticket:",
143+
res.status,
144+
res.statusText,
145+
await res.text(),
146+
);
147+
return {
148+
success: false,
149+
message: "Error retrieving ticket. Please try again later.",
150+
};
151+
}
152+
153+
const json = (await res.json()) as UnthreadMessagesResponse;
154+
155+
return {
156+
success: true,
157+
result: json,
158+
};
159+
}

0 commit comments

Comments
 (0)