Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.
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
13 changes: 10 additions & 3 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import React, { FC } from 'react'
import styled from '@emotion/styled'
import Header from './Header'
import Favourites from './Favourites'
import Main from "./Main"

const App: FC = () => {
return (
<Container>
<Header />
{/* Happy coding! */}
<Main/>
<Favourites />
</Container>
)
}

const Container = styled.div({
margin: '0 auto',
height: '100%',
width: '560px',
paddingTop: '60px',
paddingBottom:'20px',
"@media (max-width: 500px)": {
width:'100%',
padding:'60px 10px 0'
},
})

export default App
export default App
61 changes: 61 additions & 0 deletions src/components/Favouriates.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react'
import styled from '@emotion/styled'
import { RootStateOrAny, useDispatch, useSelector } from 'react-redux'
import Heart from './Heart'
import DogImage from './dogImage'
import { favoriteFunc } from '../redux/actions'

export default function Favourites() {
const favlist: string[] = useSelector((state: RootStateOrAny) => state.favorites)
const dispatch = useDispatch()

const removefavorite = (url: string) => {
dispatch(favoriteFunc(url))
}

return (
<>
<HeadingContain>
<Heart icon="redHeartIcon" alt="red heart icon" />
<Heading>Favorites</Heading>
</HeadingContain>
{favlist.length === 0 ? (
<Msg>No dogs in favorites.</Msg>
) : (
<Result>
{favlist.map((item, index) => {
return (
<DogImage key={index} src={item} onClick={() => removefavorite(item)} icon={'redHeartIcon'} />
)
})}
</Result>
)}
</>
)
}

const HeadingContain = styled.div({
display: 'flex',
marginTop: '48px',
})



const Heading = styled.h1({
fontWeight: 'bold',
fontSize: '24px',
lineHeight: '33px',
marginLeft: '20px',
})

const Result = styled.div({
margin: '44px auto 0',
width: '100%',
display: 'grid',
gap: '30px',
gridTemplateColumns: 'repeat(3, 1fr)',
})

const Msg = styled.div({
marginTop:"20px"
})
11 changes: 4 additions & 7 deletions src/components/Heart.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import React, { FC } from 'react'
import styled from '@emotion/styled'
import { icons } from '../assets'

interface Props {
icon: string
alt: string
onClick?:React.MouseEventHandler
}

const Heart: FC<Props> = ({ icon, alt }) => {
return <HeartIcon src={icons[icon]} alt={alt} />
const Heart: FC<Props> = ({ icon, alt,onClick }) => {
return <HeartIcon onClick={onClick} src={icons[icon]} alt={alt} />
}

const HeartIcon = styled.img({
width: '17px',
height: '15px',
alignSelf: 'center',
cursor:"pointer"
})

export default Heart
61 changes: 61 additions & 0 deletions src/components/Inputbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import styled from '@emotion/styled'
import React from 'react'
import { icons } from '../assets/icons'

type PropsTypes ={
onSubmit:React.FormEventHandler<HTMLFormElement>
onChange:React.ChangeEventHandler<HTMLInputElement>
value:string
}

function Inputbox({onSubmit,value, onChange}:PropsTypes) {
return (
<Form onSubmit={onSubmit}>
<Input placeholder='Search dog breed' value={value} onChange={onChange} required />
<Button type="submit">
<img src={icons['searchIcon']} alt="" />
Search
</Button>
</Form>
)
}
const Form = styled.form({
margin: '48px auto',
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '4px',
border: 'none',
})

const Input = styled.input({
width: '100%',
display: 'block',
padding: '8px 17px',
background: '#F7F7F7',
border: 'none',
fontFamily: 'Nunito Sans',
fontStyle: 'normal',
fontWeight: 400,
fontSize: '16px',
lineHeight: '22px',
color: '#44484C',
outline: "none"
})

const Button = styled.button({
alignSelf: 'stretch',
padding: '0 16px',
background: '#0794E3',
border: 'none',
color: '#FFFFFF',
borderRadius: '4px',
display: 'flex',
gap: '5px',
alignItems: 'center',
justifyContent: 'center',
cursor: "pointer"
})

export default Inputbox
71 changes: 71 additions & 0 deletions src/components/Main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useState, FormEvent } from 'react'
import styled from '@emotion/styled'
import DogImage from './DogImage'
import { RootStateOrAny, useDispatch, useSelector } from 'react-redux'
import { favoriteFunc } from '../redux/actions'
import Inputbox from './Inputbox'

type Maintypes = {
message?: string[]
status?: string
}

function Main() {
const [search, Setsearch] = useState("")
const [doglist, Setdoglist] = useState<string[]>([])
const [err,Seterr]=useState(false)
const favlist: string[] = useSelector((state: RootStateOrAny) => state.favorites)
const dispatch = useDispatch()

const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
const res: Maintypes = await (await fetch(`https://dog.ceo/api/breed/${search}/images/random/10`)).json()
if (res.status === "success") {
Setdoglist(res.message)
}else{
Setdoglist([])
Seterr(true)
}
}

const Favorite = (url: string) => {
dispatch(favoriteFunc(url))
}


return (
<>

<Inputbox onSubmit={handleSubmit} value={search} onChange={e => Setsearch(e.target.value)}/>
{!doglist.length ?
<>
{err&&<div>No result found</div> }
</>:
<>
<Result>
{doglist.map((item, index) => {
return (
<DogImage key={index} src={item} onClick={() => Favorite(item)} icon={favlist.includes(item) ? 'redHeartIcon' : 'whiteHeartIcon'} />
)
})}
</Result>
<Line />
</>}
</>
)
}

const Result = styled.div({
margin: '48px auto',
width: '100%',
display: 'grid',
gap: '30px',
gridTemplateColumns: 'repeat(3,1fr)',
})


const Line = styled.div({
borderBottom: '1px solid #DADADA',
})

export default Main
39 changes: 39 additions & 0 deletions src/components/dogImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react'
import styled from '@emotion/styled'
import Heart from './Heart'


function DogImage({ src, icon, onClick }) {
return (
<Container>
<Image src={src} />
<Icon>
<Heart onClick={onClick} icon={icon} alt="" />
</Icon>
</Container>
)
}


const Container = styled.div({
position: "relative"
})

const Image = styled.img({
width: "160px",
height: "160px",
objectFit: "cover",
borderRadius: "4px",
"@media (max-width: 600px)": {
width:'130px',
height:"130px"
},
})

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

export default DogImage
7 changes: 7 additions & 0 deletions src/redux/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const favoriteFunc =(url:string)=>{
return {
type:"FAVORITE",
payload:url
}
}

22 changes: 19 additions & 3 deletions src/redux/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
export const reducer = (initialState = {}, action) => {
import { AnyAction } from 'redux'

interface ReducerState {
favorites: string[]
}

const initialState: ReducerState = {
favorites: [],
}

export const favoriteReducer = (state = initialState, action: AnyAction) => {
switch (action.type) {
case 'FAVORITE':
if (state.favorites.includes(action.payload)) {
return { favorites: [...state.favorites.filter((item) => item !== action.payload)] }
} else {
return { favorites: [...state.favorites, action.payload] }
}
default:
return initialState
return state
}
}
}
8 changes: 6 additions & 2 deletions src/redux/store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { createStore } from 'redux'
import { reducer } from './reducer'

export default createStore(reducer)
import { favoriteReducer } from './reducer'

const store = createStore(favoriteReducer)


export default store