import * as React from 'react'; import UsercentricsClient, { UI_LAYER } from '@usercentrics/cmp-browser-sdk'; import type { InitialUIValues, Category, UI, Maybe, DefaultLabels } from '@usercentrics/cmp-browser-sdk'; import { DecisionsProvider } from '../DecisionsContext'; import type { ContextValue, State, UsercentricsHandlers, UsercentricsProviderProps } from './types'; import { loadUsercentrics } from './loadUsercentrics'; export const defaultConsentEventName = 'dataLayerUC'; export const UsercentricsContext = React.createContext({ status: 'initializing', } as ContextValue); const initialState: State = { status: 'initializing', settings: null, settingsLabels: null, categories: [], activeLayer: UI_LAYER.NONE, }; type Action = | { type: 'INITIALIZED'; payload: { categories: Category[]; activeLayer: UI_LAYER; settings: Maybe; settingsLabels: DefaultLabels; }; } | { type: 'UPDATED_CATEGORIES'; payload: { categories: Category[] } } | { type: 'ERROR'; status: 'initializing_failed' | 'updating_failed' } | { type: 'UPDATING' } | { type: 'SET_ACTIVE_LAYER'; layer: UI_LAYER }; const reducer: React.Reducer = (state, action) => { switch (action.type) { case 'UPDATING': return { ...state, status: 'updating' }; case 'ERROR': return { ...state, status: action.status }; case 'INITIALIZED': return { ...state, ...action.payload, status: 'initialized' }; case 'UPDATED_CATEGORIES': return { ...state, ...action.payload, status: 'updated' }; case 'SET_ACTIVE_LAYER': return { ...state, activeLayer: action.layer }; default: return state; } }; const Provider = ({ children, settingsId, options, abTestValue, consentEventName = defaultConsentEventName, activeLayer, loadingUI: LoadingUI = () => <>, }: React.PropsWithChildren) => { const clientRef = React.useRef(null); const [state, dispatch] = React.useReducer(reducer, initialState); React.useEffect(() => { if (settingsId) { const Client = loadUsercentrics(); const client: UsercentricsClient = new Client(settingsId, options); client .init() .then((initialUIValues: InitialUIValues) => { clientRef.current = client; const settings = client.getSettingsUI(); const settingsLabels = client.getSettingsLabels() as DefaultLabels; client.getCategoriesFullInfo().then((categories) => { dispatch({ type: 'INITIALIZED', payload: { settings, settingsLabels, categories, activeLayer: activeLayer ?? initialUIValues.initialLayer, }, }); }); }) .catch((error: unknown) => { dispatch({ type: 'ERROR', status: 'initializing_failed' }); console.error(error); }); } }, [options, settingsId, activeLayer]); const handlers = React.useMemo(() => { if (state.status === 'initializing') { return null; } if (!clientRef.current) { return null; } const client = clientRef.current; const updateCategories = async () => { try { const categories = await client.getCategoriesFullInfo(); dispatch({ type: 'UPDATED_CATEGORIES', payload: { categories }, }); } catch (error) { console.error(error); } }; return { accept: async (userDecisions) => { try { dispatch({ type: 'UPDATING' }); await client.updateServices(userDecisions); updateCategories(); } catch (error) { dispatch({ type: 'ERROR', status: 'updating_failed' }); console.error(error); } }, acceptAll: async () => { try { dispatch({ type: 'UPDATING' }); await client.acceptAllServices(); updateCategories(); dispatch({ type: 'SET_ACTIVE_LAYER', layer: UI_LAYER.NONE }); } catch (error) { dispatch({ type: 'ERROR', status: 'updating_failed' }); console.error(error); } }, denyAll: async () => { try { dispatch({ type: 'UPDATING' }); await client.denyAllServices(); updateCategories(); } catch (error) { dispatch({ type: 'ERROR', status: 'updating_failed' }); console.error(error); } }, setActiveLayer: (layer: UI_LAYER) => dispatch({ type: 'SET_ACTIVE_LAYER', layer, }), }; }, [state.status]); if (state.status === 'initializing') { return ; } return ( {children} ); }; type UseUsercentrics = () => ContextValue; export const useUsercentrics: UseUsercentrics = () => { const context = React.useContext(UsercentricsContext); return context; }; export const UsercentricsProvider = ({ disabled, ...rest }: React.PropsWithChildren): JSX.Element => { if (disabled) { return <>{rest.children}; } return ; };