Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
72 changes: 47 additions & 25 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,38 +1,60 @@
.App {
text-align: center;
}

.App-logo {
height: 40vmin;
pointer-events: none;
}
@import url(https://fonts.googleapis.com/css?family=Open+Sans);

@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
body {
background: #f2f2f2;
font-family: 'Open Sans', sans-serif;
margin: 0;
padding: 16px;
font-family: Arial;
}

.App-header {
background-color: #282c34;
min-height: 100vh;
/* List of images */
.post-list {
display: grid;
grid-template-columns: repeat(3, minmax(100px, 293px));
justify-content: center;
grid-gap: 28px;
}
.toggle-container{
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.post {
cursor: pointer;
position: relative;
display: block;
}
.post-image {
margin: 0;
}
.post-image img {
width: 100%;
vertical-align: top;
}

.App-link {
color: #61dafb;
/* toggle componene */
.toggle {
cursor: pointer;
border: 1px solid black;
border-radius: 4px;
padding: 8px;
}

@keyframes App-logo-spin {
from {
transform: rotate(0deg);
@media screen and (max-width: 600px) {
.post-list {
display: block;
}

.post {
padding-bottom: 16px;
}
to {
transform: rotate(360deg);
}

@media screen and (max-width: 768px) {
.post-list {
grid-template-columns: repeat(2, minmax(100px, 293px));

}
}
56 changes: 42 additions & 14 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@
import logo from './logo.svg';
import { useState, useEffect } from 'react'
import SearchBar from './components/SearchInput'
import Cards from './components/Cards';
import './App.css';
import SavedImagesGallery from './components/SavedImages';

const NASA_URL = 'https://images-api.nasa.gov/search?q='

function App() {
const [ images, setImages ] = useState([])
const [ searchInput, setSearchInput ] = useState('rings')
const [ toggle, setToggle ] = useState(false)
const [ loading, setLoading ] = useState(false)

useEffect(() => {
const fetchData = async() => {
setLoading(true)
const res = await fetch(`${NASA_URL}${searchInput}&media_type=image`)
const data = await res.json()

setImages(data.collection.items.slice(0, 25))
setLoading(false)
}

fetchData()
}, [searchInput])

return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
<SearchBar handleSearchInput={setSearchInput} />

<div className='imageContainer'>
<div
className='toggle-container'
>
Learn React
</a>
</header>
<div className='toggle' onClick={() => setToggle(!toggle)}>
{toggle ? 'See Nasa Search Results' : 'See Saved Images'}
</div>
</div>
{images.length === 0 && (
<div className='toggle-container'>No images with that name. Try with some Keywords such as "Space", "Moon", "Earth"</div>
)}
{loading && <div className='toggle-container'>Loading...</div>}
{(!toggle && !loading) && <Cards images={images}/>}

{toggle && <SavedImagesGallery />}

</div>
</div>
);
}
Expand Down
51 changes: 51 additions & 0 deletions src/components/Cards/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import './style.css'
function Cards({ images }) {

// move this to a hook
const handleSaveImage = (href, title) => {
const imageToSave = {
href,
title
}

const storedImages = window.localStorage.getItem("saved_images")

if(!storedImages) {
return window.localStorage.setItem("saved_images", JSON.stringify([imageToSave]))
}
const parsedStoredImages = JSON.parse(storedImages)

const objectToInsert = [
...parsedStoredImages,
imageToSave
]

window.localStorage.setItem("saved_images", JSON.stringify(objectToInsert))
}
return (
<div className='post-list'>
{images.map( (image, index) => (
<div className='post' key={index}>
<figure className='post-image'>
<img src={image.links[0].href} alt={image.data[0].title}/>
</figure>
<div>
<div className='download-button-container'>
<div
className='download-button'
onClick={() => handleSaveImage(
image.links[0].href,
image.data[0].title
)}
>
Save Image
</div>
</div>
</div>
</div>
))}
</div>
);
}

export default Cards;
8 changes: 8 additions & 0 deletions src/components/Cards/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.download-button-container {
padding-top: 8px;
}
.download-button {
font-weight: bold;
cursor: pointer;
text-decoration: underline;
}
32 changes: 32 additions & 0 deletions src/components/SavedImages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useState, useEffect } from 'react'
import './style.css';

function SavedImagesGallery() {
const [savedImages, setSavedImages] = useState([])

useEffect(() => {
const data = window.localStorage.getItem("saved_images")
if(data) {
setSavedImages(JSON.parse(data))
}

}, [])

return (
<div className='post-list'>
{savedImages.map( (image, index) => (
<div className='post' key={index}>
<figure className='post-image'>
<img src={image.href} alt={image.title}/>
</figure>
<div>
<span>{image.title}</span>

</div>
</div>
))}
</div>
);
}

export default SavedImagesGallery;
Empty file.
50 changes: 50 additions & 0 deletions src/components/SearchInput/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useState } from 'react'
import './style.css';

import useUserLastSearch from '../../hooks/useUserLastSearch';

function SearchBar({ handleSearchInput }) {
const [ keyword, setKeyword ] = useState('')
const { handleUserLastSearch, userLastSearch} = useUserLastSearch({ searchInput: keyword})

return (
<div>
<div className='search-container'>
<div className='search'>
<input
className='search-term'
placeholder='Search a nasa related image'
type='text'
onChange={(e) => setKeyword(e.target.value)}
value={keyword}
/>
<button
className='search-button'
onClick={() => {
handleSearchInput(keyword)
handleUserLastSearch(keyword)
}}
>
Search
</button>
</div>


{ userLastSearch.length > 0 && (
<div className='search-results'>
{userLastSearch.map(lastSearch => (
<div className='search-item' onClick={() => {
setKeyword(lastSearch)
handleSearchInput(keyword)
}}>
{lastSearch}
</div>
))}
</div>
)}
</div>
</div>
);
}

export default SearchBar;
58 changes: 58 additions & 0 deletions src/components/SearchInput/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.search {
width: 100%;
position: relative;
display: flex;
}

.search-term {
width: 100%;
border: 1px solid #041C32;
border-right: none;
padding: 7px;
height: 20px;
border-radius: 5px 0 0 5px;
outline: none;
color: #9DBFAF;
}

.search-term:focus{
color: #041C32;
}

.search-button {
width: 100px;
height: 36px;
border: 1px solid #041C32;
background-color: #fff;
color: #041C32;
text-align: center;
border-radius: 0 5px 5px 0;
cursor: pointer;
font-size: 20px;
}

.search-results {
padding-top: 8px;
border: 1px solid #041C32;
border-radius: 4px;

}

/*Resize the wrap to see the search bar change!*/
.search-container{
width: 50%;
margin: 0 auto;
}

.search-item {
width: 100%;
padding: 4px;
cursor: pointer;

}
@media screen and (max-width: 600px) {
.search-container{
width: 100%;
margin: 0 auto;
}
}
23 changes: 23 additions & 0 deletions src/hooks/useUserLastSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useState } from 'react'

const useUserLastSearch = ({
searchInput
}) => {
const [userLastSearch, setUserLastSearch] = useState([])

const handleUserLastSearch = () => {
if(userLastSearch.length === 5) {
// if array is equal than 5 remove the first element to insert the new one
setUserLastSearch(userLastSearch => userLastSearch.filter((_, i) => i !== 0))
}

setUserLastSearch(userLastSearch => [...userLastSearch, searchInput])
}

return {
handleUserLastSearch,
userLastSearch
}
}

export default useUserLastSearch