mirror of
https://github.com/langgenius/dify.git
synced 2026-02-09 23:20:12 -05:00
refactor(query-state): migrate query param state management to nuqs (#30184)
Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
|
||||
import * as React from 'react'
|
||||
import { defaultPlan } from '@/app/components/billing/config'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
@@ -72,9 +73,11 @@ const createPlan = (overrides: PlanOverrides = {}): PlanShape => ({
|
||||
})
|
||||
|
||||
const renderProvider = () => render(
|
||||
<ModalContextProvider>
|
||||
<div data-testid="modal-context-test-child" />
|
||||
</ModalContextProvider>,
|
||||
<NuqsTestingAdapter>
|
||||
<ModalContextProvider>
|
||||
<div data-testid="modal-context-test-child" />
|
||||
</ModalContextProvider>
|
||||
</NuqsTestingAdapter>,
|
||||
)
|
||||
|
||||
describe('ModalContextProvider trigger events limit modal', () => {
|
||||
|
||||
@@ -24,21 +24,22 @@ import type {
|
||||
import type { ModerationConfig, PromptVariable } from '@/models/debug'
|
||||
import { noop } from 'es-toolkit/compat'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { createContext, useContext, useContextSelector } from 'use-context-selector'
|
||||
import {
|
||||
ACCOUNT_SETTING_MODAL_ACTION,
|
||||
DEFAULT_ACCOUNT_SETTING_TAB,
|
||||
isValidAccountSettingTab,
|
||||
} from '@/app/components/header/account-setting/constants'
|
||||
import {
|
||||
EDUCATION_PRICING_SHOW_ACTION,
|
||||
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
|
||||
} from '@/app/education-apply/constants'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { removeSpecificQueryParam } from '@/utils'
|
||||
import {
|
||||
useAccountSettingModal,
|
||||
usePricingModal,
|
||||
} from '@/hooks/use-query-params'
|
||||
|
||||
import {
|
||||
|
||||
useTriggerEventsLimitModal,
|
||||
@@ -125,8 +126,6 @@ export type ModalContextState = {
|
||||
setShowEducationExpireNoticeModal: Dispatch<SetStateAction<ModalState<ExpireNoticeModalPayloadProps> | null>>
|
||||
setShowTriggerEventsLimitModal: Dispatch<SetStateAction<ModalState<TriggerEventsLimitModalPayload> | null>>
|
||||
}
|
||||
const PRICING_MODAL_QUERY_PARAM = 'pricing'
|
||||
const PRICING_MODAL_QUERY_VALUE = 'open'
|
||||
|
||||
const ModalContext = createContext<ModalContextState>({
|
||||
setShowAccountSettingModal: noop,
|
||||
@@ -157,16 +156,16 @@ type ModalContextProviderProps = {
|
||||
export const ModalContextProvider = ({
|
||||
children,
|
||||
}: ModalContextProviderProps) => {
|
||||
const searchParams = useSearchParams()
|
||||
// Use nuqs hooks for URL-based modal state management
|
||||
const [showPricingModal, setPricingModalOpen] = usePricingModal()
|
||||
const [urlAccountModalState, setUrlAccountModalState] = useAccountSettingModal<AccountSettingTab>()
|
||||
|
||||
const [showAccountSettingModal, setShowAccountSettingModal] = useState<ModalState<AccountSettingTab> | null>(() => {
|
||||
if (searchParams.get('action') === ACCOUNT_SETTING_MODAL_ACTION) {
|
||||
const tabParam = searchParams.get('tab')
|
||||
const tab = isValidAccountSettingTab(tabParam) ? tabParam : DEFAULT_ACCOUNT_SETTING_TAB
|
||||
return { payload: tab }
|
||||
}
|
||||
return null
|
||||
})
|
||||
const accountSettingCallbacksRef = useRef<Omit<ModalState<AccountSettingTab>, 'payload'> | null>(null)
|
||||
const accountSettingTab = urlAccountModalState.isOpen
|
||||
? (isValidAccountSettingTab(urlAccountModalState.payload)
|
||||
? urlAccountModalState.payload
|
||||
: DEFAULT_ACCOUNT_SETTING_TAB)
|
||||
: null
|
||||
const [showApiBasedExtensionModal, setShowApiBasedExtensionModal] = useState<ModalState<ApiBasedExtension> | null>(null)
|
||||
const [showModerationSettingModal, setShowModerationSettingModal] = useState<ModalState<ModerationConfig> | null>(null)
|
||||
const [showExternalDataToolModal, setShowExternalDataToolModal] = useState<ModalState<ExternalDataTool> | null>(null)
|
||||
@@ -182,9 +181,6 @@ export const ModalContextProvider = ({
|
||||
const [showEducationExpireNoticeModal, setShowEducationExpireNoticeModal] = useState<ModalState<ExpireNoticeModalPayloadProps> | null>(null)
|
||||
const { currentWorkspace } = useAppContext()
|
||||
|
||||
const [showPricingModal, setShowPricingModal] = useState(
|
||||
searchParams.get(PRICING_MODAL_QUERY_PARAM) === PRICING_MODAL_QUERY_VALUE,
|
||||
)
|
||||
const [showAnnotationFullModal, setShowAnnotationFullModal] = useState(false)
|
||||
const handleCancelAccountSettingModal = () => {
|
||||
const educationVerifying = localStorage.getItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM)
|
||||
@@ -192,54 +188,34 @@ export const ModalContextProvider = ({
|
||||
if (educationVerifying === 'yes')
|
||||
localStorage.removeItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM)
|
||||
|
||||
removeSpecificQueryParam('action')
|
||||
removeSpecificQueryParam('tab')
|
||||
setShowAccountSettingModal(null)
|
||||
if (showAccountSettingModal?.onCancelCallback)
|
||||
showAccountSettingModal?.onCancelCallback()
|
||||
accountSettingCallbacksRef.current?.onCancelCallback?.()
|
||||
accountSettingCallbacksRef.current = null
|
||||
setUrlAccountModalState(null)
|
||||
}
|
||||
|
||||
const handleAccountSettingTabChange = useCallback((tab: AccountSettingTab) => {
|
||||
setShowAccountSettingModal((prev) => {
|
||||
if (!prev)
|
||||
return { payload: tab }
|
||||
if (prev.payload === tab)
|
||||
return prev
|
||||
return { ...prev, payload: tab }
|
||||
})
|
||||
}, [setShowAccountSettingModal])
|
||||
setUrlAccountModalState({ payload: tab })
|
||||
}, [setUrlAccountModalState])
|
||||
|
||||
const setShowAccountSettingModal = useCallback((next: SetStateAction<ModalState<AccountSettingTab> | null>) => {
|
||||
const currentState = accountSettingTab
|
||||
? { payload: accountSettingTab, ...(accountSettingCallbacksRef.current ?? {}) }
|
||||
: null
|
||||
const resolvedState = typeof next === 'function' ? next(currentState) : next
|
||||
if (!resolvedState) {
|
||||
accountSettingCallbacksRef.current = null
|
||||
setUrlAccountModalState(null)
|
||||
return
|
||||
}
|
||||
const { payload, ...callbacks } = resolvedState
|
||||
accountSettingCallbacksRef.current = callbacks
|
||||
setUrlAccountModalState({ payload })
|
||||
}, [accountSettingTab, setUrlAccountModalState])
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined')
|
||||
return
|
||||
const url = new URL(window.location.href)
|
||||
if (!showAccountSettingModal?.payload) {
|
||||
if (url.searchParams.get('action') !== ACCOUNT_SETTING_MODAL_ACTION)
|
||||
return
|
||||
url.searchParams.delete('action')
|
||||
url.searchParams.delete('tab')
|
||||
window.history.replaceState(null, '', url.toString())
|
||||
return
|
||||
}
|
||||
url.searchParams.set('action', ACCOUNT_SETTING_MODAL_ACTION)
|
||||
url.searchParams.set('tab', showAccountSettingModal.payload)
|
||||
window.history.replaceState(null, '', url.toString())
|
||||
}, [showAccountSettingModal])
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined')
|
||||
return
|
||||
const url = new URL(window.location.href)
|
||||
if (showPricingModal) {
|
||||
url.searchParams.set(PRICING_MODAL_QUERY_PARAM, PRICING_MODAL_QUERY_VALUE)
|
||||
}
|
||||
else {
|
||||
url.searchParams.delete(PRICING_MODAL_QUERY_PARAM)
|
||||
if (url.searchParams.get('action') === EDUCATION_PRICING_SHOW_ACTION)
|
||||
url.searchParams.delete('action')
|
||||
}
|
||||
window.history.replaceState(null, '', url.toString())
|
||||
}, [showPricingModal])
|
||||
if (!urlAccountModalState.isOpen)
|
||||
accountSettingCallbacksRef.current = null
|
||||
}, [urlAccountModalState.isOpen])
|
||||
|
||||
const { plan, isFetchedPlan } = useProviderContext()
|
||||
const {
|
||||
@@ -337,12 +313,12 @@ export const ModalContextProvider = ({
|
||||
}
|
||||
|
||||
const handleShowPricingModal = useCallback(() => {
|
||||
setShowPricingModal(true)
|
||||
}, [])
|
||||
setPricingModalOpen(true)
|
||||
}, [setPricingModalOpen])
|
||||
|
||||
const handleCancelPricingModal = useCallback(() => {
|
||||
setShowPricingModal(false)
|
||||
}, [])
|
||||
setPricingModalOpen(false)
|
||||
}, [setPricingModalOpen])
|
||||
|
||||
return (
|
||||
<ModalContext.Provider value={{
|
||||
@@ -364,9 +340,9 @@ export const ModalContextProvider = ({
|
||||
<>
|
||||
{children}
|
||||
{
|
||||
!!showAccountSettingModal && (
|
||||
accountSettingTab && (
|
||||
<AccountSetting
|
||||
activeTab={showAccountSettingModal.payload}
|
||||
activeTab={accountSettingTab}
|
||||
onCancel={handleCancelAccountSettingModal}
|
||||
onTabChange={handleAccountSettingTabChange}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user