From de26a9703f690590296565f42b6084f1635ae06f Mon Sep 17 00:00:00 2001 From: Nick Carboni Date: Mon, 3 Mar 2025 01:05:20 -0800 Subject: [PATCH 1/2] feat: dynamically render POIs list by calling the api/poi endpoint. Fixed progress bar to avoid division by zero errors while data is still loading in. Fixed POI interface to properly match the Mongo schema. --- src/components/poiList.tsx | 203 ++++++------------------------------- 1 file changed, 31 insertions(+), 172 deletions(-) diff --git a/src/components/poiList.tsx b/src/components/poiList.tsx index f1a06fd..6ea47fa 100644 --- a/src/components/poiList.tsx +++ b/src/components/poiList.tsx @@ -12,23 +12,42 @@ interface POI { description: string; audioField: string; duration: string; - imageUrl: string; + image: string; isComplete: boolean; } export default function POICardList() { const [cardsDone, setCardsDone] = useState(0); - const [data, setData] = useState(() => { - if (typeof window !== "undefined") { - const storedData = sessionStorage.getItem("poiData"); - return storedData ? JSON.parse(storedData) : initialData; + const [data, setData] = useState([]); + + useEffect(() => { + //fetch POIs, parse and extract + async function fetchData() { + fetch("/api/poi") + .then((response) => response.json()) + .then((data) => { + setData(data.POIs); + }) + .catch((error) => console.error("Error fetching POI data:", error)); + } + + //use sessionStorage if possible, otherwise make GET + const storedData = sessionStorage.getItem("poiData"); + if (storedData) { + setData(JSON.parse(storedData)); + } else { + fetchData(); } - return initialData; - }); + }, []); useEffect(() => { - sessionStorage.setItem("poiData", JSON.stringify(data)); - setCardsDone(data.filter((item) => item.isComplete).length); + console.log("done: " + cardsDone); + console.log("total: " + data.length); + + if (data.length > 0) { + sessionStorage.setItem("poiData", JSON.stringify(data)); + setCardsDone(data.filter((item) => item.isComplete).length); + } }, [data]); const toggleComplete = (id: string) => { @@ -52,7 +71,7 @@ export default function POICardList() {
Visited Spots: {cardsDone + "/" + data.length}
- + 0 ? (cardsDone / data.length) * 100 : 0} colorPalette="orange" />
@@ -64,7 +83,7 @@ export default function POICardList() { query: { name: POI.name, duration: POI.duration, - url: POI.imageUrl, + url: POI.image, description: POI.description, progress: cardsDone, totalCards: data.length, @@ -95,7 +114,7 @@ export default function POICardList() { {POI.isComplete && }
- + ))} @@ -104,163 +123,3 @@ export default function POICardList() { ); } - -const initialData: POI[] = [ - { - _id: "1", - name: "Eiffel Tower", - description: - "A wrought-iron lattice tower in Paris, France, known for its stunning architecture and panoramic views.", - audioField: "eiffel_tower_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "2", - name: "Great Wall of China", - description: "A historic fortification stretching across China, built to protect against invasions.", - audioField: "great_wall_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "3", - name: "Machu Picchu", - description: "An ancient Incan citadel set high in the Andes Mountains in Peru, known for its breathtaking ruins.", - audioField: "machu_picchu_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "4", - name: "Colosseum", - description: - "A massive ancient amphitheater in Rome, Italy, once used for gladiatorial contests and public spectacles.", - audioField: "colosseum_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "5", - name: "Statue of Liberty", - description: "A symbol of freedom and democracy, located on Liberty Island in New York Harbor.", - audioField: "statue_liberty_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "6", - name: "Taj Mahal", - description: - "A stunning white marble mausoleum in India, built by Mughal Emperor Shah Jahan in memory of his wife.", - audioField: "taj_mahal_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "7", - name: "Christ the Redeemer", - description: - "A colossal statue of Jesus Christ in Rio de Janeiro, Brazil, overlooking the city from Mount Corcovado.", - audioField: "christ_redeemer_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "8", - name: "Sydney Opera House", - description: - "An architectural masterpiece in Australia, known for its sail-like design and world-class performances.", - audioField: "sydney_opera_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "9", - name: "Pyramids of Giza", - description: "One of the Seven Wonders of the Ancient World, located in Egypt and built as tombs for pharaohs.", - audioField: "pyramids_giza_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "10", - name: "Niagara Falls", - description: - "A powerful set of waterfalls on the border of the USA and Canada, attracting millions of visitors each year.", - audioField: "niagara_falls_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "11", - name: "Grand Canyon", - description: "A breathtaking natural wonder in Arizona, USA, carved by the Colorado River over millions of years.", - audioField: "grand_canyon_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "12", - name: "Mount Everest", - description: - "The tallest mountain in the world, located in the Himalayas, attracting climbers from around the globe.", - audioField: "mount_everest_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "13", - name: "Stonehenge", - description: "A mysterious prehistoric stone circle in England, believed to have been used for ceremonies.", - audioField: "stonehenge_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "14", - name: "The Louvre Museum", - description: "A world-famous museum in Paris, home to iconic artworks like the Mona Lisa and the Venus de Milo.", - audioField: "louvre_museum_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, - { - _id: "15", - name: "Chichen Itza", - description: "A significant Mayan archaeological site in Mexico, featuring the famous pyramid El Castillo.", - audioField: "chichen_itza_audio.mp3", - duration: "1:00", - imageUrl: - "https://images.unsplash.com/uploads/141148589884100082977/a816dbd7?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - isComplete: false, - }, -]; From 2db9fa827abbc6330f6f28f7e8299d0d4b6115fb Mon Sep 17 00:00:00 2001 From: Nick Carboni Date: Mon, 3 Mar 2025 19:30:56 -0800 Subject: [PATCH 2/2] feat: using api/audiofile endpoint, fetch duration info and merge it with POI data to display duration on the POI cards --- src/components/poiList.tsx | 43 +++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/components/poiList.tsx b/src/components/poiList.tsx index 6ea47fa..f0f71f3 100644 --- a/src/components/poiList.tsx +++ b/src/components/poiList.tsx @@ -16,19 +16,45 @@ interface POI { isComplete: boolean; } +interface Audio { + _id: string; + name: string; + url: string; + duration: string; + description: string; + __v: number; +} + export default function POICardList() { const [cardsDone, setCardsDone] = useState(0); const [data, setData] = useState([]); useEffect(() => { - //fetch POIs, parse and extract async function fetchData() { - fetch("/api/poi") - .then((response) => response.json()) - .then((data) => { - setData(data.POIs); - }) - .catch((error) => console.error("Error fetching POI data:", error)); + try { + //fetch POI data + const poiResponse = await fetch("/api/poi"); + const poiData = await poiResponse.json(); + + //fetch audio data so we also have audio duration info + const audioResponse = await fetch("/api/audiofile"); + const audioData = await audioResponse.json(); + + // Create a lookup map using the name field + const audioMap = Object.fromEntries(audioData.map((audio: Audio) => [audio.name, audio.duration])); + + // Merge POIs with corresponding audio durations based on name + const mergedData = poiData.POIs.map((poi: POI) => ({ + ...poi, + duration: audioMap[poi.name] || "0:00", // fallback of 0:00 + })); + + // Store in sessionStorage and update state + sessionStorage.setItem("poiData", JSON.stringify(mergedData)); + setData(mergedData); + } catch (error) { + console.error("Error fetching data:", error); + } } //use sessionStorage if possible, otherwise make GET @@ -41,9 +67,6 @@ export default function POICardList() { }, []); useEffect(() => { - console.log("done: " + cardsDone); - console.log("total: " + data.length); - if (data.length > 0) { sessionStorage.setItem("poiData", JSON.stringify(data)); setCardsDone(data.filter((item) => item.isComplete).length);