mirror of
https://github.com/langgenius/dify.git
synced 2026-01-14 06:07:33 +08:00
refactor(web): setup status caching (#30798)
This commit is contained in:
parent
491e1fd6a4
commit
1fbdf6b465
@ -53,6 +53,7 @@ vi.mock('@/context/global-public-context', () => {
|
|||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
useGlobalPublicStore,
|
useGlobalPublicStore,
|
||||||
|
useIsSystemFeaturesPending: () => false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import {
|
|||||||
EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION,
|
EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION,
|
||||||
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
|
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
|
||||||
} from '@/app/education-apply/constants'
|
} from '@/app/education-apply/constants'
|
||||||
import { fetchSetupStatus } from '@/service/common'
|
|
||||||
import { sendGAEvent } from '@/utils/gtag'
|
import { sendGAEvent } from '@/utils/gtag'
|
||||||
|
import { fetchSetupStatusWithCache } from '@/utils/setup-status'
|
||||||
import { resolvePostLoginRedirect } from '../signin/utils/post-login-redirect'
|
import { resolvePostLoginRedirect } from '../signin/utils/post-login-redirect'
|
||||||
import { trackEvent } from './base/amplitude'
|
import { trackEvent } from './base/amplitude'
|
||||||
|
|
||||||
@ -33,15 +33,8 @@ export const AppInitializer = ({
|
|||||||
|
|
||||||
const isSetupFinished = useCallback(async () => {
|
const isSetupFinished = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
if (localStorage.getItem('setup_status') === 'finished')
|
const setUpStatus = await fetchSetupStatusWithCache()
|
||||||
return true
|
return setUpStatus.step === 'finished'
|
||||||
const setUpStatus = await fetchSetupStatus()
|
|
||||||
if (setUpStatus.step !== 'finished') {
|
|
||||||
localStorage.removeItem('setup_status')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
localStorage.setItem('setup_status', 'finished')
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
|||||||
@ -125,7 +125,6 @@ const resetAccessControlStore = () => {
|
|||||||
const resetGlobalStore = () => {
|
const resetGlobalStore = () => {
|
||||||
useGlobalPublicStore.setState({
|
useGlobalPublicStore.setState({
|
||||||
systemFeatures: defaultSystemFeatures,
|
systemFeatures: defaultSystemFeatures,
|
||||||
isGlobalPending: false,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,14 @@ vi.mock('@/service/common', () => ({
|
|||||||
getSystemFeatures: vi.fn(),
|
getSystemFeatures: vi.fn(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/global-public-context', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('@/context/global-public-context')>()
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useIsSystemFeaturesPending: () => false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const mockFetchSetupStatus = vi.mocked(fetchSetupStatus)
|
const mockFetchSetupStatus = vi.mocked(fetchSetupStatus)
|
||||||
const mockFetchInitValidateStatus = vi.mocked(fetchInitValidateStatus)
|
const mockFetchInitValidateStatus = vi.mocked(fetchInitValidateStatus)
|
||||||
const mockSetup = vi.mocked(setup)
|
const mockSetup = vi.mocked(setup)
|
||||||
|
|||||||
@ -2,42 +2,61 @@
|
|||||||
import type { FC, PropsWithChildren } from 'react'
|
import type { FC, PropsWithChildren } from 'react'
|
||||||
import type { SystemFeatures } from '@/types/feature'
|
import type { SystemFeatures } from '@/types/feature'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { useEffect } from 'react'
|
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { getSystemFeatures } from '@/service/common'
|
import { getSystemFeatures } from '@/service/common'
|
||||||
import { defaultSystemFeatures } from '@/types/feature'
|
import { defaultSystemFeatures } from '@/types/feature'
|
||||||
|
import { fetchSetupStatusWithCache } from '@/utils/setup-status'
|
||||||
|
|
||||||
type GlobalPublicStore = {
|
type GlobalPublicStore = {
|
||||||
isGlobalPending: boolean
|
|
||||||
setIsGlobalPending: (isPending: boolean) => void
|
|
||||||
systemFeatures: SystemFeatures
|
systemFeatures: SystemFeatures
|
||||||
setSystemFeatures: (systemFeatures: SystemFeatures) => void
|
setSystemFeatures: (systemFeatures: SystemFeatures) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGlobalPublicStore = create<GlobalPublicStore>(set => ({
|
export const useGlobalPublicStore = create<GlobalPublicStore>(set => ({
|
||||||
isGlobalPending: true,
|
|
||||||
setIsGlobalPending: (isPending: boolean) => set(() => ({ isGlobalPending: isPending })),
|
|
||||||
systemFeatures: defaultSystemFeatures,
|
systemFeatures: defaultSystemFeatures,
|
||||||
setSystemFeatures: (systemFeatures: SystemFeatures) => set(() => ({ systemFeatures })),
|
setSystemFeatures: (systemFeatures: SystemFeatures) => set(() => ({ systemFeatures })),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const systemFeaturesQueryKey = ['systemFeatures'] as const
|
||||||
|
const setupStatusQueryKey = ['setupStatus'] as const
|
||||||
|
|
||||||
|
async function fetchSystemFeatures() {
|
||||||
|
const data = await getSystemFeatures()
|
||||||
|
const { setSystemFeatures } = useGlobalPublicStore.getState()
|
||||||
|
setSystemFeatures({ ...defaultSystemFeatures, ...data })
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSystemFeaturesQuery() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: systemFeaturesQueryKey,
|
||||||
|
queryFn: fetchSystemFeatures,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIsSystemFeaturesPending() {
|
||||||
|
const { isPending } = useSystemFeaturesQuery()
|
||||||
|
return isPending
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSetupStatusQuery() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: setupStatusQueryKey,
|
||||||
|
queryFn: fetchSetupStatusWithCache,
|
||||||
|
staleTime: Infinity,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const GlobalPublicStoreProvider: FC<PropsWithChildren> = ({
|
const GlobalPublicStoreProvider: FC<PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { isPending, data } = useQuery({
|
// Fetch systemFeatures and setupStatus in parallel to reduce waterfall.
|
||||||
queryKey: ['systemFeatures'],
|
// setupStatus is prefetched here and cached in localStorage for AppInitializer.
|
||||||
queryFn: getSystemFeatures,
|
const { isPending } = useSystemFeaturesQuery()
|
||||||
})
|
|
||||||
const { setSystemFeatures, setIsGlobalPending: setIsPending } = useGlobalPublicStore()
|
|
||||||
useEffect(() => {
|
|
||||||
if (data)
|
|
||||||
setSystemFeatures({ ...defaultSystemFeatures, ...data })
|
|
||||||
}, [data, setSystemFeatures])
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Prefetch setupStatus for AppInitializer (result not needed here)
|
||||||
setIsPending(isPending)
|
useSetupStatusQuery()
|
||||||
}, [isPending, setIsPending])
|
|
||||||
|
|
||||||
if (isPending)
|
if (isPending)
|
||||||
return <div className="flex h-screen w-screen items-center justify-center"><Loading /></div>
|
return <div className="flex h-screen w-screen items-center justify-center"><Loading /></div>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { getProcessedSystemVariablesFromUrlParams } from '@/app/components/base/
|
|||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { AccessMode } from '@/models/access-control'
|
import { AccessMode } from '@/models/access-control'
|
||||||
import { useGetWebAppAccessModeByCode } from '@/service/use-share'
|
import { useGetWebAppAccessModeByCode } from '@/service/use-share'
|
||||||
import { useGlobalPublicStore } from './global-public-context'
|
import { useIsSystemFeaturesPending } from './global-public-context'
|
||||||
|
|
||||||
type WebAppStore = {
|
type WebAppStore = {
|
||||||
shareCode: string | null
|
shareCode: string | null
|
||||||
@ -65,7 +65,7 @@ const getShareCodeFromPathname = (pathname: string): string | null => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const WebAppStoreProvider: FC<PropsWithChildren> = ({ children }) => {
|
const WebAppStoreProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||||
const isGlobalPending = useGlobalPublicStore(s => s.isGlobalPending)
|
const isGlobalPending = useIsSystemFeaturesPending()
|
||||||
const updateWebAppAccessMode = useWebAppStore(state => state.updateWebAppAccessMode)
|
const updateWebAppAccessMode = useWebAppStore(state => state.updateWebAppAccessMode)
|
||||||
const updateShareCode = useWebAppStore(state => state.updateShareCode)
|
const updateShareCode = useWebAppStore(state => state.updateShareCode)
|
||||||
const updateEmbeddedUserId = useWebAppStore(state => state.updateEmbeddedUserId)
|
const updateEmbeddedUserId = useWebAppStore(state => state.updateEmbeddedUserId)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { act, renderHook } from '@testing-library/react'
|
import { act, renderHook } from '@testing-library/react'
|
||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore, useIsSystemFeaturesPending } from '@/context/global-public-context'
|
||||||
/**
|
/**
|
||||||
* Test suite for useDocumentTitle hook
|
* Test suite for useDocumentTitle hook
|
||||||
*
|
*
|
||||||
@ -15,6 +15,14 @@ import { useGlobalPublicStore } from '@/context/global-public-context'
|
|||||||
import { defaultSystemFeatures } from '@/types/feature'
|
import { defaultSystemFeatures } from '@/types/feature'
|
||||||
import useDocumentTitle from './use-document-title'
|
import useDocumentTitle from './use-document-title'
|
||||||
|
|
||||||
|
vi.mock('@/context/global-public-context', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('@/context/global-public-context')>()
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useIsSystemFeaturesPending: vi.fn(() => false),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/service/common', () => ({
|
vi.mock('@/service/common', () => ({
|
||||||
getSystemFeatures: vi.fn(() => ({ ...defaultSystemFeatures })),
|
getSystemFeatures: vi.fn(() => ({ ...defaultSystemFeatures })),
|
||||||
}))
|
}))
|
||||||
@ -24,10 +32,12 @@ vi.mock('@/service/common', () => ({
|
|||||||
* Title should remain empty to prevent flicker
|
* Title should remain empty to prevent flicker
|
||||||
*/
|
*/
|
||||||
describe('title should be empty if systemFeatures is pending', () => {
|
describe('title should be empty if systemFeatures is pending', () => {
|
||||||
act(() => {
|
beforeEach(() => {
|
||||||
useGlobalPublicStore.setState({
|
vi.mocked(useIsSystemFeaturesPending).mockReturnValue(true)
|
||||||
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } },
|
act(() => {
|
||||||
isGlobalPending: true,
|
useGlobalPublicStore.setState({
|
||||||
|
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } },
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
@ -52,9 +62,9 @@ describe('title should be empty if systemFeatures is pending', () => {
|
|||||||
*/
|
*/
|
||||||
describe('use default branding', () => {
|
describe('use default branding', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
vi.mocked(useIsSystemFeaturesPending).mockReturnValue(false)
|
||||||
act(() => {
|
act(() => {
|
||||||
useGlobalPublicStore.setState({
|
useGlobalPublicStore.setState({
|
||||||
isGlobalPending: false,
|
|
||||||
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } },
|
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -84,9 +94,9 @@ describe('use default branding', () => {
|
|||||||
*/
|
*/
|
||||||
describe('use specific branding', () => {
|
describe('use specific branding', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
vi.mocked(useIsSystemFeaturesPending).mockReturnValue(false)
|
||||||
act(() => {
|
act(() => {
|
||||||
useGlobalPublicStore.setState({
|
useGlobalPublicStore.setState({
|
||||||
isGlobalPending: false,
|
|
||||||
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: true, application_title: 'Test' } },
|
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: true, application_title: 'Test' } },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useFavicon, useTitle } from 'ahooks'
|
import { useFavicon, useTitle } from 'ahooks'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore, useIsSystemFeaturesPending } from '@/context/global-public-context'
|
||||||
import { basePath } from '@/utils/var'
|
import { basePath } from '@/utils/var'
|
||||||
|
|
||||||
export default function useDocumentTitle(title: string) {
|
export default function useDocumentTitle(title: string) {
|
||||||
const isPending = useGlobalPublicStore(s => s.isGlobalPending)
|
const isPending = useIsSystemFeaturesPending()
|
||||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||||
const prefix = title ? `${title} - ` : ''
|
const prefix = title ? `${title} - ` : ''
|
||||||
let titleStr = ''
|
let titleStr = ''
|
||||||
|
|||||||
139
web/utils/setup-status.spec.ts
Normal file
139
web/utils/setup-status.spec.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import type { SetupStatusResponse } from '@/models/common'
|
||||||
|
|
||||||
|
import { fetchSetupStatus } from '@/service/common'
|
||||||
|
|
||||||
|
import { fetchSetupStatusWithCache } from './setup-status'
|
||||||
|
|
||||||
|
vi.mock('@/service/common', () => ({
|
||||||
|
fetchSetupStatus: vi.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockFetchSetupStatus = vi.mocked(fetchSetupStatus)
|
||||||
|
|
||||||
|
describe('setup-status utilities', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
localStorage.clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('fetchSetupStatusWithCache', () => {
|
||||||
|
describe('when cache exists', () => {
|
||||||
|
it('should return cached finished status without API call', async () => {
|
||||||
|
localStorage.setItem('setup_status', 'finished')
|
||||||
|
|
||||||
|
const result = await fetchSetupStatusWithCache()
|
||||||
|
|
||||||
|
expect(result).toEqual({ step: 'finished' })
|
||||||
|
expect(mockFetchSetupStatus).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not modify localStorage when returning cached value', async () => {
|
||||||
|
localStorage.setItem('setup_status', 'finished')
|
||||||
|
|
||||||
|
await fetchSetupStatusWithCache()
|
||||||
|
|
||||||
|
expect(localStorage.getItem('setup_status')).toBe('finished')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when cache does not exist', () => {
|
||||||
|
it('should call API and cache finished status', async () => {
|
||||||
|
const apiResponse: SetupStatusResponse = { step: 'finished' }
|
||||||
|
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||||
|
|
||||||
|
const result = await fetchSetupStatusWithCache()
|
||||||
|
|
||||||
|
expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1)
|
||||||
|
expect(result).toEqual(apiResponse)
|
||||||
|
expect(localStorage.getItem('setup_status')).toBe('finished')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call API and remove cache when not finished', async () => {
|
||||||
|
const apiResponse: SetupStatusResponse = { step: 'not_started' }
|
||||||
|
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||||
|
|
||||||
|
const result = await fetchSetupStatusWithCache()
|
||||||
|
|
||||||
|
expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1)
|
||||||
|
expect(result).toEqual(apiResponse)
|
||||||
|
expect(localStorage.getItem('setup_status')).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear stale cache when API returns not_started', async () => {
|
||||||
|
localStorage.setItem('setup_status', 'some_invalid_value')
|
||||||
|
const apiResponse: SetupStatusResponse = { step: 'not_started' }
|
||||||
|
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||||
|
|
||||||
|
const result = await fetchSetupStatusWithCache()
|
||||||
|
|
||||||
|
expect(result).toEqual(apiResponse)
|
||||||
|
expect(localStorage.getItem('setup_status')).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('cache edge cases', () => {
|
||||||
|
it('should call API when cache value is empty string', async () => {
|
||||||
|
localStorage.setItem('setup_status', '')
|
||||||
|
const apiResponse: SetupStatusResponse = { step: 'finished' }
|
||||||
|
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||||
|
|
||||||
|
const result = await fetchSetupStatusWithCache()
|
||||||
|
|
||||||
|
expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1)
|
||||||
|
expect(result).toEqual(apiResponse)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call API when cache value is not "finished"', async () => {
|
||||||
|
localStorage.setItem('setup_status', 'not_started')
|
||||||
|
const apiResponse: SetupStatusResponse = { step: 'finished' }
|
||||||
|
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||||
|
|
||||||
|
const result = await fetchSetupStatusWithCache()
|
||||||
|
|
||||||
|
expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1)
|
||||||
|
expect(result).toEqual(apiResponse)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call API when localStorage key does not exist', async () => {
|
||||||
|
const apiResponse: SetupStatusResponse = { step: 'finished' }
|
||||||
|
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||||
|
|
||||||
|
const result = await fetchSetupStatusWithCache()
|
||||||
|
|
||||||
|
expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1)
|
||||||
|
expect(result).toEqual(apiResponse)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('API response handling', () => {
|
||||||
|
it('should preserve setup_at from API response', async () => {
|
||||||
|
const setupDate = new Date('2024-01-01')
|
||||||
|
const apiResponse: SetupStatusResponse = {
|
||||||
|
step: 'finished',
|
||||||
|
setup_at: setupDate,
|
||||||
|
}
|
||||||
|
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||||
|
|
||||||
|
const result = await fetchSetupStatusWithCache()
|
||||||
|
|
||||||
|
expect(result).toEqual(apiResponse)
|
||||||
|
expect(result.setup_at).toEqual(setupDate)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should propagate API errors', async () => {
|
||||||
|
const apiError = new Error('Network error')
|
||||||
|
mockFetchSetupStatus.mockRejectedValue(apiError)
|
||||||
|
|
||||||
|
await expect(fetchSetupStatusWithCache()).rejects.toThrow('Network error')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not update cache when API call fails', async () => {
|
||||||
|
mockFetchSetupStatus.mockRejectedValue(new Error('API error'))
|
||||||
|
|
||||||
|
await expect(fetchSetupStatusWithCache()).rejects.toThrow()
|
||||||
|
|
||||||
|
expect(localStorage.getItem('setup_status')).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
21
web/utils/setup-status.ts
Normal file
21
web/utils/setup-status.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { SetupStatusResponse } from '@/models/common'
|
||||||
|
import { fetchSetupStatus } from '@/service/common'
|
||||||
|
|
||||||
|
const SETUP_STATUS_KEY = 'setup_status'
|
||||||
|
|
||||||
|
const isSetupStatusCached = (): boolean =>
|
||||||
|
localStorage.getItem(SETUP_STATUS_KEY) === 'finished'
|
||||||
|
|
||||||
|
export const fetchSetupStatusWithCache = async (): Promise<SetupStatusResponse> => {
|
||||||
|
if (isSetupStatusCached())
|
||||||
|
return { step: 'finished' }
|
||||||
|
|
||||||
|
const status = await fetchSetupStatus()
|
||||||
|
|
||||||
|
if (status.step === 'finished')
|
||||||
|
localStorage.setItem(SETUP_STATUS_KEY, 'finished')
|
||||||
|
else
|
||||||
|
localStorage.removeItem(SETUP_STATUS_KEY)
|
||||||
|
|
||||||
|
return status
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user