Files
Mangarr/assets/react/app/presentation/context/MangaContext.jsx
ext.jeremy.guillot@maxicoffee.domains 4f4f86fb91 feat: front update
2025-03-22 15:38:05 +01:00

194 lines
6.3 KiB
JavaScript

import React, { createContext, useContext, useReducer, useCallback, useEffect } from 'react';
import { ApiMangaRepository } from '../../infrastructure/api/apiMangaRepository';
import { GetMangaCollection } from '../../application/useCases/getMangaCollection';
import { GetMangaDetail } from '../../application/useCases/getMangaDetail';
const mangaRepository = new ApiMangaRepository();
const getMangaCollection = new GetMangaCollection(mangaRepository);
const getMangaDetail = new GetMangaDetail(mangaRepository);
const MangaContext = createContext(null);
const initialState = {
collection: null,
detailedMangas: {},
loading: false,
error: null,
lastCollectionUpdate: null,
isBackgroundLoading: false
};
function mangaReducer(state, action) {
switch (action.type) {
case 'SET_LOADING':
return { ...state, loading: action.payload };
case 'SET_BACKGROUND_LOADING':
return { ...state, isBackgroundLoading: action.payload };
case 'SET_ERROR':
return { ...state, error: action.payload, loading: false };
case 'SET_COLLECTION':
return {
...state,
collection: action.payload,
loading: false,
error: null,
lastCollectionUpdate: Date.now()
};
case 'UPDATE_COLLECTION':
return {
...state,
collection: action.payload,
isBackgroundLoading: false,
lastCollectionUpdate: Date.now()
};
case 'SET_MANGA_DETAIL':
// Mettre à jour également le manga dans la collection si présent
const updatedCollection = state.collection ? {
...state.collection,
items: state.collection.items.map(manga =>
manga.slug === action.payload.slug
? {
...manga,
...action.payload,
createdAt: manga.createdAt || action.payload.createdAt
}
: manga
)
} : state.collection;
return {
...state,
collection: updatedCollection,
detailedMangas: {
...state.detailedMangas,
[action.payload.slug]: {
...action.payload,
createdAt: state.collection?.items.find(m => m.slug === action.payload.slug)?.createdAt || action.payload.createdAt
}
},
loading: false,
error: null
};
default:
return state;
}
}
export function MangaProvider({ children }) {
const [state, dispatch] = useReducer(mangaReducer, initialState);
// Fonction pour charger la collection en arrière-plan
const refreshCollectionInBackground = useCallback(async () => {
if (state.isBackgroundLoading) return;
dispatch({ type: 'SET_BACKGROUND_LOADING', payload: true });
try {
const collection = await getMangaCollection.execute(1);
dispatch({ type: 'UPDATE_COLLECTION', payload: collection });
} catch (error) {
console.error('Background collection refresh failed:', error);
dispatch({ type: 'SET_BACKGROUND_LOADING', payload: false });
}
}, [state.isBackgroundLoading]);
// Rafraîchir la collection toutes les 5 minutes si elle est chargée
useEffect(() => {
if (!state.collection) return;
const interval = setInterval(() => {
refreshCollectionInBackground();
}, 5 * 60 * 1000);
return () => clearInterval(interval);
}, [state.collection, refreshCollectionInBackground]);
const loadCollection = useCallback(async () => {
// Si nous avons déjà des données, les afficher immédiatement
if (state.collection) {
// Rafraîchir en arrière-plan si les données sont vieilles de plus de 30 secondes
const isStale = state.lastCollectionUpdate &&
(Date.now() - state.lastCollectionUpdate) > 30 * 1000;
if (isStale && !state.isBackgroundLoading) {
refreshCollectionInBackground();
}
return;
}
dispatch({ type: 'SET_LOADING', payload: true });
try {
const collection = await getMangaCollection.execute(1);
dispatch({ type: 'SET_COLLECTION', payload: collection });
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: 'Failed to load manga collection' });
console.error(error);
}
}, [state.collection, state.lastCollectionUpdate, state.isBackgroundLoading, refreshCollectionInBackground]);
const loadMangaDetail = useCallback(async (slug) => {
// Retourner les données en cache si disponibles
if (state.detailedMangas[slug]) {
// Rafraîchir en arrière-plan si les données sont vieilles de plus de 5 minutes
const cachedManga = state.detailedMangas[slug];
const isStale = cachedManga.lastUpdate &&
(Date.now() - cachedManga.lastUpdate) > 5 * 60 * 1000;
if (isStale) {
// Charger les nouvelles données en arrière-plan
getMangaDetail.execute(slug).then(manga => {
dispatch({ type: 'SET_MANGA_DETAIL', payload: { ...manga, lastUpdate: Date.now() } });
}).catch(console.error);
}
return state.detailedMangas[slug];
}
// Si le manga est dans la collection, l'utiliser comme données temporaires
const collectionManga = getMangaFromCollection(slug);
if (collectionManga) {
dispatch({
type: 'SET_MANGA_DETAIL',
payload: { ...collectionManga, isPartial: true, lastUpdate: Date.now() }
});
}
// Charger les détails complets
dispatch({ type: 'SET_LOADING', payload: true });
try {
const manga = await getMangaDetail.execute(slug);
dispatch({ type: 'SET_MANGA_DETAIL', payload: { ...manga, lastUpdate: Date.now() } });
return manga;
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: 'Failed to load manga details' });
console.error(error);
return null;
}
}, [state.detailedMangas]);
const getMangaFromCollection = useCallback((slug) => {
if (!state.collection) return null;
return state.collection.items.find(manga => manga.slug === slug);
}, [state.collection]);
const value = {
...state,
loadCollection,
loadMangaDetail,
getMangaFromCollection,
refreshCollectionInBackground
};
return (
<MangaContext.Provider value={value}>
{children}
</MangaContext.Provider>
);
}
export function useManga() {
const context = useContext(MangaContext);
if (!context) {
throw new Error('useManga must be used within a MangaProvider');
}
return context;
}