This commit is contained in:
Iliyan Angelov
2025-11-16 20:05:08 +02:00
parent 98ccd5b6ff
commit 48353cde9c
118 changed files with 9488 additions and 1336 deletions

View 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;
};

View 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>
);
};