Merge branch 'main' into avm99963-monorail

Merged commit 4137ed7879acadbf891e8c471108acb874dae886.

GitOrigin-RevId: b6100ffc5b1da355a35f37b13fcaaf746ee8b307
diff --git a/static_src/react/issue-wizard/CustomQuestionsStep.tsx b/static_src/react/issue-wizard/CustomQuestionsStep.tsx
new file mode 100644
index 0000000..9e1ef72
--- /dev/null
+++ b/static_src/react/issue-wizard/CustomQuestionsStep.tsx
@@ -0,0 +1,186 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import React from 'react';
+import {makeStyles} from '@material-ui/styles';
+import {grey} from '@material-ui/core/colors';
+import DotMobileStepper from './DotMobileStepper.tsx';
+import {CustomQuestion, CustomQuestionType} from './IssueWizardTypes.tsx';
+import CustomQuestionInput from './CustomQuestions/CustomQuestionInput.tsx';
+import CustomQuestionTextarea from './CustomQuestions/CustomQuestionTextarea.tsx';
+import CustomQuestionSelector from './CustomQuestions/CustomQuestionSelector.tsx';
+import Alert from '@material-ui/core/Alert';
+import AttachmentUploader from './AttachmentUploader.tsx';
+import Modal from '@material-ui/core/Modal';
+import Box from '@material-ui/core/Box';
+import {LABELS_PREFIX} from './IssueWizardConfig.ts';
+
+const userStyles = makeStyles({
+  greyText: {
+    color: grey[600],
+  },
+  root: {
+    width: '100%',
+  },
+  modalBox: {
+    position: 'absolute',
+    top: '40%',
+    left: '50%',
+    transform: 'translate(-50%, -50%)',
+    width: 400,
+    backgroundColor: 'white',
+    borderRadius: '10px',
+    padding: '10px',
+  },
+  modalTitle: {
+    fontSize: '20px',
+    margin: '5px 0px',
+  },
+  modalContext: {
+    fontSize: '15px',
+  },
+});
+
+type Props = {
+  setActiveStep: Function,
+  questions: CustomQuestion[],
+  onSubmit: Function,
+  setnewIssueID: Function,
+};
+
+export default function CustomQuestionsStep(props: Props): React.ReactElement {
+
+  const {setActiveStep, questions, onSubmit, setnewIssueID} = props;
+  const classes = userStyles();
+
+  const customQuestions = new Array();
+
+  const [additionalComments, setAdditionalComments] = React.useState('');
+  const [attachments, setAttachments] = React.useState([]);
+  const [answers, setAnswers] = React.useState(Array(questions.length).fill(''));
+  const [hasError, setHasError] = React.useState(false);
+  const [submitEnable, setSubmitEnable] = React.useState(true);
+  const [isSubmitting, setIsSubmitting] = React.useState(false);
+
+  const updateAnswer = (answer: string, index: number) => {
+    const updatedAnswers = answers;
+    const answerPrefix = questions[index].answerPrefix !== LABELS_PREFIX ?
+      '<b>' + questions[index].answerPrefix + '</b> ' : LABELS_PREFIX;
+    updatedAnswers[index] = answerPrefix + answer;
+    setAnswers(updatedAnswers);
+  }
+
+  questions.forEach((q, i) => {
+    switch(q.type) {
+      case CustomQuestionType.Input:
+        customQuestions.push(
+          <CustomQuestionInput
+            question={q.question}
+            updateAnswers={(answer: string) => updateAnswer(answer, i)}
+          />
+        );
+        return;
+      case CustomQuestionType.Text:
+          customQuestions.push(
+            <CustomQuestionTextarea
+              question={q.question}
+              tip={q.tip}
+              updateAnswers={(answer: string) => updateAnswer(answer, i)}
+            />
+          );
+          return;
+      case CustomQuestionType.Select:
+        customQuestions.push(
+          <CustomQuestionSelector
+            question={q.question}
+            tip={q.tip}
+            options={q.options}
+            subQuestions={q.subQuestions}
+            updateAnswers={(answer: string) => updateAnswer(answer, i)}
+          />
+        );
+        return;
+      default:
+        return;
+    }
+  });
+
+  const loadFiles = () => {
+    if (!attachments || attachments.length === 0) {
+      return Promise.resolve([]);
+    }
+    const loads = attachments.map(loadLocalFile);
+    return Promise.all(loads);
+  }
+
+  const loadLocalFile = (f: File) => {
+    return new Promise((resolve, reject) => {
+      const r = new FileReader();
+      r.onloadend = () => {
+        resolve({filename: f.name, content: btoa(r.result)});
+      };
+      r.onerror = () => {
+        reject(r.error);
+      };
+
+      r.readAsBinaryString(f);
+    });
+  }
+
+  const onSuccess = (response: Issue) => {
+    //redirect to issue
+    setIsSubmitting(false);
+    const issueId = response.name.split('/')[3];
+    setnewIssueID(issueId);
+    setActiveStep(3);
+  };
+
+  const onFailure = () => {
+    setIsSubmitting(false);
+    setHasError(true);
+  }
+
+  const onMakeIssue = () => {
+    setHasError(false);
+    setIsSubmitting(true);
+    try {
+      const uploads = loadFiles();
+      uploads.then((files) => {
+        // TODO: add attachments to request
+        onSubmit(additionalComments, answers, files, onSuccess, onFailure);
+      }, onFailure)
+    } catch (e) {
+      onFailure();
+    }
+  }
+
+  return (
+    <>
+      <h2 className={classes.greyText}>Extra Information about the Issue</h2>
+      {hasError
+        ? <Alert severity="error" onClose={() => {setHasError(false)}}>Something went wrong, please try again later.</Alert>
+        : null
+      }
+      <div className={classes.root}>
+        {customQuestions}
+
+        <CustomQuestionTextarea
+          question="Additional comments"
+          updateAnswers={(answer: string) => setAdditionalComments(answer)}
+        />
+
+        <h3>Upload any relevant screenshots</h3>
+        <AttachmentUploader files={attachments} setFiles={setAttachments} setSubmitEnable={setSubmitEnable}/>
+
+      </div>
+      <DotMobileStepper nextEnabled={submitEnable} activeStep={2} setActiveStep={setActiveStep} onSubmit={onMakeIssue}/>
+      <Modal open={isSubmitting} >
+        <Box className={classes.modalBox}>
+          <p className={classes.modalTitle}>Thanks for contributing to Chromium!</p>
+          <p>Stay put, we're filing your issue!</p>
+        </Box>
+      </Modal>
+    </>
+  );
+}