Adrià Vilanova MartÃnez | ac4a644 | 2022-05-15 19:05:13 +0200 | [diff] [blame] | 1 | // Copyright 2021 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import React from 'react'; |
| 6 | import {makeStyles} from '@material-ui/styles'; |
| 7 | import {grey} from '@material-ui/core/colors'; |
| 8 | import DotMobileStepper from './DotMobileStepper.tsx'; |
| 9 | import {CustomQuestion, CustomQuestionType} from './IssueWizardTypes.tsx'; |
| 10 | import CustomQuestionInput from './CustomQuestions/CustomQuestionInput.tsx'; |
| 11 | import CustomQuestionTextarea from './CustomQuestions/CustomQuestionTextarea.tsx'; |
| 12 | import CustomQuestionSelector from './CustomQuestions/CustomQuestionSelector.tsx'; |
| 13 | import Alert from '@material-ui/core/Alert'; |
| 14 | import AttachmentUploader from './AttachmentUploader.tsx'; |
| 15 | import Modal from '@material-ui/core/Modal'; |
| 16 | import Box from '@material-ui/core/Box'; |
| 17 | import {LABELS_PREFIX} from './IssueWizardConfig.ts'; |
| 18 | |
| 19 | const 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 | |
| 45 | type Props = { |
| 46 | setActiveStep: Function, |
| 47 | questions: CustomQuestion[], |
| 48 | onSubmit: Function, |
| 49 | setnewIssueID: Function, |
| 50 | }; |
| 51 | |
| 52 | export 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 | } |