diff --git a/web/app/components/app/overview/settings/__tests__/index.spec.tsx b/web/app/components/app/overview/settings/__tests__/index.spec.tsx index eb849273598..909d69014cd 100644 --- a/web/app/components/app/overview/settings/__tests__/index.spec.tsx +++ b/web/app/components/app/overview/settings/__tests__/index.spec.tsx @@ -71,7 +71,6 @@ const buildModalContext = (): ModalContextState => ({ setShowExternalKnowledgeAPIModal: vi.fn(), setShowOpeningModal: vi.fn(), setShowUpdatePluginModal: vi.fn(), - setShowTriggerEventsLimitModal: vi.fn(), }) vi.mock('@/context/modal-context', () => ({ diff --git a/web/app/components/tools/edit-custom-collection-modal/__tests__/index.spec.tsx b/web/app/components/tools/edit-custom-collection-modal/__tests__/index.spec.tsx index 3ef9f9a29a6..812e3712a4a 100644 --- a/web/app/components/tools/edit-custom-collection-modal/__tests__/index.spec.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/__tests__/index.spec.tsx @@ -33,7 +33,6 @@ vi.mock('@/context/modal-context', () => ({ setShowExternalKnowledgeAPIModal: vi.fn(), setShowOpeningModal: vi.fn(), setShowUpdatePluginModal: vi.fn(), - setShowTriggerEventsLimitModal: vi.fn(), }), })) diff --git a/web/context/hooks/use-trigger-events-limit-modal.ts b/web/context/hooks/use-trigger-events-limit-modal.ts index bb8bb02790e..2cb12460036 100644 --- a/web/context/hooks/use-trigger-events-limit-modal.ts +++ b/web/context/hooks/use-trigger-events-limit-modal.ts @@ -1,5 +1,3 @@ -import type { Dispatch, SetStateAction } from 'react' -import type { ModalState } from '../modal-context' import { useSuspenseQuery } from '@tanstack/react-query' import dayjs from 'dayjs' import { useCallback, useEffect, useRef, useState } from 'react' @@ -8,12 +6,15 @@ import { Plan } from '@/app/components/billing/type' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { isServer } from '@/utils/client' -export type TriggerEventsLimitModalPayload = { +type TriggerEventsLimitModalContent = { usage: number total: number resetInDays?: number - storageKey?: string - persistDismiss?: boolean +} + +type TriggerEventsLimitModalState = TriggerEventsLimitModalContent & { + storageKey: string + persistDismiss: boolean } type TriggerPlanInfo = { @@ -30,11 +31,8 @@ type UseTriggerEventsLimitModalOptions = { } type UseTriggerEventsLimitModalResult = { - showTriggerEventsLimitModal: ModalState | null - setShowTriggerEventsLimitModal: Dispatch< - SetStateAction | null> - > - persistTriggerEventsLimitModalDismiss: () => void + triggerEventsLimitModal: TriggerEventsLimitModalContent | null + dismissTriggerEventsLimitModal: () => void } const TRIGGER_EVENTS_LOCALSTORAGE_PREFIX = 'trigger-events-limit-dismissed' @@ -48,8 +46,8 @@ export const useTriggerEventsLimitModal = ({ ...systemFeaturesQueryOptions(), select: ({ deployment_edition }) => deployment_edition, }) - const [showTriggerEventsLimitModal, setShowTriggerEventsLimitModal] = - useState | null>(null) + const [triggerEventsLimitModal, setTriggerEventsLimitModal] = + useState(null) const dismissedTriggerEventsLimitStorageKeysRef = useRef>({}) useEffect(() => { @@ -57,7 +55,7 @@ export const useTriggerEventsLimitModal = ({ if (isServer) return if (!currentWorkspaceId) return if (!isFetchedPlan) { - setShowTriggerEventsLimitModal(null) + setTriggerEventsLimitModal(null) return } @@ -66,7 +64,7 @@ export const useTriggerEventsLimitModal = ({ const reachedLimit = total.triggerEvents > 0 && usage.triggerEvents >= total.triggerEvents if (type === Plan.team || isUnlimited || !reachedLimit) { - if (showTriggerEventsLimitModal) setShowTriggerEventsLimitModal(null) + if (triggerEventsLimitModal) setTriggerEventsLimitModal(null) return } @@ -92,36 +90,36 @@ export const useTriggerEventsLimitModal = ({ } if (hasDismissed) return - if (showTriggerEventsLimitModal?.payload.storageKey === storageKey) return + if (triggerEventsLimitModal?.storageKey === storageKey) return - setShowTriggerEventsLimitModal({ - payload: { - usage: usage.triggerEvents, - total: total.triggerEvents, - resetInDays: triggerResetInDays, - storageKey, - persistDismiss, - }, + setTriggerEventsLimitModal({ + usage: usage.triggerEvents, + total: total.triggerEvents, + resetInDays: triggerResetInDays, + storageKey, + persistDismiss, }) - }, [plan, isFetchedPlan, showTriggerEventsLimitModal, currentWorkspaceId, deploymentEdition]) + }, [plan, isFetchedPlan, triggerEventsLimitModal, currentWorkspaceId, deploymentEdition]) - const persistTriggerEventsLimitModalDismiss = useCallback(() => { - const storageKey = showTriggerEventsLimitModal?.payload.storageKey - if (!storageKey) return - if (showTriggerEventsLimitModal?.payload.persistDismiss) { + const dismissTriggerEventsLimitModal = useCallback(() => { + if (!triggerEventsLimitModal) return + + const { storageKey, persistDismiss } = triggerEventsLimitModal + if (persistDismiss) { try { localStorage.setItem(storageKey, '1') + setTriggerEventsLimitModal(null) return } catch { // ignore error and fall back to in-memory guard } } dismissedTriggerEventsLimitStorageKeysRef.current[storageKey] = true - }, [showTriggerEventsLimitModal]) + setTriggerEventsLimitModal(null) + }, [triggerEventsLimitModal]) return { - showTriggerEventsLimitModal, - setShowTriggerEventsLimitModal, - persistTriggerEventsLimitModalDismiss, + triggerEventsLimitModal, + dismissTriggerEventsLimitModal, } } diff --git a/web/context/modal-context-provider.tsx b/web/context/modal-context-provider.tsx index c5b3b5fb483..4413694c6bb 100644 --- a/web/context/modal-context-provider.tsx +++ b/web/context/modal-context-provider.tsx @@ -93,11 +93,7 @@ export const ModalContextProvider = ({ children }: ModalContextProviderProps) => const [showAnnotationFullModal, setShowAnnotationFullModal] = useState(false) const { plan, isFetchedPlan } = useProviderContext() - const { - showTriggerEventsLimitModal, - setShowTriggerEventsLimitModal, - persistTriggerEventsLimitModalDismiss, - } = useTriggerEventsLimitModal({ + const { triggerEventsLimitModal, dismissTriggerEventsLimitModal } = useTriggerEventsLimitModal({ plan, isFetchedPlan, currentWorkspaceId, @@ -204,7 +200,7 @@ export const ModalContextProvider = ({ children }: ModalContextProviderProps) => showExternalKnowledgeAPIModal || showOpeningModal || showUpdatePluginModal || - showTriggerEventsLimitModal, + triggerEventsLimitModal, ) return ( @@ -219,7 +215,6 @@ export const ModalContextProvider = ({ children }: ModalContextProviderProps) => setShowExternalKnowledgeAPIModal, setShowOpeningModal, setShowUpdatePluginModal, - setShowTriggerEventsLimitModal, }} > <> @@ -298,19 +293,15 @@ export const ModalContextProvider = ({ children }: ModalContextProviderProps) => }} /> )} - {!!showTriggerEventsLimitModal && ( + {!!triggerEventsLimitModal && ( { - persistTriggerEventsLimitModalDismiss() - setShowTriggerEventsLimitModal(null) - }} + usage={triggerEventsLimitModal.usage} + total={triggerEventsLimitModal.total} + resetInDays={triggerEventsLimitModal.resetInDays} + onClose={dismissTriggerEventsLimitModal} onUpgrade={() => { - persistTriggerEventsLimitModalDismiss() - setShowTriggerEventsLimitModal(null) + dismissTriggerEventsLimitModal() handleShowPricingModal() }} /> diff --git a/web/context/modal-context.test.tsx b/web/context/modal-context.test.tsx index 1fab9346d7e..a5cfa87d35e 100644 --- a/web/context/modal-context.test.tsx +++ b/web/context/modal-context.test.tsx @@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event' import * as React from 'react' import { defaultPlan } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' +import { useModalContextSelector } from '@/context/modal-context' import { ModalContextProvider } from '@/context/modal-context-provider' import { createConsoleQueryWrapper } from '@/test/console/query-data' import { render } from '@/test/console/render' @@ -60,9 +61,13 @@ const createPlan = (overrides: PlanOverrides = {}): PlanShape => ({ }, }) -const renderProvider = ( - children: React.ReactNode =
, -) => { +const ModalBlockingState = () => { + const hasBlockingModalOpen = useModalContextSelector((state) => state.hasBlockingModalOpen) + + return {hasBlockingModalOpen ? 'blocked' : 'clear'} +} + +const renderProvider = (children: React.ReactNode = ) => { const { wrapper: QueryWrapper } = createConsoleQueryWrapper({ systemFeatures: { deployment_edition: 'CLOUD' }, }) @@ -112,10 +117,12 @@ describe('ModalContextProvider trigger events limit modal', () => { await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) expect(screen.getAllByText('3000')).toHaveLength(2) + expect(screen.getByText('blocked')).toBeInTheDocument() await user.click(screen.getByRole('button', { name: 'billing.triggerLimitModal.dismiss' })) await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()) + expect(screen.getByText('clear')).toBeInTheDocument() await waitFor(() => { expect(setItemSpy.mock.calls.length).toBeGreaterThan(0) }) @@ -141,13 +148,20 @@ describe('ModalContextProvider trigger events limit modal', () => { const setItemSpy = vi.spyOn(localStorage, 'setItem') const user = userEvent.setup() - renderProvider() + const { rerender } = renderProvider() await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) await user.click(screen.getByRole('button', { name: 'billing.triggerLimitModal.dismiss' })) await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()) + rerender( + + + , + ) + await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()) + expect(screen.getByText('clear')).toBeInTheDocument() expect(setItemSpy).not.toHaveBeenCalled() }) @@ -167,13 +181,20 @@ describe('ModalContextProvider trigger events limit modal', () => { }) const user = userEvent.setup() - renderProvider() + const { rerender } = renderProvider() await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) await user.click(screen.getByRole('button', { name: 'billing.triggerLimitModal.dismiss' })) await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()) + rerender( + + + , + ) + await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()) + expect(screen.getByText('clear')).toBeInTheDocument() }) it('closes the trigger events limit modal and opens pricing when upgrading', async () => { @@ -199,5 +220,6 @@ describe('ModalContextProvider trigger events limit modal', () => { expect(screen.getByText('billing.plansCommon.mostPopular')).toBeInTheDocument(), ) expect(screen.queryByText('400')).not.toBeInTheDocument() + expect(screen.getByText('blocked')).toBeInTheDocument() }) }) diff --git a/web/context/modal-context.ts b/web/context/modal-context.ts index 8800a8ebc8d..a99dfad1987 100644 --- a/web/context/modal-context.ts +++ b/web/context/modal-context.ts @@ -1,7 +1,6 @@ 'use client' import type { Dispatch, SetStateAction } from 'react' -import type { TriggerEventsLimitModalPayload } from './hooks/use-trigger-events-limit-modal' import type { OpeningStatement } from '@/app/components/base/features/types' import type { CreateExternalAPIReq } from '@/app/components/datasets/external-api/declarations' import type { @@ -60,9 +59,6 @@ export type ModalContextState = { > | null> > setShowUpdatePluginModal: Dispatch | null>> - setShowTriggerEventsLimitModal: Dispatch< - SetStateAction | null> - > } export const ModalContext = createContext({ @@ -75,7 +71,6 @@ export const ModalContext = createContext({ setShowExternalKnowledgeAPIModal: noop, setShowOpeningModal: noop, setShowUpdatePluginModal: noop, - setShowTriggerEventsLimitModal: noop, }) export const useModalContext = () => useContext(ModalContext)