Skip to content

Commit 2444f98

Browse files
committed
Added IMDB ratings to The Movies page
1 parent 9a9cb71 commit 2444f98

2 files changed

Lines changed: 71 additions & 43 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VITE_OMDB_API_KEY=your_api_key_here

src/pages/Movies.jsx

Lines changed: 70 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,3 @@
1-
/**
2-
* MOVIES (GHIBLI) DASHBOARD TODOs
3-
* -------------------------------
4-
* Easy:
5-
* - [ ] Add select dropdown for director filtering instead of text filter
6-
* - [ ] Add film poster (map titles to known images or placeholder search)
7-
* - [ ] Show running time, score, producer fields
8-
* - [ ] Expand/collapse description
9-
* Medium:
10-
* - [ ] Client-side pagination / virtualization for performance (future if many APIs)
11-
* - [ ] Favorites / watchlist (localStorage)
12-
* - [ ] Sort (Year asc/desc, Title A-Z, RT Score)
13-
* - [ ] Detail modal with full info & external links
14-
* Advanced:
15-
* - [ ] Pre-fetch details or combine with other Studio Ghibli endpoints (people, locations)
16-
* - [ ] Add fuzzy search (title, director, description)
17-
* - [ ] Offline cache using indexedDB (e.g., idb library)
18-
* - [ ] Extract data layer + hook (useGhibliFilms)
19-
*/
201
import { useEffect, useState } from 'react';
212
import Loading from '../components/Loading.jsx';
223
import ErrorMessage from '../components/ErrorMessage.jsx';
@@ -33,20 +14,55 @@ export default function Movies() {
3314
const [isModalOpen, setIsModalOpen] = useState(false);
3415
const [selectedFilm, setSelectedFilm] = useState(null);
3516

17+
// 🗝️ Replace with your own OMDb API key
18+
const OMDB_API_KEY = import.meta.env.VITE_OMDB_API_KEY;
3619

37-
useEffect(() => { fetchFilms(); }, []);
20+
21+
useEffect(() => {
22+
fetchFilms();
23+
}, []);
3824

3925
async function fetchFilms() {
4026
try {
41-
setLoading(true); setError(null);
27+
setLoading(true);
28+
setError(null);
29+
30+
// Step 1: Fetch Ghibli films
4231
const res = await fetch('https://ghibliapi.vercel.app/films');
4332
if (!res.ok) throw new Error('Failed to fetch');
44-
const json = await res.json();
45-
setFilms(json);
46-
} catch (e) { setError(e); } finally { setLoading(false); }
33+
const filmsData = await res.json();
34+
35+
// Step 2: Fetch IMDb rating for each film
36+
const filmsWithRatings = await Promise.all(
37+
filmsData.map(async (film) => {
38+
try {
39+
const omdbRes = await fetch(
40+
`https://www.omdbapi.com/?t=${encodeURIComponent(film.title)}&apikey=${OMDB_API_KEY}`
41+
);
42+
const omdbData = await omdbRes.json();
43+
return {
44+
...film,
45+
imdbRating: omdbData.imdbRating || 'N/A', // ⭐ IMDb rating section
46+
};
47+
} catch {
48+
return { ...film, imdbRating: 'N/A' };
49+
}
50+
})
51+
);
52+
53+
setFilms(filmsWithRatings);
54+
} catch (e) {
55+
setError(e);
56+
} finally {
57+
setLoading(false);
58+
}
4759
}
4860

49-
const filtered = films.filter(f => f.director.toLowerCase().includes(filter.toLowerCase()) || f.release_date.includes(filter));
61+
const filtered = films.filter(
62+
(f) =>
63+
f.director.toLowerCase().includes(filter.toLowerCase()) ||
64+
f.release_date.includes(filter)
65+
);
5066

5167
const openModal = (film) => {
5268
setSelectedFilm(film);
@@ -61,21 +77,25 @@ export default function Movies() {
6177
return (
6278
<div>
6379
<HeroSection
64-
image={Cinema}
65-
title={
66-
<>
67-
Lights, Camera, <span style={{ color: 'darkred' }}>Binge!</span>
68-
</>
69-
}
70-
subtitle="Dive deep into storytelling, performances, and the art of filmmaking."
71-
/>
80+
image={Cinema}
81+
title={
82+
<>
83+
Lights, Camera, <span style={{ color: 'darkred' }}>Binge!</span>
84+
</>
85+
}
86+
subtitle="Dive deep into storytelling, performances, and the art of filmmaking."
87+
/>
7288
<h2>Studio Ghibli Films</h2>
73-
<input value={filter} onChange={e => setFilter(e.target.value)} placeholder="Filter by director or year" />
89+
<input
90+
value={filter}
91+
onChange={(e) => setFilter(e.target.value)}
92+
placeholder="Filter by director or year"
93+
/>
7494
{loading && <Loading />}
7595
<ErrorMessage error={error} />
7696
<div className="grid">
77-
{filtered.map(f => (
78-
<div
97+
{filtered.map((f) => (
98+
<div
7999
key={f.id}
80100
type="button"
81101
onClick={() => openModal(f)}
@@ -85,18 +105,25 @@ export default function Movies() {
85105
cursor: 'pointer',
86106
}}
87107
>
88-
<Card title={`${f.title} (${f.release_date})`} japaneseTitle={f.original_title} image={f.image}>
89-
<p><strong>Director:</strong> {f.director}</p>
90-
<p>{f.description.slice(0,120)}...</p>
91-
{/* TODO: Add poster images (need mapping) */}
108+
<Card
109+
title={`${f.title} (${f.release_date})`}
110+
japaneseTitle={f.original_title}
111+
image={f.image}
112+
>
113+
<p>
114+
<strong>Director:</strong> {f.director}
115+
</p>
116+
<p>
117+
<strong>IMDb Rating:</strong>{f.imdbRating}
118+
</p>
119+
<p>{f.description.slice(0, 120)}...</p>
92120
</Card>
93121
</div>
94-
95122
))}
96123
</div>
97-
{isModalOpen && selectedFilm && (
124+
{isModalOpen && selectedFilm && (
98125
<Modal open={isModalOpen} onClose={closeModal} film={selectedFilm} />
99126
)}
100127
</div>
101128
);
102-
}
129+
}

0 commit comments

Comments
 (0)