diff --git a/src/components/App.tsx b/src/components/App.tsx
index e8c7d74..ed5fb25 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -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 (
{/* Happy coding! */}
+
+ {dogs.status === 'loading' ? (
+
+
+
+ ) : (
+ <>
+ {dogs.status === 'rejected' ? (
+
+ ) : (
+ <>
+ Showing images of {dogs.breed} dog
+
+ {dogs.data && dogs.data.map((dog) => )}
+
+ >
+ )}
+ >
+ )}
+ {favorites.length > 0 && }
)
}
@@ -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
diff --git a/src/components/Favorite.tsx b/src/components/Favorite.tsx
new file mode 100644
index 0000000..c14ca41
--- /dev/null
+++ b/src/components/Favorite.tsx
@@ -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 (
+
+
+
+ Favorites
+
+
+ {dogs.map((dog) => (
+
+ ))}
+
+
+ )
+}
+
+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
diff --git a/src/components/ImageBox.tsx b/src/components/ImageBox.tsx
new file mode 100644
index 0000000..4abe265
--- /dev/null
+++ b/src/components/ImageBox.tsx
@@ -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 (
+
+
+
+
+
+
+ )
+}
+
+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
diff --git a/src/components/NoDogImage.tsx b/src/components/NoDogImage.tsx
new file mode 100644
index 0000000..061c651
--- /dev/null
+++ b/src/components/NoDogImage.tsx
@@ -0,0 +1,31 @@
+import styled from '@emotion/styled'
+import React, { FC } from 'react'
+
+const NoDogImage: FC<{ message: string }> = ({ message }) => {
+ return (
+
+ {message}
+ Try again
+
+ )
+}
+
+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
diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx
new file mode 100644
index 0000000..8850c45
--- /dev/null
+++ b/src/components/SearchBar.tsx
@@ -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) =>{
+ return setSearchTerm(e.target.value)
+ }
+ const handelClick = ()=>{
+ dispatch(setDogBreed(searchTerm))
+ return setSearchTerm('')
+ }
+
+ return (
+
+
+
+
+ )
+}
+
+
+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
\ No newline at end of file
diff --git a/src/components/Spinner.tsx b/src/components/Spinner.tsx
new file mode 100644
index 0000000..278eeba
--- /dev/null
+++ b/src/components/Spinner.tsx
@@ -0,0 +1,29 @@
+import React from 'react'
+
+import { keyframes } from '@emotion/core'
+import styled from '@emotion/styled'
+
+const Spinner = () => {
+ return
+}
+
+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
diff --git a/src/redux/actions.ts b/src/redux/actions.ts
index e69de29..af39a39 100644
--- a/src/redux/actions.ts
+++ b/src/redux/actions.ts
@@ -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 }
+}
diff --git a/src/redux/reducer.ts b/src/redux/reducer.ts
index be51d22..dea7de5 100644
--- a/src/redux/reducer.ts
+++ b/src/redux/reducer.ts
@@ -1,6 +1,46 @@
-export const reducer = (initialState = {}, action) => {
+import { ACTION_TYPE } from './actions'
+
+export type DogDataProps = { id: number; url: string }[]
+export type StatusProps = 'loading' | 'rejected' | 'completed' | undefined
+export type DogsProps = {
+ breed: string
+ data: DogDataProps
+ status: StatusProps
+}
+
+export type StateProps = {
+ favorites: { id: number; url: string }[]
+ dogs: DogsProps
+}
+
+const initialState: StateProps = {
+ favorites: [],
+ dogs: { breed: 'frise', data: [], status: undefined },
+}
+
+export const reducer = (state = initialState, action) => {
switch (action.type) {
+ case ACTION_TYPE.SetDogBreed:
+ return {
+ ...state,
+ dogs: { ...state.dogs, breed: action.payload },
+ }
+ case ACTION_TYPE.SetDogsData:
+ return {
+ ...state,
+ dogs: action.payload,
+ }
+ case ACTION_TYPE.AddToFavorite:
+ return {
+ ...state,
+ favorites: [...state.favorites, action.payload],
+ }
+ case ACTION_TYPE.RemoveFromFavorite:
+ return {
+ ...state,
+ favorites: state.favorites.filter((dog) => dog.id !== action.payload.id),
+ }
default:
- return initialState
+ return state
}
}