Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

dog breed challenge #67

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
Binary file added src/assets/icons/heart-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icons/search-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icons/search.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
112 changes: 108 additions & 4 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -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<string[]>([])
const [selectedBreed, setSelectedBreed] = useState<string>('')
const [dogImages, setDogImages] = useState<string[]>([])
const [searchQuery, setSearchQuery] = useState<string>('')
const [favorites, setFavorites] = useState<FavoriteBreed[]>([])
const [loading, setLoading] = useState<boolean>(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 (
<Container>
<Header />
{/* Happy coding! */}
<Search
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
handleDogSearch={handleDogSearch}
/>
<Dogs
dogImages={dogImages}
setSelectedBreed={setSelectedBreed}
favorites={favorites}
selectedBreed={selectedBreed}
addFavBreed={addFavBreed}
removeFromFavorites={removeFromFavorites}
/>
<HR />
<FavDogList favorites={favorites} removeFromFavorites={removeFromFavorites} />
</Container>
)
}

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',
})

Expand Down
44 changes: 44 additions & 0 deletions src/components/Dog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react'
import styled from '@emotion/styled'

const Dog = ({ image, favorites, selectedBreed, addFavBreed, removeFromFavorites }) => {
return (
<ImgContainer className="image-wrapper" key={image}>
<Image src={image} alt={`${selectedBreed}`} />

{favorites.includes(selectedBreed) || favorites.includes(image) ? (
<HeartIcon onClick={() => removeFromFavorites(selectedBreed)}>❤️</HeartIcon>
) : (
<HeartIcon onClick={addFavBreed}>🤍</HeartIcon>
)}
</ImgContainer>
)
}

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
48 changes: 48 additions & 0 deletions src/components/Dogs.tsx
Original file line number Diff line number Diff line change
@@ -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 && (
<DogContainer>
{dogImages && Array.isArray(dogImages)
? dogImages?.map((image, index) => (
<Dog
key={index}
favorites={favorites}
image={image}
selectedBreed={selectedBreed}
addFavBreed={addFavBreed}
removeFromFavorites={removeFromFavorites}
/>
))
: null}
</DogContainer>
)}
</>
)
}

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
65 changes: 65 additions & 0 deletions src/components/FavDogList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react'
import styled from '@emotion/styled'
import Loader from './Loader'

const FavDogList = ({ favorites, removeFromFavorites }) => {
return (
<div className="favorites">
<FavoritesContainer>
<h2>Favorites</h2>
{favorites.length > 0 ? (
<FavoriteImageContainer>
{favorites.map((favorite) => (
<FavImgContainer>
{favorite.images.map((image) => (
<div style={{ position: 'relative' }}>
<FavoriteImage key={image} src={image} alt={favorite.breed} />
<FavIcon onClick={() => removeFromFavorites(favorite.breed)}>❤️</FavIcon>
</div>
))}
</FavImgContainer>
))}
</FavoriteImageContainer>
) : (
<Loader />
)}
</FavoritesContainer>
</div>
)
}

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
3 changes: 2 additions & 1 deletion src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
})

Expand Down
26 changes: 26 additions & 0 deletions src/components/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import styled from '@emotion/styled'
const Loader = () => {
return <Spinner />
}

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
66 changes: 66 additions & 0 deletions src/components/Search.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<SearchContainer>
<Input
type="text"
placeholder="Search for a dog"
value={searchQuery}
onChange={handleSearch}
/>
<Button onClick={handleDogSearch}>
<Img src={searchIcon} alt="searchlogo" />
Search
</Button>
</SearchContainer>
)
}

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
Loading