Просмотр исходного кода

Merge pull request #26312 from overleaf/as-10s-compile-timeout

[web] 10s Compile Timeout: Warning Phase

GitOrigin-RevId: 749baad646fa7ef0d3a8e14fbbb5edec7b227ed3
MoxAmber 1 год назад
Родитель
Сommit
0a79ac75ff
21 измененных файлов с 411 добавлено и 94 удалено
  1. 10 0
      services/web/app/src/Features/Project/ProjectController.js
  2. 1 0
      services/web/app/views/project/editor/_meta.pug
  3. 10 5
      services/web/frontend/extracted-translations.json
  4. 122 0
      services/web/frontend/js/features/pdf-preview/components/compile-time-changing-soon.tsx
  5. 6 3
      services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner.tsx
  6. 80 10
      services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx
  7. 3 2
      services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.tsx
  8. 4 2
      services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx
  9. 58 19
      services/web/frontend/js/features/pdf-preview/components/timeout-message-after-paywall-dismissal.tsx
  10. 36 15
      services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-paywall-prompt.tsx
  11. 36 24
      services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx
  12. 9 2
      services/web/frontend/js/main/account-upgrade.ts
  13. 6 3
      services/web/frontend/js/shared/components/start-free-trial-button.tsx
  14. 1 1
      services/web/frontend/js/shared/components/upgrade-prompt.tsx
  15. 4 1
      services/web/frontend/js/utils/meta.ts
  16. 1 0
      services/web/locales/da.json
  17. 12 7
      services/web/locales/en.json
  18. 2 0
      services/web/locales/ru.json
  19. 2 0
      services/web/locales/zh-CN.json
  20. 4 0
      services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx
  21. 4 0
      services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx

+ 10 - 0
services/web/app/src/Features/Project/ProjectController.js

