blob: 8b65f08e98b3775e2a7b0612dec78d1ddb619911 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001// Copyright 2021 The Chromium Authors
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +02002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import React from 'react';
6import {makeStyles} from '@material-ui/styles';
7import {grey} from '@material-ui/core/colors';
8import DotMobileStepper from './DotMobileStepper.tsx';
9import {CustomQuestion, CustomQuestionType} from './IssueWizardTypes.tsx';
10import CustomQuestionInput from './CustomQuestions/CustomQuestionInput.tsx';
11import CustomQuestionTextarea from './CustomQuestions/CustomQuestionTextarea.tsx';
12import CustomQuestionSelector from './CustomQuestions/CustomQuestionSelector.tsx';
13import Alert from '@material-ui/core/Alert';
14import AttachmentUploader from './AttachmentUploader.tsx';
15import Modal from '@material-ui/core/Modal';
16import Box from '@material-ui/core/Box';
17import {LABELS_PREFIX} from './IssueWizardConfig.ts';
18
19const userStyles = makeStyles({
20 greyText: {
21 color: grey[600],
22 },
23 root: {
24 width: '100%',
25 },
26 modalBox: {
27 position: 'absolute',
28 top: '40%',
29 left: '50%',
30 transform: 'translate(-50%, -50%)',
31 width: 400,
32 backgroundColor: 'white',
33 borderRadius: '10px',
34 padding: '10px',
35 },
36 modalTitle: {
37 fontSize: '20px',
38 margin: '5px 0px',
39 },
40 modalContext: {
41 fontSize: '15px',
42 },
43});
44
45type Props = {
46 setActiveStep: Function,
47 questions: CustomQuestion[],
48 onSubmit: Function,
49 setnewIssueID: Function,
50};
51
52export default function CustomQuestionsStep(props: Props): React.ReactElement {
53
54 const {setActiveStep, questions, onSubmit, setnewIssueID} = props;
55 const classes = userStyles();
56
57 const customQuestions = new Array();
58
59 const [additionalComments, setAdditionalComments] = React.useState('');
60 const [attachments, setAttachments] = React.useState([]);
61 const [answers, setAnswers] = React.useState(Array(questions.length).fill(''));
62 const [hasError, setHasError] = React.useState(false);
63 const [submitEnable, setSubmitEnable] = React.useState(true);
64 const [isSubmitting, setIsSubmitting] = React.useState(false);
65
66 const updateAnswer = (answer: string, index: number) => {
67 const updatedAnswers = answers;
68 const answerPrefix = questions[index].answerPrefix !== LABELS_PREFIX ?
69 '<b>' + questions[index].answerPrefix + '</b> ' : LABELS_PREFIX;
70 updatedAnswers[index] = answerPrefix + answer;
71 setAnswers(updatedAnswers);
72 }
73
74 questions.forEach((q, i) => {
75 switch(q.type) {
76 case CustomQuestionType.Input:
77 customQuestions.push(
78 <CustomQuestionInput
79 question={q.question}
80 updateAnswers={(answer: string) => updateAnswer(answer, i)}
81 />
82 );
83 return;
84 case CustomQuestionType.Text:
85 customQuestions.push(
86 <CustomQuestionTextarea
87 question={q.question}
88 tip={q.tip}
89 updateAnswers={(answer: string) => updateAnswer(answer, i)}
90 />
91 );
92 return;
93 case CustomQuestionType.Select:
94 customQuestions.push(
95 <CustomQuestionSelector
96 question={q.question}
97 tip={q.tip}
98 options={q.options}
99 subQuestions={q.subQuestions}
100 updateAnswers={(answer: string) => updateAnswer(answer, i)}
101 />
102 );
103 return;
104 default:
105 return;
106 }
107 });
108
109 const loadFiles = () => {
110 if (!attachments || attachments.length === 0) {
111 return Promise.resolve([]);
112 }
113 const loads = attachments.map(loadLocalFile);
114 return Promise.all(loads);
115 }
116
117 const loadLocalFile = (f: File) => {
118 return new Promise((resolve, reject) => {
119 const r = new FileReader();
120 r.onloadend = () => {
121 resolve({filename: f.name, content: btoa(r.result)});
122 };
123 r.onerror = () => {
124 reject(r.error);
125 };
126
127 r.readAsBinaryString(f);
128 });
129 }
130
131 const onSuccess = (response: Issue) => {
132 //redirect to issue
133 setIsSubmitting(false);
134 const issueId = response.name.split('/')[3];
135 setnewIssueID(issueId);
136 setActiveStep(3);
137 };
138
139 const onFailure = () => {
140 setIsSubmitting(false);
141 setHasError(true);
142 }
143
144 const onMakeIssue = () => {
145 setHasError(false);
146 setIsSubmitting(true);
147 try {
148 const uploads = loadFiles();
149 uploads.then((files) => {
150 // TODO: add attachments to request
151 onSubmit(additionalComments, answers, files, onSuccess, onFailure);
152 }, onFailure)
153 } catch (e) {
154 onFailure();
155 }
156 }
157
158 return (
159 <>
160 <h2 className={classes.greyText}>Extra Information about the Issue</h2>
161 {hasError
162 ? <Alert severity="error" onClose={() => {setHasError(false)}}>Something went wrong, please try again later.</Alert>
163 : null
164 }
165 <div className={classes.root}>
166 {customQuestions}
167
168 <CustomQuestionTextarea
169 question="Additional comments"
170 updateAnswers={(answer: string) => setAdditionalComments(answer)}
171 />
172
173 <h3>Upload any relevant screenshots</h3>
174 <AttachmentUploader files={attachments} setFiles={setAttachments} setSubmitEnable={setSubmitEnable}/>
175
176 </div>
177 <DotMobileStepper nextEnabled={submitEnable} activeStep={2} setActiveStep={setActiveStep} onSubmit={onMakeIssue}/>
178 <Modal open={isSubmitting} >
179 <Box className={classes.modalBox}>
180 <p className={classes.modalTitle}>Thanks for contributing to Chromium!</p>
181 <p>Stay put, we're filing your issue!</p>
182 </Box>
183 </Modal>
184 </>
185 );
186}