import { OnboardingModes } from './enums'; import { onboardingModesSchema, onboardingTopicSchema } from './schemas'; import { useLocalStorage } from '@/hooks'; import _ from 'lodash'; import { useDataProvider, useGetIdentity } from 'ra-core'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import * as yup from 'yup'; type IOnboardingDataProvider = { onboardingRequired: boolean; acknowledgeOnboarding: () => Promise; }; function useUserProfileDataProvider( topic: string ): (IOnboardingDataProvider | object) & { loading: boolean; error: boolean } { const queryClient = useQueryClient(); const [onboardingRequired, setOnboardingRequired] = useState(false); const [acknowledgeOnboarding, setAcknowledgeOnboarding] = useState(() => () => Promise.resolve()); const identity = useGetIdentity(); const user = identity?.data?.email; const dataProvider = useDataProvider(); const fetch = useCallback(() => { if (!user) { return Promise.reject(); } else { return dataProvider.get('onboarding').then((response: any) => { return response?.data?.value; }); } }, [dataProvider, user]); const { data, isLoading, isError } = useQuery({ queryKey: ['GET', 'onboarding', user], queryFn: fetch, staleTime: 24 * 60 * 60 * 1000, retry: user !== undefined }); const [post, setPost] = useState(() => () => Promise.resolve()); const mutation = useMutation({ mutationFn: post }); const mutateAsync = mutation.mutateAsync; useEffect(() => { function post() { const newValue = _.chain(data ?? {}) .clone() .extend({ [topic]: Date.now() }) .value(); return dataProvider.post('onboarding', newValue).then(() => { queryClient.invalidateQueries(['GET', 'onboarding', user]); }); } setPost(() => post); }, [dataProvider, data, topic, queryClient, user]); useEffect(() => { setOnboardingRequired(_.isNil((data ?? {})[topic])); }, [data, topic, setOnboardingRequired]); useEffect(() => { setAcknowledgeOnboarding(() => () => { return mutateAsync(); }); }, [mutateAsync]); return { loading: isLoading, error: isError, onboardingRequired: onboardingRequired, acknowledgeOnboarding: acknowledgeOnboarding }; } function useLocalStorageDataProvider(topic: string): IOnboardingDataProvider { const [localStorageValue, setLocalStorageValue] = useLocalStorage('onboarding', {}) as [ { [key: string]: number }, (value: (oldValue: { [key: string]: number }) => { [key: string]: number }) => { [key: string]: number } ]; const [onboardingRequired, setOnboardingRequired] = useState(false); const [acknowledgeOnboarding, setAcknowledgeOnboarding] = useState(() => () => Promise.resolve()); useEffect(() => { setOnboardingRequired(_.isNil((localStorageValue ?? {})[topic])); }, [localStorageValue, topic, setOnboardingRequired]); useEffect(() => { setAcknowledgeOnboarding(() => () => { setLocalStorageValue((oldValue) => _.extend(oldValue, { [topic]: Date.now() })); return Promise.resolve(); }); }, [topic, setLocalStorageValue, setAcknowledgeOnboarding]); return { onboardingRequired: onboardingRequired, acknowledgeOnboarding: acknowledgeOnboarding }; } function useFakeLocalStorageDataProvider(): IOnboardingDataProvider { const setterRef = useRef(() => Promise.resolve()); return { onboardingRequired: false, acknowledgeOnboarding: setterRef.current }; } function useOnboardingDataProvider(mode: OnboardingModes, topic: string): IOnboardingDataProvider { yup.object({ mode: onboardingModesSchema, topic: onboardingTopicSchema }).validateSync({ mode: mode, topic: topic }); const userProfileDataProvider = useUserProfileDataProvider(topic); const localStorageDataProvider = useLocalStorageDataProvider(topic); const fakeLocalStorageDataProvider = useFakeLocalStorageDataProvider(); switch (mode) { case OnboardingModes.USER: if (userProfileDataProvider.loading) { return fakeLocalStorageDataProvider; } else if (userProfileDataProvider.error) { return localStorageDataProvider; } else { return _.pick(userProfileDataProvider, [ 'onboardingRequired', 'acknowledgeOnboarding' ]) as IOnboardingDataProvider; } case OnboardingModes.BROWSER: return localStorageDataProvider; default: return fakeLocalStorageDataProvider; } } export { useOnboardingDataProvider };