diff --git a/src/assets/icons/heart-icon.png b/src/assets/icons/heart-icon.png new file mode 100644 index 0000000..a1395a4 Binary files /dev/null and b/src/assets/icons/heart-icon.png differ diff --git a/src/assets/icons/search-logo.png b/src/assets/icons/search-logo.png new file mode 100644 index 0000000..c46d7d9 Binary files /dev/null and b/src/assets/icons/search-logo.png differ diff --git a/src/assets/icons/search.png b/src/assets/icons/search.png new file mode 100644 index 0000000..d5430b7 Binary files /dev/null and b/src/assets/icons/search.png differ diff --git a/src/components/App.tsx b/src/components/App.tsx index e8c7d74..29facec 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,20 +1,124 @@ -import React, { FC } from 'react' +import React, { FC, useState, useEffect } from 'react' import styled from '@emotion/styled' import Header from './Header' +import Dogs from './Dogs' +import Search from './Search' +import FavDogList from './FavDogList' + +interface BreedImage { + message: string[] + status: string +} +interface FavoriteBreed { + breed: string + images: string[] +} const App: FC = () => { + const [dogsdata, setDogsData] = useState([]) + const [selectedBreed, setSelectedBreed] = useState('') + const [dogImages, setDogImages] = useState([]) + const [searchQuery, setSearchQuery] = useState('') + const [favorites, setFavorites] = useState([]) + const [loading, setLoading] = useState(false) + const url = `https://dog.ceo/api/breed/hound/images` + + useEffect(() => { + const fetchDogs = async () => { + const res = await fetch(url) + const data = await res.json() + const breedList: string[] = await Object.keys(data.message) + setDogsData(breedList) + } + fetchDogs() + }, []) + + useEffect(() => { + if (selectedBreed) { + setLoading(true) + fetch(`https://dog.ceo/api/breed/${selectedBreed}/images`) + .then((response) => response.json()) + .then((data: BreedImage) => { + setDogImages(data.message.slice(0, 10)) + setSearchQuery('') + }) + .catch((error) => console.error(error)) + .finally(() => setLoading(false)) + } + }, [selectedBreed]) + + const handleDogSearch = () => { + if (searchQuery) { + setSelectedBreed(searchQuery) + } + } + + const addFavBreed = () => { + if (selectedBreed && !favorites.some((favorite) => favorite.breed === selectedBreed)) { + const breedImages = dogImages.map((image) => image) + setFavorites((prevFavorites) => [ + ...prevFavorites, + { breed: selectedBreed, images: breedImages }, + ]) + } + alert('Add to fav list..') + } + + const fetchFavoriteImages = async () => { + try { + const imageData = favorites.map((favourite) => + fetch(`https://dog.ceo/api/breed/${favourite}/images/random`) + .then((res) => res.json()) + .then((data) => data.message), + ) + + const favoriteImages = await Promise.all(imageData) + setDogImages(favoriteImages) + } catch (error) { + console.log(error) + } + } + + useEffect(() => { + fetchFavoriteImages() + }, [favorites]) + + const removeFromFavorites = (breed: string) => { + setFavorites((prevFavorites) => prevFavorites.filter((favorite) => favorite.breed !== breed)) + } + return (
- {/* Happy coding! */} + + +
+ ) } +const HR = styled.hr({ + width: '780px', + height: '2px', + color: '#DADADA', +}) + const Container = styled.div({ - margin: '0 auto', + padding: '20%', height: '100%', - width: '560px', + width: '600px', paddingTop: '60px', }) diff --git a/src/components/Dog.tsx b/src/components/Dog.tsx new file mode 100644 index 0000000..450ae36 --- /dev/null +++ b/src/components/Dog.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import styled from '@emotion/styled' + +const Dog = ({ image, favorites, selectedBreed, addFavBreed, removeFromFavorites }) => { + return ( + + {`${selectedBreed}`} + + {favorites.includes(selectedBreed) || favorites.includes(image) ? ( + removeFromFavorites(selectedBreed)}>❤️ + ) : ( + 🤍 + )} + + ) +} + +const ImgContainer = styled.div({ + width: '250px', + height: '350px', + position: 'relative', +}) + +const Image = styled.img({ + width: '250px', + height: '250px', + borderRadius: '5px', + object: 'contain', + aspectRatio: '16/9', + position: 'relative', +}) + +const HeartIcon = styled.span({ + position: 'absolute', + bottom: '100px', + right: '10px', + fontSize: '20px', + color: 'red', + cursor: 'pointer', + border: 'none', + outline: 'none', +}) + +export default Dog diff --git a/src/components/Dogs.tsx b/src/components/Dogs.tsx new file mode 100644 index 0000000..3a4c37d --- /dev/null +++ b/src/components/Dogs.tsx @@ -0,0 +1,48 @@ +import React from 'react' +import Dog from './Dog' +import styled from '@emotion/styled' + +const Dogs = ({ + dogImages, + setSelectedBreed, + favorites, + selectedBreed, + removeFromFavorites, + addFavBreed, +}) => { + return ( + <> + {setSelectedBreed && ( + + {dogImages && Array.isArray(dogImages) + ? dogImages?.map((image, index) => ( + + )) + : null} + + )} + + ) +} + +const DogContainer = styled.div({ + display: 'grid', + gridTemplateColumns: 'repeat(3,1fr)', + gap: '20px', + paddingTop: '40px', +}) + +const Para = styled.p({ + width: '500px', + textAlign: 'center', + fontSize: '30px', +}) + +export default Dogs diff --git a/src/components/FavDogList.tsx b/src/components/FavDogList.tsx new file mode 100644 index 0000000..45e339d --- /dev/null +++ b/src/components/FavDogList.tsx @@ -0,0 +1,65 @@ +import React from 'react' +import styled from '@emotion/styled' +import Loader from './Loader' + +const FavDogList = ({ favorites, removeFromFavorites }) => { + return ( +
+ +

Favorites

+ {favorites.length > 0 ? ( + + {favorites.map((favorite) => ( + + {favorite.images.map((image) => ( +
+ + removeFromFavorites(favorite.breed)}>❤️ +
+ ))} +
+ ))} +
+ ) : ( + + )} +
+
+ ) +} + +const FavoritesContainer = styled.div({ + width: '780px', + marginTop: '50px', +}) + +const FavoriteImageContainer = styled.div({ + display: 'flex', + flexWrap: 'wrap', + gap: '10px', +}) + +const FavImgContainer = styled.div({ + display: 'flex', + flexWrap: 'wrap', + gap: '5px', +}) + +const FavoriteImage = styled.img({ + maxWidth: '150px', + maxHeight: '150px', + objectFit: 'cover', + borderRadius: '5px', + aspectRatio: '16/9', + position: 'relative', +}) +const FavIcon = styled.span({ + cursor: 'pointer', + marginleft: '5px', + color: 'red', + position: 'absolute', + right: '5px', + bottom: '5px', +}) + +export default FavDogList diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 275fbf8..856e89a 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -12,13 +12,14 @@ const Header: FC = () => { } const Container = styled.div({ + width: '780px', display: 'flex', justifyContent: 'space-between', }) const Title = styled.h1({ fontWeight: 'bold', - fontSize: '24px', + fontSize: '28px', lineHeight: '33px', }) diff --git a/src/components/Loader.tsx b/src/components/Loader.tsx new file mode 100644 index 0000000..4b3443a --- /dev/null +++ b/src/components/Loader.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import styled from '@emotion/styled' +const Loader = () => { + return +} + +const Spinner = styled.div` + border: 4px solid rgba(0, 0, 0, 0.1); + border-top: 4px solid #000; + border-radius: 50%; + width: 30px; + height: 30px; + animation: spin 1s linear infinite; + margin: 20px auto; + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } +` + +export default Loader diff --git a/src/components/Search.tsx b/src/components/Search.tsx new file mode 100644 index 0000000..a723d3e --- /dev/null +++ b/src/components/Search.tsx @@ -0,0 +1,66 @@ +import styled from '@emotion/styled' +import React from 'react' +import searchIcon from '../assets/icons/search-logo.png' + +function Search({ searchQuery, setSearchQuery, handleDogSearch }) { + const handleSearch = (e) => { + setSearchQuery(e.target.value) + } + return ( + + + + + ) +} + +const SearchContainer = styled.div({ + width: '780px', + display: 'flex', + justifyContent: 'space-between', + marginTop: '20px', +}) + +const Input = styled.input({ + width: '100%', + background: '#F7F7F7', + height: '36px', + fontSize: '18px', + padding: '25px', + borderRadius: '4px', + border: 'none', + outline: 'none', +}) + +const Button = styled.button({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '200px', + background: '#0794E3', + fontSize: '18px', + color: '#fff', + height: '36px', + padding: '25px', + borderRadius: '4px', + border: 'none', + outline: 'none', + cursor: 'pointer', +}) + +const Img = styled.img({ + width: '25px', + height: '25px', + color: '#fff', + marginRight: '20px', +}) + +export default Search diff --git a/yarn.lock b/yarn.lock index 2b88693..7d2000a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10214,10 +10214,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.6.4: - version "3.9.9" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674" - integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w== +typescript@4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" + integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== uglify-js@3.4.x: version "3.4.10"