1414 * Advanced:
1515 * - [ ] Pre-fetch details or combine with other Studio Ghibli endpoints (people, locations)
1616 * - [ ] Add fuzzy search (title, director, description)
17- * - [ ] Offline cache using indexedDB (e.g., idb library)
17+ * - [X ] Offline cache using indexedDB (e.g., idb library)
1818 * - [ ] Extract data layer + hook (useGhibliFilms)
1919 */
2020import { useEffect , useState } from 'react' ;
@@ -24,6 +24,7 @@ import Card from '../components/Card.jsx';
2424import HeroSection from '../components/HeroSection' ;
2525import Cinema from '../Images/Movie.jpg' ;
2626import Modal from '../components/Modal.jsx' ;
27+ import { getCachedMovies , saveMoviesToCache } from '../utilities/db' ;
2728
2829export default function Movies ( ) {
2930 const [ films , setFilms ] = useState ( [ ] ) ;
@@ -32,19 +33,78 @@ export default function Movies() {
3233 const [ filter , setFilter ] = useState ( '' ) ;
3334 const [ isModalOpen , setIsModalOpen ] = useState ( false ) ;
3435 const [ selectedFilm , setSelectedFilm ] = useState ( null ) ;
36+ const [ isOffline , setIsOffline ] = useState ( ! navigator . onLine ) ;
3537
38+ useEffect ( ( ) => {
39+ const handleOffline = ( ) => {
40+ console . log ( 'App is offline' ) ;
41+ setIsOffline ( true ) ;
42+ } ;
43+ const handleOnline = ( ) => {
44+ console . log ( 'App is online' ) ;
45+ setIsOffline ( false ) ;
46+ } ;
3647
37- useEffect ( ( ) => { fetchFilms ( ) ; } , [ ] ) ;
48+ window . addEventListener ( 'offline' , handleOffline ) ;
49+ window . addEventListener ( 'online' , handleOnline ) ;
3850
39- async function fetchFilms ( ) {
40- try {
41- setLoading ( true ) ; setError ( null ) ;
42- const res = await fetch ( 'https://ghibliapi.vercel.app/films' ) ;
43- 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 ) ; }
47- }
51+ return ( ) => {
52+ window . removeEventListener ( 'offline' , handleOffline ) ;
53+ window . removeEventListener ( 'online' , handleOnline ) ;
54+ } ;
55+ } , [ ] ) ;
56+
57+ useEffect ( ( ) => {
58+ async function loadMovies ( ) {
59+ try {
60+ setLoading ( true ) ;
61+ setError ( null ) ;
62+
63+ if ( isOffline ) {
64+ // --- OFFLINE LOGIC ---
65+ const cachedFilms = await getCachedMovies ( ) ;
66+ if ( cachedFilms . length > 0 ) {
67+ setFilms ( cachedFilms ) ;
68+ } else {
69+ setError ( new Error ( "You are offline and no cached movies are available." ) ) ;
70+ }
71+ } else {
72+ // --- ONLINE LOGIC ---
73+ const res = await fetch ( 'https://ghibliapi.vercel.app/films' ) ;
74+ if ( ! res . ok ) throw new Error ( 'Failed to fetch from API' ) ;
75+
76+ const json = await res . json ( ) ;
77+ setFilms ( json ) ;
78+
79+ await saveMoviesToCache ( json ) ;
80+ }
81+ } catch ( e ) {
82+ console . error ( 'Error during data loading:' , e ) ;
83+
84+ if ( ! isOffline ) {
85+ console . log ( 'API failed, attempting to load from cache...' ) ;
86+ try {
87+ const cachedFilms = await getCachedMovies ( ) ;
88+ if ( cachedFilms . length > 0 ) {
89+ setFilms ( cachedFilms ) ;
90+ setError ( null ) ;
91+ } else {
92+ setError ( new Error ( "API failed and no cached data is available." ) ) ;
93+ }
94+ } catch ( cacheError ) {
95+ console . error ( 'Cache fallback failed:' , cacheError ) ;
96+ setError ( e ) ;
97+ }
98+ } else {
99+ setError ( e ) ;
100+ }
101+ } finally {
102+ setLoading ( false ) ;
103+ }
104+ }
105+
106+ loadMovies ( ) ;
107+ } , [ isOffline ] ) ;
48108
49109 const filtered = films . filter ( f => f . director . toLowerCase ( ) . includes ( filter . toLowerCase ( ) ) || f . release_date . includes ( filter ) ) ;
50110
@@ -69,6 +129,19 @@ export default function Movies() {
69129 }
70130 subtitle = "Dive deep into storytelling, performances, and the art of filmmaking."
71131 />
132+ { isOffline && (
133+ < div style = { {
134+ padding : '10px' ,
135+ backgroundColor : '#333' ,
136+ color : 'white' ,
137+ textAlign : 'center' ,
138+ fontWeight : 'bold' ,
139+ margin : '10px 0'
140+ } } >
141+ You are in offline mode.
142+ </ div >
143+ ) }
144+
72145 < h2 > Studio Ghibli Films</ h2 >
73146 < input value = { filter } onChange = { e => setFilter ( e . target . value ) } placeholder = "Filter by director or year" />
74147 { loading && < Loading /> }
@@ -99,4 +172,4 @@ export default function Movies() {
99172 ) }
100173 </ div >
101174 ) ;
102- }
175+ }
0 commit comments