@@ -765,6 +765,12 @@ const _ProjectController = {
         isOverleafAssistBundleEnabled &&
         isOverleafAssistBundleEnabled &&
         (await ProjectController._getAddonPrices(req, res))
         (await ProjectController._getAddonPrices(req, res))
 
 
+      const reducedTimeoutWarning =
+        await SplitTestHandler.promises.getAssignmentForUser(
+          project.owner_ref,
+          '10s-timeout-warning'
+        )
+
       let planCode = subscription?.planCode
       let planCode = subscription?.planCode
       if (!planCode && !userInNonIndividualSub) {
       if (!planCode && !userInNonIndividualSub) {
         planCode = 'personal'
         planCode = 'personal'
@@ -879,6 +885,10 @@ const _ProjectController = {
         paywallPlans,
         paywallPlans,
         customerIoEnabled,
         customerIoEnabled,
         addonPrices,
         addonPrices,
+        compileSettings: {
+          reducedTimeoutWarning: reducedTimeoutWarning?.variant,
+          compileTimeout: ownerFeatures?.compileTimeout,
+        },
       })
       })
       timer.done()
       timer.done()
     } catch (err) {
     } catch (err) {

+ 1 - 0
services/web/app/views/project/editor/_meta.pug

@@ -43,6 +43,7 @@ meta(name="ol-shouldLoadHotjar" data-type="boolean" content=shouldLoadHotjar)
 meta(name="ol-odcRole" data-type="string" content=odcRole)
 meta(name="ol-odcRole" data-type="string" content=odcRole)
 meta(name="ol-isPaywallChangeCompileTimeoutEnabled" data-type="boolean" content=isPaywallChangeCompileTimeoutEnabled)
 meta(name="ol-isPaywallChangeCompileTimeoutEnabled" data-type="boolean" content=isPaywallChangeCompileTimeoutEnabled)
 meta(name='ol-customerIoEnabled' data-type="boolean" content=customerIoEnabled)
 meta(name='ol-customerIoEnabled' data-type="boolean" content=customerIoEnabled)
+meta(name='ol-compileSettings' data-type="json" content=compileSettings)
 if(isPaywallChangeCompileTimeoutEnabled)
 if(isPaywallChangeCompileTimeoutEnabled)
 	//- expose plans info to show prices in paywall-change-compile-timeout test 
 	//- expose plans info to show prices in paywall-change-compile-timeout test 
 	meta(name="ol-paywallPlans", data-type="json" content=paywallPlans)
 	meta(name="ol-paywallPlans", data-type="json" content=paywallPlans)

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

@@ -1,9 +1,9 @@
 {
 {
   "0_free_suggestions": "",
   "0_free_suggestions": "",
-  "12x_more_compile_time": "",
   "1_2_width": "",
   "1_2_width": "",
   "1_4_width": "",
   "1_4_width": "",
   "1_free_suggestion": "",
   "1_free_suggestion": "",
+  "24x_more_compile_time": "",
   "3_4_width": "",
   "3_4_width": "",
   "About": "",
   "About": "",
   "Account": "",
   "Account": "",
@@ -133,6 +133,7 @@
   "an_email_has_already_been_sent_to": "",
   "an_email_has_already_been_sent_to": "",
   "an_error_occured_while_restoring_project": "",
   "an_error_occured_while_restoring_project": "",
   "an_error_occurred_when_verifying_the_coupon_code": "",
   "an_error_occurred_when_verifying_the_coupon_code": "",
+  "and_upgrade_for_compile_time": "",
   "annual_discount": "",
   "annual_discount": "",
   "anonymous": "",
   "anonymous": "",
   "anyone_with_link_can_edit": "",
   "anyone_with_link_can_edit": "",
@@ -846,6 +847,7 @@
   "integrations": "",
   "integrations": "",
   "integrations_like_github": "",
   "integrations_like_github": "",
   "interested_in_cheaper_personal_plan": "",
   "interested_in_cheaper_personal_plan": "",
+  "introducing_shorter_compile_timeout": "",
   "invalid_confirmation_code": "",
   "invalid_confirmation_code": "",
   "invalid_email": "",
   "invalid_email": "",
   "invalid_file_name": "",
   "invalid_file_name": "",
@@ -1160,7 +1162,6 @@
   "organization_name": "",
   "organization_name": "",
   "organize_tags": "",
   "organize_tags": "",
   "other": "",
   "other": "",
-  "other_causes_of_compile_timeouts": "",
   "other_logs_and_files": "",
   "other_logs_and_files": "",
   "other_output_files": "",
   "other_output_files": "",
   "our_team_will_get_back_to_you_shortly": "",
   "our_team_will_get_back_to_you_shortly": "",
@@ -1341,6 +1342,8 @@
   "reactivate_subscription": "",
   "reactivate_subscription": "",
   "read_lines_from_path": "",
   "read_lines_from_path": "",
   "read_more": "",
   "read_more": "",
+  "read_more_about_compile_timeout_changes": "",
+  "read_more_about_fix_prevent_timeout": "",
   "read_more_about_free_compile_timeouts_servers": "",
   "read_more_about_free_compile_timeouts_servers": "",
   "read_only_dropbox_sync_message": "",
   "read_only_dropbox_sync_message": "",
   "read_only_token": "",
   "read_only_token": "",
@@ -1787,6 +1790,7 @@
   "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_project_already_has_maximum_collaborators": "",
   "this_project_already_has_maximum_collaborators": "",
+  "this_project_compiled_but_soon_might_not": "",
   "this_project_contains_a_file_called_output": "",
   "this_project_contains_a_file_called_output": "",
   "this_project_exceeded_collaborator_limit": "",
   "this_project_exceeded_collaborator_limit": "",
   "this_project_exceeded_compile_timeout_limit_on_free_plan": "",
   "this_project_exceeded_compile_timeout_limit_on_free_plan": "",
@@ -1971,7 +1975,7 @@
   "updating": "",
   "updating": "",
   "upgrade": "",
   "upgrade": "",
   "upgrade_cc_btn": "",
   "upgrade_cc_btn": "",
-  "upgrade_for_12x_more_compile_time": "",
+  "upgrade_for_more_compile_time": "",
   "upgrade_my_plan": "",
   "upgrade_my_plan": "",
   "upgrade_now": "",
   "upgrade_now": "",
   "upgrade_plan": "",
   "upgrade_plan": "",
@@ -2069,8 +2073,7 @@
   "were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "",
   "were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "",
   "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": "",
-  "weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "",
-  "weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "",
+  "were_reducing_compile_timeout": "",
   "what_do_you_need_help_with": "",
   "what_do_you_need_help_with": "",
   "what_does_this_mean": "",
   "what_does_this_mean": "",
   "what_does_this_mean_for_you": "",
   "what_does_this_mean_for_you": "",
@@ -2141,6 +2144,7 @@
   "you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "",
   "you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "",
   "you_have_x_licenses_and_your_plan_supports_up_to_y": "",
   "you_have_x_licenses_and_your_plan_supports_up_to_y": "",
   "you_have_x_licenses_on_your_subscription": "",
   "you_have_x_licenses_on_your_subscription": "",
+  "you_may_be_able_to_fix_issues_to_speed_up_the_compile": "",
   "you_need_to_configure_your_sso_settings": "",
   "you_need_to_configure_your_sso_settings": "",
   "you_unpaused_your_subscription": "",
   "you_unpaused_your_subscription": "",
   "you_will_be_able_to_reassign_subscription": "",
   "you_will_be_able_to_reassign_subscription": "",
@@ -2175,6 +2179,7 @@
   "your_plan_is_limited_to_n_editors": "",
   "your_plan_is_limited_to_n_editors": "",
   "your_plan_is_limited_to_n_editors_plural": "",
   "your_plan_is_limited_to_n_editors_plural": "",
   "your_premium_plan_is_paused": "",
   "your_premium_plan_is_paused": "",
+  "your_project_compiled_but_soon_might_not": "",
   "your_project_exceeded_collaborator_limit": "",
   "your_project_exceeded_collaborator_limit": "",
   "your_project_exceeded_compile_timeout_limit_on_free_plan": "",
   "your_project_exceeded_compile_timeout_limit_on_free_plan": "",
   "your_project_near_compile_timeout_limit": "",
   "your_project_near_compile_timeout_limit": "",

+ 122 - 0
services/web/frontend/js/features/pdf-preview/components/compile-time-changing-soon.tsx

@@ -0,0 +1,122 @@
+import Notification from '@/shared/components/notification'
+import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
+import { Trans, useTranslation } from 'react-i18next'
+import * as eventTracking from '@/infrastructure/event-tracking'
+import { FC } from 'react'
+
+export const CompileTimeoutChangingSoon: FC<{
+  isProjectOwner?: boolean
+  handleDismissChangingSoon: () => void
+  segmentation?: eventTracking.Segmentation
+}> = ({ isProjectOwner = false, handleDismissChangingSoon, segmentation }) => {
+  const { t } = useTranslation()
+
+  const sendInfoClickEvent = () => {
+    eventTracking.sendMB('paywall-info-click', {
+      'paywall-type': 'compile-time-warning',
+      ...segmentation,
+      content: 'blog',
+    })
+  }
+
+  const compileTimeoutChangesBlogLink = (
+    /* eslint-disable-next-line jsx-a11y/anchor-has-content */
+    <a
+      aria-label={t('read_more_about_compile_timeout_changes')}
+      href="/blog/changes-to-free-compile-timeout"
+      key="compileTimeoutBlogLink1"
+      rel="noopener noreferrer"
+      target="_blank"
+      onClick={sendInfoClickEvent}
+    />
+  )
+
+  const fixingCompileTimeoutsLearnLink = (
+    /* eslint-disable-next-line jsx-a11y/anchor-has-content */
+    <a
+      aria-label={t('read_more_about_fix_prevent_timeout')}
+      href="/learn/how-to/Fixing_and_preventing_compile_timeouts"
+      key="compileTimeoutBlogLink2"
+      target="_blank"
+      rel="noopener noreferrer"
+    />
+  )
+
+  if (isProjectOwner) {
+    return (
+      <Notification
+        action={
+          <StartFreeTrialButton
+            segmentation={segmentation}
+            source="compile-time-warning"
+            buttonProps={{
+              variant: 'secondary',
+              size: 'sm',
+            }}
+          >
+            {t('start_free_trial_without_exclamation')}
+          </StartFreeTrialButton>
+        }
+        ariaLive="polite"
+        content={
+          <div>
+            <p>
+              <Trans
+                i18nKey="introducing_shorter_compile_timeout"
+                components={[compileTimeoutChangesBlogLink]}
+              />
+            </p>
+            <p className="row-spaced">
+              <Trans
+                i18nKey="you_may_be_able_to_fix_issues_to_speed_up_the_compile"
+                components={[fixingCompileTimeoutsLearnLink]}
+              />{' '}
+              <Trans
+                i18nKey="and_upgrade_for_compile_time"
+                components={{ strong: <strong /> }}
+              />
+            </p>
+          </div>
+        }
+        title={t('your_project_compiled_but_soon_might_not')}
+        type="warning"
+        isActionBelowContent
+        isDismissible
+        onDismiss={handleDismissChangingSoon}
+      />
+    )
+  }
+
+  return (
+    <Notification
+      ariaLive="polite"
+      content={
+        <div>
+          <p>
+            <Trans
+              i18nKey="introducing_shorter_compile_timeout"
+              components={[compileTimeoutChangesBlogLink]}
+            />{' '}
+            <Trans
+              i18nKey="you_may_be_able_to_fix_issues_to_speed_up_the_compile"
+              components={[fixingCompileTimeoutsLearnLink]}
+            />
+          </p>
+          <p className="row-spaced">
+            <Trans
+              i18nKey="tell_the_project_owner_and_ask_them_to_upgrade"
+              components={[
+                // eslint-disable-next-line react/jsx-key
+                <strong />,
+              ]}
+            />
+          </p>
+        </div>
+      }
+      title={t('this_project_compiled_but_soon_might_not')}
+      type="warning"
+      isDismissible
+      onDismiss={handleDismissChangingSoon}
+    />
+  )
+}

+ 6 - 3
services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner.tsx

@@ -1,21 +1,24 @@
 import Notification from '@/shared/components/notification'
 import Notification from '@/shared/components/notification'
 import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
 import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import { FC } from 'react'
 import { FC } from 'react'
 
 
 export const CompileTimeWarningUpgradePromptInner: FC<{
 export const CompileTimeWarningUpgradePromptInner: FC<{
   handleDismissWarning: () => void
   handleDismissWarning: () => void
-}> = ({ handleDismissWarning }) => {
+  segmentation: eventTracking.Segmentation
+}> = ({ handleDismissWarning, segmentation }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   return (
   return (
     <Notification
     <Notification
       action={
       action={
         <StartFreeTrialButton
         <StartFreeTrialButton
-          variant="new-10s"
           source="compile-time-warning"
           source="compile-time-warning"
+          segmentation={segmentation}
           buttonProps={{
           buttonProps={{
             variant: 'secondary',
             variant: 'secondary',
+            size: 'sm',
           }}
           }}
         >
         >
           {t('start_free_trial_without_exclamation')}
           {t('start_free_trial_without_exclamation')}
@@ -27,7 +30,7 @@ export const CompileTimeWarningUpgradePromptInner: FC<{
           <div>
           <div>
             <span>{t('your_project_near_compile_timeout_limit')}</span>
             <span>{t('your_project_near_compile_timeout_limit')}</span>
           </div>
           </div>
-          <strong>{t('upgrade_for_12x_more_compile_time')}</strong>
+          <strong>{t('upgrade_for_more_compile_time')}</strong>
           {'. '}
           {'. '}
         </div>
         </div>
       }
       }

+ 80 - 10
services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx

@@ -1,23 +1,56 @@
-import { memo, useCallback, useEffect, useState } from 'react'
+import { memo, useCallback, useEffect, useMemo, useState } from 'react'
 import * as eventTracking from '@/infrastructure/event-tracking'
 import * as eventTracking from '@/infrastructure/event-tracking'
 import { useDetachCompileContext } from '@/shared/context/detach-compile-context'
 import { useDetachCompileContext } from '@/shared/context/detach-compile-context'
 import usePersistedState from '@/shared/hooks/use-persisted-state'
 import usePersistedState from '@/shared/hooks/use-persisted-state'
 import { CompileTimeWarningUpgradePromptInner } from '@/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner'
 import { CompileTimeWarningUpgradePromptInner } from '@/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner'
 import getMeta from '@/utils/meta'
 import getMeta from '@/utils/meta'
+import { CompileTimeoutChangingSoon } from './compile-time-changing-soon'
 
 
 function CompileTimeWarningUpgradePrompt() {
 function CompileTimeWarningUpgradePrompt() {
   const { isProjectOwner, deliveryLatencies, compiling, showLogs, error } =
   const { isProjectOwner, deliveryLatencies, compiling, showLogs, error } =
     useDetachCompileContext()
     useDetachCompileContext()
 
 
   const [showWarning, setShowWarning] = useState(false)
   const [showWarning, setShowWarning] = useState(false)
+  const [showChangingSoon, setShowChangingSoon] = useState(false)
   const [dismissedUntilWarning, setDismissedUntilWarning] = usePersistedState<
   const [dismissedUntilWarning, setDismissedUntilWarning] = usePersistedState<
     Date | undefined
     Date | undefined
   >(`has-dismissed-10s-compile-time-warning-until`)
   >(`has-dismissed-10s-compile-time-warning-until`)
+  const { reducedTimeoutWarning } = getMeta('ol-compileSettings')
+  const warningThreshold = reducedTimeoutWarning === 'enabled' ? 7 : 10
+
+  const sharedSegmentation = useMemo(
+    () => ({
+      '10s-timeout-warning': reducedTimeoutWarning,
+      'is-owner': isProjectOwner,
+    }),
+    [isProjectOwner, reducedTimeoutWarning]
+  )
+
+  const warningSegmentation = useMemo(
+    () => ({
+      content: 'warning',
+      time: warningThreshold,
+      ...sharedSegmentation,
+    }),
+    [sharedSegmentation, warningThreshold]
+  )
+
+  const changingSoonSegmentation = useMemo(
+    () => ({
+      content: 'changes',
+      time: 10,
+      ...sharedSegmentation,
+    }),
+    [sharedSegmentation]
+  )
 
 
   const handleNewCompile = useCallback(
   const handleNewCompile = useCallback(
     (compileTime: number) => {
     (compileTime: number) => {
       setShowWarning(false)
       setShowWarning(false)
-      if (compileTime > 10000) {
+      setShowChangingSoon(false)
+      if (reducedTimeoutWarning === 'enabled' && compileTime > 10000) {
+        setShowChangingSoon(true)
+      } else if (compileTime > warningThreshold * 1000) {
         if (isProjectOwner) {
         if (isProjectOwner) {
           if (
           if (
             !dismissedUntilWarning ||
             !dismissedUntilWarning ||
@@ -25,26 +58,52 @@ function CompileTimeWarningUpgradePrompt() {
           ) {
           ) {
             setShowWarning(true)
             setShowWarning(true)
             eventTracking.sendMB('compile-time-warning-displayed', {
             eventTracking.sendMB('compile-time-warning-displayed', {
-              time: 10,
+              time: warningThreshold,
               isProjectOwner,
               isProjectOwner,
             })
             })
           }
           }
         }
         }
       }
       }
     },
     },
-    [isProjectOwner, dismissedUntilWarning]
+    [
+      isProjectOwner,
+      dismissedUntilWarning,
+      reducedTimeoutWarning,
+      warningThreshold,
+    ]
   )
   )
 
 
   const handleDismissWarning = useCallback(() => {
   const handleDismissWarning = useCallback(() => {
     eventTracking.sendMB('compile-time-warning-dismissed', {
     eventTracking.sendMB('compile-time-warning-dismissed', {
-      time: 10,
+      time: warningThreshold,
       isProjectOwner,
       isProjectOwner,
     })
     })
+    eventTracking.sendMB('paywall-dismiss', {
+      'paywall-type': 'compile-time-warning',
+      content: 'warning',
+      time: warningThreshold,
+      ...sharedSegmentation,
+    })
     setShowWarning(false)
     setShowWarning(false)
     const until = new Date()
     const until = new Date()
     until.setDate(until.getDate() + 1) // 1 day
     until.setDate(until.getDate() + 1) // 1 day
     setDismissedUntilWarning(until)
     setDismissedUntilWarning(until)
-  }, [isProjectOwner, setDismissedUntilWarning])
+  }, [
+    isProjectOwner,
+    setDismissedUntilWarning,
+    warningThreshold,
+    sharedSegmentation,
+  ])
+
+  const handleDismissChangingSoon = useCallback(() => {
+    eventTracking.sendMB('paywall-dismiss', {
+      'paywall-type': 'compile-time-warning',
+      time: 10,
+      content: 'changes',
+      ...sharedSegmentation,
+    })
+    setShowChangingSoon(false)
+  }, [sharedSegmentation])
 
 
   useEffect(() => {
   useEffect(() => {
     if (compiling || error || showLogs) return
     if (compiling || error || showLogs) return
@@ -55,21 +114,32 @@ function CompileTimeWarningUpgradePrompt() {
     return null
     return null
   }
   }
 
 
-  if (compiling || error || showLogs) {
+  if (
+    compiling ||
+    error ||
+    showLogs ||
+    !deliveryLatencies.compileTimeServerE2E
+  ) {
     return null
     return null
   }
   }
 
 
-  if (!showWarning) {
+  if (!showWarning && !showChangingSoon) {
     return null
     return null
   }
   }
 
 
-  // if showWarning is true then the 10s warning is shown
-
   return (
   return (
     <div>
     <div>
       {showWarning && isProjectOwner && (
       {showWarning && isProjectOwner && (
         <CompileTimeWarningUpgradePromptInner
         <CompileTimeWarningUpgradePromptInner
           handleDismissWarning={handleDismissWarning}
           handleDismissWarning={handleDismissWarning}
+          segmentation={warningSegmentation}
+        />
+      )}
+      {showChangingSoon && (
+        <CompileTimeoutChangingSoon
+          isProjectOwner={isProjectOwner}
+          handleDismissChangingSoon={handleDismissChangingSoon}
+          segmentation={changingSoonSegmentation}
         />
         />
       )}
       )}
     </div>
     </div>

+ 3 - 2
services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.tsx

@@ -21,7 +21,6 @@ function PdfLogsViewer({ alwaysVisible = false }: { alwaysVisible?: boolean }) {
   const {
   const {
     codeCheckFailed,
     codeCheckFailed,
     error,
     error,
-    hasShortCompileTimeout,
     logEntries,
     logEntries,
     rawLog,
     rawLog,
     validationIssues,
     validationIssues,
@@ -32,6 +31,8 @@ function PdfLogsViewer({ alwaysVisible = false }: { alwaysVisible?: boolean }) {
 
 
   const { loadingError } = usePdfPreviewContext()
   const { loadingError } = usePdfPreviewContext()
 
 
+  const { compileTimeout } = getMeta('ol-compileSettings')
+
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   const [
   const [
@@ -58,7 +59,7 @@ function PdfLogsViewer({ alwaysVisible = false }: { alwaysVisible?: boolean }) {
 
 
         {loadingError && <PdfPreviewError error="pdf-viewer-loading-error" />}
         {loadingError && <PdfPreviewError error="pdf-viewer-loading-error" />}
 
 
-        {hasShortCompileTimeout && error === 'timedout' ? (
+        {compileTimeout < 60 && error === 'timedout' ? (
           isCompileTimeoutPaywallDisplay ? (
           isCompileTimeoutPaywallDisplay ? (
             <TimeoutUpgradePaywallPrompt
             <TimeoutUpgradePaywallPrompt
               setIsShowingPrimary={setIsShowingPrimaryCompileTimeoutPaywall}
               setIsShowingPrimary={setIsShowingPrimaryCompileTimeoutPaywall}

+ 4 - 2
services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx

@@ -13,9 +13,11 @@ import PdfErrorState from '@/features/ide-redesign/components/pdf-preview/pdf-er
 import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
 import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
 import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
 import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
 import PdfCodeCheckFailedBanner from '@/features/ide-redesign/components/pdf-preview/pdf-code-check-failed-banner'
 import PdfCodeCheckFailedBanner from '@/features/ide-redesign/components/pdf-preview/pdf-code-check-failed-banner'
+import getMeta from '@/utils/meta'
 
 
 function PdfPreviewPane() {
 function PdfPreviewPane() {
-  const { pdfUrl, hasShortCompileTimeout } = useCompileContext()
+  const { pdfUrl } = useCompileContext()
+  const { compileTimeout } = getMeta('ol-compileSettings')
   const classes = classNames('pdf', 'full-size', {
   const classes = classNames('pdf', 'full-size', {
     'pdf-empty': !pdfUrl,
     'pdf-empty': !pdfUrl,
   })
   })
@@ -35,7 +37,7 @@ function PdfPreviewPane() {
         )}
         )}
         {newEditor && <PdfCodeCheckFailedBanner />}
         {newEditor && <PdfCodeCheckFailedBanner />}
         <PdfPreviewMessages>
         <PdfPreviewMessages>
-          {hasShortCompileTimeout && <CompileTimeWarningUpgradePrompt />}
+          {compileTimeout < 60 && <CompileTimeWarningUpgradePrompt />}
         </PdfPreviewMessages>
         </PdfPreviewMessages>
         <Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
         <Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
           <div className="pdf-viewer">
           <div className="pdf-viewer">

+ 58 - 19
services/web/frontend/js/features/pdf-preview/components/timeout-message-after-paywall-dismissal.tsx

@@ -1,20 +1,31 @@
 import getMeta from '@/utils/meta'
 import getMeta from '@/utils/meta'
 import { Trans, useTranslation } from 'react-i18next'
 import { Trans, useTranslation } from 'react-i18next'
-import { memo, useEffect } from 'react'
+import { memo, useMemo } from 'react'
 import { useDetachCompileContext } from '@/shared/context/detach-compile-context'
 import { useDetachCompileContext } from '@/shared/context/detach-compile-context'
 import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
 import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
 import MaterialIcon from '@/shared/components/material-icon'
 import MaterialIcon from '@/shared/components/material-icon'
 import * as eventTracking from '@/infrastructure/event-tracking'
 import * as eventTracking from '@/infrastructure/event-tracking'
 import PdfLogEntry from './pdf-log-entry'
 import PdfLogEntry from './pdf-log-entry'
 
 
-function TimeoutMessageAfterPaywallDismissal() {
-  const { lastCompileOptions, isProjectOwner } = useDetachCompileContext()
+type TimeoutMessageProps = {
+  segmentation?: eventTracking.Segmentation
+}
 
 
+function TimeoutMessageAfterPaywallDismissal({
+  segmentation,
+}: TimeoutMessageProps) {
+  const { lastCompileOptions, isProjectOwner } = useDetachCompileContext()
   return (
   return (
     <div className="website-redesign timeout-upgrade-paywall-prompt">
     <div className="website-redesign timeout-upgrade-paywall-prompt">
-      <CompileTimeout isProjectOwner={isProjectOwner} />
+      <CompileTimeout
+        isProjectOwner={isProjectOwner}
+        segmentation={segmentation}
+      />
       {getMeta('ol-ExposedSettings').enableSubscriptions && (
       {getMeta('ol-ExposedSettings').enableSubscriptions && (
-        <PreventTimeoutHelpMessage lastCompileOptions={lastCompileOptions} />
+        <PreventTimeoutHelpMessage
+          lastCompileOptions={lastCompileOptions}
+          segmentation={segmentation}
+        />
       )}
       )}
     </div>
     </div>
   )
   )
@@ -22,26 +33,22 @@ function TimeoutMessageAfterPaywallDismissal() {
 
 
 type CompileTimeoutProps = {
 type CompileTimeoutProps = {
   isProjectOwner: boolean
   isProjectOwner: boolean
+  segmentation?: eventTracking.Segmentation
 }
 }
 
 
 const CompileTimeout = memo(function CompileTimeout({
 const CompileTimeout = memo(function CompileTimeout({
   isProjectOwner,
   isProjectOwner,
+  segmentation,
 }: CompileTimeoutProps) {
 }: CompileTimeoutProps) {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
-  useEffect(() => {
-    eventTracking.sendMB('paywall-prompt', {
-      'paywall-type': 'compile-timeout',
-      'paywall-version': 'secondary',
-    })
-  }, [])
-
-  function onPaywallClick() {
-    eventTracking.sendMB('paywall-click', {
-      'paywall-type': 'compile-timeout',
+  const eventSegmentation = useMemo(
+    () => ({
+      ...segmentation,
       'paywall-version': 'secondary',
       'paywall-version': 'secondary',
-    })
-  }
+    }),
+    [segmentation]
+  )
 
 
   return (
   return (
     <PdfLogEntry
     <PdfLogEntry
@@ -86,7 +93,7 @@ const CompileTimeout = memo(function CompileTimeout({
                 <StartFreeTrialButton
                 <StartFreeTrialButton
                   source="compile-timeout"
                   source="compile-timeout"
                   buttonProps={{ variant: 'secondary' }}
                   buttonProps={{ variant: 'secondary' }}
-                  handleClick={onPaywallClick}
+                  segmentation={eventSegmentation}
                 >
                 >
                   {t('try_for_free')}
                   {t('try_for_free')}
                 </StartFreeTrialButton>
                 </StartFreeTrialButton>
@@ -104,18 +111,50 @@ const CompileTimeout = memo(function CompileTimeout({
 
 
 type PreventTimeoutHelpMessageProps = {
 type PreventTimeoutHelpMessageProps = {
   lastCompileOptions: any
   lastCompileOptions: any
+  segmentation?: eventTracking.Segmentation
 }
 }
 
 
 const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
 const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
   lastCompileOptions,
   lastCompileOptions,
+  segmentation,
 }: PreventTimeoutHelpMessageProps) {
 }: PreventTimeoutHelpMessageProps) {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
+  function sendInfoClickEvent() {
+    eventTracking.sendMB('paywall-info-click', {
+      'paywall-type': 'compile-timeout',
+      content: 'blog',
+      ...segmentation,
+    })
+  }
+
+  const compileTimeoutChangesBlogLink = (
+    /* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
+    <a
+      aria-label={t('read_more_about_free_compile_timeouts_servers')}
+      href="/blog/changes-to-free-compile-timeout"
+      rel="noopener noreferrer"
+      target="_blank"
+      onClick={sendInfoClickEvent}
+    />
+  )
+
   return (
   return (
     <PdfLogEntry
     <PdfLogEntry
-      headerTitle={t('other_causes_of_compile_timeouts')}
+      headerTitle={t('reasons_for_compile_timeouts')}
       formattedContent={
       formattedContent={
         <>
         <>
+          {segmentation?.['10s-timeout-warning'] === 'enabled' && (
+            <p>
+              <em>
+                <Trans
+                  i18nKey="were_reducing_compile_timeout"
+                  components={[compileTimeoutChangesBlogLink]}
+                />
+              </em>
+            </p>
+          )}
+          <p>{t('common_causes_of_compile_timeouts_include')}:</p>
           <ul>
           <ul>
             <li>
             <li>
               {t('large_or_high_resolution_images_taking_too_long_to_process')}
               {t('large_or_high_resolution_images_taking_too_long_to_process')}

+ 36 - 15
services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-paywall-prompt.tsx

@@ -1,8 +1,16 @@
-import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react'
+import {
+  Dispatch,
+  SetStateAction,
+  useCallback,
+  useEffect,
+  useMemo,
+  useState,
+} from 'react'
 import getMeta from '@/utils/meta'
 import getMeta from '@/utils/meta'
 import * as eventTracking from '@/infrastructure/event-tracking'
 import * as eventTracking from '@/infrastructure/event-tracking'
 import TimeoutMessageAfterPaywallDismissal from './timeout-message-after-paywall-dismissal'
 import TimeoutMessageAfterPaywallDismissal from './timeout-message-after-paywall-dismissal'
 import { UpgradePrompt } from '@/shared/components/upgrade-prompt'
 import { UpgradePrompt } from '@/shared/components/upgrade-prompt'
+import { useDetachCompileContext } from '@/shared/context/detach-compile-context'
 
 
 const studentRoles = [
 const studentRoles = [
   'High-school student',
   'High-school student',
@@ -11,11 +19,6 @@ const studentRoles = [
   'Doctoral student (e.g. PhD, MD, EngD)',
   'Doctoral student (e.g. PhD, MD, EngD)',
 ]
 ]
 
 
-type Segmentation = Record<
-  string,
-  string | number | boolean | undefined | unknown | any
->
-
 interface TimeoutUpgradePaywallPromptProps {
 interface TimeoutUpgradePaywallPromptProps {
   setIsShowingPrimary?: Dispatch<SetStateAction<boolean>>
   setIsShowingPrimary?: Dispatch<SetStateAction<boolean>>
 }
 }
@@ -26,16 +29,32 @@ function TimeoutUpgradePaywallPrompt({
   const odcRole = getMeta('ol-odcRole')
   const odcRole = getMeta('ol-odcRole')
   const planPrices = getMeta('ol-paywallPlans')
   const planPrices = getMeta('ol-paywallPlans')
   const isStudent = useMemo(() => studentRoles.includes(odcRole), [odcRole])
   const isStudent = useMemo(() => studentRoles.includes(odcRole), [odcRole])
+  const { isProjectOwner } = useDetachCompileContext()
 
 
   const [isPaywallDismissed, setIsPaywallDismissed] = useState<boolean>(false)
   const [isPaywallDismissed, setIsPaywallDismissed] = useState<boolean>(false)
+  const { reducedTimeoutWarning, compileTimeout } =
+    getMeta('ol-compileSettings')
 
 
-  function sendPaywallEvent(event: string, segmentation?: Segmentation) {
-    eventTracking.sendMB(event, {
-      'paywall-type': 'compile-timeout',
-      'paywall-version': 'primary',
-      ...segmentation,
-    })
-  }
+  const sharedSegmentation = useMemo(
+    () => ({
+      '10s-timeout-warning': reducedTimeoutWarning,
+      'is-owner': isProjectOwner,
+      time: compileTimeout,
+    }),
+    [isProjectOwner, reducedTimeoutWarning, compileTimeout]
+  )
+
+  const sendPaywallEvent = useCallback(
+    (event: string, segmentation?: eventTracking.Segmentation) => {
+      eventTracking.sendMB(event, {
+        'paywall-type': 'compile-timeout',
+        'paywall-version': 'primary',
+        ...sharedSegmentation,
+        ...segmentation,
+      })
+    },
+    [sharedSegmentation]
+  )
 
 
   function onClose() {
   function onClose() {
     sendPaywallEvent('paywall-dismiss')
     sendPaywallEvent('paywall-dismiss')
@@ -62,7 +81,7 @@ function TimeoutUpgradePaywallPrompt({
     if (setIsShowingPrimary) {
     if (setIsShowingPrimary) {
       setIsShowingPrimary(true)
       setIsShowingPrimary(true)
     }
     }
-  }, [isStudent, setIsShowingPrimary])
+  }, [isStudent, setIsShowingPrimary, sendPaywallEvent])
 
 
   return (
   return (
     <div>
     <div>
@@ -81,7 +100,9 @@ function TimeoutUpgradePaywallPrompt({
           onClickPaywall={onClickPaywall}
           onClickPaywall={onClickPaywall}
         />
         />
       ) : (
       ) : (
-        <TimeoutMessageAfterPaywallDismissal />
+        <TimeoutMessageAfterPaywallDismissal
+          segmentation={sharedSegmentation}
+        />
       )}
       )}
     </div>
     </div>
   )
   )

+ 36 - 24
services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx

@@ -1,7 +1,7 @@
 import { Trans, useTranslation } from 'react-i18next'
 import { Trans, useTranslation } from 'react-i18next'
 import { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
 import { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
 import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
 import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
-import { memo, useCallback } from 'react'
+import { memo, useCallback, useMemo } from 'react'
 import PdfLogEntry from './pdf-log-entry'
 import PdfLogEntry from './pdf-log-entry'
 import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
 import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
 import OLButton from '@/features/ui/components/ol/ol-button'
 import OLButton from '@/features/ui/components/ol/ol-button'
@@ -26,14 +26,29 @@ function TimeoutUpgradePromptNew() {
     setAnimateCompileDropdownArrow(true)
     setAnimateCompileDropdownArrow(true)
   }, [enableStopOnFirstError, startCompile, setAnimateCompileDropdownArrow])
   }, [enableStopOnFirstError, startCompile, setAnimateCompileDropdownArrow])
 
 
+  const { reducedTimeoutWarning, compileTimeout } =
+    getMeta('ol-compileSettings')
+
+  const sharedSegmentation = useMemo(
+    () => ({
+      '10s-timeout-warning': reducedTimeoutWarning,
+      'is-owner': isProjectOwner,
+      time: compileTimeout,
+    }),
+    [isProjectOwner, reducedTimeoutWarning, compileTimeout]
+  )
+
   return (
   return (
     <>
     <>
-      <CompileTimeout isProjectOwner={isProjectOwner} />
+      <CompileTimeout
+        isProjectOwner={isProjectOwner}
+        segmentation={sharedSegmentation}
+      />
       {getMeta('ol-ExposedSettings').enableSubscriptions && (
       {getMeta('ol-ExposedSettings').enableSubscriptions && (
         <PreventTimeoutHelpMessage
         <PreventTimeoutHelpMessage
           handleEnableStopOnFirstErrorClick={handleEnableStopOnFirstErrorClick}
           handleEnableStopOnFirstErrorClick={handleEnableStopOnFirstErrorClick}
           lastCompileOptions={lastCompileOptions}
           lastCompileOptions={lastCompileOptions}
-          isProjectOwner={isProjectOwner}
+          segmentation={sharedSegmentation}
         />
         />
       )}
       )}
     </>
     </>
@@ -42,10 +57,12 @@ function TimeoutUpgradePromptNew() {
 
 
 type CompileTimeoutProps = {
 type CompileTimeoutProps = {
   isProjectOwner: boolean
   isProjectOwner: boolean
+  segmentation: eventTracking.Segmentation
 }
 }
 
 
 const CompileTimeout = memo(function CompileTimeout({
 const CompileTimeout = memo(function CompileTimeout({
   isProjectOwner,
   isProjectOwner,
+  segmentation,
 }: CompileTimeoutProps) {
 }: CompileTimeoutProps) {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
@@ -62,7 +79,7 @@ const CompileTimeout = memo(function CompileTimeout({
             </p>
             </p>
             {isProjectOwner ? (
             {isProjectOwner ? (
               <p>
               <p>
-                <strong>{t('upgrade_for_12x_more_compile_time')}</strong>{' '}
+                <strong>{t('upgrade_for_more_compile_time')}</strong>{' '}
                 {t(
                 {t(
                   'plus_additional_collaborators_document_history_track_changes_and_more'
                   'plus_additional_collaborators_document_history_track_changes_and_more'
                 )}
                 )}
@@ -82,6 +99,7 @@ const CompileTimeout = memo(function CompileTimeout({
                 <StartFreeTrialButton
                 <StartFreeTrialButton
                   source="compile-timeout"
                   source="compile-timeout"
                   buttonProps={{ variant: 'primary', className: 'w-100' }}
                   buttonProps={{ variant: 'primary', className: 'w-100' }}
+                  segmentation={segmentation}
                 >
                 >
                   {t('start_a_free_trial')}
                   {t('start_a_free_trial')}
                 </StartFreeTrialButton>
                 </StartFreeTrialButton>
@@ -100,18 +118,19 @@ const CompileTimeout = memo(function CompileTimeout({
 type PreventTimeoutHelpMessageProps = {
 type PreventTimeoutHelpMessageProps = {
   lastCompileOptions: any
   lastCompileOptions: any
   handleEnableStopOnFirstErrorClick: () => void
   handleEnableStopOnFirstErrorClick: () => void
-  isProjectOwner: boolean
+  segmentation: eventTracking.Segmentation
 }
 }
 
 
 const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
 const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
   lastCompileOptions,
   lastCompileOptions,
   handleEnableStopOnFirstErrorClick,
   handleEnableStopOnFirstErrorClick,
-  isProjectOwner,
+  segmentation,
 }: PreventTimeoutHelpMessageProps) {
 }: PreventTimeoutHelpMessageProps) {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   function sendInfoClickEvent() {
   function sendInfoClickEvent() {
     eventTracking.sendMB('paywall-info-click', {
     eventTracking.sendMB('paywall-info-click', {
+      ...segmentation,
       'paywall-type': 'compile-timeout',
       'paywall-type': 'compile-timeout',
       content: 'blog',
       content: 'blog',
     })
     })
@@ -121,7 +140,7 @@ const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
     /* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
     /* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
     <a
     <a
       aria-label={t('read_more_about_free_compile_timeouts_servers')}
       aria-label={t('read_more_about_free_compile_timeouts_servers')}
-      href="/blog/changes-to-free-compile-timeouts-and-servers"
+      href="/blog/changes-to-free-compile-timeout"
       rel="noopener noreferrer"
       rel="noopener noreferrer"
       target="_blank"
       target="_blank"
       onClick={sendInfoClickEvent}
       onClick={sendInfoClickEvent}
@@ -133,6 +152,16 @@ const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
       headerTitle={t('reasons_for_compile_timeouts')}
       headerTitle={t('reasons_for_compile_timeouts')}
       formattedContent={
       formattedContent={
         <>
         <>
+          {segmentation?.['10s-timeout-warning'] === 'enabled' && (
+            <p>
+              <em>
+                <Trans
+                  i18nKey="were_reducing_compile_timeout"
+                  components={[compileTimeoutChangesBlogLink]}
+                />
+              </em>
+            </p>
+          )}
           <p>{t('common_causes_of_compile_timeouts_include')}:</p>
           <p>{t('common_causes_of_compile_timeouts_include')}:</p>
           <ul>
           <ul>
             <li>
             <li>
@@ -194,23 +223,6 @@ const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
               ]}
               ]}
             />
             />
           </p>
           </p>
-          <p>
-            <em>
-              <>
-                {isProjectOwner ? (
-                  <Trans
-                    i18nKey="weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project"
-                    components={[compileTimeoutChangesBlogLink]}
-                  />
-                ) : (
-                  <Trans
-                    i18nKey="weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project"
-                    components={[compileTimeoutChangesBlogLink]}
-                  />
-                )}
-              </>
-            </em>
-          </p>
         </>
         </>
       }
       }
       // @ts-ignore
       // @ts-ignore

+ 9 - 2
services/web/frontend/js/main/account-upgrade.ts

@@ -1,7 +1,14 @@
 import * as eventTracking from '../infrastructure/event-tracking'
 import * as eventTracking from '../infrastructure/event-tracking'
 
 
-export function startFreeTrial(source: string, variant?: string) {
-  const eventSegmentation: Record<string, string> = { 'paywall-type': source }
+export function startFreeTrial(
+  source: string,
+  variant?: string,
+  segmentation?: eventTracking.Segmentation
+) {
+  const eventSegmentation: Record<string, string> = {
+    'paywall-type': source,
+    ...segmentation,
+  }
   if (variant) {
   if (variant) {
     eventSegmentation.variant = variant
     eventSegmentation.variant = variant
   }
   }

+ 6 - 3
services/web/frontend/js/shared/components/start-free-trial-button.tsx

@@ -10,6 +10,7 @@ type StartFreeTrialButtonProps = {
   buttonProps?: React.ComponentProps<typeof OLButton>
   buttonProps?: React.ComponentProps<typeof OLButton>
   children?: React.ReactNode
   children?: React.ReactNode
   handleClick?: React.ComponentProps<typeof OLButton>['onClick']
   handleClick?: React.ComponentProps<typeof OLButton>['onClick']
+  segmentation?: eventTracking.Segmentation
 }
 }
 
 
 export default function StartFreeTrialButton({
 export default function StartFreeTrialButton({
@@ -20,18 +21,20 @@ export default function StartFreeTrialButton({
   handleClick,
   handleClick,
   source,
   source,
   variant,
   variant,
+  segmentation,
 }: StartFreeTrialButtonProps) {
 }: StartFreeTrialButtonProps) {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   useEffect(() => {
   useEffect(() => {
     const eventSegmentation: { [key: string]: unknown } = {
     const eventSegmentation: { [key: string]: unknown } = {
       'paywall-type': source,
       'paywall-type': source,
+      ...segmentation,
     }
     }
     if (variant) {
     if (variant) {
       eventSegmentation.variant = variant
       eventSegmentation.variant = variant
     }
     }
     eventTracking.sendMB('paywall-prompt', eventSegmentation)
     eventTracking.sendMB('paywall-prompt', eventSegmentation)
-  }, [source, variant])
+  }, [source, variant, segmentation])
 
 
   const onClick = useCallback(
   const onClick = useCallback(
     (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
     (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
@@ -41,9 +44,9 @@ export default function StartFreeTrialButton({
         handleClick(event)
         handleClick(event)
       }
       }
 
 
-      startFreeTrial(source, variant)
+      startFreeTrial(source, variant, segmentation)
     },
     },
-    [handleClick, source, variant]
+    [handleClick, source, variant, segmentation]
   )
   )
 
 
   return (
   return (

+ 1 - 1
services/web/frontend/js/shared/components/upgrade-prompt.tsx

@@ -104,7 +104,7 @@ export function UpgradePrompt({
             <OLRow>
             <OLRow>
               <ul className="upgrade-prompt-list">
               <ul className="upgrade-prompt-list">
                 <IconListItem icon="hourglass_top">
                 <IconListItem icon="hourglass_top">
-                  {t('12x_more_compile_time')}
+                  {t('24x_more_compile_time')}
                 </IconListItem>
                 </IconListItem>
                 <IconListItem icon="group_add">
                 <IconListItem icon="group_add">
                   {t('collabs_per_proj', { collabcount: isStudent ? 6 : 10 })}
                   {t('collabs_per_proj', { collabcount: isStudent ? 6 : 10 })}

+ 4 - 1
services/web/frontend/js/utils/meta.ts

@@ -86,7 +86,10 @@ export interface Meta {
   'ol-cannot-reactivate-subscription': boolean
   'ol-cannot-reactivate-subscription': boolean
   'ol-cannot-use-ai': boolean
   'ol-cannot-use-ai': boolean
   'ol-chatEnabled': boolean
   'ol-chatEnabled': boolean
-
+  'ol-compileSettings': {
+    reducedTimeoutWarning: string
+    compileTimeout: number
+  }
   'ol-compilesUserContentDomain': string
   'ol-compilesUserContentDomain': string
   'ol-countryCode': PricingFormState['country']
   'ol-countryCode': PricingFormState['country']
   'ol-couponCode': PricingFormState['coupon']
   'ol-couponCode': PricingFormState['coupon']

+ 1 - 0
services/web/locales/da.json

@@ -2,6 +2,7 @@
   "12x_basic": "12x basis",
   "12x_basic": "12x basis",
   "1_2_width": "½ bredde",
   "1_2_width": "½ bredde",
   "1_4_width": "¼ bredde",
   "1_4_width": "¼ bredde",
+  "24x_basic": "24x basis",
   "3_4_width": "¾ bredde",
   "3_4_width": "¾ bredde",
   "About": "Om",
   "About": "Om",
   "Account": "Konto",
   "Account": "Konto",

+ 12 - 7
services/web/locales/en.json

@@ -1,10 +1,10 @@
 {
 {
   "0_free_suggestions": "0 free suggestions",
   "0_free_suggestions": "0 free suggestions",
-  "12x_basic": "12x Basic",
-  "12x_more_compile_time": "12x more compile time on our fastest servers",
   "1_2_width": "½ width",
   "1_2_width": "½ width",
   "1_4_width": "¼ width",
   "1_4_width": "¼ width",
   "1_free_suggestion": "1 free suggestion",
   "1_free_suggestion": "1 free suggestion",
+  "24x_basic": "24x Basic",
+  "24x_more_compile_time": "24x more compile time on our fastest servers",
   "3_4_width": "¾ width",
   "3_4_width": "¾ width",
   "About": "About",
   "About": "About",
   "Account": "Account",
   "Account": "Account",
@@ -163,6 +163,7 @@
   "an_error_occured_while_restoring_project": "An error occured while restoring the project",
   "an_error_occured_while_restoring_project": "An error occured while restoring the project",
   "an_error_occurred_when_verifying_the_coupon_code": "An error occurred when verifying the coupon code",
   "an_error_occurred_when_verifying_the_coupon_code": "An error occurred when verifying the coupon code",
   "and": "and",
   "and": "and",
+  "and_upgrade_for_compile_time": "And you can <strong>upgrade to get more compile time</strong>.",
   "annual": "Annual",
   "annual": "Annual",
   "annual_discount": "Annual discount",
   "annual_discount": "Annual discount",
   "anonymous": "Anonymous",
   "anonymous": "Anonymous",
@@ -1085,6 +1086,7 @@
   "integrations": "Integrations",
   "integrations": "Integrations",
   "integrations_like_github": "Integrations like GitHub Sync",
   "integrations_like_github": "Integrations like GitHub Sync",
   "interested_in_cheaper_personal_plan": "Would you be interested in the cheaper <0>__price__</0> Personal plan?",
   "interested_in_cheaper_personal_plan": "Would you be interested in the cheaper <0>__price__</0> Personal plan?",
+  "introducing_shorter_compile_timeout": "We’re <0>introducing a shorter compile timeout limit</0>. This project currently exceeds the new limit, so it might not compile from 16th July 2025.",
   "invalid_certificate": "Invalid certificate. Please check the certificate and try again.",
   "invalid_certificate": "Invalid certificate. Please check the certificate and try again.",
   "invalid_confirmation_code": "That didn’t work. Please check the code and try again.",
   "invalid_confirmation_code": "That didn’t work. Please check the code and try again.",
   "invalid_email": "An email address is invalid",
   "invalid_email": "An email address is invalid",
@@ -1524,7 +1526,6 @@
   "original_price": "Original price",
   "original_price": "Original price",
   "other": "Other",
   "other": "Other",
   "other_actions": "Other Actions",
   "other_actions": "Other Actions",
-  "other_causes_of_compile_timeouts": "Other causes of compile timeouts",
   "other_logs_and_files": "Other logs and files",
   "other_logs_and_files": "Other logs and files",
   "other_output_files": "Download other output files",
   "other_output_files": "Download other output files",
   "other_sessions": "Other Sessions",
   "other_sessions": "Other Sessions",
@@ -1756,6 +1757,8 @@
   "reactivate_subscription": "Reactivate your subscription",
   "reactivate_subscription": "Reactivate your subscription",
   "read_lines_from_path": "Read lines from __path__",
   "read_lines_from_path": "Read lines from __path__",
   "read_more": "Read more",
   "read_more": "Read more",
+  "read_more_about_compile_timeout_changes": "Read more about changes to compile timeout limits",
+  "read_more_about_fix_prevent_timeout": "Read more about how to fix and prevent timeouts",
   "read_more_about_free_compile_timeouts_servers": "Read more about changes to free compile timeouts and servers",
   "read_more_about_free_compile_timeouts_servers": "Read more about changes to free compile timeouts and servers",
   "read_only_dropbox_sync_message": "As a read-only viewer you can sync the current project version to Dropbox, but changes made in Dropbox will <0>not</0> sync back to Overleaf.",
   "read_only_dropbox_sync_message": "As a read-only viewer you can sync the current project version to Dropbox, but changes made in Dropbox will <0>not</0> sync back to Overleaf.",
   "read_only_token": "Read-Only Token",
   "read_only_token": "Read-Only Token",
@@ -2312,6 +2315,7 @@
   "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.",
   "this_is_your_template": "This is your template from your project",
   "this_is_your_template": "This is your template from your project",
   "this_project_already_has_maximum_collaborators": "This project already has the maximum number of collaborators permitted on the owner’s plan. This means you can view but not edit or review the project.",
   "this_project_already_has_maximum_collaborators": "This project already has the maximum number of collaborators permitted on the owner’s plan. This means you can view but not edit or review the project.",
+  "this_project_compiled_but_soon_might_not": "This project compiled, but soon it might not",
   "this_project_contains_a_file_called_output": "This project contains a file called output.pdf. If that file exists, please rename it and compile again.",
   "this_project_contains_a_file_called_output": "This project contains a file called output.pdf. If that file exists, please rename it and compile again.",
   "this_project_exceeded_collaborator_limit": "This project exceeded the collaborator limit for your plan. All other users now have view-only access.",
   "this_project_exceeded_collaborator_limit": "This project exceeded the collaborator limit for your plan. All other users now have view-only access.",
   "this_project_exceeded_compile_timeout_limit_on_free_plan": "This project exceeded the compile timeout limit on our free plan.",
   "this_project_exceeded_compile_timeout_limit_on_free_plan": "This project exceeded the compile timeout limit on our free plan.",
@@ -2507,7 +2511,7 @@
   "updating_site": "Updating Site",
   "updating_site": "Updating Site",
   "upgrade": "Upgrade",
   "upgrade": "Upgrade",
   "upgrade_cc_btn": "Upgrade now, pay after 7 days",
   "upgrade_cc_btn": "Upgrade now, pay after 7 days",
-  "upgrade_for_12x_more_compile_time": "Upgrade to get 12x more compile time",
+  "upgrade_for_more_compile_time": "Upgrade to get more compile time",
   "upgrade_my_plan": "Upgrade my plan",
   "upgrade_my_plan": "Upgrade my plan",
   "upgrade_now": "Upgrade now",
   "upgrade_now": "Upgrade now",
   "upgrade_plan": "Upgrade plan",
   "upgrade_plan": "Upgrade plan",
@@ -2515,7 +2519,7 @@
   "upgrade_to_add_more_collaborators_and_access_collaboration_features": "Upgrade to add more collaborators and access collaboration features like track changes and full project history.",
   "upgrade_to_add_more_collaborators_and_access_collaboration_features": "Upgrade to add more collaborators and access collaboration features like track changes and full project history.",
   "upgrade_to_get_feature": "Upgrade to get __feature__, plus:",
   "upgrade_to_get_feature": "Upgrade to get __feature__, plus:",
   "upgrade_to_review": "Upgrade to Review",
   "upgrade_to_review": "Upgrade to Review",
-  "upgrade_to_unlock_more_time": "Upgrade now to unlock 12x more compile time on our fastest servers.",
+  "upgrade_to_unlock_more_time": "Upgrade now to unlock 24x more compile time on our fastest servers.",
   "upgrade_your_subscription": "Upgrade your subscription",
   "upgrade_your_subscription": "Upgrade your subscription",
   "upload": "Upload",
   "upload": "Upload",
   "upload_failed": "Upload failed",
   "upload_failed": "Upload failed",
@@ -2617,8 +2621,7 @@
   "were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "We’re making some <0>changes to project sharing</0>. This means, as someone with edit access, your name and email address will be visible to the project owner and other editors.",
   "were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "We’re making some <0>changes to project sharing</0>. This means, as someone with edit access, your name and email address will be visible to the project owner and other editors.",
   "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.)",
-  "weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "We’ve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected this project.",
-  "weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "We’ve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected your project.",
+  "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.",
   "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": "What does this mean?",
   "what_does_this_mean": "What does this mean?",
@@ -2698,6 +2701,7 @@
   "you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "You have been removed from this project, and will no longer have access to it. You will be redirected to your project dashboard momentarily.",
   "you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "You have been removed from this project, and will no longer have access to it. You will be redirected to your project dashboard momentarily.",
   "you_have_x_licenses_and_your_plan_supports_up_to_y": "You have __addedUsersSize__ licenses and your plan supports up to __groupSize__.",
   "you_have_x_licenses_and_your_plan_supports_up_to_y": "You have __addedUsersSize__ licenses and your plan supports up to __groupSize__.",
   "you_have_x_licenses_on_your_subscription": "You have __groupSize__ licenses on your subscription.",
   "you_have_x_licenses_on_your_subscription": "You have __groupSize__ licenses on your subscription.",
+  "you_may_be_able_to_fix_issues_to_speed_up_the_compile": "You may be able to fix issues to <0>speed up the compile</0>.",
   "you_need_to_configure_your_sso_settings": "You need to configure and test your SSO settings before enabling SSO",
   "you_need_to_configure_your_sso_settings": "You need to configure and test your SSO settings before enabling SSO",
   "you_unpaused_your_subscription": "You unpaused your subscription.",
   "you_unpaused_your_subscription": "You unpaused your subscription.",
   "you_will_be_able_to_contact_us_any_time_to_share_your_feedback": "<0>You will be able to contact us</0> any time to share your feedback",
   "you_will_be_able_to_contact_us_any_time_to_share_your_feedback": "<0>You will be able to contact us</0> any time to share your feedback",
@@ -2735,6 +2739,7 @@
   "your_plan_is_limited_to_n_editors": "Your plan allows __count__ collaborator with edit access and unlimited viewers.",
   "your_plan_is_limited_to_n_editors": "Your plan allows __count__ collaborator with edit access and unlimited viewers.",
   "your_plan_is_limited_to_n_editors_plural": "Your plan allows __count__ collaborators with edit access and unlimited viewers.",
   "your_plan_is_limited_to_n_editors_plural": "Your plan allows __count__ collaborators with edit access and unlimited viewers.",
   "your_premium_plan_is_paused": "Your Premium plan is <0>paused</0>.",
   "your_premium_plan_is_paused": "Your Premium plan is <0>paused</0>.",
+  "your_project_compiled_but_soon_might_not": "Your project compiled, but soon it might not",
   "your_project_exceeded_collaborator_limit": "Your project exceeded the collaborator limit and access levels were changed. Select a new access level for your collaborators, or upgrade to add more editors or reviewers.",
   "your_project_exceeded_collaborator_limit": "Your project exceeded the collaborator limit and access levels were changed. Select a new access level for your collaborators, or upgrade to add more editors or reviewers.",
   "your_project_exceeded_compile_timeout_limit_on_free_plan": "Your project exceeded the compile timeout limit on our free plan.",
   "your_project_exceeded_compile_timeout_limit_on_free_plan": "Your project exceeded the compile timeout limit on our free plan.",
   "your_project_near_compile_timeout_limit": "Your project is near the compile timeout limit for our free plan.",
   "your_project_near_compile_timeout_limit": "Your project is near the compile timeout limit for our free plan.",

+ 2 - 0
services/web/locales/ru.json

@@ -3,6 +3,8 @@
   "12x_more_compile_time": "В 12 раз больше времени компиляции на наших быстрейших серверах",
   "12x_more_compile_time": "В 12 раз больше времени компиляции на наших быстрейших серверах",
   "1_2_width": "½ ширины",
   "1_2_width": "½ ширины",
   "1_4_width": "¼ ширины",
   "1_4_width": "¼ ширины",
+  "24x_basic": "24х базовый",
+  "24x_more_compile_time": "В 24 раз больше времени компиляции на наших быстрейших серверах",
   "3_4_width": "¾ ширины",
   "3_4_width": "¾ ширины",
   "About": "О сайте",
   "About": "О сайте",
   "Account": "Аккаунт",
   "Account": "Аккаунт",

+ 2 - 0
services/web/locales/zh-CN.json

@@ -3,6 +3,8 @@
   "12x_more_compile_time": "在更快的服务器上获取12倍的编译时间",
   "12x_more_compile_time": "在更快的服务器上获取12倍的编译时间",
   "1_2_width": "½ 宽度",
   "1_2_width": "½ 宽度",
   "1_4_width": "¼ 宽度",
   "1_4_width": "¼ 宽度",
+  "24x_basic": "24倍 免费时长 (240s)",
+  "24x_more_compile_time": "在更快的服务器上获取24倍的编译时间",
   "3_4_width": "¾ 宽度",
   "3_4_width": "¾ 宽度",
   "About": "关于",
   "About": "关于",
   "Account": "账户",
   "Account": "账户",

+ 4 - 0
services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx

@@ -8,6 +8,10 @@ describe('<PdfPreviewDetachedRoot/>', function () {
     window.metaAttributesCache.set('ol-detachRole', 'detached')
     window.metaAttributesCache.set('ol-detachRole', 'detached')
     window.metaAttributesCache.set('ol-projectName', 'Project Name')
     window.metaAttributesCache.set('ol-projectName', 'Project Name')
     window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
     window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
+    window.metaAttributesCache.set('ol-compileSettings', {
+      reducedWarningThreshold: 'default',
+      compileTimeout: 240,
+    })
 
 
     cy.interceptEvents()
     cy.interceptEvents()
   })
   })

+ 4 - 0
services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx

@@ -50,6 +50,10 @@ describe('<PdfPreview/>', function () {
       'populate-clsi-cache': 'enabled',
       'populate-clsi-cache': 'enabled',
     })
     })
     window.metaAttributesCache.set('ol-projectOwnerHasPremiumOnPageLoad', true)
     window.metaAttributesCache.set('ol-projectOwnerHasPremiumOnPageLoad', true)
+    window.metaAttributesCache.set('ol-compileSettings', {
+      reducedWarningThreshold: 'default',
+      compileTimeout: 240,
+    })
     cy.interceptEvents()
     cy.interceptEvents()
   })
   })