Ver código fonte

Merge pull request #27329 from overleaf/dp-editor-redesign-beta

Prepare editor redesign for beta release

GitOrigin-RevId: 584c0303f652640669234bd227780ed4f20b76c0
Mathias Jakobsen 1 ano atrás
pai
commit
8f2001e477

+ 2 - 0
services/web/app/src/Features/Tutorial/TutorialController.mjs

@@ -19,6 +19,8 @@ const VALID_KEYS = [
   'try-redesign-again-nudge-promo',
   'try-redesign-again-nudge-promo',
   'write-and-cite-nudge-in-linked-file',
   'write-and-cite-nudge-in-linked-file',
   'ide-redesign-new-survey-promo',
   'ide-redesign-new-survey-promo',
+  'ide-redesign-beta-intro',
+  'ide-redesign-labs-user-beta-promo',
 ]
 ]
 
 
 async function completeTutorial(req, res, next) {
 async function completeTutorial(req, res, next) {

+ 10 - 0
services/web/frontend/extracted-translations.json

@@ -180,9 +180,11 @@
   "back_to_your_projects": "",
   "back_to_your_projects": "",
   "basic_compile_time": "",
   "basic_compile_time": "",
   "before_you_use_error_assistant": "",
   "before_you_use_error_assistant": "",
+  "beta": "",
   "beta_program_already_participating": "",
   "beta_program_already_participating": "",
   "beta_program_benefits": "",
   "beta_program_benefits": "",
   "beta_program_not_participating": "",
   "beta_program_not_participating": "",
+  "beta_program_the_new_overleaf_editor": "",
   "billed_annually_at": "",
   "billed_annually_at": "",
   "billed_monthly_at": "",
   "billed_monthly_at": "",
   "billed_yearly": "",
   "billed_yearly": "",
@@ -892,6 +894,7 @@
   "it_looks_like_your_account_is_billed_manually_purchasing_additional_license_or_upgrading_subscription": "",
   "it_looks_like_your_account_is_billed_manually_purchasing_additional_license_or_upgrading_subscription": "",
   "it_looks_like_your_payment_details_are_missing_please_update_your_billing_information": "",
   "it_looks_like_your_payment_details_are_missing_please_update_your_billing_information": "",
   "italics": "",
   "italics": "",
+  "join_beta": "",
   "join_beta_program": "",
   "join_beta_program": "",
   "join_now": "",
   "join_now": "",
   "join_overleaf_labs": "",
   "join_overleaf_labs": "",
@@ -1779,6 +1782,7 @@
   "thanks_for_subscribing_to_plan_with_add_on": "",
   "thanks_for_subscribing_to_plan_with_add_on": "",
   "thanks_for_subscribing_to_the_add_on": "",
   "thanks_for_subscribing_to_the_add_on": "",
   "thanks_for_subscribing_you_help_sl": "",
   "thanks_for_subscribing_you_help_sl": "",
+  "thanks_for_trying_the_new_editor": "",
   "thanks_settings_updated": "",
   "thanks_settings_updated": "",
   "the_add_on_will_remain_active_until": "",
   "the_add_on_will_remain_active_until": "",
   "the_code_editor_color_scheme": "",
   "the_code_editor_color_scheme": "",
@@ -1787,8 +1791,10 @@
   "the_following_files_and_folders_already_exist_in_this_project": "",
   "the_following_files_and_folders_already_exist_in_this_project": "",
   "the_following_folder_already_exists_in_this_project": "",
   "the_following_folder_already_exists_in_this_project": "",
   "the_following_folder_already_exists_in_this_project_plural": "",
   "the_following_folder_already_exists_in_this_project_plural": "",
+  "the_labs_experiment_has_now_finished": "",
   "the_latex_engine_used_for_compiling": "",
   "the_latex_engine_used_for_compiling": "",
   "the_new_overleaf_editor": "",
   "the_new_overleaf_editor": "",
+  "the_new_overleaf_editor_try_now_in_beta": "",
   "the_next_payment_will_be_collected_on": "",
   "the_next_payment_will_be_collected_on": "",
   "the_original_text_has_changed": "",
   "the_original_text_has_changed": "",
   "the_overleaf_color_scheme": "",
   "the_overleaf_color_scheme": "",
@@ -1817,6 +1823,7 @@
   "this_experiment_isnt_accepting_new_participants": "",
   "this_experiment_isnt_accepting_new_participants": "",
   "this_field_is_required": "",
   "this_field_is_required": "",
   "this_grants_access_to_features_2": "",
   "this_grants_access_to_features_2": "",
+  "this_is_a_beta_release_for_the_new_overleaf_editor": "",
   "this_is_a_labs_experiment_for_the_new_overleaf_editor_some_features_are_still_in_progress": "",
   "this_is_a_labs_experiment_for_the_new_overleaf_editor_some_features_are_still_in_progress": "",
   "this_is_a_new_feature": "",
   "this_is_a_new_feature": "",
   "this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "",
   "this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "",
@@ -2102,11 +2109,13 @@
   "were_performing_maintenance": "",
   "were_performing_maintenance": "",
   "were_redesigning_our_editor_to_make_it_easier_to_use": "",
   "were_redesigning_our_editor_to_make_it_easier_to_use": "",
   "were_reducing_compile_timeout": "",
   "were_reducing_compile_timeout": "",
+  "weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready": "",
   "what_did_you_find_most_helpful": "",
   "what_did_you_find_most_helpful": "",
   "what_do_you_need_help_with": "",
   "what_do_you_need_help_with": "",
   "what_does_this_mean_for_you": "",
   "what_does_this_mean_for_you": "",
   "what_happens_when_sso_is_enabled": "",
   "what_happens_when_sso_is_enabled": "",
   "what_should_we_call_you": "",
   "what_should_we_call_you": "",
+  "whats_different": "",
   "whats_different_in_the_new_editor": "",
   "whats_different_in_the_new_editor": "",
   "when_you_tick_the_include_caption_box": "",
   "when_you_tick_the_include_caption_box": "",
   "why_might_this_happen": "",
   "why_might_this_happen": "",
@@ -2162,6 +2171,7 @@
   "you_can_select_or_invite_collaborator": "",
   "you_can_select_or_invite_collaborator": "",
   "you_can_select_or_invite_collaborator_plural": "",
   "you_can_select_or_invite_collaborator_plural": "",
   "you_can_still_use_your_premium_features": "",
   "you_can_still_use_your_premium_features": "",
+  "you_can_switch_back_to_the_old_editor_at_any_time": "",
   "you_cant_add_or_change_password_due_to_sso": "",
   "you_cant_add_or_change_password_due_to_sso": "",
   "you_cant_join_this_group_subscription": "",
   "you_cant_join_this_group_subscription": "",
   "you_dont_have_any_add_ons_on_your_account": "",
   "you_dont_have_any_add_ons_on_your_account": "",

+ 5 - 1
services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx

@@ -3,10 +3,12 @@ import OLButton from '../ui/components/ol/ol-button'
 import { useIdeRedesignSwitcherContext } from '../ide-react/context/ide-redesign-switcher-context'
 import { useIdeRedesignSwitcherContext } from '../ide-react/context/ide-redesign-switcher-context'
 import MaterialIcon from '@/shared/components/material-icon'
 import MaterialIcon from '@/shared/components/material-icon'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
+import { isNewEditorInBeta } from '../ide-redesign/utils/new-editor-utils'
 
 
 const TryNewEditorButton = () => {
 const TryNewEditorButton = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { setShowSwitcherModal } = useIdeRedesignSwitcherContext()
   const { setShowSwitcherModal } = useIdeRedesignSwitcherContext()
+  const newEditorBeta = isNewEditorInBeta()
 
 
   const onClick = useCallback(() => {
   const onClick = useCallback(() => {
     setShowSwitcherModal(true)
     setShowSwitcherModal(true)
@@ -17,7 +19,9 @@ const TryNewEditorButton = () => {
         className="toolbar-experiment-button"
         className="toolbar-experiment-button"
         onClick={onClick}
         onClick={onClick}
         size="sm"
         size="sm"
-        leadingIcon={<MaterialIcon type="experiment" unfilled />}
+        leadingIcon={
+          !newEditorBeta && <MaterialIcon type="experiment" unfilled />
+        }
         variant="secondary"
         variant="secondary"
       >
       >
         {t('try_the_new_editor')}
         {t('try_the_new_editor')}

+ 2 - 0
services/web/frontend/js/features/ide-react/components/layout/main-layout.tsx

@@ -16,6 +16,7 @@ import HistoryContainer from '@/features/ide-react/components/history-container'
 import getMeta from '@/utils/meta'
 import getMeta from '@/utils/meta'
 import { useEditorContext } from '@/shared/context/editor-context'
 import { useEditorContext } from '@/shared/context/editor-context'
 import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
 import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
+import LabsUserBetaPromo from '@/features/ide-redesign/components/labs-user-beta-promo'
 
 
 const mainEditorLayoutModalsModules: Array<{
 const mainEditorLayoutModalsModules: Array<{
   import: { default: ElementType }
   import: { default: ElementType }
@@ -99,6 +100,7 @@ export const MainLayout: FC = () => {
             <PanelGroup autoSaveId="ide-inner-layout" direction="horizontal">
             <PanelGroup autoSaveId="ide-inner-layout" direction="horizontal">
               <Panel className="ide-react-panel" id="panel-main" order={1}>
               <Panel className="ide-react-panel" id="panel-main" order={1}>
                 <HistoryContainer />
                 <HistoryContainer />
+                <LabsUserBetaPromo />
                 <EditorAndPdf />
                 <EditorAndPdf />
               </Panel>
               </Panel>
 
 

+ 16 - 2
services/web/frontend/js/features/ide-react/components/modals/modals.tsx

@@ -2,15 +2,29 @@ import { memo } from 'react'
 import ForceDisconnected from '@/features/ide-react/components/modals/force-disconnected'
 import ForceDisconnected from '@/features/ide-react/components/modals/force-disconnected'
 import { UnsavedDocs } from '@/features/ide-react/components/unsaved-docs/unsaved-docs'
 import { UnsavedDocs } from '@/features/ide-react/components/unsaved-docs/unsaved-docs'
 import SystemMessages from '@/shared/components/system-messages'
 import SystemMessages from '@/shared/components/system-messages'
-import { IdeRedesignSwitcherModal } from '@/features/ide-redesign/components/switcher-modal/modal'
+import { IdeRedesignSwitcherModal as IdeRedesignSwitcherModalLabs } from '@/features/ide-redesign/components/switcher-modal/modal'
+import {
+  IdeRedesignSwitcherModal as IdeRedesignSwitcherModalBeta,
+  IdeRedesignIntroModal,
+} from '@/features/ide-redesign/components/switcher-modal/beta-modal'
+import { isNewEditorInBeta } from '@/features/ide-redesign/utils/new-editor-utils'
 
 
 export const Modals = memo(() => {
 export const Modals = memo(() => {
+  const newEditorBeta = isNewEditorInBeta()
+
   return (
   return (
     <>
     <>
       <ForceDisconnected />
       <ForceDisconnected />
       <UnsavedDocs />
       <UnsavedDocs />
       <SystemMessages />
       <SystemMessages />
-      <IdeRedesignSwitcherModal />
+      {newEditorBeta ? (
+        <>
+          <IdeRedesignIntroModal />
+          <IdeRedesignSwitcherModalBeta />
+        </>
+      ) : (
+        <IdeRedesignSwitcherModalLabs />
+      )}
     </>
     </>
   )
   )
 })
 })

+ 76 - 0
services/web/frontend/js/features/ide-redesign/components/labs-user-beta-promo.tsx

@@ -0,0 +1,76 @@
+import OLButton from '@/features/ui/components/ol/ol-button'
+import MaterialIcon from '@/shared/components/material-icon'
+import Notification from '@/shared/components/notification'
+import { useEditorContext } from '@/shared/context/editor-context'
+import useTutorial from '@/shared/hooks/promotions/use-tutorial'
+import { useEffect } from 'react'
+import { isNewEditorInBeta } from '../utils/new-editor-utils'
+import { isInExperiment } from '@/utils/labs-utils'
+import { useUserContext } from '@/shared/context/user-context'
+import { useTranslation } from 'react-i18next'
+
+const TUTORIAL_KEY = 'ide-redesign-labs-user-beta-promo'
+
+export default function LabsUserBetaPromoWrapper() {
+  const user = useUserContext()
+
+  // Show beta promo to labs users who are not in beta
+  // when the redesign switches to beta
+  if (
+    isNewEditorInBeta() &&
+    isInExperiment('editor-redesign') &&
+    !user.betaProgram
+  ) {
+    return <LabsUserBetaPromo />
+  }
+}
+
+const LabsUserBetaPromo = () => {
+  const { inactiveTutorials } = useEditorContext()
+  const { showPopup, tryShowingPopup, dismissTutorial } = useTutorial(
+    TUTORIAL_KEY,
+    { name: TUTORIAL_KEY }
+  )
+
+  useEffect(() => {
+    if (!inactiveTutorials.includes(TUTORIAL_KEY)) {
+      tryShowingPopup()
+    }
+  }, [tryShowingPopup, inactiveTutorials])
+
+  if (!showPopup) {
+    return null
+  }
+
+  return (
+    <Notification
+      className="ide-redesign-labs-user-beta-promo"
+      customIcon={<MaterialIcon type="experiment" unfilled />}
+      type="success"
+      isDismissible
+      content={<NotificationContent />}
+      onDismiss={dismissTutorial}
+    />
+  )
+}
+
+const NotificationContent = () => {
+  const { t } = useTranslation()
+  return (
+    <>
+      <div className="ide-redesign-labs-user-beta-promo-title">
+        {t('thanks_for_trying_the_new_editor')}
+      </div>
+      <div>{t('the_labs_experiment_has_now_finished')}</div>
+      <OLButton
+        variant="secondary"
+        className="ide-redesign-labs-user-beta-promo-button"
+        href="/beta/participate"
+        target="_blank"
+        rel="noreferrer noopener"
+      >
+        {t('join_beta')}
+      </OLButton>
+    </>
+  )
+}

+ 243 - 0
services/web/frontend/js/features/ide-redesign/components/switcher-modal/beta-modal.tsx

@@ -0,0 +1,243 @@
+import { useIdeRedesignSwitcherContext } from '@/features/ide-react/context/ide-redesign-switcher-context'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import OLModal, {
+  OLModalBody,
+  OLModalFooter,
+  OLModalHeader,
+  OLModalTitle,
+} from '@/features/ui/components/ol/ol-modal'
+import { FC, useCallback, useEffect } from 'react'
+import {
+  canUseNewEditor,
+  useIsNewEditorEnabled,
+} from '../../utils/new-editor-utils'
+import Notification from '@/shared/components/notification'
+import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state'
+import { useTranslation } from 'react-i18next'
+import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
+import { useSurveyUrl } from '../../hooks/use-survey-url'
+import useTutorial from '@/shared/hooks/promotions/use-tutorial'
+import { useEditorContext } from '@/shared/context/editor-context'
+
+const TUTORIAL_KEY = 'ide-redesign-beta-intro'
+
+export const IdeRedesignIntroModal: FC = () => {
+  const { t } = useTranslation()
+  const { inactiveTutorials } = useEditorContext()
+  const { showPopup, tryShowingPopup, dismissTutorial } = useTutorial(
+    TUTORIAL_KEY,
+    {
+      name: TUTORIAL_KEY,
+    }
+  )
+
+  useEffect(() => {
+    if (!inactiveTutorials.includes(TUTORIAL_KEY)) {
+      tryShowingPopup()
+    }
+  }, [tryShowingPopup, inactiveTutorials])
+
+  const hasAccess = canUseNewEditor()
+  if (!hasAccess) {
+    return null
+  }
+
+  return (
+    <OLModal
+      show={showPopup}
+      onHide={dismissTutorial}
+      className="ide-redesign-switcher-modal"
+    >
+      <OLModalHeader closeButton>
+        <OLModalTitle>
+          {t('the_new_overleaf_editor_try_now_in_beta')}
+        </OLModalTitle>
+      </OLModalHeader>
+      <OLModalBody>
+        <p>
+          {t(
+            'weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready'
+          )}{' '}
+          {t('you_can_switch_back_to_the_old_editor_at_any_time')}
+        </p>
+        <SwitcherWhatsDifferent />
+      </OLModalBody>
+      <OLModalFooter>
+        <OLButton onClick={dismissTutorial} variant="secondary">
+          {t('close')}
+        </OLButton>
+      </OLModalFooter>
+    </OLModal>
+  )
+}
+
+export const IdeRedesignSwitcherModal = () => {
+  const { t } = useTranslation()
+  const { showSwitcherModal, setShowSwitcherModal } =
+    useIdeRedesignSwitcherContext()
+  const onHide = useCallback(
+    () => setShowSwitcherModal(false),
+    [setShowSwitcherModal]
+  )
+  const { loading, error, setEditorRedesignStatus } =
+    useSwitchEnableNewEditorState()
+  const enabled = useIsNewEditorEnabled()
+  const hasAccess = canUseNewEditor()
+  if (!hasAccess) {
+    return null
+  }
+
+  const Content = enabled
+    ? SwitcherModalContentEnabled
+    : SwitcherModalContentDisabled
+
+  return (
+    <OLModal
+      show={showSwitcherModal}
+      onHide={onHide}
+      className="ide-redesign-switcher-modal"
+    >
+      <OLModalHeader closeButton>
+        <OLModalTitle>
+          {enabled
+            ? t('beta_program_the_new_overleaf_editor')
+            : t('the_new_overleaf_editor_try_now_in_beta')}
+        </OLModalTitle>
+      </OLModalHeader>
+      {error && <Notification type="error" content={error} isDismissible />}
+      <Content
+        setEditorRedesignStatus={setEditorRedesignStatus}
+        hide={onHide}
+        loading={loading}
+      />
+    </OLModal>
+  )
+}
+
+type ModalContentProps = {
+  setEditorRedesignStatus: (enabled: boolean) => Promise<void>
+  hide: () => void
+  loading: boolean
+}
+
+const SwitcherModalContentEnabled: FC<ModalContentProps> = ({
+  setEditorRedesignStatus,
+  hide,
+  loading,
+}) => {
+  const { t } = useTranslation()
+  const { sendEvent } = useEditorAnalytics()
+  const disable = useCallback(() => {
+    sendEvent('editor-redesign-toggle', {
+      action: 'disable',
+      location: 'modal',
+    })
+    setEditorRedesignStatus(false)
+      .then(hide)
+      .catch(() => {
+        // do nothing, we're already showing the error
+      })
+  }, [setEditorRedesignStatus, hide, sendEvent])
+
+  const surveyURL = useSurveyUrl()
+
+  return (
+    <>
+      <OLModalBody>
+        <p>
+          {t(
+            'weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready'
+          )}
+        </p>
+        <SwitcherWhatsDifferent />
+      </OLModalBody>
+      <OLModalFooter>
+        <OLButton
+          onClick={disable}
+          variant="secondary"
+          className="me-auto"
+          disabled={loading}
+        >
+          {t('switch_to_old_editor')}
+        </OLButton>
+        <OLButton onClick={hide} variant="secondary">
+          {t('cancel')}
+        </OLButton>
+        <OLButton
+          href={surveyURL}
+          target="_blank"
+          rel="noopener noreferrer"
+          variant="primary"
+        >
+          {t('give_feedback')}
+        </OLButton>
+      </OLModalFooter>
+    </>
+  )
+}
+
+const SwitcherModalContentDisabled: FC<ModalContentProps> = ({
+  setEditorRedesignStatus,
+  hide,
+  loading,
+}) => {
+  const { t } = useTranslation()
+  const { sendEvent } = useEditorAnalytics()
+  const enable = useCallback(() => {
+    sendEvent('editor-redesign-toggle', {
+      action: 'enable',
+      location: 'modal',
+    })
+    setEditorRedesignStatus(true)
+      .then(hide)
+      .catch(() => {
+        // do nothing, we're already showing the error
+      })
+  }, [setEditorRedesignStatus, hide, sendEvent])
+  return (
+    <>
+      <OLModalBody>
+        <p>
+          {t(
+            'weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready'
+          )}
+        </p>
+        <SwitcherWhatsDifferent />
+        <LeavingNote />
+      </OLModalBody>
+      <OLModalFooter>
+        <OLButton onClick={hide} variant="secondary">
+          {t('cancel')}
+        </OLButton>
+        <OLButton onClick={enable} variant="primary" disabled={loading}>
+          {t('switch_to_new_editor')}
+        </OLButton>
+      </OLModalFooter>
+    </>
+  )
+}
+
+const SwitcherWhatsDifferent = () => {
+  const { t } = useTranslation()
+
+  return (
+    <div className="ide-redesign-switcher-modal-whats-new">
+      <h4>{t('whats_different')}</h4>
+      <ul>
+        <li>{t('new_look_and_feel')}</li>
+        <li>
+          {t('new_navigation_introducing_left_hand_side_rail_and_top_menus')}
+        </li>
+        <li>{t('new_look_and_placement_of_the_settings')}</li>
+        <li>{t('improved_dark_mode')}</li>
+        <li>{t('review_panel_and_error_logs_moved_to_the_left')}</li>
+      </ul>
+    </div>
+  )
+}
+
+const LeavingNote = () => {
+  const { t } = useTranslation()
+
+  return <p>{t('you_can_switch_back_to_the_old_editor_at_any_time')}</p>
+}

+ 36 - 0
services/web/frontend/js/features/ide-redesign/components/toolbar/beta-actions.tsx

@@ -0,0 +1,36 @@
+import { useIdeRedesignSwitcherContext } from '@/features/ide-react/context/ide-redesign-switcher-context'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import { GiveFeedbackLink } from './give-feedback-link'
+
+export const BetaActions = () => {
+  const { t } = useTranslation()
+  const { setShowSwitcherModal } = useIdeRedesignSwitcherContext()
+  const openEditorRedesignSwitcherModal = useCallback(() => {
+    setShowSwitcherModal(true)
+  }, [setShowSwitcherModal])
+
+  return (
+    <>
+      <div className="ide-redesign-toolbar-button-container">
+        <OLTooltip
+          id="tooltip-beta-button"
+          description={t('this_is_a_beta_release_for_the_new_overleaf_editor')}
+          overlayProps={{ delay: 0, placement: 'bottom' }}
+        >
+          <OLButton
+            size="sm"
+            variant="secondary"
+            className="ide-redesign-beta-button"
+            onClick={openEditorRedesignSwitcherModal}
+          >
+            {t('beta')}
+          </OLButton>
+        </OLTooltip>
+      </div>
+      <GiveFeedbackLink />
+    </>
+  )
+}

+ 20 - 0
services/web/frontend/js/features/ide-redesign/components/toolbar/give-feedback-link.tsx

@@ -0,0 +1,20 @@
+import { useTranslation } from 'react-i18next'
+import { useSurveyUrl } from '../../hooks/use-survey-url'
+
+export const GiveFeedbackLink = () => {
+  const { t } = useTranslation()
+  const surveyURL = useSurveyUrl()
+
+  return (
+    <div className="ide-redesign-toolbar-button-container">
+      <a
+        href={surveyURL}
+        rel="noopener noreferrer"
+        target="_blank"
+        className="ide-redesign-toolbar-labs-feedback-link"
+      >
+        {t('give_feedback')}
+      </a>
+    </div>
+  )
+}

+ 2 - 12
services/web/frontend/js/features/ide-redesign/components/toolbar/labs-actions.tsx

@@ -4,7 +4,7 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
 import MaterialIcon from '@/shared/components/material-icon'
 import MaterialIcon from '@/shared/components/material-icon'
 import { useCallback } from 'react'
 import { useCallback } from 'react'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
-import { useSurveyUrl } from '../../hooks/use-survey-url'
+import { GiveFeedbackLink } from './give-feedback-link'
 
 
 export const LabsActions = () => {
 export const LabsActions = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
@@ -12,7 +12,6 @@ export const LabsActions = () => {
   const openEditorRedesignSwitcherModal = useCallback(() => {
   const openEditorRedesignSwitcherModal = useCallback(() => {
     setShowSwitcherModal(true)
     setShowSwitcherModal(true)
   }, [setShowSwitcherModal])
   }, [setShowSwitcherModal])
-  const surveyURL = useSurveyUrl()
 
 
   return (
   return (
     <>
     <>
@@ -35,16 +34,7 @@ export const LabsActions = () => {
           </OLButton>
           </OLButton>
         </OLTooltip>
         </OLTooltip>
       </div>
       </div>
-      <div className="ide-redesign-toolbar-button-container">
-        <a
-          href={surveyURL}
-          rel="noopener noreferrer"
-          target="_blank"
-          className="ide-redesign-toolbar-labs-feedback-link"
-        >
-          {t('give_feedback')}
-        </a>
-      </div>
+      <GiveFeedbackLink />
     </>
     </>
   )
   )
 }
 }

+ 4 - 1
services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx

@@ -16,6 +16,8 @@ import importOverleafModules from '../../../../../macros/import-overleaf-module.
 import UpgradeButton from './upgrade-button'
 import UpgradeButton from './upgrade-button'
 import getMeta from '@/utils/meta'
 import getMeta from '@/utils/meta'
 import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
 import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
+import { isNewEditorInBeta } from '../../utils/new-editor-utils'
+import { BetaActions } from './beta-actions'
 
 
 const [publishModalModules] = importOverleafModules('publishModal')
 const [publishModalModules] = importOverleafModules('publishModal')
 const SubmitProjectButton = publishModalModules?.import.NewPublishToolbarButton
 const SubmitProjectButton = publishModalModules?.import.NewPublishToolbarButton
@@ -28,6 +30,7 @@ export const Toolbar = () => {
   const shouldDisplaySubmitButton =
   const shouldDisplaySubmitButton =
     (permissionsLevel === 'owner' || permissionsLevel === 'readAndWrite') &&
     (permissionsLevel === 'owner' || permissionsLevel === 'readAndWrite') &&
     SubmitProjectButton
     SubmitProjectButton
+  const newEditorBeta = isNewEditorInBeta()
 
 
   const handleBackToEditorClick = useCallback(() => {
   const handleBackToEditorClick = useCallback(() => {
     eventTracking.sendMB('navigation-clicked-history', { action: 'close' })
     eventTracking.sendMB('navigation-clicked-history', { action: 'close' })
@@ -54,7 +57,7 @@ export const Toolbar = () => {
       </div>
       </div>
       <ToolbarProjectTitle />
       <ToolbarProjectTitle />
       <div className="ide-redesign-toolbar-actions">
       <div className="ide-redesign-toolbar-actions">
-        <LabsActions />
+        {newEditorBeta ? <BetaActions /> : <LabsActions />}
         <OnlineUsers />
         <OnlineUsers />
         <ShowHistoryButton />
         <ShowHistoryButton />
         <ChangeLayoutButton />
         <ChangeLayoutButton />

+ 15 - 1
services/web/frontend/js/features/ide-redesign/hooks/use-survey-url.tsx

@@ -1,7 +1,21 @@
-import { useFeatureFlag } from '@/shared/context/split-test-context'
+import {
+  useFeatureFlag,
+  useSplitTest,
+} from '@/shared/context/split-test-context'
+import { isNewEditorInBeta } from '../utils/new-editor-utils'
 
 
 export const useSurveyUrl = () => {
 export const useSurveyUrl = () => {
   const newErrorlogs = useFeatureFlag('new-editor-error-logs-redesign')
   const newErrorlogs = useFeatureFlag('new-editor-error-logs-redesign')
+  const splitTestConfig = useSplitTest('editor-redesign')
+  const newEditorBeta = isNewEditorInBeta()
+
+  if (newEditorBeta) {
+    return (
+      splitTestConfig.info?.badgeInfo?.url ||
+      'https://forms.gle/NGkALNUiMbanjp3Q7'
+    )
+  }
+
   if (newErrorlogs) {
   if (newErrorlogs) {
     return 'https://forms.gle/83QJ9ALJkiugxTZf8'
     return 'https://forms.gle/83QJ9ALJkiugxTZf8'
   }
   }

+ 13 - 2
services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts

@@ -1,9 +1,20 @@
 import { useUserSettingsContext } from '@/shared/context/user-settings-context'
 import { useUserSettingsContext } from '@/shared/context/user-settings-context'
 import { isInExperiment } from '@/utils/labs-utils'
 import { isInExperiment } from '@/utils/labs-utils'
+import getMeta from '@/utils/meta'
 import { isSplitTestEnabled } from '@/utils/splitTestUtils'
 import { isSplitTestEnabled } from '@/utils/splitTestUtils'
 
 
-export const canUseNewEditor = () =>
-  isInExperiment('editor-redesign') || isSplitTestEnabled('editor-redesign')
+export const isNewEditorInBeta = () => {
+  const splitTestInfo = getMeta('ol-splitTestInfo') || {}
+  return splitTestInfo['editor-redesign']?.phase === 'beta'
+}
+
+export const canUseNewEditor = () => {
+  const inBetaPhase = isNewEditorInBeta()
+  return (
+    (!inBetaPhase && isInExperiment('editor-redesign')) ||
+    isSplitTestEnabled('editor-redesign')
+  )
+}
 
 
 export const useIsNewEditorEnabled = () => {
 export const useIsNewEditorEnabled = () => {
   const { userSettings } = useUserSettingsContext()
   const { userSettings } = useUserSettingsContext()

+ 25 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide-redesign.scss

@@ -53,3 +53,28 @@
 .ide-redesign-editor-content {
 .ide-redesign-editor-content {
   height: 100%;
   height: 100%;
 }
 }
+
+.ide-redesign-labs-user-beta-promo {
+  position: absolute;
+  top: 60px;
+  right: 28px;
+  z-index: 100000;
+  max-width: 560px;
+  font-size: var(--font-size-03);
+  line-height: var(--line-height-03);
+
+  .notification-content {
+    display: flex;
+    flex-direction: column;
+    gap: var(--spacing-02);
+    align-items: flex-start;
+  }
+}
+
+.ide-redesign-labs-user-beta-promo-title {
+  font-weight: bold;
+}
+
+.ide-redesign-labs-user-beta-promo-button {
+  margin-top: var(--spacing-05);
+}

+ 4 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar-redesign.scss

@@ -237,3 +237,7 @@
 .ide-redesign-labs-button.btn.btn-secondary {
 .ide-redesign-labs-button.btn.btn-secondary {
   @include labs-button;
   @include labs-button;
 }
 }
+
+.ide-redesign-beta-button.btn.btn-secondary {
+  @include labs-button;
+}

+ 9 - 0
services/web/locales/en.json

@@ -237,6 +237,7 @@
   "beta_program_not_participating": "You are not enrolled in the Beta Program",
   "beta_program_not_participating": "You are not enrolled in the Beta Program",
   "beta_program_opt_in_action": "Opt-In to Beta Program",
   "beta_program_opt_in_action": "Opt-In to Beta Program",
   "beta_program_opt_out_action": "Opt-Out of Beta Program",
   "beta_program_opt_out_action": "Opt-Out of Beta Program",
+  "beta_program_the_new_overleaf_editor": "Beta program: the new Overleaf editor",
   "bibliographies": "Bibliographies",
   "bibliographies": "Bibliographies",
   "billed_annually_at": "Billed annually at <0>__price__</0> <1>(includes plan and any add-ons)</1>",
   "billed_annually_at": "Billed annually at <0>__price__</0> <1>(includes plan and any add-ons)</1>",
   "billed_monthly_at": "Billed monthly at <0>__price__</0> <1>(includes plan and any add-ons)</1>",
   "billed_monthly_at": "Billed monthly at <0>__price__</0> <1>(includes plan and any add-ons)</1>",
@@ -1149,6 +1150,7 @@
   "italics": "Italics",
   "italics": "Italics",
   "ja": "Japanese",
   "ja": "Japanese",
   "january": "January",
   "january": "January",
+  "join_beta": "Join beta",
   "join_beta_program": "Join the beta program",
   "join_beta_program": "Join the beta program",
   "join_labs": "Join Labs",
   "join_labs": "Join Labs",
   "join_now": "Join now",
   "join_now": "Join now",
@@ -2293,6 +2295,7 @@
   "thanks_for_subscribing_to_plan_with_add_on": "Thank you for subscribing to the __planName__ plan with the __addOnName__ add-on. It’s support from people like yourself that allows __appName__ to continue to grow and improve.",
   "thanks_for_subscribing_to_plan_with_add_on": "Thank you for subscribing to the __planName__ plan with the __addOnName__ add-on. It’s support from people like yourself that allows __appName__ to continue to grow and improve.",
   "thanks_for_subscribing_to_the_add_on": "Thank you for subscribing to the __addOnName__ add-on. It’s support from people like yourself that allows __appName__ to continue to grow and improve.",
   "thanks_for_subscribing_to_the_add_on": "Thank you for subscribing to the __addOnName__ add-on. It’s support from people like yourself that allows __appName__ to continue to grow and improve.",
   "thanks_for_subscribing_you_help_sl": "Thank you for subscribing to the __planName__ plan. It’s support from people like yourself that allows __appName__ to continue to grow and improve.",
   "thanks_for_subscribing_you_help_sl": "Thank you for subscribing to the __planName__ plan. It’s support from people like yourself that allows __appName__ to continue to grow and improve.",
+  "thanks_for_trying_the_new_editor": "Thanks for trying the new editor",
   "thanks_settings_updated": "Thanks, your settings have been updated.",
   "thanks_settings_updated": "Thanks, your settings have been updated.",
   "the_add_on_will_remain_active_until": "The add-on will remain active until the end of the current billing period.",
   "the_add_on_will_remain_active_until": "The add-on will remain active until the end of the current billing period.",
   "the_code_editor_color_scheme": "The code editor color scheme",
   "the_code_editor_color_scheme": "The code editor color scheme",
@@ -2302,8 +2305,10 @@
   "the_following_files_and_folders_already_exist_in_this_project": "The following files and folders already exist in this project:",
   "the_following_files_and_folders_already_exist_in_this_project": "The following files and folders already exist in this project:",
   "the_following_folder_already_exists_in_this_project": "The following folder already exists in this project:",
   "the_following_folder_already_exists_in_this_project": "The following folder already exists in this project:",
   "the_following_folder_already_exists_in_this_project_plural": "The following folders already exist in this project:",
   "the_following_folder_already_exists_in_this_project_plural": "The following folders already exist in this project:",
+  "the_labs_experiment_has_now_finished": "The Labs experiment has now finished, but you can still use the new editor by joining our beta program.",
   "the_latex_engine_used_for_compiling": "The LaTeX engine used for compiling",
   "the_latex_engine_used_for_compiling": "The LaTeX engine used for compiling",
   "the_new_overleaf_editor": "The new Overleaf editor",
   "the_new_overleaf_editor": "The new Overleaf editor",
+  "the_new_overleaf_editor_try_now_in_beta": "The new Overleaf editor — try now in beta",
   "the_next_payment_will_be_collected_on": "The next payment will be collected on <strong>__date__</strong>.",
   "the_next_payment_will_be_collected_on": "The next payment will be collected on <strong>__date__</strong>.",
   "the_original_text_has_changed": "The original text has changed, so this suggestion can’t be applied",
   "the_original_text_has_changed": "The original text has changed, so this suggestion can’t be applied",
   "the_overleaf_color_scheme": "The __appName__ color scheme",
   "the_overleaf_color_scheme": "The __appName__ color scheme",
@@ -2341,6 +2346,7 @@
   "this_experiment_isnt_accepting_new_participants": "This experiment isn’t accepting new participants.",
   "this_experiment_isnt_accepting_new_participants": "This experiment isn’t accepting new participants.",
   "this_field_is_required": "This field is required",
   "this_field_is_required": "This field is required",
   "this_grants_access_to_features_2": "This grants you access to <0>__appName__</0> <0>__featureType__</0> features.",
   "this_grants_access_to_features_2": "This grants you access to <0>__appName__</0> <0>__featureType__</0> features.",
+  "this_is_a_beta_release_for_the_new_overleaf_editor": "This is a beta release for the new Overleaf editor. You can switch back to the old editor at any time.",
   "this_is_a_labs_experiment_for_the_new_overleaf_editor_some_features_are_still_in_progress": "This is a Labs experiment for the new Overleaf editor. Some features are still in progress, so if you need something that’s missing, you can switch back to the old editor at any time.",
   "this_is_a_labs_experiment_for_the_new_overleaf_editor_some_features_are_still_in_progress": "This is a Labs experiment for the new Overleaf editor. Some features are still in progress, so if you need something that’s missing, you can switch back to the old editor at any time.",
   "this_is_a_new_feature": "This is a new feature",
   "this_is_a_new_feature": "This is a new feature",
   "this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "This is the file that references pulled from your reference manager will be added to.",
   "this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "This is the file that references pulled from your reference manager will be added to.",
@@ -2650,12 +2656,14 @@
   "were_performing_maintenance": "We’re performing maintenance on Overleaf and you need to wait a moment. Sorry for any inconvenience. The editor will refresh automatically in __seconds__ seconds.",
   "were_performing_maintenance": "We’re performing maintenance on Overleaf and you need to wait a moment. Sorry for any inconvenience. The editor will refresh automatically in __seconds__ seconds.",
   "were_redesigning_our_editor_to_make_it_easier_to_use": "We’re redesigning our editor to make it easier to use and ensure it’s future ready. Try it out and give us your feedback to help us get this right. (Some features are still in the works, so you can switch back at any time.)",
   "were_redesigning_our_editor_to_make_it_easier_to_use": "We’re redesigning our editor to make it easier to use and ensure it’s future ready. Try it out and give us your feedback to help us get this right. (Some features are still in the works, so you can switch back at any time.)",
   "were_reducing_compile_timeout": "We’re in the process of <0>reducing the compile timeout limit</0> on our free plan, which may affect your project in future.",
   "were_reducing_compile_timeout": "We’re in the process of <0>reducing the compile timeout limit</0> on our free plan, which may affect your project in future.",
+  "weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready": "We’ve redesigned our editor to make it easier to use and future ready. It’s now in beta, so try it out and give us your feedback.",
   "what_did_you_find_most_helpful": "What did you find most helpful?",
   "what_did_you_find_most_helpful": "What did you find most helpful?",
   "what_do_you_need": "What do you need?",
   "what_do_you_need": "What do you need?",
   "what_do_you_need_help_with": "What do you need help with?",
   "what_do_you_need_help_with": "What do you need help with?",
   "what_does_this_mean_for_you": "This means:",
   "what_does_this_mean_for_you": "This means:",
   "what_happens_when_sso_is_enabled": "What happens when SSO is enabled?",
   "what_happens_when_sso_is_enabled": "What happens when SSO is enabled?",
   "what_should_we_call_you": "What should we call you?",
   "what_should_we_call_you": "What should we call you?",
+  "whats_different": "What’s different?",
   "whats_different_in_the_new_editor": "What’s different in the new editor?",
   "whats_different_in_the_new_editor": "What’s different in the new editor?",
   "when_you_join_labs": "When you join Labs, you can choose which experiments you want to be part of. Once you’ve done that, you can use Overleaf as normal, but you’ll see any labs features marked with this badge:",
   "when_you_join_labs": "When you join Labs, you can choose which experiments you want to be part of. Once you’ve done that, you can use Overleaf as normal, but you’ll see any labs features marked with this badge:",
   "when_you_tick_the_include_caption_box": "When you tick the box “Include caption” the image will be inserted into your document with a placeholder caption. To edit it, you simply select the placeholder text and type to replace it with your own.",
   "when_you_tick_the_include_caption_box": "When you tick the box “Include caption” the image will be inserted into your document with a placeholder caption. To edit it, you simply select the placeholder text and type to replace it with your own.",
@@ -2719,6 +2727,7 @@
   "you_can_select_or_invite_collaborator": "You can select or invite __count__ collaborator on your current plan. Upgrade to add more editors or reviewers.",
   "you_can_select_or_invite_collaborator": "You can select or invite __count__ collaborator on your current plan. Upgrade to add more editors or reviewers.",
   "you_can_select_or_invite_collaborator_plural": "You can select or invite __count__ collaborators on your current plan. Upgrade to add more editors or reviewers.",
   "you_can_select_or_invite_collaborator_plural": "You can select or invite __count__ collaborators on your current plan. Upgrade to add more editors or reviewers.",
   "you_can_still_use_your_premium_features": "You can still use your premium features until the pause becomes active.",
   "you_can_still_use_your_premium_features": "You can still use your premium features until the pause becomes active.",
+  "you_can_switch_back_to_the_old_editor_at_any_time": "You can switch back to the old editor at any time.",
   "you_cant_add_or_change_password_due_to_sso": "You can’t add or change your password because your group or organization uses <0>single sign-on (SSO)</0>.",
   "you_cant_add_or_change_password_due_to_sso": "You can’t add or change your password because your group or organization uses <0>single sign-on (SSO)</0>.",
   "you_cant_join_this_group_subscription": "You can’t join this group subscription",
   "you_cant_join_this_group_subscription": "You can’t join this group subscription",
   "you_cant_reset_password_due_to_sso": "You can’t reset your password because your group or organization uses SSO. <0>Log in with SSO</0>.",
   "you_cant_reset_password_due_to_sso": "You can’t reset your password because your group or organization uses SSO. <0>Log in with SSO</0>.",