Преглед на файлове

Add review mode tutorial popover (#25709)

GitOrigin-RevId: bf2a365b21da780786f2736efb0770cea5f5b656
Alf Eaton преди 1 година
родител
ревизия
80897001a5

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

@@ -14,6 +14,7 @@ const VALID_KEYS = [
   'full-project-search-promo',
   'full-project-search-promo',
   'editor-popup-ux-survey',
   'editor-popup-ux-survey',
   'wf-features-moved',
   'wf-features-moved',
+  'review-mode',
 ]
 ]
 
 
 async function completeTutorial(req, res, next) {
 async function completeTutorial(req, res, next) {

+ 80 - 0
services/web/frontend/js/features/review-panel-new/components/review-mode-promo.tsx

@@ -0,0 +1,80 @@
+import { FC, RefObject, useCallback, useEffect } from 'react'
+import { Button, Overlay, Popover } from 'react-bootstrap'
+import Close from '@/shared/components/close'
+
+export const ReviewModePromo: FC<{
+  target: RefObject<HTMLSpanElement>
+  showPopup: boolean
+  tryShowingPopup: () => void
+  hideUntilReload: () => void
+  completeTutorial: (props: {
+    action: 'complete'
+    event: 'promo-click' | 'promo-dismiss'
+  }) => void
+}> = ({
+  showPopup,
+  tryShowingPopup,
+  hideUntilReload,
+  completeTutorial,
+  target,
+}) => {
+  useEffect(() => {
+    tryShowingPopup()
+  }, [tryShowingPopup])
+
+  const handleHide = useCallback(() => {
+    hideUntilReload()
+  }, [hideUntilReload])
+
+  const handleClose = useCallback(() => {
+    completeTutorial({
+      action: 'complete',
+      event: 'promo-dismiss',
+    })
+  }, [completeTutorial])
+
+  const handleAccept = useCallback(() => {
+    completeTutorial({
+      action: 'complete',
+      event: 'promo-click',
+    })
+  }, [completeTutorial])
+
+  if (!showPopup) {
+    return null
+  }
+
+  return (
+    <Overlay
+      target={target.current}
+      placement="bottom"
+      show
+      onHide={handleHide}
+    >
+      <Popover>
+        <Popover.Body style={{ width: 246 }}>
+          <Close variant="dark" onDismiss={handleClose} />
+          <p style={{ fontWeight: 'bold' }}>Track changes have moved</p>
+          <p>
+            Choose <b>Reviewing</b> mode in the dropdown to turn on track
+            changes.
+          </p>
+          <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
+            <Button
+              href="/learn/how-to/Reviewing_and_reviewers_on_Overleaf"
+              target="_blank"
+              size="sm"
+              variant="link"
+              style={{ color: 'inherit' }}
+            >
+              Learn more
+            </Button>
+            <Button onClick={handleAccept} size="sm" variant="secondary">
+              OK
+            </Button>
+          </div>
+        </Popover.Body>
+      </Popover>
+    </Overlay>
+  )
+}

+ 66 - 19
services/web/frontend/js/features/review-panel-new/components/review-mode-switcher.tsx

@@ -1,4 +1,4 @@
-import { forwardRef, memo, MouseEventHandler, useState } from 'react'
+import { forwardRef, memo, MouseEventHandler, useRef, useState } from 'react'
 import {
 import {
   Dropdown,
   Dropdown,
   DropdownMenu,
   DropdownMenu,
@@ -19,6 +19,9 @@ import { sendMB } from '@/infrastructure/event-tracking'
 import { useEditorContext } from '@/shared/context/editor-context'
 import { useEditorContext } from '@/shared/context/editor-context'
 import { useProjectContext } from '@/shared/context/project-context'
 import { useProjectContext } from '@/shared/context/project-context'
 import UpgradeTrackChangesModal from './upgrade-track-changes-modal'
 import UpgradeTrackChangesModal from './upgrade-track-changes-modal'
+import { ReviewModePromo } from '@/features/review-panel-new/components/review-mode-promo'
+import useTutorial from '@/shared/hooks/promotions/use-tutorial'
+import { useLayoutContext } from '@/shared/context/layout-context'
 
 
 type Mode = 'view' | 'review' | 'edit'
 type Mode = 'view' | 'review' | 'edit'
 
 
@@ -141,6 +144,7 @@ const ModeSwitcherToggleButton = forwardRef<
         iconType="edit"
         iconType="edit"
         label={t('editing')}
         label={t('editing')}
         ariaExpanded={ariaExpanded}
         ariaExpanded={ariaExpanded}
+        currentMode={mode}
       />
       />
     )
     )
   } else if (mode === 'review') {
   } else if (mode === 'review') {
@@ -152,6 +156,7 @@ const ModeSwitcherToggleButton = forwardRef<
         iconType="rate_review"
         iconType="rate_review"
         label={t('reviewing')}
         label={t('reviewing')}
         ariaExpanded={ariaExpanded}
         ariaExpanded={ariaExpanded}
+        currentMode={mode}
       />
       />
     )
     )
   }
   }
