overleaf-cep/services/web/frontend/js/features/ide-react/components/editor-survey.tsx
David 79f9957b68 Merge pull request #25164 from overleaf/dp-editor-survey
Create Editor Survey

GitOrigin-RevId: dc11ef16c0a00aa846ac7a664dd88e9531e832f2
2025-05-02 08:05:47 +00:00

211 lines
5.8 KiB
TypeScript

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>
)
}