mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 06:03:45 +01:00
Merge pull request #25164 from overleaf/dp-editor-survey
Create Editor Survey GitOrigin-RevId: dc11ef16c0a00aa846ac7a664dd88e9531e832f2
This commit is contained in:
@@ -356,6 +356,7 @@ const _ProjectController = {
|
||||
'overleaf-assist-bundle',
|
||||
'wf-feature-rebrand',
|
||||
'word-count-client',
|
||||
'editor-popup-ux-survey',
|
||||
].filter(Boolean)
|
||||
|
||||
const getUserValues = async userId =>
|
||||
|
||||
@@ -11,6 +11,7 @@ const VALID_KEYS = [
|
||||
'history-restore-promo',
|
||||
'us-gov-banner',
|
||||
'us-gov-banner-fedramp',
|
||||
'editor-popup-ux-survey',
|
||||
]
|
||||
|
||||
async function completeTutorial(req, res, next) {
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"after_that_well_bill_you_x_total_y_subtotal_z_tax_annually_on_date_unless_you_cancel": "",
|
||||
"aggregate_changed": "",
|
||||
"aggregate_to": "",
|
||||
"agree": "",
|
||||
"agree_with_the_terms": "",
|
||||
"ai_assist_in_overleaf_is_included_via_writefull": "",
|
||||
"ai_assistance_to_help_you": "",
|
||||
@@ -401,6 +402,7 @@
|
||||
"disable_stop_on_first_error": "",
|
||||
"disabled": "",
|
||||
"disabling": "",
|
||||
"disagree": "",
|
||||
"disconnected": "",
|
||||
"discount": "",
|
||||
"discount_of": "",
|
||||
@@ -1047,6 +1049,7 @@
|
||||
"need_more_than_x_licenses": "",
|
||||
"need_to_add_new_primary_before_remove": "",
|
||||
"need_to_leave": "",
|
||||
"neither_agree_nor_disagree": "",
|
||||
"new_compile_domain_notice": "",
|
||||
"new_file": "",
|
||||
"new_folder": "",
|
||||
@@ -1143,8 +1146,10 @@
|
||||
"overall_theme": "",
|
||||
"overleaf": "",
|
||||
"overleaf_history_system": "",
|
||||
"overleaf_is_easy_to_use": "",
|
||||
"overleaf_labs": "",
|
||||
"overleaf_logo": "",
|
||||
"overleafs_functionality_meets_my_needs": "",
|
||||
"overview": "",
|
||||
"overwrite": "",
|
||||
"overwriting_the_original_folder": "",
|
||||
@@ -1636,6 +1641,8 @@
|
||||
"stop_on_validation_error": "",
|
||||
"store_your_work": "",
|
||||
"stretch_width_to_text": "",
|
||||
"strongly_agree": "",
|
||||
"strongly_disagree": "",
|
||||
"student": "",
|
||||
"student_disclaimer": "",
|
||||
"subject": "",
|
||||
@@ -1704,6 +1711,7 @@
|
||||
"test_configuration_successful": "",
|
||||
"tex_live_version": "",
|
||||
"texgpt": "",
|
||||
"thank_you": "",
|
||||
"thank_you_exclamation": "",
|
||||
"thank_you_for_your_feedback": "",
|
||||
"thanks_for_being_part_of_this_labs_experiment_your_feedback_will_help_us_make_the_new_editor_the_best_yet": "",
|
||||
@@ -2123,6 +2131,7 @@
|
||||
"your_current_plan_gives_you": "",
|
||||
"your_current_plan_supports_up_to_x_licenses": "",
|
||||
"your_current_project_will_revert_to_the_version_from_time": "",
|
||||
"your_feedback_matters_answer_two_quick_questions": "",
|
||||
"your_git_access_info": "",
|
||||
"your_git_access_info_bullet_1": "",
|
||||
"your_git_access_info_bullet_2": "",
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -10,7 +10,12 @@ import { useHasLintingError } from '@/features/ide-react/hooks/use-has-linting-e
|
||||
import { Modals } from '@/features/ide-react/components/modals/modals'
|
||||
import { GlobalAlertsProvider } from '@/features/ide-react/context/global-alerts-context'
|
||||
import { GlobalToasts } from '../global-toasts'
|
||||
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
|
||||
import {
|
||||
canUseNewEditor,
|
||||
useIsNewEditorEnabled,
|
||||
} from '@/features/ide-redesign/utils/new-editor-utils'
|
||||
import EditorSurvey from '../editor-survey'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
|
||||
const MainLayoutNew = lazy(
|
||||
() => import('@/features/ide-redesign/components/main-layout')
|
||||
@@ -27,6 +32,9 @@ export default function IdePage() {
|
||||
useHasLintingError() // pass editor:lint hasLintingError to the compiler
|
||||
|
||||
const newEditor = useIsNewEditorEnabled()
|
||||
const canAccessNewEditor = canUseNewEditor()
|
||||
const editorSurveyFlag = useFeatureFlag('editor-popup-ux-survey')
|
||||
const showEditorSurvey = editorSurveyFlag && !canAccessNewEditor
|
||||
|
||||
return (
|
||||
<GlobalAlertsProvider>
|
||||
@@ -44,6 +52,7 @@ export default function IdePage() {
|
||||
</>
|
||||
)}
|
||||
<GlobalToasts />
|
||||
{showEditorSurvey && <EditorSurvey />}
|
||||
</GlobalAlertsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
@import 'editor/table-generator-column-width-modal';
|
||||
@import 'editor/math-preview';
|
||||
@import 'editor/references-search';
|
||||
@import 'editor/editor-survey';
|
||||
@import 'website-redesign';
|
||||
@import 'group-settings';
|
||||
@import 'templates-v2';
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
.editor-survey-toast {
|
||||
position: fixed;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.editor-survey-question-toast {
|
||||
.notification-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-survey-question {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
font-size: var(--font-size-02);
|
||||
|
||||
.btn {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-check {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
padding-left: 0;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.form-check-input {
|
||||
margin: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-survey-question-top-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.btn-ghost {
|
||||
--bs-btn-bg: transparent;
|
||||
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-survey-question-label {
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.editor-survey-question-form {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.editor-survey-question-options {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.editor-survey-option-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--content-secondary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
@@ -124,6 +124,7 @@
|
||||
"after_that_well_bill_you_x_total_y_subtotal_z_tax_annually_on_date_unless_you_cancel": "After that, we’ll bill you __totalAmount__ (__subtotalAmount__ + __taxAmount__ tax) annually on __date__, unless you cancel.",
|
||||
"aggregate_changed": "Changed",
|
||||
"aggregate_to": "to",
|
||||
"agree": "Agree",
|
||||
"agree_with_the_terms": "I agree with the Overleaf terms",
|
||||
"ai_assist_in_overleaf_is_included_via_writefull": "AI Assist in Overleaf is included as part of your Writefull subscription. You can cancel or manage your access to AI Assist in your Writefull subscription settings.",
|
||||
"ai_assistance_to_help_you": "AI assistance to help you fix LaTeX errors",
|
||||
@@ -524,6 +525,7 @@
|
||||
"disable_stop_on_first_error": "Disable “Stop on first error”",
|
||||
"disabled": "Disabled",
|
||||
"disabling": "Disabling",
|
||||
"disagree": "Disagree",
|
||||
"disconnected": "Disconnected",
|
||||
"discount": "Discount",
|
||||
"discount_of": "Discount of __amount__",
|
||||
@@ -1375,6 +1377,7 @@
|
||||
"need_more_than_x_licenses": "Need more than __x__ licenses?",
|
||||
"need_to_add_new_primary_before_remove": "You’ll need to add a new primary email address before you can remove this one.",
|
||||
"need_to_leave": "Need to leave?",
|
||||
"neither_agree_nor_disagree": "Neither agree nor disagree",
|
||||
"new_compile_domain_notice": "We’ve recently migrated PDF downloads to a new domain. Something might be blocking your browser from accessing that new domain, <0>__compilesUserContentDomain__</0>. This could be caused by network blocking or a strict browser plugin rule. Please follow our <1>troubleshooting guide</1>.",
|
||||
"new_file": "New file",
|
||||
"new_folder": "New folder",
|
||||
@@ -1514,10 +1517,12 @@
|
||||
"overleaf_group_plans": "Overleaf group plans",
|
||||
"overleaf_history_system": "Overleaf History System",
|
||||
"overleaf_individual_plans": "Overleaf individual plans",
|
||||
"overleaf_is_easy_to_use": "Overleaf is easy to use.",
|
||||
"overleaf_labs": "Overleaf Labs",
|
||||
"overleaf_logo": "Overleaf logo",
|
||||
"overleaf_plans_and_pricing": "overleaf plans and pricing",
|
||||
"overleaf_template_gallery": "overleaf template gallery",
|
||||
"overleafs_functionality_meets_my_needs": "Overleaf’s functionality meets my needs.",
|
||||
"overview": "Overview",
|
||||
"overwrite": "Overwrite",
|
||||
"overwriting_the_original_folder": "Overwriting the original folder will delete it and all the files it contains.",
|
||||
@@ -2128,6 +2133,8 @@
|
||||
"stop_on_validation_error": "Check syntax before compile",
|
||||
"store_your_work": "Store your work on your own infrastructure",
|
||||
"stretch_width_to_text": "Stretch width to text",
|
||||
"strongly_agree": "Strongly agree",
|
||||
"strongly_disagree": "Strongly disagree",
|
||||
"student": "Student",
|
||||
"student_disclaimer": "The educational discount applies to all students at secondary and postsecondary institutions (schools and universities). We may contact you to confirm that you’re eligible for the discount.",
|
||||
"student_verification_required": "Student verification required",
|
||||
@@ -2683,6 +2690,7 @@
|
||||
"your_current_plan_gives_you": "By pausing your subscription, you’ll be able to access your premium features faster when you need them again.",
|
||||
"your_current_plan_supports_up_to_x_licenses": "Your current plan supports up to __users__ licenses.",
|
||||
"your_current_project_will_revert_to_the_version_from_time": "Your current project will revert to the version from __timestamp__",
|
||||
"your_feedback_matters_answer_two_quick_questions": "Your feedback matters! Answer two quick questions.",
|
||||
"your_git_access_info": "Your Git authentication tokens should be entered whenever you’re prompted for a password.",
|
||||
"your_git_access_info_bullet_1": "You can have up to 10 tokens.",
|
||||
"your_git_access_info_bullet_2": "If you reach the maximum limit, you’ll need to delete a token before you can generate a new one.",
|
||||
|
||||
Reference in New Issue
Block a user