@@ -164,6 +169,7 @@ const ModeSwitcherToggleButton = forwardRef<
       iconType="visibility"
       iconType="visibility"
       label={t('viewing')}
       label={t('viewing')}
       ariaExpanded={ariaExpanded}
       ariaExpanded={ariaExpanded}
+      currentMode={mode}
     />
     />
   )
   )
 })
 })
@@ -176,31 +182,72 @@ const ModeSwitcherToggleButtonContent = forwardRef<
     iconType: string
     iconType: string
     label: string
     label: string
     ariaExpanded: boolean
     ariaExpanded: boolean
+    currentMode: string
   }
   }
->(({ onClick, className, iconType, label, ariaExpanded }, ref) => {
+>(({ onClick, className, iconType, label, ariaExpanded, currentMode }, ref) => {
   const [isFirstTimeUsed, setIsFirstTimeUsed] = usePersistedState(
   const [isFirstTimeUsed, setIsFirstTimeUsed] = usePersistedState(
     `modeSwitcherFirstTimeUsed`,
     `modeSwitcherFirstTimeUsed`,
     true
     true
   )
   )
 
 
+  const tutorialProps = useTutorial('review-mode', {
+    name: 'review-mode-notification',
+  })
+
+  const user = useUserContext()
+  const project = useProjectContext()
+  const { reviewPanelOpen } = useLayoutContext()
+  const { inactiveTutorials } = useEditorContext()
+
+  const hasCompletedReviewModeTutorial =
+    inactiveTutorials.includes('review-mode')
+
+  const canShowReviewModePromo =
+    reviewPanelOpen &&
+    currentMode !== 'review' &&
+    project.features.trackChanges &&
+    user.signUpDate &&
+    user.signUpDate < '2025-03-15' &&
+    !hasCompletedReviewModeTutorial
+
+  const containerRef = useRef<HTMLSpanElement | null>(null)
+
   return (
   return (
-    <button
-      className={classNames('review-mode-switcher-toggle-button', className, {
-        'review-mode-switcher-toggle-button-expanded': isFirstTimeUsed,
-      })}
-      ref={ref}
-      onClick={event => {
-        setIsFirstTimeUsed(false)
-        onClick(event)
-      }}
-      aria-expanded={ariaExpanded}
-    >
-      <MaterialIcon className="material-symbols-outlined" type={iconType} />
-      <div className="review-mode-switcher-toggle-label" aria-label={label}>
-        {label}
-      </div>
-      <MaterialIcon type="keyboard_arrow_down" />
-    </button>
+    <>
+      <span ref={containerRef}>
+        <button
+          className={classNames(
+            'review-mode-switcher-toggle-button',
+            className,
+            {
+              'review-mode-switcher-toggle-button-expanded': isFirstTimeUsed,
+            }
+          )}
+          ref={ref}
+          onClick={event => {
+            setIsFirstTimeUsed(false)
+            if (!hasCompletedReviewModeTutorial) {
+              tutorialProps.completeTutorial({
+                action: 'complete',
+                event: 'promo-click',
+              })
+            }
+            onClick(event)
+          }}
+          aria-expanded={ariaExpanded}
+        >
+          <MaterialIcon className="material-symbols-outlined" type={iconType} />
+          <div className="review-mode-switcher-toggle-label" aria-label={label}>
+            {label}
+          </div>
+          <MaterialIcon type="keyboard_arrow_down" />
+        </button>
+      </span>
+
+      {canShowReviewModePromo && (
+        <ReviewModePromo target={containerRef} {...tutorialProps} />
+      )}
+    </>
   )
   )
 })
 })