From 76fbb5610720af9e716c851082a57b2f88fa1bc8 Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Wed, 6 May 2026 19:44:45 +0100 Subject: [PATCH] [web] Delay suggest fix paywall until suggest button has been clicked (#33458) GitOrigin-RevId: 11d2ec0c9c33aea3fedff57d5f1a74d6ce774017 --- ...rror-assistant-ai-paywall-notification.tsx | 30 +++++ .../pdf-preview/components/error-logs.tsx | 4 +- ...assistant-ai-paywall-notification.spec.tsx | 103 ++++++++++++++++++ .../frontend/helpers/editor-providers.tsx | 16 ++- 4 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 services/web/frontend/js/features/pdf-preview/components/error-assistant-ai-paywall-notification.tsx create mode 100644 services/web/test/frontend/components/pdf-preview/error-assistant-ai-paywall-notification.spec.tsx diff --git a/services/web/frontend/js/features/pdf-preview/components/error-assistant-ai-paywall-notification.tsx b/services/web/frontend/js/features/pdf-preview/components/error-assistant-ai-paywall-notification.tsx new file mode 100644 index 0000000000..c4cf64de63 --- /dev/null +++ b/services/web/frontend/js/features/pdf-preview/components/error-assistant-ai-paywall-notification.tsx @@ -0,0 +1,30 @@ +import { useCallback, useEffect, useState } from 'react' +import AiPaywallNotification from '@/shared/components/ai-paywall-notification' +import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context' +import useEventListener from '@/shared/hooks/use-event-listener' + +export default function ErrorAssistantAiPaywallNotification() { + const { showLogs } = useCompileContext() + const [hasTriggered, setHasTriggered] = useState(false) + + useEventListener( + 'aiAssist:showPaywall', + useCallback((event: CustomEvent<{ origin?: string }>) => { + if (event.detail?.origin === 'suggest-fix') { + setHasTriggered(true) + } + }, []) + ) + + useEffect(() => { + if (!showLogs) { + setHasTriggered(false) + } + }, [showLogs]) + + if (!hasTriggered) { + return null + } + + return +} diff --git a/services/web/frontend/js/features/pdf-preview/components/error-logs.tsx b/services/web/frontend/js/features/pdf-preview/components/error-logs.tsx index 5910194db4..73f28b2f43 100644 --- a/services/web/frontend/js/features/pdf-preview/components/error-logs.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/error-logs.tsx @@ -16,7 +16,7 @@ import getMeta from '@/utils/meta' import PdfClearCacheButton from '@/features/pdf-preview/components/pdf-clear-cache-button' import PdfDownloadFilesButton from '@/features/pdf-preview/components/pdf-download-files-button' import RollingBuildSelectedReminder from './rolling-build-selected-reminder' -import AiPaywallNotification from '@/shared/components/ai-paywall-notification' +import ErrorAssistantAiPaywallNotification from './error-assistant-ai-paywall-notification' import importOverleafModules from '../../../../macros/import-overleaf-module.macro' // todo: quota clean-up remove unneeded old paywall component @@ -84,7 +84,7 @@ function ErrorLogs({ {logsComponents.map(({ import: { default: Component }, path }) => ( ))} - +
diff --git a/services/web/test/frontend/components/pdf-preview/error-assistant-ai-paywall-notification.spec.tsx b/services/web/test/frontend/components/pdf-preview/error-assistant-ai-paywall-notification.spec.tsx new file mode 100644 index 0000000000..6c472de7a4 --- /dev/null +++ b/services/web/test/frontend/components/pdf-preview/error-assistant-ai-paywall-notification.spec.tsx @@ -0,0 +1,103 @@ +import { FC, PropsWithChildren, useState } from 'react' +import { DetachCompileContext } from '@/shared/context/detach-compile-context' +import ErrorAssistantAiPaywallNotification from '@/features/pdf-preview/components/error-assistant-ai-paywall-notification' +import { + EditorProviders, + makeEditorProvider, +} from '../../helpers/editor-providers' + +const PAYWALL_TEXT = 'You’ve reached the fair usage limit on your plan' + +const futureDate = () => new Date(Date.now() + 60 * 60 * 1000) + +function dispatchSuggestFixPaywall() { + cy.window().then(win => { + win.dispatchEvent( + new CustomEvent('aiAssist:showPaywall', { + detail: { origin: 'suggest-fix' }, + }) + ) + }) +} + +function makeShowLogsCompileProvider(initialShowLogs: boolean) { + const Provider: FC = ({ children }) => { + const [showLogs, setShowLogs] = useState(initialShowLogs) + return ( + + + {children} + + ) + } + return Provider +} + +function mountSuggestFixPaywall(initialShowLogs = true) { + cy.window().then(win => { + win.metaAttributesCache.set('ol-showAiFeatures', true) + }) + + cy.mount( + + + + ) +} + +describe('', function () { + it('does not render the paywall before the suggest-fix paywall event fires', function () { + mountSuggestFixPaywall() + cy.contains(PAYWALL_TEXT).should('not.exist') + }) + + it('renders the paywall after the suggest-fix paywall event fires', function () { + mountSuggestFixPaywall() + dispatchSuggestFixPaywall() + cy.contains(PAYWALL_TEXT).should('be.visible') + }) + + it('ignores paywall events from other origins', function () { + mountSuggestFixPaywall() + cy.window().then(win => { + win.dispatchEvent( + new CustomEvent('aiAssist:showPaywall', { + detail: { origin: 'workbench' }, + }) + ) + }) + cy.contains(PAYWALL_TEXT).should('not.exist') + }) + + it('hides the paywall when the logs panel is closed', function () { + mountSuggestFixPaywall() + dispatchSuggestFixPaywall() + cy.contains(PAYWALL_TEXT).should('be.visible') + + cy.findByTestId('toggle-logs').click() + cy.contains(PAYWALL_TEXT).should('not.exist') + }) + + it('does not re-show the paywall when the logs panel is reopened', function () { + mountSuggestFixPaywall() + dispatchSuggestFixPaywall() + cy.findByTestId('toggle-logs').click() + cy.findByTestId('toggle-logs').click() + cy.contains(PAYWALL_TEXT).should('not.exist') + }) +}) diff --git a/services/web/test/frontend/helpers/editor-providers.tsx b/services/web/test/frontend/helpers/editor-providers.tsx index d81e4716e7..ce92da2684 100644 --- a/services/web/test/frontend/helpers/editor-providers.tsx +++ b/services/web/test/frontend/helpers/editor-providers.tsx @@ -277,23 +277,31 @@ export function makeEditorProvider({ cobranding = undefined, renameProject = () => {}, isRestrictedTokenMember, + hasSuggestionsLeft = false, + hasTokensLeft = false, + premiumSuggestionResetDate = new Date(), + tokenResetDate = new Date(), }: { isProjectOwner?: boolean cobranding?: Cobranding renameProject?: () => void isRestrictedTokenMember?: boolean + hasSuggestionsLeft?: boolean + hasTokensLeft?: boolean + premiumSuggestionResetDate?: Date + tokenResetDate?: Date } = {}) { const EditorProvider: FC = ({ children }) => { const value = { isProjectOwner, renameProject, isPendingEditor: false, - hasSuggestionsLeft: false, - premiumSuggestionResetDate: new Date(), - hasTokensLeft: false, + hasSuggestionsLeft, + premiumSuggestionResetDate, + hasTokensLeft, tokensLeft: 0, setTokensLeft: () => {}, - tokenResetDate: new Date(), + tokenResetDate, setTokenResetDate: () => {}, suggestionsLeft: 0, setSuggestionsLeft: () => {},