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

Project Finished (Lakshmikanta Patra) #62

Open
wants to merge 2 commits 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
73 changes: 73 additions & 0 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,75 @@
import React, { FC } from 'react'
import styled from '@emotion/styled'
import Header from './Header'
import SearchBar from './SearchBar'
import ImageBox from './ImageBox'
import { useDispatch, useSelector } from 'react-redux'
import { StateProps } from '../redux/reducer'
import Favorite from './Favorite'
import { setDogData } from '../redux/actions'
import { Dispatch } from 'redux'
import Spinner from './Spinner'
import NoDogImage from './NoDogImage'

const App: FC = () => {
const { favorites, dogs } = useSelector((state: StateProps) => state)
const dispatch: Dispatch = useDispatch()

const getDogs = async (breed: string) => {
dispatch(setDogData({ breed, data: [], status: 'loading' }))
try {
const res = await fetch(`https://dog.ceo/api/breed/${breed}/images`)
if (!res.ok) throw new Error()
const data = await res.json()
const imgArr = data.message.slice(1, 11)
const imgData = imgArr.map((el, i) => {
return {
id: i + 1,
url: el,
}
})
return dispatch(setDogData({ breed, data: imgData, status: 'completed' }))
} catch (error) {
dispatch(setDogData({ breed: dogs.breed, data: [], status: 'rejected' }))
}
}

React.useEffect(() => {
getDogs(dogs.breed)
}, [dogs.breed])

console.log(dogs)

return (
<Container>
<Header />
{/* Happy coding! */}
<SearchBar />
{dogs.status === 'loading' ? (
<div
style={{
minHeight: '80vh',
display: 'grid',
placeContent: 'center',
}}
>
<Spinner />
</div>
) : (
<>
{dogs.status === 'rejected' ? (
<NoDogImage message={`No images found for ${dogs.breed} breed`} />
) : (
<>
<h2>Showing images of {dogs.breed} dog</h2>
<ImagesContainer>
{dogs.data && dogs.data.map((dog) => <ImageBox dog={dog} key={dog.id} />)}
</ImagesContainer>
</>
)}
</>
)}
{favorites.length > 0 && <Favorite />}
</Container>
)
}
Expand All @@ -18,4 +81,14 @@ const Container = styled.div({
paddingTop: '60px',
})

const ImagesContainer = styled.div({
display: 'flex',
flexWrap: 'wrap',
gap: '30px',
justifyContent: 'space-between',
paddingBottom: '30px',
marginBottom: '20px',
borderBottom: '2px solid #ccc',
})

export default App
45 changes: 45 additions & 0 deletions src/components/Favorite.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { FC } from 'react'
import { useSelector } from 'react-redux'
import { StateProps } from '../redux/reducer'

import Heart from './Heart'
import ImageBox from './ImageBox'
import styled from '@emotion/styled'

const Favorite: FC = () => {
const dogs = useSelector((state: StateProps) => state.favorites)
return (
<div>
<TitleDiv>
<Heart icon="redHeartIcon" alt="" />
<Title>Favorites</Title>
</TitleDiv>
<ImagesContainer>
{dogs.map((dog) => (
<ImageBox dog={dog} key={dog.id} />
))}
</ImagesContainer>
</div>
)
}

const TitleDiv = styled.div({
display: 'flex',
alignItems: 'center',
})

const Title = styled.h2({
fontWeight: 'bold',
fontSize: '24px',
lineHeight: '33px',
marginLeft: '40px',
})

const ImagesContainer = styled.div({
display: 'flex',
flexWrap: 'wrap',
gap: '16px',
justifyContent: 'flex-start',
})

export default Favorite
58 changes: 58 additions & 0 deletions src/components/ImageBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { FC } from 'react'
import styled from '@emotion/styled'
import Heart from './Heart'
import { Dispatch } from 'redux'
import { useDispatch, useSelector } from 'react-redux'
import { addToFavorite, removeFromFavorite } from '../redux/actions'
import { StateProps } from '../redux/reducer'

const ImageBox: FC<{ dog: { id: number; url: string } }> = ({ dog }) => {
const { favorites } = useSelector((state: StateProps) => state)
const dispatch: Dispatch = useDispatch()

const isImageInFavorites = favorites.find((el) => el.id === dog.id)

const handelClick = () => {
if (isImageInFavorites) {
return dispatch(removeFromFavorite(dog))
} else dispatch(addToFavorite(dog))
}

return (
<ImageContainer onClick={handelClick}>
<Image src={dog.url} alt="dog image" />
<HeartBtn>
<Heart icon={isImageInFavorites ? 'redHeartIcon' : 'whiteHeartIcon'} alt="" />
</HeartBtn>
</ImageContainer>
)
}

