194 lines
6.3 KiB
JavaScript
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;
|
|
} |