feat: SPA pour les pages existantes

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-02-17 14:50:36 +01:00
parent 668702b1fb
commit 140cc14316
11 changed files with 331 additions and 164 deletions

View File

@@ -1,4 +1,4 @@
import React, { createContext, useContext, useReducer, useCallback } from 'react';
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';
@@ -13,20 +13,48 @@ const initialState = {
collection: null,
detailedMangas: {},
loading: false,
error: null
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 };
case 'SET_MANGA_DETAIL':
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 }
: manga
)
} : state.collection;
return {
...state,
collection: updatedCollection,
detailedMangas: {
...state.detailedMangas,
[action.payload.slug]: action.payload
@@ -42,8 +70,43 @@ function mangaReducer(state, action) {
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 () => {
if (state.collection) return; // Return if already loaded
// 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 1 minute
const isStale = state.lastCollectionUpdate &&
(Date.now() - state.lastCollectionUpdate) > 60 * 1000;
if (isStale) {
refreshCollectionInBackground();
}
return;
}
dispatch({ type: 'SET_LOADING', payload: true });
try {
@@ -53,23 +116,47 @@ export function MangaProvider({ children }) {
dispatch({ type: 'SET_ERROR', payload: 'Failed to load manga collection' });
console.error(error);
}
}, []);
}, [state.collection, state.lastCollectionUpdate, refreshCollectionInBackground]);
const loadMangaDetail = useCallback(async (slug) => {
// Return cached data if available
if (state.detailedMangas[slug]) return state.detailedMangas[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 });
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;
@@ -80,7 +167,8 @@ export function MangaProvider({ children }) {
...state,
loadCollection,
loadMangaDetail,
getMangaFromCollection
getMangaFromCollection,
refreshCollectionInBackground
};
return (