Skip to content

Commit 6bae702

Browse files
committed
Reply to thread
1 parent 6587f9d commit 6bae702

File tree

12 files changed

+96
-12
lines changed

12 files changed

+96
-12
lines changed

README.md

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Now you will need to create an API key for the support portal backend to authent
1818
- Fill in the description and save the key
1919

2020
```
21-
attachment:download,company:read,customer:read,customerGroup:read,customerGroupMembership:read,customerTenantMembership:read,email:read,label:read,labelType:read,note:read,roles:read,serviceLevelAgreement:read,tenant:read,tenant:search,thread:read,tier:read,tierMembership:read,timeline:read,user:read,workspace:read,threadField:read,threadFieldSchema:read,customer:create,customer:edit,thread:create,thread:edit
21+
attachment:download,company:read,customer:read,customerGroup:read,customerGroupMembership:read,customerTenantMembership:read,email:read,label:read,labelType:read,note:read,roles:read,serviceLevelAgreement:read,tenant:read,tenant:search,thread:read,tier:read,tierMembership:read,timeline:read,user:read,workspace:read,threadField:read,threadFieldSchema:read,customer:create,customer:edit,thread:create,thread:edit,thread:reply
2222
```
2323

2424
Once you have your key you can get started:
@@ -29,11 +29,3 @@ PLAIN_API_KEY=<your_key> npm run dev
2929
```
3030

3131
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
32-
33-
## Screenshots
34-
35-
![View the list of requests](/screenshots/thread-list.png)
36-
37-
![See the details of a support request](/screenshots/thread-page.png)
38-
39-
![Create a new support request](/screenshots/new-request.png)

app/api/thread-reply/route.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { plainClient } from "@/lib/plainClient";
2+
import { inspect } from "util";
3+
4+
export async function POST(request: Request) {
5+
// In production validation of the request body might be necessary.
6+
const body = await request.json();
7+
8+
const replyRes = await plainClient.replyToThread({textContent: body.message, threadId: body.threadId})
9+
if (replyRes.error) {
10+
console.error(
11+
inspect(replyRes.error, {
12+
showHidden: false,
13+
depth: null,
14+
colors: true,
15+
})
16+
);
17+
return new Response(replyRes.error.message, { status: 500 });
18+
}
19+
20+
console.log('Replied to thread', body.threadId);
21+
return new Response("", { status: 200 });
22+
}

app/globals.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
html,
1313
body {
1414
max-width: 100vw;
15+
height: 100%;
16+
display: flex;
17+
flex-direction: column;
1518
overflow-x: hidden;
1619
font: var(--font);
1720
}

app/page.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
.main {
22
display: flex;
33
flex-direction: column;
4+
flex-grow: 1;
45
gap: 32px;
56
align-items: center;
67
padding: 2rem;
7-
min-height: 100vh;
88
}
99

1010
.list {

app/thread/[threadId]/page.module.css

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
.main {
22
display: flex;
33
justify-content: space-between;
4-
min-height: 100vh;
4+
flex-grow: 1;
55
width: 100%;
66
}
77

88
.timeline {
99
padding: 24px;
1010
flex-grow: 1;
11+
display: flex;
12+
flex-direction: column;
13+
justify-content: space-between;
1114
}
1215

1316
.threadInfo {

app/thread/[threadId]/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getActorFullName } from "@/lib/getActorFullName";
44
import { getFormattedDate } from "@/lib/getFormattedDate";
55
import { getPriority } from "@/lib/getPriority";
66
import { fetchThreadTimelineEntries } from "@/lib/fetchThreadTimelineEntries";
7+
import { ReplyToThread } from "@/components/replyToThread";
78

89
export default async function ThreadPage({
910
params,
@@ -85,6 +86,7 @@ export default async function ThreadPage({
8586
</div>
8687
);
8788
})}
89+
<ReplyToThread />
8890
</div>
8991

9092
<div className={styles.threadInfo}>

components/button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ interface ButtonProps {
44
label: string;
55
isLoading?: boolean;
66
isDisabled?: boolean;
7+
onClick?: () => void;
78
}
89

910
export function Button(props: ButtonProps) {
1011
return (
11-
<button type="submit" className={styles.button} disabled={props.isDisabled}>
12+
<button type="submit" className={styles.button} disabled={props.isDisabled} onClick={props.onClick}>
1213
{props.isLoading && <span className={styles.spinner} />}
1314
<span style={{ opacity: props.isLoading ? 0 : 1 }}>{props.label}</span>
1415
</button>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.root {
2+
display: flex;
3+
justify-content: center;
4+
}
5+
6+
.container {
7+
display: flex;
8+
/* align-items: center; */
9+
/* justify-content: center; */
10+
gap: 8px;
11+
width: 100%;
12+
}
13+
14+
.button {
15+
flex-grow: 0;
16+
display: flex;
17+
flex-direction: column-reverse
18+
}

components/replyToThread.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use client";
2+
3+
import { useCallback, useState } from "react";
4+
import { Textarea } from "./textArea";
5+
import { Button } from "./button";
6+
import toast from "react-hot-toast";
7+
import { useParams, useRouter } from "next/navigation";
8+
import styles from "./replyToThread.module.css";
9+
10+
export function ReplyToThread() {
11+
const [message, setMessage] = useState("");
12+
const router = useRouter();
13+
const params = useParams<{threadId: string}>();
14+
15+
const sendReply = useCallback(async () => {
16+
try {
17+
const result = await fetch("/api/thread-reply/", {
18+
method: "POST",
19+
body: JSON.stringify({ message, threadId: params.threadId}),
20+
});
21+
if (result.ok) {
22+
toast.success("Reply sent");
23+
router.push("/");
24+
} else {
25+
toast.error("Oops");
26+
}
27+
} catch (error) {
28+
console.error(error);
29+
toast.error("Oops");
30+
}
31+
}, [message, params.threadId, router]);
32+
33+
return (
34+
<div className={styles.root}>
35+
<div className={styles.container}>
36+
<Textarea value={message} onChange={setMessage} placeholder="" />
37+
<div className={styles.button}>
38+
<Button label="Reply" onClick={sendReply} />
39+
</div>
40+
</div>
41+
</div>
42+
);
43+
}

screenshots/new-request.png

-38.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)