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

himanshu #51

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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"@hot-loader/react-dom": "16.10.2",
"react-hot-loader": "^4.12.15",
"react-redux": "^7.1.1",
"redux": "^4.0.4"
"redux": "^4.0.4",
"redux-devtools-extension": "^2.13.9",
"redux-thunk": "^2.4.2"
}
}
7 changes: 6 additions & 1 deletion src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React, { FC } from 'react'
import styled from '@emotion/styled'
import Header from './Header'
import SearchBox from './SearchBox'
import SearchResults from './SearchResults'
import Favourites from './Favourites'

const App: FC = () => {
return (
<Container>
<Header />
{/* Happy coding! */}
<SearchBox />
<SearchResults />
<Favourites />
</Container>
)
}
Expand Down
48 changes: 48 additions & 0 deletions src/components/Dog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react'
import styled from '@emotion/styled'
import { setDogAsFavorites, removeDogFromFavorites } from '../redux/actions'
import { useSelector, useDispatch } from 'react-redux'
import Heart from './Heart'
import { defaultRootState } from '../types/dogs-data'

export default function Dog({ dog }: { dog: string }) {
const { favourites } = useSelector((state: defaultRootState) => state)
const dispatch = useDispatch()

const isDogFavourite = () => {
return favourites.includes(dog)
}

const handleFavourite = () => {
if (isDogFavourite()) {
dispatch(removeDogFromFavorites(dog))
} else {
dispatch(setDogAsFavorites(dog))
}
}

return (
<DogImageContainer>
<DogImage src={dog} alt="" key={dog} loading="lazy" />
<HeartIconPositionContaianer onClick={handleFavourite}>
<Heart icon={isDogFavourite() ? 'redHeartIcon' : 'whiteHeartIcon'} alt="" />
</HeartIconPositionContaianer>
</DogImageContainer>
)
}

const DogImageContainer = styled.div({
position: 'relative',
})

const DogImage = styled.img({
width: '167.92px',
height: '191.58px',
objectFit: 'fill',
})

const HeartIconPositionContaianer = styled.div({
position: 'absolute',
bottom: '10px',
right: '10px',
})
52 changes: 52 additions & 0 deletions src/components/Favourites.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react'
import styled from '@emotion/styled'
import { useSelector } from 'react-redux'
import Heart from './Heart'
import Dog from './Dog'
import { defaultRootState } from '../types/dogs-data'

export default function Favourites() {
const { favourites } = useSelector((state: defaultRootState) => state)

return (
<>
<TitleWrapper>
<Heart icon="redHeartIcon" alt="red heart icon" />
<Title>Favorites</Title>
</TitleWrapper>
{favourites.length === 0 ? (
<P>No dogs were added as favorites.</P>
) : (
<FavouriteImagesContainer>
{favourites?.map((imgLink) => (
<Dog dog={imgLink} />
))}
</FavouriteImagesContainer>
)}
</>
)
}

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

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

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

const P = styled.div({
marginTop: '10px',
})
67 changes: 67 additions & 0 deletions src/components/SearchBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { FormEvent } from 'react'
import styled from '@emotion/styled'
import { icons } from '../assets/icons'
import { useDispatch } from 'react-redux'
import { fetchDogsData } from './../redux/actions'

export default function SearchBox() {
const [breed, setBreed] = React.useState('')

const handleChange = (event: { target: { value: React.SetStateAction<string> } }) => {
setBreed(event.target.value)
}

const dispatch = useDispatch()

const handleSearch = (event: FormEvent) => {
event.preventDefault()
if (breed) dispatch(fetchDogsData(breed))
}

return (
<SearchBoxForm onSubmit={handleSearch}>
<Input onChange={handleChange} value={breed} required />
<Button type="submit">
<img src={icons['searchIcon']} alt="" />
Search
</Button>
</SearchBoxForm>
)
}

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

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',
})
44 changes: 44 additions & 0 deletions src/components/SearchResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react'
import styled from '@emotion/styled'
import { useSelector } from 'react-redux'
import Dog from './Dog'
import { defaultRootState } from '../types/dogs-data'

