From ee625533f7b29d8146d2493137c7d2873ede5dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=B3czi=20Levente?= Date: Thu, 4 Dec 2025 19:21:44 +0200 Subject: [PATCH 1/9] added search api, console logs the searches --- src/api/nasaEpic.api.ts | 4 -- src/api/nasaImageAndVideoLibrary.api.ts | 23 +++++++++++ src/components/NavigationBar.tsx | 15 ++++++- src/types/NasaImageAndVideoLibraryType.ts | 49 +++++++++++++++++++++++ 4 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 src/api/nasaImageAndVideoLibrary.api.ts create mode 100644 src/types/NasaImageAndVideoLibraryType.ts diff --git a/src/api/nasaEpic.api.ts b/src/api/nasaEpic.api.ts index 97ee065..b310f5c 100644 --- a/src/api/nasaEpic.api.ts +++ b/src/api/nasaEpic.api.ts @@ -2,7 +2,6 @@ import axios from 'axios'; import type { NasaEpicDataType, NasaEpicDataTypesForDates } from '../types/NasaEpicDataTypes.ts'; -//const API_KEY = import.meta.env.NASA_API_KEY ?? 'DEMO_KEY'; export const nasaEpicApi = axios.create({ baseURL: `https://epic.gsfc.nasa.gov/api/natural/`, @@ -10,9 +9,6 @@ export const nasaEpicApi = axios.create({ Accept: 'application/json', 'Content-Type': 'application/json', }, - /*params: { - api_key: API_KEY, - }*/ }); export async function getNasaEpicData(): Promise { diff --git a/src/api/nasaImageAndVideoLibrary.api.ts b/src/api/nasaImageAndVideoLibrary.api.ts new file mode 100644 index 0000000..babf6b5 --- /dev/null +++ b/src/api/nasaImageAndVideoLibrary.api.ts @@ -0,0 +1,23 @@ +import axios from 'axios'; + +import type { NasaImageAndVideoLibraryItemAssetType, NasaImageAndVideoLibraryType } from '../types/NasaImageAndVideoLibraryType.ts'; + + +export const nasaEpicApi = axios.create({ + baseURL: `https://images-api.nasa.gov`, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, +}); + +export async function searchNasaLibrary(query: string): Promise { + const res = await nasaEpicApi.get(`/search?q=${query}`); + return res.data; +} + +export async function searchNasaLibraryAsset(nasaId: string): Promise { + const res = await nasaEpicApi.get(`/asset/${nasaId}`); + return res.data; +} + diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index 17a799b..61c83ca 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -1,17 +1,28 @@ +import { type FormEvent, useState } from 'react'; import { Form } from 'react-bootstrap'; import Container from 'react-bootstrap/Container'; import Image from 'react-bootstrap/Image'; import Nav from 'react-bootstrap/Nav'; import Navbar from 'react-bootstrap/Navbar'; +import { searchNasaLibrary } from '../api/nasaImageAndVideoLibrary.api.ts'; import style from './NavigationBar.module.css'; function NavigationBar() { + const [query, setQuery] = useState(''); + + const searchHandler = async (e: FormEvent) => { + e.preventDefault(); + + const data = await searchNasaLibrary(query); + console.log(data); + }; + return ( -
- + void searchHandler(e)}> + setQuery(e.target.value)} /> Logo diff --git a/src/types/NasaImageAndVideoLibraryType.ts b/src/types/NasaImageAndVideoLibraryType.ts new file mode 100644 index 0000000..ad463c5 --- /dev/null +++ b/src/types/NasaImageAndVideoLibraryType.ts @@ -0,0 +1,49 @@ +export interface NasaImageAndVideoLibraryType { + collection: { + version: string; + href: string; + items: NasaImageAndVideoLibraryType[]; + metadata: { + total_hits: number; + }, + links: { + href: string; + rel: string; + prompt: string; + }[]; + } +} + +export interface NasaImageAndVideoLibraryItemType { + href: string; + data: { + center: string; + date_created: string; + description: string; + description_508: string; + keywords: string[]; + location: string; + media_type: string; + nasa_id: string; + photographer: string; + title: string; + }[]; + links: { + href: string; + rel: string; + render: string; + width: number; + size: number; + height: number; + }[]; +} + +export interface NasaImageAndVideoLibraryItemAssetType { + collection: { + version: string; + href: string; + items: { + href: string; + }[]; + } +} \ No newline at end of file From 97a7fb89fb9cefab41e5387208c344c8c1b3d7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=B3czi=20Levente?= Date: Thu, 4 Dec 2025 19:22:29 +0200 Subject: [PATCH 2/9] lint fix --- src/api/nasaEpic.api.ts | 1 - src/api/nasaImageAndVideoLibrary.api.ts | 7 ++++--- src/components/NavigationBar.tsx | 9 ++++++++- src/types/NasaImageAndVideoLibraryType.ts | 8 ++++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/api/nasaEpic.api.ts b/src/api/nasaEpic.api.ts index b310f5c..1270863 100644 --- a/src/api/nasaEpic.api.ts +++ b/src/api/nasaEpic.api.ts @@ -2,7 +2,6 @@ import axios from 'axios'; import type { NasaEpicDataType, NasaEpicDataTypesForDates } from '../types/NasaEpicDataTypes.ts'; - export const nasaEpicApi = axios.create({ baseURL: `https://epic.gsfc.nasa.gov/api/natural/`, headers: { diff --git a/src/api/nasaImageAndVideoLibrary.api.ts b/src/api/nasaImageAndVideoLibrary.api.ts index babf6b5..cb1a282 100644 --- a/src/api/nasaImageAndVideoLibrary.api.ts +++ b/src/api/nasaImageAndVideoLibrary.api.ts @@ -1,7 +1,9 @@ import axios from 'axios'; -import type { NasaImageAndVideoLibraryItemAssetType, NasaImageAndVideoLibraryType } from '../types/NasaImageAndVideoLibraryType.ts'; - +import type { + NasaImageAndVideoLibraryItemAssetType, + NasaImageAndVideoLibraryType, +} from '../types/NasaImageAndVideoLibraryType.ts'; export const nasaEpicApi = axios.create({ baseURL: `https://images-api.nasa.gov`, @@ -20,4 +22,3 @@ export async function searchNasaLibraryAsset(nasaId: string): Promise(`/asset/${nasaId}`); return res.data; } - diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index 61c83ca..7c9760d 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -15,6 +15,7 @@ function NavigationBar() { e.preventDefault(); const data = await searchNasaLibrary(query); + // eslint-disable-next-line no-console console.log(data); }; @@ -22,7 +23,13 @@ function NavigationBar() {
void searchHandler(e)}> - setQuery(e.target.value)} /> + setQuery(e.target.value)} + /> Logo diff --git a/src/types/NasaImageAndVideoLibraryType.ts b/src/types/NasaImageAndVideoLibraryType.ts index ad463c5..b56fc1c 100644 --- a/src/types/NasaImageAndVideoLibraryType.ts +++ b/src/types/NasaImageAndVideoLibraryType.ts @@ -5,13 +5,13 @@ export interface NasaImageAndVideoLibraryType { items: NasaImageAndVideoLibraryType[]; metadata: { total_hits: number; - }, + }; links: { href: string; rel: string; prompt: string; }[]; - } + }; } export interface NasaImageAndVideoLibraryItemType { @@ -45,5 +45,5 @@ export interface NasaImageAndVideoLibraryItemAssetType { items: { href: string; }[]; - } -} \ No newline at end of file + }; +} From 66bcac478875e673855b03c99174cfa383439c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=B3czi=20Levente?= Date: Mon, 8 Dec 2025 20:20:57 +0200 Subject: [PATCH 3/9] basic search with results top5 displayed done --- src/components/NavigationBar.module.css | 1 + src/components/NavigationBar.tsx | 18 +++++-- .../search/SearchResults.module.css | 25 ++++++++++ src/components/search/SearchResults.tsx | 49 +++++++++++++++++++ src/types/NasaImageAndVideoLibraryType.ts | 2 +- 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 src/components/search/SearchResults.module.css create mode 100644 src/components/search/SearchResults.tsx diff --git a/src/components/NavigationBar.module.css b/src/components/NavigationBar.module.css index 8dfd117..841da9a 100644 --- a/src/components/NavigationBar.module.css +++ b/src/components/NavigationBar.module.css @@ -33,6 +33,7 @@ margin-right: auto; max-width: 130px; width: 100%; + position: relative; } /* --------------------------------------------------------- */ diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index 7c9760d..1491a94 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -6,17 +6,26 @@ import Nav from 'react-bootstrap/Nav'; import Navbar from 'react-bootstrap/Navbar'; import { searchNasaLibrary } from '../api/nasaImageAndVideoLibrary.api.ts'; +import type { NasaImageAndVideoLibraryType } from '../types/NasaImageAndVideoLibraryType.ts'; import style from './NavigationBar.module.css'; +import SearchResults from './search/SearchResults.tsx'; function NavigationBar() { const [query, setQuery] = useState(''); + const [results, setResults] = useState(null); + const [showResults, setShowResults] = useState(false); - const searchHandler = async (e: FormEvent) => { + const searchHandler = (e: FormEvent) => { e.preventDefault(); - const data = await searchNasaLibrary(query); - // eslint-disable-next-line no-console - console.log(data); + searchNasaLibrary(query).then(results => { + setResults(results); + setShowResults(true); + // eslint-disable-next-line no-console + console.log(results); + }).catch(error => { + console.error('Error fetching search results:', error); + }); }; return ( @@ -30,6 +39,7 @@ function NavigationBar() { aria-label="Search" onChange={(e) => setQuery(e.target.value)} /> + {showResults ? : null} Logo diff --git a/src/components/search/SearchResults.module.css b/src/components/search/SearchResults.module.css new file mode 100644 index 0000000..18a9620 --- /dev/null +++ b/src/components/search/SearchResults.module.css @@ -0,0 +1,25 @@ +.searchResultsContainer { + position: absolute; + top: 100%; + left: 0; + width: 250px; + max-height: 400px; + overflow-y: auto; + background-color: #fff; + border: 1px solid #ced4da; + border-radius: 0.375rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + z-index: 1000; + margin-top: 5px; +} + +.searchResultsItem { + padding: 10px; + color: #212529; + border-bottom: 1px solid #e9ecef; + cursor: pointer; + font-size: 0.9rem; + transition: background-color 0.2s; + text-decoration: none; + display: block; +} \ No newline at end of file diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx new file mode 100644 index 0000000..dcc34ed --- /dev/null +++ b/src/components/search/SearchResults.tsx @@ -0,0 +1,49 @@ +import type { NasaImageAndVideoLibraryItemType, NasaImageAndVideoLibraryType } from '../../types/NasaImageAndVideoLibraryType.ts'; +import style from "./SearchResults.module.css"; + +interface SearchResultsProps { + results: NasaImageAndVideoLibraryType | null; + +} + +function SearchResults(props: SearchResultsProps) { + const { results } = props; + const items: NasaImageAndVideoLibraryItemType[] = results ? getDistinctItemsByTitle(results.collection.items) : []; + + + + return ( +
+ {items.length === 0 ? ( +

Error FIX IT

+ ) : ( + /*Only the top 5 distinct based on title*/ + items.slice(0, 5).map((item, i) => ( +
+

{item.data[0].title}

+
+ )) + )} +
+ ); +} + +function getDistinctItemsByTitle(items: NasaImageAndVideoLibraryItemType[]): NasaImageAndVideoLibraryItemType[] { + + const allValues = items.map(item => item.data[0].title); + + const uniqueValues = Array.from(new Set(allValues)); + + const distinctItems: NasaImageAndVideoLibraryItemType[] = []; + + uniqueValues.forEach(title => { + const foundItem = items.find(item => item.data[0].title === title); + if (foundItem) { + distinctItems.push(foundItem); + } + }); + + return distinctItems; +} + +export default SearchResults; \ No newline at end of file diff --git a/src/types/NasaImageAndVideoLibraryType.ts b/src/types/NasaImageAndVideoLibraryType.ts index b56fc1c..4e4f25c 100644 --- a/src/types/NasaImageAndVideoLibraryType.ts +++ b/src/types/NasaImageAndVideoLibraryType.ts @@ -2,7 +2,7 @@ export interface NasaImageAndVideoLibraryType { collection: { version: string; href: string; - items: NasaImageAndVideoLibraryType[]; + items: NasaImageAndVideoLibraryItemType[]; metadata: { total_hits: number; }; From 73e7daa2d13c432a0d005f4a981c0f88ae3e6c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=B3czi=20Levente?= Date: Mon, 8 Dec 2025 20:21:16 +0200 Subject: [PATCH 4/9] lint fix --- src/components/NavigationBar.tsx | 20 ++++++++++--------- .../search/SearchResults.module.css | 2 +- src/components/search/SearchResults.tsx | 19 +++++++++--------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index 1491a94..8b0097f 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -18,14 +18,16 @@ function NavigationBar() { const searchHandler = (e: FormEvent) => { e.preventDefault(); - searchNasaLibrary(query).then(results => { - setResults(results); - setShowResults(true); - // eslint-disable-next-line no-console - console.log(results); - }).catch(error => { - console.error('Error fetching search results:', error); - }); + searchNasaLibrary(query) + .then((results) => { + setResults(results); + setShowResults(true); + // eslint-disable-next-line no-console + console.log(results); + }) + .catch((error) => { + console.error('Error fetching search results:', error); + }); }; return ( @@ -39,7 +41,7 @@ function NavigationBar() { aria-label="Search" onChange={(e) => setQuery(e.target.value)} /> - {showResults ? : null} + {showResults ? : null} Logo diff --git a/src/components/search/SearchResults.module.css b/src/components/search/SearchResults.module.css index 18a9620..59b5aa4 100644 --- a/src/components/search/SearchResults.module.css +++ b/src/components/search/SearchResults.module.css @@ -22,4 +22,4 @@ transition: background-color 0.2s; text-decoration: none; display: block; -} \ No newline at end of file +} diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index dcc34ed..e302440 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -1,17 +1,17 @@ -import type { NasaImageAndVideoLibraryItemType, NasaImageAndVideoLibraryType } from '../../types/NasaImageAndVideoLibraryType.ts'; -import style from "./SearchResults.module.css"; +import type { + NasaImageAndVideoLibraryItemType, + NasaImageAndVideoLibraryType, +} from '../../types/NasaImageAndVideoLibraryType.ts'; +import style from './SearchResults.module.css'; interface SearchResultsProps { results: NasaImageAndVideoLibraryType | null; - } function SearchResults(props: SearchResultsProps) { const { results } = props; const items: NasaImageAndVideoLibraryItemType[] = results ? getDistinctItemsByTitle(results.collection.items) : []; - - return (
{items.length === 0 ? ( @@ -29,15 +29,14 @@ function SearchResults(props: SearchResultsProps) { } function getDistinctItemsByTitle(items: NasaImageAndVideoLibraryItemType[]): NasaImageAndVideoLibraryItemType[] { - - const allValues = items.map(item => item.data[0].title); + const allValues = items.map((item) => item.data[0].title); const uniqueValues = Array.from(new Set(allValues)); const distinctItems: NasaImageAndVideoLibraryItemType[] = []; - uniqueValues.forEach(title => { - const foundItem = items.find(item => item.data[0].title === title); + uniqueValues.forEach((title) => { + const foundItem = items.find((item) => item.data[0].title === title); if (foundItem) { distinctItems.push(foundItem); } @@ -46,4 +45,4 @@ function getDistinctItemsByTitle(items: NasaImageAndVideoLibraryItemType[]): Nas return distinctItems; } -export default SearchResults; \ No newline at end of file +export default SearchResults; From 61259a366780a6d49e17a3085d7f980f48f1a2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=B3czi=20Levente?= Date: Mon, 8 Dec 2025 20:39:29 +0200 Subject: [PATCH 5/9] now if clicked on an item it will be console loged --- src/components/search/SearchResults.tsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index e302440..ef1ca63 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -12,6 +12,13 @@ function SearchResults(props: SearchResultsProps) { const { results } = props; const items: NasaImageAndVideoLibraryItemType[] = results ? getDistinctItemsByTitle(results.collection.items) : []; + const clickedItem = (item: NasaImageAndVideoLibraryItemType) => { + // eslint-disable-next-line no-console + console.log(item.data[0]); + // eslint-disable-next-line no-console + item.links.forEach((link) => console.log(link.href)); + }; + return (
{items.length === 0 ? ( @@ -19,7 +26,18 @@ function SearchResults(props: SearchResultsProps) { ) : ( /*Only the top 5 distinct based on title*/ items.slice(0, 5).map((item, i) => ( -
+
clickedItem(item)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + clickedItem(item); + } + }} + >

{item.data[0].title}

)) From 97ca921136e83a93fb455082cf334cc102d6a0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=B3czi=20Levente?= Date: Sat, 13 Dec 2025 15:52:20 +0200 Subject: [PATCH 6/9] page for the search result done --- src/Root.tsx | 2 + src/api/nasaImageAndVideoLibrary.api.ts | 9 +++- src/components/NavigationBar.tsx | 8 +++- src/components/search/SearchResults.tsx | 14 +++++-- src/hooks/useNasaItem.ts | 50 +++++++++++++++++++++++ src/pages/SearchItem.tsx | 22 ++++++++++ src/types/NasaImageAndVideoLibraryType.ts | 9 ++++ 7 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 src/hooks/useNasaItem.ts create mode 100644 src/pages/SearchItem.tsx diff --git a/src/Root.tsx b/src/Root.tsx index 9b44189..4ae3af6 100644 --- a/src/Root.tsx +++ b/src/Root.tsx @@ -10,6 +10,7 @@ import EpicDataPage from './pages/EpicDataPage.tsx'; import EpicDataPostPage from './pages/EpicDataPostPage.tsx'; import HomePage from './pages/HomePage.tsx'; import ImageOfTheDayPage from './pages/ImageOfTheDayPage.tsx'; +import SearchItem from './pages/SearchItem.tsx'; function Root() { return ( @@ -25,6 +26,7 @@ function Root() { } /> } /> } /> + } /> diff --git a/src/api/nasaImageAndVideoLibrary.api.ts b/src/api/nasaImageAndVideoLibrary.api.ts index cb1a282..e5d3013 100644 --- a/src/api/nasaImageAndVideoLibrary.api.ts +++ b/src/api/nasaImageAndVideoLibrary.api.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import type { - NasaImageAndVideoLibraryItemAssetType, + NasaImageAndVideoLibraryItemAssetType, NasaImageAndVideoLibraryItemMetadataType, NasaImageAndVideoLibraryType, } from '../types/NasaImageAndVideoLibraryType.ts'; @@ -22,3 +22,10 @@ export async function searchNasaLibraryAsset(nasaId: string): Promise(`/asset/${nasaId}`); return res.data; } + +export async function searchNasaLibraryMetadata(nasaId: string): Promise { + const res1 = await nasaEpicApi.get<{"location" : string}>(`/metadata/${nasaId}`); + const location: string = res1.data.location; + const res2 = await axios.get(location); + return res2.data; +} diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index 8b0097f..332f552 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -30,6 +30,12 @@ function NavigationBar() { }); }; + const searchClosedOrSearched = () => { + setShowResults(false); + setQuery(''); + setResults(null); + }; + return ( @@ -41,7 +47,7 @@ function NavigationBar() { aria-label="Search" onChange={(e) => setQuery(e.target.value)} /> - {showResults ? : null} + {showResults ? : null} Logo diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index ef1ca63..5b4da45 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -1,3 +1,5 @@ +import { useNavigate } from 'react-router'; + import type { NasaImageAndVideoLibraryItemType, NasaImageAndVideoLibraryType, @@ -6,17 +8,21 @@ import style from './SearchResults.module.css'; interface SearchResultsProps { results: NasaImageAndVideoLibraryType | null; + searchClosedOrSearched: () => void; } function SearchResults(props: SearchResultsProps) { - const { results } = props; + const { results, searchClosedOrSearched } = props; const items: NasaImageAndVideoLibraryItemType[] = results ? getDistinctItemsByTitle(results.collection.items) : []; + const navigate = useNavigate(); - const clickedItem = (item: NasaImageAndVideoLibraryItemType) => { + const clickedItem = async (item: NasaImageAndVideoLibraryItemType) => { // eslint-disable-next-line no-console console.log(item.data[0]); // eslint-disable-next-line no-console item.links.forEach((link) => console.log(link.href)); + searchClosedOrSearched(); + await navigate(`/search/item/${item.data[0].nasa_id}`); }; return ( @@ -29,12 +35,12 @@ function SearchResults(props: SearchResultsProps) {
clickedItem(item)} + onClick={() => void clickedItem(item)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { - clickedItem(item); + void clickedItem(item); } }} > diff --git a/src/hooks/useNasaItem.ts b/src/hooks/useNasaItem.ts new file mode 100644 index 0000000..133f067 --- /dev/null +++ b/src/hooks/useNasaItem.ts @@ -0,0 +1,50 @@ +import { useEffect, useState } from 'react'; + +import { searchNasaLibraryAsset, searchNasaLibraryMetadata } from '../api/nasaImageAndVideoLibrary.api.ts'; +import type { NasaImageAndVideoLibraryItemAssetType, NasaImageAndVideoLibraryItemMetadataType } from '../types/NasaImageAndVideoLibraryType.ts'; + +export function useNasaItem(nasaId: string | undefined) { + const [metadata, setMetadata] = useState(null); + const [assets, setAssets] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [images, setImages] = useState([]); + + useEffect(() => { + if (!nasaId) return; + + let isMounted = true; + + const fetchData = async () => { + setLoading(true); + setError(null); + try { + const [metadataResponse, assetsResponse] = await Promise.all([ + searchNasaLibraryMetadata(nasaId), + searchNasaLibraryAsset(nasaId) + ]); + if (isMounted) { + setMetadata(metadataResponse); + setAssets(assetsResponse); + setImages(assetsResponse.collection.items.map(item => item.href)); + } + } catch (err) { + if (isMounted) { + setError((err as Error).message); + } + } finally { + if (isMounted) { + setLoading(false); + } + } + }; + + void fetchData(); + + return () => { + isMounted = false; + }; + }, [nasaId]); + + return { metadata, assets, images, loading, error }; +} \ No newline at end of file diff --git a/src/pages/SearchItem.tsx b/src/pages/SearchItem.tsx new file mode 100644 index 0000000..00a3b84 --- /dev/null +++ b/src/pages/SearchItem.tsx @@ -0,0 +1,22 @@ +import { useEffect } from 'react'; +import { useParams } from 'react-router'; + +import { useNasaItem } from '../hooks/useNasaItem.ts'; + +function SearchItem() { + const { nasaId } = useParams(); + const {metadata, images, loading} = useNasaItem(nasaId); + + useEffect(() => { + console.log('Assets:', images); + }, [images]); + + return (
+

{metadata?.['AVAIL:Title']}

+

{metadata?.['AVAIL:Description']}

+ {loading ?

Loading...

: null} +
+ ); +} + +export default SearchItem; \ No newline at end of file diff --git a/src/types/NasaImageAndVideoLibraryType.ts b/src/types/NasaImageAndVideoLibraryType.ts index 4e4f25c..2e1ca65 100644 --- a/src/types/NasaImageAndVideoLibraryType.ts +++ b/src/types/NasaImageAndVideoLibraryType.ts @@ -47,3 +47,12 @@ export interface NasaImageAndVideoLibraryItemAssetType { }[]; }; } + +export interface NasaImageAndVideoLibraryItemMetadataType { + "AVAIL:Description": string; + "AVAIL:Title": string; + "AVAIL:Keywords": string[]; + "AVAIL:Location": string; + "AVAIL:DateCreated": string; + "AVAIL:Photographer": string; +} \ No newline at end of file From 1fbe0c5b3b1e19ce4cd5081795ea304454419407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=B3czi=20Levente?= Date: Sat, 13 Dec 2025 19:29:26 +0200 Subject: [PATCH 7/9] Searched page implemented --- src/components/NavigationBar.tsx | 3 +- src/hooks/useNasaItem.ts | 12 ++++++-- src/pages/SearchItem.module.css | 50 ++++++++++++++++++++++++++++++++ src/pages/SearchItem.tsx | 36 ++++++++++++++++++----- 4 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 src/pages/SearchItem.module.css diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index 332f552..7d5a451 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -22,8 +22,6 @@ function NavigationBar() { .then((results) => { setResults(results); setShowResults(true); - // eslint-disable-next-line no-console - console.log(results); }) .catch((error) => { console.error('Error fetching search results:', error); @@ -45,6 +43,7 @@ function NavigationBar() { placeholder="Search" className="me-2" aria-label="Search" + value={query} onChange={(e) => setQuery(e.target.value)} /> {showResults ? : null} diff --git a/src/hooks/useNasaItem.ts b/src/hooks/useNasaItem.ts index 133f067..9085ff6 100644 --- a/src/hooks/useNasaItem.ts +++ b/src/hooks/useNasaItem.ts @@ -3,12 +3,18 @@ import { useEffect, useState } from 'react'; import { searchNasaLibraryAsset, searchNasaLibraryMetadata } from '../api/nasaImageAndVideoLibrary.api.ts'; import type { NasaImageAndVideoLibraryItemAssetType, NasaImageAndVideoLibraryItemMetadataType } from '../types/NasaImageAndVideoLibraryType.ts'; +function returnImagesOnly(assets: NasaImageAndVideoLibraryItemAssetType) { + return assets.collection.items.filter((item) => { + return item.href.endsWith('.jpg') || item.href.endsWith('.png') || item.href.endsWith('.jpeg'); + }); +} + export function useNasaItem(nasaId: string | undefined) { const [metadata, setMetadata] = useState(null); const [assets, setAssets] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [images, setImages] = useState([]); + const [image, setImage] = useState(null); useEffect(() => { if (!nasaId) return; @@ -26,7 +32,7 @@ export function useNasaItem(nasaId: string | undefined) { if (isMounted) { setMetadata(metadataResponse); setAssets(assetsResponse); - setImages(assetsResponse.collection.items.map(item => item.href)); + setImage(returnImagesOnly(assetsResponse)[0]?.href || null); } } catch (err) { if (isMounted) { @@ -46,5 +52,5 @@ export function useNasaItem(nasaId: string | undefined) { }; }, [nasaId]); - return { metadata, assets, images, loading, error }; + return { metadata, assets, image, loading, error }; } \ No newline at end of file diff --git a/src/pages/SearchItem.module.css b/src/pages/SearchItem.module.css new file mode 100644 index 0000000..0ceb95c --- /dev/null +++ b/src/pages/SearchItem.module.css @@ -0,0 +1,50 @@ +.searchItemContainer { + margin-left: 10rem; + margin-top: 5rem; + display: flex; + flex-direction: row; +} + +.leftDiv { + flex: 1; + margin-right: 2rem; +} + +.rightDiv { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + margin-right: 9rem; + position: relative; +} + +.image { + width: 80%; /* fill container width */ + height: auto; /* preserve aspect ratio */ + max-height: 60rem; /* prevents extreme vertical stretching */ + object-fit: contain; +} + + +/* --------------------------------------------------------- */ +/* MOBILE & TABLET FIX (Screen width < 992px) */ +/* --------------------------------------------------------- */ +@media (max-width: 992px) { + .searchItemContainer { + flex-direction: column; + margin-left: 2.8rem; + margin-right: 2.8rem; + margin-top: 2rem; + } + + .leftDiv { + margin-right: 0; + margin-top: 2rem; + } + + .rightDiv { + margin-right: 0 !important; + position: relative; + } +} diff --git a/src/pages/SearchItem.tsx b/src/pages/SearchItem.tsx index 00a3b84..a724c5c 100644 --- a/src/pages/SearchItem.tsx +++ b/src/pages/SearchItem.tsx @@ -1,21 +1,41 @@ import { useEffect } from 'react'; +import { Spinner } from 'react-bootstrap'; import { useParams } from 'react-router'; import { useNasaItem } from '../hooks/useNasaItem.ts'; +import style from './SearchItem.module.css'; function SearchItem() { const { nasaId } = useParams(); - const {metadata, images, loading} = useNasaItem(nasaId); + const { metadata, image, loading } = useNasaItem(nasaId); useEffect(() => { - console.log('Assets:', images); - }, [images]); + console.log('Assets:', image); + }, [image]); - return (
-

{metadata?.['AVAIL:Title']}

-

{metadata?.['AVAIL:Description']}

- {loading ?

Loading...

: null} -
+ if (loading) { + return ( +
+ + Loading... + +
+ ); + } + + return ( +
+
+

{metadata?.['AVAIL:Title']}

+

{metadata?.['AVAIL:Description']}

+
+
+ {loading ?

Loading...

: null} + {!loading && image ?
+ {metadata?.['AVAIL:Title']} +
: null} +
+
); } From 1bb146bdbe7d0266a9093618c89c12d75b43c702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=B3czi=20Levente?= Date: Sat, 13 Dec 2025 19:44:19 +0200 Subject: [PATCH 8/9] Added HTML sanitize for descriptions with HTML content in it --- package.json | 2 ++ src/api/nasaImageAndVideoLibrary.api.ts | 5 ++-- src/components/NavigationBar.tsx | 4 +++- src/components/search/SafeHtml.module.css | 20 ++++++++++++++++ src/components/search/SafeHtml.tsx | 16 +++++++++++++ src/components/search/SearchResults.tsx | 4 ---- src/hooks/useNasaItem.ts | 9 +++++--- src/pages/SearchItem.module.css | 1 - src/pages/SearchItem.tsx | 28 +++++++++++------------ src/types/NasaImageAndVideoLibraryType.ts | 14 ++++++------ yarn.lock | 19 +++++++++++++++ 11 files changed, 89 insertions(+), 33 deletions(-) create mode 100644 src/components/search/SafeHtml.module.css create mode 100644 src/components/search/SafeHtml.tsx diff --git a/package.json b/package.json index eb67434..ff6a70a 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@typescript-eslint/parser": "^8.46.3", "axios": "^1.13.2", "bootstrap": "^5.3.8", + "dompurify": "^3.3.1", "react": "^19.1.1", "react-bootstrap": "^2.10.10", "react-datepicker": "^8.9.0", @@ -29,6 +30,7 @@ "devDependencies": { "@babel/eslint-parser": "^7.28.5", "@eslint/js": "^9.39.1", + "@types/dompurify": "^3.2.0", "@types/node": "^24.6.0", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", diff --git a/src/api/nasaImageAndVideoLibrary.api.ts b/src/api/nasaImageAndVideoLibrary.api.ts index e5d3013..17472aa 100644 --- a/src/api/nasaImageAndVideoLibrary.api.ts +++ b/src/api/nasaImageAndVideoLibrary.api.ts @@ -1,7 +1,8 @@ import axios from 'axios'; import type { - NasaImageAndVideoLibraryItemAssetType, NasaImageAndVideoLibraryItemMetadataType, + NasaImageAndVideoLibraryItemAssetType, + NasaImageAndVideoLibraryItemMetadataType, NasaImageAndVideoLibraryType, } from '../types/NasaImageAndVideoLibraryType.ts'; @@ -24,7 +25,7 @@ export async function searchNasaLibraryAsset(nasaId: string): Promise { - const res1 = await nasaEpicApi.get<{"location" : string}>(`/metadata/${nasaId}`); + const res1 = await nasaEpicApi.get<{ location: string }>(`/metadata/${nasaId}`); const location: string = res1.data.location; const res2 = await axios.get(location); return res2.data; diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index 7d5a451..a715f7e 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -46,7 +46,9 @@ function NavigationBar() { value={query} onChange={(e) => setQuery(e.target.value)} /> - {showResults ? : null} + {showResults ? ( + + ) : null} Logo diff --git a/src/components/search/SafeHtml.module.css b/src/components/search/SafeHtml.module.css new file mode 100644 index 0000000..63d0506 --- /dev/null +++ b/src/components/search/SafeHtml.module.css @@ -0,0 +1,20 @@ +.formattedText { + /* 1. This preserves the \n characters as actual line breaks */ + white-space: pre-wrap; + + /* 2. Standard formatting */ + font-family: inherit; + line-height: 1.6; + color: inherit; +} + +/* 3. Make sure the links inside look nice and don't overflow */ +.formattedText a { + color: #0d6efd; /* Bootstrap Blue */ + text-decoration: none; + word-break: break-word; /* Prevents long URLs from breaking mobile layout */ +} + +.formattedText a:hover { + text-decoration: underline; +} diff --git a/src/components/search/SafeHtml.tsx b/src/components/search/SafeHtml.tsx new file mode 100644 index 0000000..067abd4 --- /dev/null +++ b/src/components/search/SafeHtml.tsx @@ -0,0 +1,16 @@ +import DOMPurify from 'dompurify'; + +import style from './SafeHtml.module.css'; + +interface SafeHtmlProps { + htmlContent: string | undefined; +} + +const SafeHtml = ({ htmlContent }: SafeHtmlProps) => { + const sanitizeFn = (DOMPurify as unknown as { sanitize: (dirty: string) => string }).sanitize; + const cleanHtml: string = sanitizeFn(htmlContent ?? ''); + + return
; +}; + +export default SafeHtml; diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index 5b4da45..a96c6fc 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -17,10 +17,6 @@ function SearchResults(props: SearchResultsProps) { const navigate = useNavigate(); const clickedItem = async (item: NasaImageAndVideoLibraryItemType) => { - // eslint-disable-next-line no-console - console.log(item.data[0]); - // eslint-disable-next-line no-console - item.links.forEach((link) => console.log(link.href)); searchClosedOrSearched(); await navigate(`/search/item/${item.data[0].nasa_id}`); }; diff --git a/src/hooks/useNasaItem.ts b/src/hooks/useNasaItem.ts index 9085ff6..8d03737 100644 --- a/src/hooks/useNasaItem.ts +++ b/src/hooks/useNasaItem.ts @@ -1,7 +1,10 @@ import { useEffect, useState } from 'react'; import { searchNasaLibraryAsset, searchNasaLibraryMetadata } from '../api/nasaImageAndVideoLibrary.api.ts'; -import type { NasaImageAndVideoLibraryItemAssetType, NasaImageAndVideoLibraryItemMetadataType } from '../types/NasaImageAndVideoLibraryType.ts'; +import type { + NasaImageAndVideoLibraryItemAssetType, + NasaImageAndVideoLibraryItemMetadataType, +} from '../types/NasaImageAndVideoLibraryType.ts'; function returnImagesOnly(assets: NasaImageAndVideoLibraryItemAssetType) { return assets.collection.items.filter((item) => { @@ -27,7 +30,7 @@ export function useNasaItem(nasaId: string | undefined) { try { const [metadataResponse, assetsResponse] = await Promise.all([ searchNasaLibraryMetadata(nasaId), - searchNasaLibraryAsset(nasaId) + searchNasaLibraryAsset(nasaId), ]); if (isMounted) { setMetadata(metadataResponse); @@ -53,4 +56,4 @@ export function useNasaItem(nasaId: string | undefined) { }, [nasaId]); return { metadata, assets, image, loading, error }; -} \ No newline at end of file +} diff --git a/src/pages/SearchItem.module.css b/src/pages/SearchItem.module.css index 0ceb95c..8fe44e6 100644 --- a/src/pages/SearchItem.module.css +++ b/src/pages/SearchItem.module.css @@ -26,7 +26,6 @@ object-fit: contain; } - /* --------------------------------------------------------- */ /* MOBILE & TABLET FIX (Screen width < 992px) */ /* --------------------------------------------------------- */ diff --git a/src/pages/SearchItem.tsx b/src/pages/SearchItem.tsx index a724c5c..9e0d822 100644 --- a/src/pages/SearchItem.tsx +++ b/src/pages/SearchItem.tsx @@ -1,7 +1,7 @@ -import { useEffect } from 'react'; import { Spinner } from 'react-bootstrap'; import { useParams } from 'react-router'; +import SafeHtml from '../components/search/SafeHtml.tsx'; import { useNasaItem } from '../hooks/useNasaItem.ts'; import style from './SearchItem.module.css'; @@ -9,17 +9,13 @@ function SearchItem() { const { nasaId } = useParams(); const { metadata, image, loading } = useNasaItem(nasaId); - useEffect(() => { - console.log('Assets:', image); - }, [image]); - if (loading) { return ( -
- - Loading... - -
+
+ + Loading... + +
); } @@ -27,16 +23,18 @@ function SearchItem() {

{metadata?.['AVAIL:Title']}

-

{metadata?.['AVAIL:Description']}

+
{loading ?

Loading...

: null} - {!loading && image ?
- {metadata?.['AVAIL:Title']} -
: null} + {!loading && image ? ( +
+ {metadata?.['AVAIL:Title']} +
+ ) : null}
); } -export default SearchItem; \ No newline at end of file +export default SearchItem; diff --git a/src/types/NasaImageAndVideoLibraryType.ts b/src/types/NasaImageAndVideoLibraryType.ts index 2e1ca65..3102680 100644 --- a/src/types/NasaImageAndVideoLibraryType.ts +++ b/src/types/NasaImageAndVideoLibraryType.ts @@ -49,10 +49,10 @@ export interface NasaImageAndVideoLibraryItemAssetType { } export interface NasaImageAndVideoLibraryItemMetadataType { - "AVAIL:Description": string; - "AVAIL:Title": string; - "AVAIL:Keywords": string[]; - "AVAIL:Location": string; - "AVAIL:DateCreated": string; - "AVAIL:Photographer": string; -} \ No newline at end of file + 'AVAIL:Description': string; + 'AVAIL:Title': string; + 'AVAIL:Keywords': string[]; + 'AVAIL:Location': string; + 'AVAIL:DateCreated': string; + 'AVAIL:Photographer': string; +} diff --git a/yarn.lock b/yarn.lock index f63171c..a493a9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -754,6 +754,13 @@ dependencies: "@babel/types" "^7.28.2" +"@types/dompurify@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-3.2.0.tgz#56610bf3e4250df57744d61fbd95422e07dfb840" + integrity sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg== + dependencies: + dompurify "*" + "@types/estree@1.0.8", "@types/estree@^1.0.6": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" @@ -793,6 +800,11 @@ dependencies: csstype "^3.0.2" +"@types/trusted-types@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== + "@types/warning@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.3.tgz#d1884c8cc4a426d1ac117ca2611bf333834c6798" @@ -1316,6 +1328,13 @@ dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" +dompurify@*, dompurify@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.3.1.tgz#c7e1ddebfe3301eacd6c0c12a4af284936dbbb86" + integrity sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q== + optionalDependencies: + "@types/trusted-types" "^2.0.7" + dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" From 11662ca2b330211287b09700ee3d15ed605a29c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=B3czi=20Levente?= Date: Sat, 13 Dec 2025 20:27:11 +0200 Subject: [PATCH 9/9] Added config to dompurify, fixed issues --- src/Root.tsx | 4 ++-- src/api/nasaImageAndVideoLibrary.api.ts | 12 ++++++---- src/components/search/SafeHtml.tsx | 22 +++++++++++++++--- src/components/search/SearchResults.tsx | 23 ++++++++----------- src/hooks/useNasaItem.ts | 3 ++- .../{SearchItem.tsx => SearchItemPage.tsx} | 10 ++++---- src/types/NasaImageAndVideoLibraryType.ts | 6 ++--- 7 files changed, 49 insertions(+), 31 deletions(-) rename src/pages/{SearchItem.tsx => SearchItemPage.tsx} (84%) diff --git a/src/Root.tsx b/src/Root.tsx index 4ae3af6..6531ca4 100644 --- a/src/Root.tsx +++ b/src/Root.tsx @@ -10,7 +10,7 @@ import EpicDataPage from './pages/EpicDataPage.tsx'; import EpicDataPostPage from './pages/EpicDataPostPage.tsx'; import HomePage from './pages/HomePage.tsx'; import ImageOfTheDayPage from './pages/ImageOfTheDayPage.tsx'; -import SearchItem from './pages/SearchItem.tsx'; +import SearchItemPage from './pages/SearchItemPage.tsx'; function Root() { return ( @@ -26,7 +26,7 @@ function Root() { } /> } /> } /> - } /> + } /> diff --git a/src/api/nasaImageAndVideoLibrary.api.ts b/src/api/nasaImageAndVideoLibrary.api.ts index 17472aa..5f392aa 100644 --- a/src/api/nasaImageAndVideoLibrary.api.ts +++ b/src/api/nasaImageAndVideoLibrary.api.ts @@ -6,7 +6,7 @@ import type { NasaImageAndVideoLibraryType, } from '../types/NasaImageAndVideoLibraryType.ts'; -export const nasaEpicApi = axios.create({ +export const nasaImageAndVideoLibraryApi = axios.create({ baseURL: `https://images-api.nasa.gov`, headers: { Accept: 'application/json', @@ -15,17 +15,21 @@ export const nasaEpicApi = axios.create({ }); export async function searchNasaLibrary(query: string): Promise { - const res = await nasaEpicApi.get(`/search?q=${query}`); + const res = await nasaImageAndVideoLibraryApi.get('/search', { + params: { q: query }, + }); return res.data; } export async function searchNasaLibraryAsset(nasaId: string): Promise { - const res = await nasaEpicApi.get(`/asset/${nasaId}`); + const res = await nasaImageAndVideoLibraryApi.get( + `/asset/${encodeURIComponent(nasaId)}`, + ); return res.data; } export async function searchNasaLibraryMetadata(nasaId: string): Promise { - const res1 = await nasaEpicApi.get<{ location: string }>(`/metadata/${nasaId}`); + const res1 = await nasaImageAndVideoLibraryApi.get<{ location: string }>(`/metadata/${encodeURIComponent(nasaId)}`); const location: string = res1.data.location; const res2 = await axios.get(location); return res2.data; diff --git a/src/components/search/SafeHtml.tsx b/src/components/search/SafeHtml.tsx index 067abd4..f03fc1d 100644 --- a/src/components/search/SafeHtml.tsx +++ b/src/components/search/SafeHtml.tsx @@ -3,12 +3,28 @@ import DOMPurify from 'dompurify'; import style from './SafeHtml.module.css'; interface SafeHtmlProps { - htmlContent: string | undefined; + htmlContent?: string; +} + +interface DomPurifyConfig { + ALLOWED_TAGS?: string[]; + ALLOWED_ATTR?: string[]; + [key: string]: unknown; } const SafeHtml = ({ htmlContent }: SafeHtmlProps) => { - const sanitizeFn = (DOMPurify as unknown as { sanitize: (dirty: string) => string }).sanitize; - const cleanHtml: string = sanitizeFn(htmlContent ?? ''); + const sanitizeFn = ( + DOMPurify as unknown as { + sanitize: (dirty: string, config?: DomPurifyConfig) => string; + } + ).sanitize; + + const DOMPURIFY_CONFIG: DomPurifyConfig = { + ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li', 'p', 'br', 'span', 'code', 'pre'], + ALLOWED_ATTR: ['href', 'title', 'target', 'rel', 'class'], + }; + + const cleanHtml: string = sanitizeFn(htmlContent ?? '', DOMPURIFY_CONFIG); return
; }; diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index a96c6fc..0110a43 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -36,6 +36,9 @@ function SearchResults(props: SearchResultsProps) { tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { + if (e.key === ' ') { + e.preventDefault(); + } void clickedItem(item); } }} @@ -49,20 +52,14 @@ function SearchResults(props: SearchResultsProps) { } function getDistinctItemsByTitle(items: NasaImageAndVideoLibraryItemType[]): NasaImageAndVideoLibraryItemType[] { - const allValues = items.map((item) => item.data[0].title); - - const uniqueValues = Array.from(new Set(allValues)); - - const distinctItems: NasaImageAndVideoLibraryItemType[] = []; - - uniqueValues.forEach((title) => { - const foundItem = items.find((item) => item.data[0].title === title); - if (foundItem) { - distinctItems.push(foundItem); + const titleMap = new Map(); + for (const item of items) { + const title = item.data[0].title; + if (!titleMap.has(title)) { + titleMap.set(title, item); } - }); - - return distinctItems; + } + return Array.from(titleMap.values()); } export default SearchResults; diff --git a/src/hooks/useNasaItem.ts b/src/hooks/useNasaItem.ts index 8d03737..c082311 100644 --- a/src/hooks/useNasaItem.ts +++ b/src/hooks/useNasaItem.ts @@ -35,7 +35,8 @@ export function useNasaItem(nasaId: string | undefined) { if (isMounted) { setMetadata(metadataResponse); setAssets(assetsResponse); - setImage(returnImagesOnly(assetsResponse)[0]?.href || null); + const images = returnImagesOnly(assetsResponse); + setImage(images.length > 0 ? images[0].href : null); } } catch (err) { if (isMounted) { diff --git a/src/pages/SearchItem.tsx b/src/pages/SearchItemPage.tsx similarity index 84% rename from src/pages/SearchItem.tsx rename to src/pages/SearchItemPage.tsx index 9e0d822..16f6896 100644 --- a/src/pages/SearchItem.tsx +++ b/src/pages/SearchItemPage.tsx @@ -5,9 +5,9 @@ import SafeHtml from '../components/search/SafeHtml.tsx'; import { useNasaItem } from '../hooks/useNasaItem.ts'; import style from './SearchItem.module.css'; -function SearchItem() { +function SearchItemPage() { const { nasaId } = useParams(); - const { metadata, image, loading } = useNasaItem(nasaId); + const { metadata, image, loading, error } = useNasaItem(nasaId); if (loading) { return ( @@ -21,13 +21,13 @@ function SearchItem() { return (
+ {error ?

Error

: null}

{metadata?.['AVAIL:Title']}

- {loading ?

Loading...

: null} - {!loading && image ? ( + {image ? (
{metadata?.['AVAIL:Title']}
@@ -37,4 +37,4 @@ function SearchItem() { ); } -export default SearchItem; +export default SearchItemPage; diff --git a/src/types/NasaImageAndVideoLibraryType.ts b/src/types/NasaImageAndVideoLibraryType.ts index 3102680..5eb73e5 100644 --- a/src/types/NasaImageAndVideoLibraryType.ts +++ b/src/types/NasaImageAndVideoLibraryType.ts @@ -20,12 +20,12 @@ export interface NasaImageAndVideoLibraryItemType { center: string; date_created: string; description: string; - description_508: string; + description_508?: string; keywords: string[]; - location: string; + location?: string; media_type: string; nasa_id: string; - photographer: string; + photographer?: string; title: string; }[]; links: {