Эх сурвалжийг харах

[web] Delay suggest fix paywall until suggest button has been clicked (#33458)

GitOrigin-RevId: 11d2ec0c9c33aea3fedff57d5f1a74d6ce774017
Mathias Jakobsen 3 сар өмнө
parent
commit
76fbb56107

+ 30 - 0
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 <AiPaywallNotification featureLocation="errorAssist" />
+}

+ 2 - 2
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 }) => (
         <Component key={path} />
       ))}
-      <AiPaywallNotification featureLocation="errorAssist" />
+      <ErrorAssistantAiPaywallNotification />
       <TabContent className="error-logs new-error-logs">
         <div className="logs-pane-content">
           <RollingBuildSelectedReminder />

+ 103 - 0
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<PropsWithChildren> = ({ children }) => {
+    const [showLogs, setShowLogs] = useState(initialShowLogs)
+    return (
+      <DetachCompileContext.Provider value={{ showLogs, setShowLogs } as any}>
+        <button
+          type="button"
+          data-testid="toggle-logs"
+          onClick={() => setShowLogs(prev => !prev)}
+        >
+          toggle logs
+        </button>
+        {children}
+      </DetachCompileContext.Provider>
+    )
+  }
+  return Provider
+}
+
+function mountSuggestFixPaywall(initialShowLogs = true) {
+  cy.window().then(win => {
+    win.metaAttributesCache.set('ol-showAiFeatures', true)
+  })
+
+  cy.mount(
+    <EditorProviders
+      features={{ aiErrorAssistant: true }}
+      providers={{
+        EditorProvider: makeEditorProvider({
+          hasSuggestionsLeft: false,
+          premiumSuggestionResetDate: futureDate(),
+        }),
+        DetachCompileProvider: makeShowLogsCompileProvider(initialShowLogs),
+      }}
+    >
+      <ErrorAssistantAiPaywallNotification />
+    </EditorProviders>
+  )
+}
+
+describe('<ErrorAssistantAiPaywallNotification />', 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')
+  })
+})

+ 12 - 4
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<PropsWithChildren> = ({ 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: () => {},