const ImageContainer = styled.div({
cursor: 'pointer',
width: '160px',
height: '160px',
position: 'relative',
borderRadius: '10px',
overflow: 'hidden',
transition: 'all 0.3s ease-in-out',

':hover': {
boxShadow: '0 0 10px rgba(0, 0, 0, 0.5)',
transform: 'translateY(-10px)',
},
})

const Image = styled.img({
width: '100%',
height: '100%',
objectFit: 'cover',
})

const HeartBtn = styled.div({
position: 'absolute',
bottom: '10px',
right: '10px',
})

export default ImageBox
31 changes: 31 additions & 0 deletions src/components/NoDogImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import styled from '@emotion/styled'
import React, { FC } from 'react'

const NoDogImage: FC<{ message: string }> = ({ message }) => {
return (
<Div>
<Message>{message}</Message>
<TryPara>Try again</TryPara>
</Div>
)
}

const Div = styled.div({
minHeight: '80vh',
display: 'grid',
placeContent: 'center',
})

const Message = styled.h2({
fontSize: '24px',
lineHeight: '20px'
})

const TryPara = styled.p({
textAlign: 'center',
TextDecoder: 'underline',
color: 'red',
fontWeight: 'bold',
})

export default NoDogImage
68 changes: 68 additions & 0 deletions src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { FC } from 'react'
import styled from '@emotion/styled'
import { icons } from '../assets'
import { useDispatch} from 'react-redux'
import { Dispatch } from 'redux'
import { setDogBreed } from '../redux/actions'

const SearchBar :FC= ()=>{
const [searchTerm , setSearchTerm] = React.useState('')
const dispatch:Dispatch = useDispatch()

const handelChange = (e:React.ChangeEvent<HTMLInputElement>) =>{
return setSearchTerm(e.target.value)
}
const handelClick = ()=>{
dispatch(setDogBreed(searchTerm))
return setSearchTerm('')
}

return (
<FlexContainer>
<SearchInput onChange={handelChange } value={searchTerm} placeholder='search dog images by breed'/>
<Button onClick={handelClick}><SearchIcon src={icons.searchIcon} alt=''/>Search</Button>
</FlexContainer>
)
}


const FlexContainer = styled.div({
display: 'flex',
alignItems: 'center',
marginTop: '48px',
marginBottom: '32px',
})


const SearchInput = styled.input({
width: '80%',
font: 'inherit',
padding: '10px',
border: '1px solid #ccc',
borderRightWidth: 0,
borderRadius: '8px 0 0 8px',
outline: 'none',
})

const Button = styled.button({
cursor: 'pointer',
width: '20%',
padding: '10px',
font: 'inherit',
backgroundColor: '#0794E3',
color: 'white',
display: 'flex',
alignItems: 'center',
border: '1px solid #ccc',
borderRadius: '0 8px 8px 0',
outline: 'none',
})

const SearchIcon = styled.img({
width: '17px',
height: '15px',
alignSelf: 'center',
marginRight: '10px',
})

export default SearchBar
29 changes: 29 additions & 0 deletions src/components/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'

import { keyframes } from '@emotion/core'
import styled from '@emotion/styled'

const Spinner = () => {
return <SpinnerDiv />
}

const rotate = keyframes`
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
`

const SpinnerDiv = styled.div({
width: '64px',
height: '64px',
border: '5px solid #ccc',
borderBottomColor: 'transparent',
borderTopColor: 'transparent',
borderRadius: '50%',
animation: `${rotate} 1s linear infinite`,
})

export default Spinner
24 changes: 24 additions & 0 deletions src/redux/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { DogsProps } from "./reducer";

export enum ACTION_TYPE {
'SetDogsData' = 'SetDogsData',
'AddToFavorite' = 'AddToFavorite',
'RemoveFromFavorite' = 'RemoveFromFavorite',
'SetDogBreed' = 'SetDogBreed',
}

export function addToFavorite(payload: { id: number; url: string }) {
return { type: ACTION_TYPE.AddToFavorite, payload }
}

export function removeFromFavorite(payload: { id: number; url: string }) {
return { type: ACTION_TYPE.RemoveFromFavorite, payload }
}

export function setDogBreed(payload: string) {
return { type: ACTION_TYPE.SetDogBreed, payload }
}

export function setDogData(payload: DogsProps) {
return { type: ACTION_TYPE.SetDogsData, payload }
}
Loading