editor-survey.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import OLButton from '@/shared/components/ol/ol-button'
  2. import OLForm from '@/shared/components/ol/ol-form'
  3. import OLFormCheckbox from '@/shared/components/ol/ol-form-checkbox'
  4. import OLFormGroup from '@/shared/components/ol/ol-form-group'
  5. import OLIconButton from '@/shared/components/ol/ol-icon-button'
  6. import { OLToast } from '@/shared/components/ol/ol-toast'
  7. import { OLToastContainer } from '@/shared/components/ol/ol-toast-container'
  8. import useTutorial from '@/shared/hooks/promotions/use-tutorial'
  9. import { memo, useCallback, useEffect, useMemo, useState } from 'react'
  10. import { sendMB } from '@/infrastructure/event-tracking'
  11. import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
  12. import { useTranslation } from 'react-i18next'
  13. import { useTutorialContext } from '@/shared/context/tutorial-context'
  14. type EditorSurveyPage = 'ease-of-use' | 'meets-my-needs' | 'thank-you'
  15. export default memo(function EditorSurvey() {
  16. return (
  17. <OLToastContainer className="editor-survey-toast">
  18. <EditorSurveyContent />
  19. </OLToastContainer>
  20. )
  21. })
  22. const TUTORIAL_KEY = 'editor-popup-ux-survey'
  23. const EditorSurveyContent = () => {
  24. const [easeOfUse, setEaseOfUse] = useState<number | null>(null)
  25. const [meetsMyNeeds, setMeetsMyNeeds] = useState<number | null>(null)
  26. const [page, setPage] = useState<EditorSurveyPage>('ease-of-use')
  27. const { inactiveTutorials } = useTutorialContext()
  28. const hasCompletedSurvey = inactiveTutorials.includes(TUTORIAL_KEY)
  29. const newEditor = useIsNewEditorEnabled()
  30. const { t } = useTranslation()
  31. const {
  32. tryShowingPopup: tryShowingSurvey,
  33. showPopup: showSurvey,
  34. dismissTutorial: dismissSurvey,
  35. completeTutorial: completeSurvey,
  36. } = useTutorial(TUTORIAL_KEY, {
  37. name: TUTORIAL_KEY,
  38. })
  39. useEffect(() => {
  40. if (!hasCompletedSurvey) {
  41. tryShowingSurvey()
  42. }
  43. }, [hasCompletedSurvey, tryShowingSurvey])
  44. const onSubmit = useCallback(() => {
  45. sendMB('editor-survey-submit', {
  46. easeOfUse,
  47. meetsMyNeeds,
  48. newEditor,
  49. })
  50. setPage('thank-you')
  51. completeSurvey({ event: 'promo-click', action: 'complete' })
  52. }, [easeOfUse, meetsMyNeeds, completeSurvey, newEditor])
  53. if (!showSurvey && page !== 'thank-you') {
  54. return null
  55. }
  56. if (page === 'ease-of-use') {
  57. return (
  58. <OLToast
  59. className="editor-survey-question-toast"
  60. content={
  61. <EditorSurveyQuestion
  62. questionText={t('overleaf_is_easy_to_use')}
  63. name="ease-of-use"
  64. buttonText={t('next')}
  65. onButtonClick={() => setPage('meets-my-needs')}
  66. value={easeOfUse}
  67. onValueChange={setEaseOfUse}
  68. onDismiss={dismissSurvey}
  69. />
  70. }
  71. type="info"
  72. />
  73. )
  74. }
  75. if (page === 'meets-my-needs') {
  76. return (
  77. <OLToast
  78. className="editor-survey-question-toast"
  79. content={
  80. <EditorSurveyQuestion
  81. questionText={t('overleafs_functionality_meets_my_needs')}
  82. name="meets-my-needs"
  83. buttonText={t('submit_title')}
  84. onButtonClick={onSubmit}
  85. value={meetsMyNeeds}
  86. onValueChange={setMeetsMyNeeds}
  87. onDismiss={dismissSurvey}
  88. />
  89. }
  90. type="info"
  91. />
  92. )
  93. }
  94. return <OLToast type="info" content={t('thank_you')} autoHide delay={3000} />
  95. }
  96. const EditorSurveyQuestion = ({
  97. onDismiss,
  98. name,
  99. questionText,
  100. buttonText,
  101. onButtonClick,
  102. value,
  103. onValueChange,
  104. }: {
  105. onDismiss: () => void
  106. name: string
  107. questionText: string
  108. buttonText: string
  109. onButtonClick: () => void
  110. value: number | null
  111. onValueChange: (newValue: number) => void
  112. }) => {
  113. const { t } = useTranslation()
  114. const options = useMemo(
  115. () => [
  116. {
  117. value: 1,
  118. description: t('strongly_disagree'),
  119. },
  120. {
  121. value: 2,
  122. description: t('disagree'),
  123. },
  124. {
  125. value: 3,
  126. description: t('neither_agree_nor_disagree'),
  127. },
  128. {
  129. value: 4,
  130. description: t('agree'),
  131. },
  132. {
  133. value: 5,
  134. description: t('strongly_agree'),
  135. },
  136. ],
  137. [t]
  138. )
  139. const onChange = useCallback(
  140. (event: React.ChangeEvent<HTMLInputElement>) => {
  141. const newValue = event.target.value
  142. if (newValue) {
  143. onValueChange(Number(newValue))
  144. }
  145. },
  146. [onValueChange]
  147. )
  148. return (
  149. <div className="editor-survey-question">
  150. <div className="editor-survey-question-top-line">
  151. <div>{t('your_feedback_matters_answer_two_quick_questions')}</div>
  152. <OLIconButton
  153. variant="ghost"
  154. size="sm"
  155. icon="close"
  156. accessibilityLabel={t('close')}
  157. onClick={onDismiss}
  158. />
  159. </div>
  160. <div>
  161. <label className="editor-survey-question-label" htmlFor={name}>
  162. {questionText}
  163. </label>
  164. <OLForm className="editor-survey-question-form">
  165. <OLFormGroup className="editor-survey-question-options">
  166. {options.map(({ value: optionValue, description }) => (
  167. <OLFormCheckbox
  168. key={optionValue}
  169. name={name}
  170. type="radio"
  171. value={optionValue}
  172. id={optionValue.toString()}
  173. label={optionValue}
  174. checked={optionValue === value}
  175. onChange={onChange}
  176. aria-label={`${optionValue} ${description}`}
  177. />
  178. ))}
  179. </OLFormGroup>
  180. </OLForm>
  181. <div className="editor-survey-option-labels">
  182. <div>{t('strongly_disagree')}</div>
  183. <div>{t('strongly_agree')}</div>
  184. </div>
  185. </div>
  186. <OLButton
  187. size="sm"
  188. variant="secondary"
  189. onClick={onButtonClick}
  190. disabled={!value}
  191. >
  192. {buttonText}
  193. </OLButton>
  194. </div>
  195. )
  196. }