|
|
@@ -0,0 +1,211 @@
|
|
|
+import OLButton from '@/features/ui/components/ol/ol-button'
|
|
|
+import OLForm from '@/features/ui/components/ol/ol-form'
|
|
|
+import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
|
|
|
+import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
|
|
+import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
|
|
+import { OLToast } from '@/features/ui/components/ol/ol-toast'
|
|
|
+import { OLToastContainer } from '@/features/ui/components/ol/ol-toast-container'
|
|
|
+import { useEditorContext } from '@/shared/context/editor-context'
|
|
|
+import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
|
|
+import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
|
+import { sendMB } from '@/infrastructure/event-tracking'
|
|
|
+import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
|
|
|
+import { useTranslation } from 'react-i18next'
|
|
|
+
|
|
|
+type EditorSurveyPage = 'ease-of-use' | 'meets-my-needs' | 'thank-you'
|
|
|
+
|
|
|
+export default function EditorSurvey() {
|
|
|
+ return (
|
|
|
+ <OLToastContainer className="editor-survey-toast">
|
|
|
+ <EditorSurveyContent />
|
|
|
+ </OLToastContainer>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const TUTORIAL_KEY = 'editor-popup-ux-survey'
|
|
|
+
|
|
|
+const EditorSurveyContent = () => {
|
|
|
+ const [easeOfUse, setEaseOfUse] = useState<number | null>(null)
|
|
|
+ const [meetsMyNeeds, setMeetsMyNeeds] = useState<number | null>(null)
|
|
|
+ const [page, setPage] = useState<EditorSurveyPage>('ease-of-use')
|
|
|
+ const { inactiveTutorials } = useEditorContext()
|
|
|
+ const hasCompletedSurvey = inactiveTutorials.includes(TUTORIAL_KEY)
|
|
|
+ const newEditor = useIsNewEditorEnabled()
|
|
|
+
|
|
|
+ const { t } = useTranslation()
|
|
|
+
|
|
|
+ const {
|
|
|
+ tryShowingPopup: tryShowingSurvey,
|
|
|
+ showPopup: showSurvey,
|
|
|
+ dismissTutorial: dismissSurvey,
|
|
|
+ completeTutorial: completeSurvey,
|
|
|
+ } = useTutorial(TUTORIAL_KEY, {
|
|
|
+ name: TUTORIAL_KEY,
|
|
|
+ })
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (!hasCompletedSurvey) {
|
|
|
+ tryShowingSurvey()
|
|
|
+ }
|
|
|
+ }, [hasCompletedSurvey, tryShowingSurvey])
|
|
|
+
|
|
|
+ const onSubmit = useCallback(() => {
|
|
|
+ sendMB('editor-survey-submit', {
|
|
|
+ easeOfUse,
|
|
|
+ meetsMyNeeds,
|
|
|
+ newEditor,
|
|
|
+ })
|
|
|
+ setPage('thank-you')
|
|
|
+ completeSurvey({ event: 'promo-click', action: 'complete' })
|
|
|
+ }, [easeOfUse, meetsMyNeeds, completeSurvey, newEditor])
|
|
|
+
|
|
|
+ if (!showSurvey && page !== 'thank-you') {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ if (page === 'ease-of-use') {
|
|
|
+ return (
|
|
|
+ <OLToast
|
|
|
+ className="editor-survey-question-toast"
|
|
|
+ content={
|
|
|
+ <EditorSurveyQuestion
|
|
|
+ questionText={t('overleaf_is_easy_to_use')}
|
|
|
+ name="ease-of-use"
|
|
|
+ buttonText={t('next')}
|
|
|
+ onButtonClick={() => setPage('meets-my-needs')}
|
|
|
+ value={easeOfUse}
|
|
|
+ onValueChange={setEaseOfUse}
|
|
|
+ onDismiss={dismissSurvey}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ type="info"
|
|
|
+ />
|
|
|
+ )
|
|
|
+ }
|
|
|
+ if (page === 'meets-my-needs') {
|
|
|
+ return (
|
|
|
+ <OLToast
|
|
|
+ className="editor-survey-question-toast"
|
|
|
+ content={
|
|
|
+ <EditorSurveyQuestion
|
|
|
+ questionText={t('overleafs_functionality_meets_my_needs')}
|
|
|
+ name="meets-my-needs"
|
|
|
+ buttonText={t('submit_title')}
|
|
|
+ onButtonClick={onSubmit}
|
|
|
+ value={meetsMyNeeds}
|
|
|
+ onValueChange={setMeetsMyNeeds}
|
|
|
+ onDismiss={dismissSurvey}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ type="info"
|
|
|
+ />
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ return <OLToast type="info" content={t('thank_you')} autoHide delay={3000} />
|
|
|
+}
|
|
|
+
|
|
|
+const EditorSurveyQuestion = ({
|
|
|
+ onDismiss,
|
|
|
+ name,
|
|
|
+ questionText,
|
|
|
+ buttonText,
|
|
|
+ onButtonClick,
|
|
|
+ value,
|
|
|
+ onValueChange,
|
|
|
+}: {
|
|
|
+ onDismiss: () => void
|
|
|
+ name: string
|
|
|
+ questionText: string
|
|
|
+ buttonText: string
|
|
|
+ onButtonClick: () => void
|
|
|
+ value: number | null
|
|
|
+ onValueChange: (newValue: number) => void
|
|
|
+}) => {
|
|
|
+ const { t } = useTranslation()
|
|
|
+
|
|
|
+ const options = useMemo(
|
|
|
+ () => [
|
|
|
+ {
|
|
|
+ value: 1,
|
|
|
+ description: t('strongly_disagree'),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: 2,
|
|
|
+ description: t('disagree'),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: 3,
|
|
|
+ description: t('neither_agree_nor_disagree'),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: 4,
|
|
|
+ description: t('agree'),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: 5,
|
|
|
+ description: t('strongly_agree'),
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ [t]
|
|
|
+ )
|
|
|
+
|
|
|
+ const onChange = useCallback(
|
|
|
+ (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
+ const newValue = event.target.value
|
|
|
+ if (newValue) {
|
|
|
+ onValueChange(Number(newValue))
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [onValueChange]
|
|
|
+ )
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="editor-survey-question">
|
|
|
+ <div className="editor-survey-question-top-line">
|
|
|
+ <div>{t('your_feedback_matters_answer_two_quick_questions')}</div>
|
|
|
+ <OLIconButton
|
|
|
+ variant="ghost"
|
|
|
+ size="sm"
|
|
|
+ icon="close"
|
|
|
+ accessibilityLabel={t('close')}
|
|
|
+ onClick={onDismiss}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label className="editor-survey-question-label" htmlFor={name}>
|
|
|
+ {questionText}
|
|
|
+ </label>
|
|
|
+ <OLForm className="editor-survey-question-form">
|
|
|
+ <OLFormGroup className="editor-survey-question-options">
|
|
|
+ {options.map(({ value: optionValue, description }) => (
|
|
|
+ <OLFormCheckbox
|
|
|
+ key={optionValue}
|
|
|
+ name={name}
|
|
|
+ type="radio"
|
|
|
+ value={optionValue}
|
|
|
+ id={optionValue.toString()}
|
|
|
+ label={optionValue}
|
|
|
+ checked={optionValue === value}
|
|
|
+ onChange={onChange}
|
|
|
+ aria-label={`${optionValue} ${description}`}
|
|
|
+ />
|
|
|
+ ))}
|
|
|
+ </OLFormGroup>
|
|
|
+ </OLForm>
|
|
|
+ <div className="editor-survey-option-labels">
|
|
|
+ <div>{t('strongly_disagree')}</div>
|
|
|
+ <div>{t('strongly_agree')}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <OLButton
|
|
|
+ size="sm"
|
|
|
+ variant="secondary"
|
|
|
+ onClick={onButtonClick}
|
|
|
+ disabled={!value}
|
|
|
+ >
|
|
|
+ {buttonText}
|
|
|
+ </OLButton>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|