Add review mode tutorial popover (#25709)

GitOrigin-RevId: bf2a365b21da780786f2736efb0770cea5f5b656
This commit is contained in:
Alf Eaton
2025-05-21 09:52:44 +01:00
committed by Copybot
parent 8a1cdab27e
commit 80897001a5
3 changed files with 147 additions and 19 deletions

View File

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

View File

@@ -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>
)
}

View File

@@ -1,4 +1,4 @@
import { forwardRef, memo, MouseEventHandler, useState } from 'react'
import { forwardRef, memo, MouseEventHandler, useRef, useState } from 'react'
import {
Dropdown,
DropdownMenu,
@@ -19,6 +19,9 @@ import { sendMB } from '@/infrastructure/event-tracking'
import { useEditorContext } from '@/shared/context/editor-context'
import { useProjectContext } from '@/shared/context/project-context'
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'
@@ -141,6 +144,7 @@ const ModeSwitcherToggleButton = forwardRef<
iconType="edit"
label={t('editing')}
ariaExpanded={ariaExpanded}
currentMode={mode}
/>
)
} else if (mode === 'review') {
@@ -152,6 +156,7 @@ const ModeSwitcherToggleButton = forwardRef<
iconType="rate_review"
label={t('reviewing')}
ariaExpanded={ariaExpanded}
currentMode={mode}
/>
)
}
@@ -164,6 +169,7 @@ const ModeSwitcherToggleButton = forwardRef<
iconType="visibility"
label={t('viewing')}
ariaExpanded={ariaExpanded}
currentMode={mode}
/>
)
})
@@ -176,31 +182,72 @@ const ModeSwitcherToggleButtonContent = forwardRef<
iconType: string
label: string
ariaExpanded: boolean
currentMode: string
}
>(({ onClick, className, iconType, label, ariaExpanded }, ref) => {
>(({ onClick, className, iconType, label, ariaExpanded, currentMode }, ref) => {
const [isFirstTimeUsed, setIsFirstTimeUsed] = usePersistedState(
`modeSwitcherFirstTimeUsed`,
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 (
<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} />
)}
</>
)
})