diff --git a/src/blog/providers/Article.tsx b/src/blog/providers/Article.tsx index 66ba460..6e28585 100644 --- a/src/blog/providers/Article.tsx +++ b/src/blog/providers/Article.tsx @@ -1,30 +1,21 @@ import React, { createContext, useState, useContext, useEffect } from 'react'; import { api } from '../utils/api'; -import { ArticleModel } from '../types/models'; +import { + ArticleModel, + ArticlesModel, + createArticlesModelObject, +} from '../types/models'; import { ArticleContextModel } from '../types/contexts'; import { useAuth } from './Author'; const ArticleContext = createContext(undefined); export const ArticleProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [articles, setArticles] = useState([]); + const [articles, setArticles] = useState(createArticlesModelObject()); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const { token, currentUser } = useAuth(); - const upsertArticleInList = (updated: ArticleModel) => { - setArticles(prev => { - const exists = prev.some(a => a._id === updated._id); - if (exists) { - // UPDATE → replace item - return prev.map(a => (a._id === updated._id ? updated : a)); - } else { - // CREATE → append to top - return [updated, ...prev]; - } - }); - }; - /** 🔹 Author IDs must be strings for API, so we normalize here */ const normalizeArticleForApi = (article: Partial) => { // Extract existing authors as a list of IDs (string[]) @@ -51,7 +42,7 @@ export const ArticleProvider: React.FC<{ children: React.ReactNode }> = ({ child const res = await api.get('/articles', { params: { skip: 0, limit: 100 } }); const formatted = res.data.map((a) => ({ ...a, id: a._id || undefined })); - setArticles(formatted); + setArticles(prev => prev.refresh(formatted)); } catch (err: any) { console.error('Failed to fetch articles:', err); setError(err.response?.data?.detail || 'Failed to fetch articles'); @@ -77,7 +68,10 @@ export const ArticleProvider: React.FC<{ children: React.ReactNode }> = ({ child setError(null); const res = await api.put(`/articles/${articleData._id}`, normalizedArticleData); - upsertArticleInList(res.data); + setArticles(prev => { + prev.update(res.data); + return { ...prev }; + }); return res.data; } catch (err: any) { console.error('Article update failed:', err); @@ -100,7 +94,10 @@ export const ArticleProvider: React.FC<{ children: React.ReactNode }> = ({ child setError(null); const res = await api.post(`/articles`, normalizedArticleData); - upsertArticleInList(res.data); + setArticles(prev => { + prev.create(res.data); + return { ...prev }; + }); return res.data; } catch (err: any) { console.error('Article create failed:', err); diff --git a/src/blog/types/contexts.ts b/src/blog/types/contexts.ts index 65d523a..7cac284 100644 --- a/src/blog/types/contexts.ts +++ b/src/blog/types/contexts.ts @@ -1,7 +1,11 @@ -import { ArticleModel, AuthorModel } from "./models"; +import { + ArticleModel, + ArticlesModel, + AuthorModel +} from "./models"; export interface ArticleContextModel { - articles: ArticleModel[]; + articles: ArticlesModel; loading: boolean; error: string | null; refreshArticles: () => Promise; diff --git a/src/blog/types/models.ts b/src/blog/types/models.ts index ab50a10..862cee8 100644 --- a/src/blog/types/models.ts +++ b/src/blog/types/models.ts @@ -1,3 +1,9 @@ +import { + createInList, readInList, updateInList, deleteInList, + createById, readById, updateById, deleteById +} from "../utils/articles"; + + export interface AuthorModel { // meta fields _id?: string | null; @@ -28,3 +34,62 @@ export interface ArticleModel { // ref fields authors: AuthorModel[]; } + +export interface ArticlesModel { + articles: ArticleModel[]; + articlesById: Record; + // articlesByTag: Record; + // articlesByAuthor: Record; + + refresh(list: ArticleModel[]): ArticlesModel; + create(a: ArticleModel): ArticlesModel; + readByIndex(index: number): ArticleModel | undefined; + readById(id: string): ArticleModel | undefined; + update(a: ArticleModel): ArticlesModel; + delete(id: string): ArticlesModel; +} + +// ---------- FACTORY ---------- +export function createArticlesModelObject(): ArticlesModel { + return { + articles: [], + articlesById: {}, + + refresh(list) { + this.articles = list; + + const map: Record = {}; + for (const a of list) { + if (a._id) map[a._id] = a; + } + this.articlesById = map; + return this; + }, + + create(a) { + this.articles = createInList(this.articles, a); + this.articlesById = createById(this.articlesById, a); + return this; + }, + + readByIndex(index) { + return readInList(this.articles, index); + }, + + readById(id) { + return readById(this.articlesById, id); + }, + + update(a) { + this.articles = updateInList(this.articles, a); + this.articlesById = updateById(this.articlesById, a); + return this; + }, + + delete(id) { + this.articles = deleteInList(this.articles, id); + this.articlesById = deleteById(this.articlesById, id); + return this; + } + }; +} diff --git a/src/blog/types/props.ts b/src/blog/types/props.ts index bbcf63e..f012335 100644 --- a/src/blog/types/props.ts +++ b/src/blog/types/props.ts @@ -1,7 +1,10 @@ -import { ArticleModel } from "./models"; +import { + ArticleModel, + ArticlesModel, +} from "./models"; export interface LatestProps { - articles: ArticleModel[]; + articles: ArticlesModel; onSelectArticle?: (index: number) => void; onLoadMore?: (offset: number, limit: number) => Promise; // optional async callback } @@ -12,7 +15,7 @@ export interface LoginProps { } export interface MainContentProps { - articles: ArticleModel[]; + articles: ArticlesModel; onSelectArticle: (index: number) => void; } @@ -49,7 +52,7 @@ export interface ArticleCardProps { } export interface ArticleGridProps { - articles: ArticleModel[]; + articles: ArticlesModel; onSelectArticle: (index: number) => void; xs?: number; // default 12 for mobile full-width md12?: number, // default 12 (full-width) diff --git a/src/blog/utils/articles.ts b/src/blog/utils/articles.ts index 2a034d2..40459b7 100644 --- a/src/blog/utils/articles.ts +++ b/src/blog/utils/articles.ts @@ -1,6 +1,5 @@ import { ArticleModel, - ArticleRepoModel } from "../types/models"; // List helpers