CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/components/support/tickets.tsx
Views: 687
1
import { Alert, Divider, Layout, Tag, Timeline, Tooltip } from "antd";
2
import { ReactNode } from "react";
3
4
import { Icon } from "@cocalc/frontend/components/icon";
5
import Markdown from "@cocalc/frontend/editors/slate/static-markdown";
6
import { capitalize, trunc } from "@cocalc/util/misc";
7
import { COLORS } from "@cocalc/util/theme";
8
import { Title } from "components/misc";
9
import A from "components/misc/A";
10
import Loading from "components/share/loading";
11
import { MAX_WIDTH } from "lib/config";
12
import { useCustomize } from "lib/customize";
13
import useAPI from "lib/hooks/api";
14
import type { Type } from "./create";
15
import { NoZendesk } from "./util";
16
17
export default function Tickets() {
18
let { result, error } = useAPI("support/tickets");
19
const { zendesk } = useCustomize();
20
21
if (!zendesk) {
22
return <NoZendesk />;
23
}
24
return (
25
<Layout.Content style={{ backgroundColor: "white" }}>
26
<div
27
style={{
28
maxWidth: MAX_WIDTH,
29
margin: "15px auto",
30
padding: "15px",
31
backgroundColor: "white",
32
color: COLORS.GRAY_D,
33
}}
34
>
35
<Title level={1} style={{ textAlign: "center" }}>
36
Support Tickets
37
</Title>
38
<p style={{ fontSize: "12pt" }}>
39
Check the status of your support tickets here or{" "}
40
<A href="/support/new">create a new ticket</A>. Newly created tickets
41
do not appear here for a few minutes.
42
</p>
43
{error && (
44
<Alert
45
style={{ margin: "30px 0" }}
46
type="error"
47
message={"Error loading support tickets"}
48
description={error}
49
/>
50
)}
51
<Divider>Tickets</Divider>
52
<br />
53
{result ? (
54
<SupportTimeline tickets={result.tickets} />
55
) : (
56
<Loading style={{ fontSize: "20pt" }} />
57
)}
58
</div>
59
</Layout.Content>
60
);
61
}
62
63
function SupportTimeline({ tickets }) {
64
const v: ReactNode[] = [];
65
for (const ticket of tickets ?? []) {
66
v.push(
67
<Timeline.Item key={ticket.id} color={statusToColor(ticket.status)}>
68
<Ticket ticket={ticket} />
69
</Timeline.Item>,
70
);
71
}
72
return <Timeline>{v}</Timeline>;
73
}
74
75
const COLOR = {
76
new: "orange",
77
open: "orange",
78
pending: "red",
79
solved: "#666",
80
};
81
82
function statusToColor(status: string): string {
83
return COLOR[status] ?? "#f5ca00";
84
}
85
86
function Ticket({ ticket }) {
87
const {
88
id,
89
userURL,
90
created_at,
91
updated_at,
92
description,
93
status,
94
subject,
95
type,
96
} = ticket;
97
return (
98
<div style={{ marginBottom: "15px" }}>
99
<div style={{ float: "right" }}>
100
<Type type={type} status={status} />
101
</div>
102
<A href={userURL}>
103
<Status status={status} />
104
<b style={{ fontSize: "13pt" }}>{trunc(subject, 80)}</b>
105
</A>
106
<br />
107
<div style={{ float: "right" }}>
108
<Tooltip title="Click to visit Zendesk and see all responses to your support request.">
109
<A href={userURL}>
110
<Icon name="external-link" /> Ticket: {id}
111
</A>
112
</Tooltip>
113
</div>
114
(created: {dateToString(created_at)}, updated: {dateToString(updated_at)})
115
<br />
116
<div
117
style={{
118
overflow: "auto",
119
maxHeight: "30vh",
120
border: "1px solid lightgrey",
121
padding: "15px",
122
marginTop: "5px",
123
backgroundColor: "#fdfdfd",
124
borderRadius: "3px",
125
}}
126
>
127
<Markdown value={description} />
128
</div>
129
</div>
130
);
131
}
132
133
// Note that this is what to show from the POV of the user.
134
// See https://github.com/sagemathinc/cocalc/issues/6239
135
136
interface StatusDescription {
137
title: string;
138
label: string;
139
color: string;
140
}
141
142
const STATUS: { [status: string]: StatusDescription } = {
143
pending: {
144
title: "We are waiting for your response.",
145
label: "AWAITING YOUR REPLY",
146
color: "#f5ca00",
147
},
148
new: {
149
title: "We are looking at your support request but have not responded yet.",
150
label: "NEW",
151
color: "#59bbe0" /* blue */,
152
},
153
open: {
154
title: "We are trying to solve your support request.",
155
label: "OPEN",
156
color: "#59bbe0",
157
},
158
solved: {
159
title: "This support request has been solved.",
160
label: "SOLVED",
161
color: "#666",
162
},
163
};
164
165
function Status({ status }) {
166
const { title, label, color } = STATUS[status] ?? {
167
title: "",
168
label: "Status",
169
color: "blue",
170
};
171
return (
172
<Tooltip title={title}>
173
<Tag color={color} style={{ fontSize: "12pt", color: "white" }}>
174
{label}
175
</Tag>
176
</Tooltip>
177
);
178
}
179
180
const TYPE_COLOR: { [name in Type]: string } = {
181
problem: "red",
182
question: "blue",
183
task: "orange",
184
purchase: "green",
185
chat: "purple",
186
};
187
188
export function Type({ status, type }: { status?: string; type: Type }) {
189
return (
190
<Tag
191
color={status == "solved" ? COLORS.GRAY_M : TYPE_COLOR[type]}
192
style={{ fontSize: "12pt" }}
193
>
194
{capitalize(type)}
195
</Tag>
196
);
197
}
198
199
function dateToString(d: string): string {
200
try {
201
return new Date(d).toLocaleString();
202
} catch (_err) {
203
return d;
204
}
205
}
206
207