update
This commit is contained in:
112
Frontend/src/contexts/CookieConsentContext.tsx
Normal file
112
Frontend/src/contexts/CookieConsentContext.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import privacyService, {
|
||||
CookieCategoryPreferences,
|
||||
CookieConsent,
|
||||
UpdateCookieConsentRequest,
|
||||
} from '../services/api/privacyService';
|
||||
|
||||
type CookieConsentContextValue = {
|
||||
consent: CookieConsent | null;
|
||||
isLoading: boolean;
|
||||
hasDecided: boolean;
|
||||
updateConsent: (payload: UpdateCookieConsentRequest) => Promise<void>;
|
||||
};
|
||||
|
||||
const CookieConsentContext = createContext<CookieConsentContextValue | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
export const CookieConsentProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [consent, setConsent] = useState<CookieConsent | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [hasDecided, setHasDecided] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const loadConsent = async () => {
|
||||
try {
|
||||
const data = await privacyService.getCookieConsent();
|
||||
if (!isMounted) return;
|
||||
setConsent(data);
|
||||
|
||||
// Prefer explicit local decision flag, fall back to server flag
|
||||
const localFlag =
|
||||
typeof window !== 'undefined'
|
||||
? window.localStorage.getItem('cookieConsentDecided')
|
||||
: null;
|
||||
const decided =
|
||||
(localFlag === 'true') || Boolean((data as any).has_decided);
|
||||
setHasDecided(decided);
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to load cookie consent', error);
|
||||
}
|
||||
} finally {
|
||||
if (isMounted) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void loadConsent();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const updateConsent = useCallback(
|
||||
async (payload: UpdateCookieConsentRequest) => {
|
||||
const updated = await privacyService.updateCookieConsent(payload);
|
||||
setConsent(updated);
|
||||
setHasDecided(true);
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem('cookieConsentDecided', 'true');
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const value: CookieConsentContextValue = {
|
||||
consent,
|
||||
isLoading,
|
||||
hasDecided,
|
||||
updateConsent,
|
||||
};
|
||||
|
||||
return (
|
||||
<CookieConsentContext.Provider value={value}>
|
||||
{children}
|
||||
</CookieConsentContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useCookieConsent = (): CookieConsentContextValue => {
|
||||
const ctx = useContext(CookieConsentContext);
|
||||
if (!ctx) {
|
||||
// Fallback to a safe default instead of throwing, to avoid crashes
|
||||
// if components are rendered outside the provider (e.g. during hot reload).
|
||||
return {
|
||||
consent: null,
|
||||
isLoading: false,
|
||||
hasDecided: false,
|
||||
updateConsent: async () => {
|
||||
// no-op when context is not yet available
|
||||
return;
|
||||
},
|
||||
};
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
|
||||
|
||||
40
Frontend/src/contexts/GlobalLoadingContext.tsx
Normal file
40
Frontend/src/contexts/GlobalLoadingContext.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { createContext, useContext, useState, ReactNode } from 'react';
|
||||
import GlobalLoading from '../components/common/GlobalLoading';
|
||||
|
||||
interface GlobalLoadingContextType {
|
||||
setLoading: (loading: boolean, text?: string) => void;
|
||||
isLoading: boolean;
|
||||
loadingText: string;
|
||||
}
|
||||
|
||||
const GlobalLoadingContext = createContext<GlobalLoadingContextType | undefined>(undefined);
|
||||
|
||||
export const useGlobalLoading = () => {
|
||||
const context = useContext(GlobalLoadingContext);
|
||||
if (!context) {
|
||||
throw new Error('useGlobalLoading must be used within GlobalLoadingProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface GlobalLoadingProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const GlobalLoadingProvider: React.FC<GlobalLoadingProviderProps> = ({ children }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loadingText, setLoadingText] = useState('Loading...');
|
||||
|
||||
const setLoading = (loading: boolean, text: string = 'Loading...') => {
|
||||
setIsLoading(loading);
|
||||
setLoadingText(text);
|
||||
};
|
||||
|
||||
return (
|
||||
<GlobalLoadingContext.Provider value={{ setLoading, isLoading, loadingText }}>
|
||||
{children}
|
||||
<GlobalLoading isLoading={isLoading} text={loadingText} />
|
||||
</GlobalLoadingContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user