Skip to content

Commit ea218f9

Browse files
committed
Added a ssmart caching mechanism that optimizes performance
1 parent d675559 commit ea218f9

File tree

2 files changed

+60
-25
lines changed

2 files changed

+60
-25
lines changed

src/pages/Header.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@
2323
.dashboard-subtitle {
2424
font-size: 1.1rem;
2525
color: #c8d6e5;
26-
}
26+
}

src/pages/Movies.jsx

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* MOVIES (GHIBLI) DASHBOARD TODOs
33
* -------------------------------
44
* Easy:
5+
* - [x] Add IMDb Ratings using OMDb API (with caching)
56
* - [ ] Add select dropdown for director filtering instead of text filter
67
* - [ ] Add film poster (map titles to known images or placeholder search)
78
* - [ ] Show running time, score, producer fields
@@ -17,6 +18,7 @@
1718
* - [ ] Offline cache using indexedDB (e.g., idb library)
1819
* - [ ] Extract data layer + hook (useGhibliFilms)
1920
*/
21+
2022
import { useEffect, useState } from 'react';
2123
import Loading from '../components/Loading.jsx';
2224
import ErrorMessage from '../components/ErrorMessage.jsx';
@@ -33,39 +35,56 @@ export default function Movies() {
3335
const [isModalOpen, setIsModalOpen] = useState(false);
3436
const [selectedFilm, setSelectedFilm] = useState(null);
3537

36-
// 🗝️ Replace with your own OMDb API key
3738
const OMDB_API_KEY = import.meta.env.VITE_OMDB_API_KEY;
39+
const isLocal = window.location.hostname === 'localhost';
3840

41+
// 🧠 Fetch and cache IMDb rating (refresh automatically in local)
42+
async function fetchIMDbRating(title) {
43+
const cacheKey = `imdb_${title}`;
44+
const cached = localStorage.getItem(cacheKey);
3945

40-
useEffect(() => {
41-
fetchFilms();
42-
}, []);
46+
// If not local and cached data exists, use it
47+
if (!isLocal && cached) {
48+
try {
49+
return JSON.parse(cached);
50+
} catch {
51+
// continue to fetch
52+
}
53+
}
54+
55+
// Otherwise, fetch fresh data
56+
try {
57+
const res = await fetch(
58+
`https://www.omdbapi.com/?t=${encodeURIComponent(title)}&apikey=${OMDB_API_KEY}`
59+
);
60+
const data = await res.json();
61+
62+
if (data && data.imdbRating) {
63+
const ratingObj = { imdbRating: data.imdbRating };
64+
if (!isLocal) localStorage.setItem(cacheKey, JSON.stringify(ratingObj));
65+
return ratingObj;
66+
}
67+
} catch (err) {
68+
console.error('Failed to fetch IMDb rating for', title, err);
69+
}
4370

71+
return { imdbRating: 'N/A' };
72+
}
73+
74+
// 🎬 Fetch Ghibli films and attach IMDb ratings
4475
async function fetchFilms() {
4576
try {
4677
setLoading(true);
4778
setError(null);
4879

49-
// Step 1: Fetch Ghibli films
5080
const res = await fetch('https://ghibliapi.vercel.app/films');
51-
if (!res.ok) throw new Error('Failed to fetch');
81+
if (!res.ok) throw new Error('Failed to fetch films');
5282
const filmsData = await res.json();
5383

54-
// Step 2: Fetch IMDb rating for each film
5584
const filmsWithRatings = await Promise.all(
5685
filmsData.map(async (film) => {
57-
try {
58-
const omdbRes = await fetch(
59-
`https://www.omdbapi.com/?t=${encodeURIComponent(film.title)}&apikey=${OMDB_API_KEY}`
60-
);
61-
const omdbData = await omdbRes.json();
62-
return {
63-
...film,
64-
imdbRating: omdbData.imdbRating || 'N/A', // ⭐ IMDb rating section
65-
};
66-
} catch {
67-
return { ...film, imdbRating: 'N/A' };
68-
}
86+
const imdb = await fetchIMDbRating(film.title);
87+
return { ...film, ...imdb };
6988
})
7089
);
7190

@@ -77,12 +96,18 @@ export default function Movies() {
7796
}
7897
}
7998

99+
useEffect(() => {
100+
fetchFilms();
101+
}, []);
102+
103+
// 🧩 Filters
80104
const filtered = films.filter(
81105
(f) =>
82106
f.director.toLowerCase().includes(filter.toLowerCase()) ||
83107
f.release_date.includes(filter)
84108
);
85109

110+
// 🎞️ Modal Handlers
86111
const openModal = (film) => {
87112
setSelectedFilm(film);
88113
setIsModalOpen(true);
@@ -105,24 +130,24 @@ export default function Movies() {
105130
subtitle="Dive deep into storytelling, performances, and the art of filmmaking."
106131
/>
107132
<h2>Studio Ghibli Films</h2>
133+
108134
<input
109135
value={filter}
110136
onChange={(e) => setFilter(e.target.value)}
111137
placeholder="Filter by director or year"
112138
/>
139+
113140
{loading && <Loading />}
114141
<ErrorMessage error={error} />
142+
115143
<div className="grid">
116144
{filtered.map((f) => (
117145
<div
118146
key={f.id}
119147
type="button"
120148
onClick={() => openModal(f)}
121149
aria-label={`Open details for ${f.title}`}
122-
style={{
123-
display: 'contents',
124-
cursor: 'pointer',
125-
}}
150+
style={{ display: 'contents', cursor: 'pointer' }}
126151
>
127152
<Card
128153
title={`${f.title} (${f.release_date})`}
@@ -133,13 +158,23 @@ export default function Movies() {
133158
<strong>Director:</strong> {f.director}
134159
</p>
135160
<p>
136-
<strong>IMDb Rating:</strong>{f.imdbRating}
161+
<span
162+
style={{
163+
fontStyle: 'italic',
164+
fontWeight: 500,
165+
color: 'white',
166+
}}
167+
>
168+
IMDb Rating:
169+
</span>{' '}
170+
{f.imdbRating || 'N/A'}
137171
</p>
138172
<p>{f.description.slice(0, 120)}...</p>
139173
</Card>
140174
</div>
141175
))}
142176
</div>
177+
143178
{isModalOpen && selectedFilm && (
144179
<Modal open={isModalOpen} onClose={closeModal} film={selectedFilm} />
145180
)}

0 commit comments

Comments
 (0)