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/create.tsx
Views: 687
1
import {
2
Alert,
3
Button,
4
Divider,
5
Input,
6
Layout,
7
Modal,
8
Radio,
9
Space,
10
} from "antd";
11
import { useRouter } from "next/router";
12
import { ReactNode, useRef, useState } from "react";
13
import { Icon } from "@cocalc/frontend/components/icon";
14
import { is_valid_email_address as isValidEmailAddress } from "@cocalc/util/misc";
15
import { COLORS } from "@cocalc/util/theme";
16
import { Paragraph, Title } from "components/misc";
17
import A from "components/misc/A";
18
import ChatGPTHelp from "components/openai/chatgpt-help";
19
import CodeMirror from "components/share/codemirror";
20
import Loading from "components/share/loading";
21
import SiteName from "components/share/site-name";
22
import apiPost from "lib/api/post";
23
import { MAX_WIDTH } from "lib/config";
24
import { useCustomize } from "lib/customize";
25
import getBrowserInfo from "./browser-info";
26
import RecentFiles from "./recent-files";
27
import { Type } from "./tickets";
28
import { NoZendesk } from "./util";
29
import { VideoItem } from "components/videos";
30
31
const CHATGPT_DISABLED = true;
32
const MIN_BODY_LENGTH = 16;
33
34
function VSpace({ children }) {
35
return (
36
<Space direction="vertical" style={{ width: "100%", fontSize: "12pt" }}>
37
{children}
38
</Space>
39
);
40
}
41
42
export type Type = "problem" | "question" | "task" | "purchase" | "chat";
43
44
function stringToType(s?: any): Type {
45
if (
46
s == "problem" ||
47
s == "question" ||
48
s == "task" ||
49
s == "purchase" ||
50
s == "chat"
51
)
52
return s;
53
return "problem"; // default;
54
}
55
56
export default function Create() {
57
const { account, onCoCalcCom, helpEmail, openaiEnabled, siteName, zendesk } =
58
useCustomize();
59
const router = useRouter();
60
// The URL the user was viewing when they requested support.
61
// This could easily be blank, but if it is set it can be useful.
62
const { url } = router.query;
63
const [files, setFiles] = useState<{ project_id: string; path?: string }[]>(
64
[],
65
);
66
const [type, setType] = useState<Type>(stringToType(router.query.type));
67
const [email, setEmail] = useState<string>(account?.email_address ?? "");
68
const [body, setBody] = useState<string>(
69
router.query.body ? `${router.query.body}` : "",
70
);
71
const required = router.query.required ? `${router.query.required}` : "";
72
const [subject, setSubject] = useState<string>(
73
router.query.subject ? `${router.query.subject}` : "",
74
);
75
76
const [submitError, setSubmitError] = useState<ReactNode>("");
77
const [submitting, setSubmitting] = useState<boolean>(false);
78
const [success, setSuccess] = useState<ReactNode>("");
79
80
const showExtra = router.query.hideExtra != "true";
81
82
// hasRequired means "has the required information", which
83
// means that body does NOT have required in it!
84
const hasRequired = !required || !body.includes(required);
85
86
const submittable = useRef<boolean>(false);
87
submittable.current = !!(
88
!submitting &&
89
!submitError &&
90
!success &&
91
isValidEmailAddress(email) &&
92
subject &&
93
(body ?? "").length >= MIN_BODY_LENGTH &&
94
hasRequired
95
);
96
97
if (!zendesk) {
98
return <NoZendesk />;
99
}
100
101
async function createSupportTicket() {
102
const info = getBrowserInfo();
103
if (router.query.context) {
104
// used to pass context info along in the url when
105
// creating a support ticket,
106
// e.g., from the crash reporter.
107
info.context = `${router.query.context}`;
108
}
109
const options = { type, files, email, body, url, subject, info };
110
setSubmitError("");
111
let result;
112
try {
113
setSubmitting(true);
114
result = await apiPost("/support/create-ticket", { options });
115
} catch (err) {
116
setSubmitError(err.message);
117
return;
118
} finally {
119
setSubmitting(false);
120
}
121
setSuccess(
122
<div>
123
<p>
124
Please save this URL: <A href={result.url}>{result.url}</A>
125
</p>
126
<p>
127
You can also see the{" "}
128
<A href="/support/tickets">status of your support tickets</A>.
129
</p>
130
</div>,
131
);
132
}
133
134
return (
135
<Layout.Content style={{ backgroundColor: "white" }}>
136
<div
137
style={{
138
maxWidth: MAX_WIDTH,
139
margin: "15px auto",
140
padding: "15px",
141
backgroundColor: "white",
142
color: COLORS.GRAY_D,
143
}}
144
>
145
<Title level={1} style={{ textAlign: "center" }}>
146
{router.query.title ?? "Create a New Support Ticket"}
147
</Title>
148
{showExtra && (
149
<>
150
<Space>
151
<p style={{ fontSize: "12pt" }}>
152
Create a new support ticket below or{" "}
153
<A href="/support/tickets">
154
check the status of your support tickets
155
</A>
156
.{" "}
157
{helpEmail ? (
158
<>
159
You can also email us directly at{" "}
160
<A href={`mailto:${helpEmail}`}>{helpEmail}</A> or{" "}
161
<A href="https://calendly.com/cocalc/discovery">
162
book a demo or discovery call
163
</A>
164
.
165
</>
166
) : undefined}
167
</p>
168
<VideoItem
169
width={600}
170
style={{ margin: "15px 0", width: "600px" }}
171
id={"4Ef9sxX59XM"}
172
/>
173
</Space>
174
{openaiEnabled && onCoCalcCom && !CHATGPT_DISABLED ? (
175
<ChatGPT siteName={siteName} />
176
) : undefined}
177
<FAQ />
178
<Title level={2}>Create Your Ticket</Title>
179
<Instructions />
180
<Divider>Support Ticket</Divider>
181
</>
182
)}
183
<form>
184
<VSpace>
185
<b>
186
<Status done={isValidEmailAddress(email)} /> Your Email Address
187
</b>
188
<Input
189
prefix={
190
<Icon name="envelope" style={{ color: "rgba(0,0,0,.25)" }} />
191
}
192
defaultValue={email}
193
placeholder="Email address..."
194
style={{ maxWidth: "500px" }}
195
onChange={(e) => setEmail(e.target.value)}
196
/>
197
<br />
198
<b>
199
<Status done={subject} /> Subject
200
</b>
201
<Input
202
placeholder="Summarize what this is about..."
203
onChange={(e) => setSubject(e.target.value)}
204
defaultValue={subject}
205
/>
206
<br />
207
<b>
208
Is this a <i>Problem</i>, <i>Question</i>, or{" "}
209
<i>Software Install Task</i>?
210
</b>
211
<Radio.Group
212
name="radiogroup"
213
defaultValue={type}
214
onChange={(e) => setType(e.target.value)}
215
>
216
<VSpace>
217
<Radio value={"problem"}>
218
<Type type="problem" /> Something is not working the way I
219
think it should work.
220
</Radio>
221
<Radio value={"question"}>
222
<Type type="question" /> I have a question about billing,
223
functionality, teaching, something not working, etc.
224
</Radio>
225
<Radio value={"task"}>
226
<Type type="task" /> Is it possible for you to install some
227
software that I need in order to use <SiteName />?
228
</Radio>
229
<Radio value={"purchase"}>
230
<Type type="purchase" /> I have a question regarding
231
purchasing a product.
232
</Radio>
233
<Radio value={"chat"}>
234
<Type type="chat" /> I would like to schedule a video chat.
235
</Radio>
236
</VSpace>
237
</Radio.Group>
238
<br />
239
{showExtra && type !== "purchase" && type != "chat" && (
240
<>
241
<Files onChange={setFiles} />
242
<br />
243
</>
244
)}
245
{type == "chat" && (
246
<h1 style={{ textAlign: "center" }}>
247
<b>
248
<A href="https://calendly.com/cocalc">Book a Video Chat...</A>
249
</b>
250
</h1>
251
)}
252
{type != "chat" && (
253
<>
254
<b>
255
<Status
256
done={body && body.length >= MIN_BODY_LENGTH && hasRequired}
257
/>{" "}
258
Description
259
</b>
260
<div
261
style={{
262
marginLeft: "30px",
263
borderLeft: "1px solid lightgrey",
264
paddingLeft: "15px",
265
}}
266
>
267
{type == "problem" && <Problem onChange={setBody} />}
268
{type == "question" && (
269
<Question onChange={setBody} defaultValue={body} />
270
)}
271
{type == "purchase" && (
272
<Purchase
273
onChange={setBody}
274
defaultValue={body}
275
showExtra={showExtra}
276
/>
277
)}
278
{type == "task" && <Task onChange={setBody} />}
279
</div>
280
</>
281
)}
282
</VSpace>
283
284
<div style={{ textAlign: "center", marginTop: "30px" }}>
285
{!hasRequired && (
286
<Alert
287
showIcon
288
style={{ margin: "15px 30px" }}
289
type="error"
290
description={`You must replace the text '${required}' everywhere above with the requested information.`}
291
/>
292
)}
293
{type != "chat" && (
294
<Button
295
shape="round"
296
size="large"
297
disabled={!submittable.current}
298
type="primary"
299
onClick={createSupportTicket}
300
>
301
<Icon name="paper-plane" />{" "}
302
{submitting
303
? "Submitting..."
304
: success
305
? "Thank you for creating a ticket"
306
: submitError
307
? "Close the error box to try again"
308
: !isValidEmailAddress(email)
309
? "Enter Valid Email Address above"
310
: !subject
311
? "Enter Subject above"
312
: (body ?? "").length < MIN_BODY_LENGTH
313
? `Describe your ${type} in detail above`
314
: "Create Support Ticket"}
315
</Button>
316
)}
317
{submitting && <Loading style={{ fontSize: "32pt" }} />}
318
{submitError && (
319
<div>
320
<Alert
321
type="error"
322
message="Error creating support ticket"
323
description={submitError}
324
closable
325
showIcon
326
onClose={() => setSubmitError("")}
327
style={{ margin: "15px auto", maxWidth: "500px" }}
328
/>
329
<br />
330
{helpEmail ? (
331
<>
332
If you continue to have problems, email us directly at{" "}
333
<A href={`mailto:${helpEmail}`}>{helpEmail}</A>.
334
</>
335
) : undefined}
336
</div>
337
)}
338
{success && (
339
<Alert
340
type="success"
341
message="Successfully created support ticket"
342
description={success}
343
onClose={() => {
344
// simplest way to reset all the information in the form.
345
router.reload();
346
}}
347
closable
348
showIcon
349
style={{ margin: "15px auto", maxWidth: "500px" }}
350
/>
351
)}
352
</div>
353
</form>
354
<p style={{ marginTop: "30px" }}>
355
After submitting this, you'll receive a link, which you should save
356
until you receive a confirmation email. You can also{" "}
357
<A href="/support/tickets">check the status of your tickets here</A>.
358
</p>
359
</div>
360
</Layout.Content>
361
);
362
}
363
364
function Files({ onChange }) {
365
return (
366
<VSpace>
367
<b>Relevant Files</b>
368
Select any relevant projects and files below. This will make it much
369
easier for us to quickly understand your problem.
370
<RecentFiles interval="1 day" onChange={onChange} />
371
</VSpace>
372
);
373
}
374
375
function Problem({ onChange }) {
376
const answers = useRef<[string, string, string]>(["", "", ""]);
377
function update(i: 0 | 1 | 2, value: string): void {
378
answers.current[i] = value;
379
onChange?.(answers.current.join("\n\n\n").trim());
380
}
381
382
return (
383
<VSpace>
384
<b>What did you do exactly?</b>
385
<Input.TextArea
386
rows={3}
387
placeholder="Describe what you did..."
388
onChange={(e) =>
389
update(
390
0,
391
e.target.value
392
? "\n\nWHAT DID YOU DO EXACTLY?\n\n" + e.target.value
393
: "",
394
)
395
}
396
/>
397
<br />
398
<b>What happened?</b>
399
<Input.TextArea
400
rows={3}
401
placeholder="Tell us what happened..."
402
onChange={(e) =>
403
update(
404
1,
405
e.target.value ? "\n\nWHAT HAPPENED?\n\n" + e.target.value : "",
406
)
407
}
408
/>
409
<br />
410
<b>How did this differ from what you expected?</b>
411
<Input.TextArea
412
rows={3}
413
placeholder="Explain how this differs from what you expected..."
414
onChange={(e) =>
415
update(
416
2,
417
e.target.value
418
? "\n\nHOW DID THIS DIFFER FROM WHAT YOU EXPECTED?\n\n" +
419
e.target.value
420
: "",
421
)
422
}
423
/>
424
</VSpace>
425
);
426
}
427
428
function Question({ defaultValue, onChange }) {
429
return (
430
<Input.TextArea
431
rows={8}
432
defaultValue={defaultValue}
433
placeholder="Your question..."
434
onChange={(e) => onChange(e.target.value)}
435
/>
436
);
437
}
438
439
function Purchase({ defaultValue, onChange, showExtra }) {
440
return (
441
<>
442
{showExtra && (
443
<Paragraph>
444
Please describe what you want to purchase. We need some context in
445
order to guide you. In particular:
446
<ul>
447
<li>
448
The expected number of projects: this is either the number of
449
users, or how many projects they'll collectively be using.
450
</li>
451
<li>
452
The kind of workload: this ranges from student projects with
453
minimal resource requirements to large and resource intensive
454
research projects.
455
</li>
456
<li>How long you expect to use the services.</li>
457
<li>
458
Your type of organization: i.e. if an academic discount applies to
459
you.
460
</li>
461
</ul>
462
</Paragraph>
463
)}
464
<Input.TextArea
465
rows={8}
466
defaultValue={defaultValue}
467
placeholder="Your purchase request..."
468
onChange={(e) => onChange(e.target.value)}
469
/>
470
</>
471
);
472
}
473
474
function Task({ onChange }) {
475
const answers = useRef<[string, string, string]>(["", "", ""]);
476
function update(i: 0 | 1 | 2, value: string): void {
477
answers.current[i] = value;
478
onChange?.(answers.current.join("\n\n\n").trim());
479
}
480
481
const [showWestPoint, setShowWestPoint] = useState<boolean>(false);
482
483
return (
484
<div>
485
<Modal
486
width="700px"
487
open={showWestPoint}
488
onCancel={() => setShowWestPoint(false)}
489
onOk={() => setShowWestPoint(false)}
490
title={
491
<div>
492
A question about CoCalc ...
493
<A href="https://www.westpoint.edu/mathematical-sciences/profile/joseph_lindquist">
494
<div
495
style={{
496
fontSize: "10px",
497
float: "right",
498
width: "125px",
499
margin: "0 20px",
500
}}
501
>
502
<img
503
style={{ width: "125px" }}
504
src="https://s3.amazonaws.com/usma-media/styles/profile_image_display/s3/inline-images/academics/academic_departments/mathematical_sciences/images/profiles/COL%20JOE%20LINDQUIST.jpg?itok=r9vjncwh"
505
/>
506
Colonel Joe Lindquist
507
<br />
508
West Point
509
</div>
510
</A>
511
</div>
512
}
513
>
514
<b>WHAT SOFTWARE DO YOU NEED?</b>
515
<br />
516
Hi Team! I'm getting ready to kick off our short course at West Point
517
that will deal with Natural Language Processing. We're still sorting out
518
the purchase request, but expect it to be complete in the next day or
519
so. It looks like you have the "big" packages installed that we will be
520
exploring... Huggingface, Transformers, NLTK, WordBlob... but another
521
package that I was hoping to use is vadersentiment (
522
<A href="https://pypi.org/project/vaderSentiment/">
523
https://pypi.org/project/vaderSentiment/
524
</A>
525
).
526
<br />
527
<br />
528
<b>HOW DO YOU PLAN TO USE THIS SOFTWARE?</b>
529
<br />
530
The course begins on 15MAR and I'd love to be able to use it for this.
531
I'm happy to assume some guidance on how to best incorporate this into
532
CoCalc if unable to install the package.
533
<br />
534
<br />
535
<b>HOW CAN WE TEST THAT THE SOFTWARE IS PROPERLY INSTALLED?</b>
536
<CodeMirror
537
fontSize={12}
538
lineNumbers={false}
539
filename="a.py"
540
content={`from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
541
sid_obj = SentimentIntensityAnalyzer()
542
text = "CoCalc is an amazing platform for students to learn how to understand NLP!"
543
print(sid_obj.polarity_scores(text))`}
544
/>
545
<br />
546
This should return:
547
<CodeMirror
548
fontSize={12}
549
lineNumbers={false}
550
filename="a.json"
551
content={
552
"{'neg': 0.0, 'neu': 0.746, 'pos': 0.254, 'compound': 0.6239}"
553
}
554
/>
555
<br />
556
One Day Later
557
<br />
558
You guys are fantastic! Such a quick turn-around. Please feel free to
559
use the request in any fashion you wish 😊
560
<br />
561
By the way… in case you were wondering, “You guys are fantastic!” has a
562
compound polarity score of 0.598 😊. I used it in CoCalc to test the
563
update.
564
</Modal>
565
Each <SiteName /> project is a Docker image running Ubuntu Linux on 64-bit
566
x86 hardware, so it is possible for us to install most standard Linux
567
software, and we have already installed{" "}
568
<A href="/software">a huge amount</A>. If there is something you need that
569
is missing, let us know below. You can also{" "}
570
<a onClick={() => setShowWestPoint(true)}>
571
view a recent ticket from West Point
572
</a>{" "}
573
for an example install request.
574
<br />
575
<br />
576
<b>What software do you need?</b> In particular, if this is a Python
577
library, explain which of the{" "}
578
<A href="software/python">many Python environments</A> you need it
579
installed into and why you can't just{" "}
580
<A href="https://doc.cocalc.com/howto/install-python-lib.html">
581
install it yourself
582
</A>
583
.
584
<br />
585
<Input.TextArea
586
style={{ marginTop: "10px" }}
587
rows={4}
588
placeholder="Describe what software you need installed..."
589
onChange={(e) =>
590
update(
591
0,
592
e.target.value
593
? "\n\nWHAT SOFTWARE DO YOU NEED?\n\n" + e.target.value
594
: "",
595
)
596
}
597
/>
598
<br />
599
<br />
600
<br />
601
<b>How do you plan to use this software?</b> For example, does it need to
602
be installed across <SiteName /> for a course you are teaching that starts
603
in 3 weeks?
604
<br />
605
<Input.TextArea
606
style={{ marginTop: "10px" }}
607
rows={3}
608
placeholder="Explain how you will use the software ..."
609
onChange={(e) =>
610
update(
611
1,
612
e.target.value
613
? "\n\nHOW DO YOU PLAN TO USE THIS SOFTWARE?\n\n" + e.target.value
614
: "",
615
)
616
}
617
/>
618
<br />
619
<br />
620
<br />
621
<b>How can we test that the software is properly installed?</b>
622
<br />
623
<Input.TextArea
624
style={{ marginTop: "10px" }}
625
rows={3}
626
placeholder="Explain how we can test the software..."
627
onChange={(e) =>
628
update(
629
2,
630
e.target.value
631
? "\n\nHOW CAN WE TEST THAT THE SOFTWARE IS PROPERLY INSTALLED?\n\n" +
632
e.target.value
633
: "",
634
)
635
}
636
/>
637
</div>
638
);
639
}
640
function Instructions() {
641
return (
642
<div>
643
<p>
644
If the above links don't help you solve your problem, please create a
645
support ticket below. Support is currently available in{" "}
646
<b>English, German, and Russian</b> only.
647
</p>
648
</div>
649
);
650
}
651
652
function ChatGPT({ siteName }) {
653
return (
654
<div style={{ margin: "15px 0 20px 0" }}>
655
<Title level={2}>ChatGPT</Title>
656
<div style={{ color: "#666" }}>
657
If you have a question about how to do something using {siteName},
658
ChatGPT might save you some time:
659
</div>
660
<ChatGPTHelp style={{ marginTop: "15px" }} tag={"support"} />
661
</div>
662
);
663
}
664
665
function FAQ() {
666
return (
667
<div>
668
<Title level={2}>Helpful Links</Title>
669
<Alert
670
message={""}
671
style={{ margin: "20px 0" }}
672
type="warning"
673
description={
674
<ul style={{ marginBottom: 0, fontSize: "11pt" }}>
675
<li>
676
<A href="https://doc.cocalc.com/">The CoCalc Manual</A>
677
</li>
678
<li>
679
<A href="https://github.com/sagemathinc/cocalc/issues">
680
Bug reports
681
</A>
682
</li>
683
<li>
684
<A href="https://github.com/sagemathinc/cocalc/discussions">
685
The CoCalc Discussion Forum
686
</A>
687
</li>
688
<li>
689
{" "}
690
<A href="https://doc.cocalc.com/howto/missing-project.html">
691
Help: My file or project appears to be missing!
692
</A>{" "}
693
</li>
694
<li>
695
{" "}
696
I have{" "}
697
<A href="https://doc.cocalc.com/howto/sage-question.html">
698
general questions about SageMath...
699
</A>
700
</li>
701
</ul>
702
}
703
/>
704
</div>
705
);
706
}
707
708
function Status({ done }) {
709
return (
710
<Icon
711
style={{
712
color: done ? "green" : "red",
713
fontWeight: "bold",
714
fontSize: "12pt",
715
}}
716
name={done ? "check" : "arrow-right"}
717
/>
718
);
719
}
720
721