export default function SearchResults() {
const { dogs, isLoading, error } = useSelector((state: defaultRootState) => state)

if (isLoading) {
return <>Loading...</>
}

if (error) {
return <>{error}</>
}

return (
<>
{!dogs?.message ? (
<p>Please search dogs by their breed to display the results.</p>
) : (
<SearchResultsContainer>
{dogs?.message?.map((imgLink) => (
<Dog dog={imgLink} />
))}
</SearchResultsContainer>
)}
<Rule />
</>
)
}

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

const Rule = styled.div({
border: '1px solid #DADADA',
})
35 changes: 35 additions & 0 deletions src/redux/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
DOGS_DATA_REQUEST,
DOGS_DATA_SUCCESS,
DOGS_DATA_FAIL,
SET_DOG_AS_FAVOURITE,
REMOVE_DOG_FROM_FAVOURITE,
} from './contants'

export const fetchDogsData = (breed: string) => async (dispatch) => {
try {
dispatch({ type: DOGS_DATA_REQUEST })

const response = await fetch(`https://dog.ceo/api/breed/${breed}/images/random/10`)

if (response.ok) {
const data = await response.json()
dispatch({ type: DOGS_DATA_SUCCESS, payload: data })
} else {
const data = await response.json()
if (data.code === 404) {
dispatch({ type: DOGS_DATA_FAIL, payload: 'Breed not found (master breed does not exist)' })
} else dispatch({ type: DOGS_DATA_FAIL, payload: 'Oops! something went wrong.' })
}
} catch (error) {
dispatch({ type: DOGS_DATA_FAIL, payload: error.message })
}
}

export const setDogAsFavorites = (dog: string) => (dispatch) => {
dispatch({ type: SET_DOG_AS_FAVOURITE, payload: dog })
}

export const removeDogFromFavorites = (dog: string) => (dispatch) => {
dispatch({ type: REMOVE_DOG_FROM_FAVOURITE, payload: dog })
}
6 changes: 6 additions & 0 deletions src/redux/contants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const DOGS_DATA_REQUEST = 'DOGS_DATA_REQUEST'
export const DOGS_DATA_SUCCESS = 'DOGS_DATA_SUCCESS'
export const DOGS_DATA_FAIL = 'DOGS_DATA_FAIL'

export const SET_DOG_AS_FAVOURITE = 'SET_DOG_AS_FAVOURITE'
export const REMOVE_DOG_FROM_FAVOURITE = 'REMOVE_DOG_FROM_FAVOURITE'
54 changes: 53 additions & 1 deletion src/redux/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,57 @@
export const reducer = (initialState = {}, action) => {
import {
DOGS_DATA_REQUEST,
DOGS_DATA_SUCCESS,
DOGS_DATA_FAIL,
SET_DOG_AS_FAVOURITE,
REMOVE_DOG_FROM_FAVOURITE,
} from './contants'

export type initialStateType = {
dogs: { message?: Array<String>; status?: string }
isLoading: Boolean
error: String
favourites: Array<String>
}

export const reducer = (initialState: initialStateType, action) => {
switch (action.type) {
case DOGS_DATA_REQUEST:
return {
...initialState,
isLoading: true,
dogs: null,
error: null,
}
case DOGS_DATA_SUCCESS:
return {
...initialState,
isLoading: false,
dogs: action.payload,
error: null,
}
case DOGS_DATA_FAIL:
return {
...initialState,
isLoading: false,
dogs: null,
error: action.payload,
}
case SET_DOG_AS_FAVOURITE: {
const dog = action.payload

if (initialState.favourites.includes(dog)) return initialState
return {
...initialState,
favourites: [...initialState.favourites, dog],
}
}
case REMOVE_DOG_FROM_FAVOURITE: {
const dog = action.payload

const newFavourites = initialState.favourites.filter((favourite) => favourite !== dog)

return { ...initialState, favourites: newFavourites }
}
default:
return initialState
}
Expand Down
19 changes: 17 additions & 2 deletions src/redux/store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
import { createStore } from 'redux'
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
import { reducer } from './reducer'

export default createStore(reducer)
const initialState = {
dogs: null,
error: null,
isLoading: false,
favourites: [],
}

const middleware = [thunk]

export default createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware)),
)
6 changes: 6 additions & 0 deletions src/types/dogs-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type defaultRootState = {
dogs: { message?: Array<string>; status?: string }
isLoading: Boolean
error: String
favourites: Array<string>
}
Loading