diff --git a/src/components/App.tsx b/src/components/App.tsx index e8c7d74..186a058 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,12 +1,17 @@ import React, { FC } from 'react' import styled from '@emotion/styled' import Header from './Header' +import Home from './Home/Home' +import { Provider } from 'react-redux' +import store from '../redux/store' const App: FC = () => { return (
- {/* Happy coding! */} + + + ) } @@ -16,6 +21,10 @@ const Container = styled.div({ height: '100%', width: '560px', paddingTop: '60px', + '@media (max-width: 600px)': { + padding: '0 20px', + width:'90%', + }, }) export default App diff --git a/src/components/Favorites/Favorites.tsx b/src/components/Favorites/Favorites.tsx new file mode 100644 index 0000000..65b4995 --- /dev/null +++ b/src/components/Favorites/Favorites.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import RedHeart from '../../assets/icons/red-heart-icon.png' +import { useSelector,useDispatch } from 'react-redux'; +import {itemRejected} from '../../redux/actions' +import styled from '@emotion/styled'; + + +export default function Favorites() { + + let favoriteImages = useSelector((state) => state) as string[]; + let dispatch = useDispatch(); + + + return ( +
+ + Heart Icon +

Favorites

+
+ {favoriteImages.length ? + { favoriteImages.map((image,idx)=>{ + return( + + + { + dispatch(itemRejected(image)) + }} /> + + ) + }) + } + + + : + +

Please Add Items

+
+ } + +
+ ) +} + +const VStack = styled.div({ + display:'flex', + alignItems:'center', + gap:'10px' +}); + +const DefaultMess = styled.div({ + textAlign: 'center', + paddingBottom: '35px', + marginTop: '20px', +}) + +const FavoriteItems = styled.div({ + paddingBottom: '20px', +}) + +const Grid = styled.div({ + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(160px, 1fr))', + gap: '20px', + marginTop: '25px', + marginBottom: '30px', + width: '100%', + placeItems: 'center', +}) + +const ImageContainer = styled.div({ + position:'relative', +}) + +const DogPhoto = styled.img({ + width: '160px', + height: '160px' +}) + +const Icon = styled.img({ + position: 'absolute', + right: '5px', + bottom: '10px' + +}) \ No newline at end of file diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 275fbf8..8ff43c8 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -13,7 +13,10 @@ const Header: FC = () => { const Container = styled.div({ display: 'flex', - justifyContent: 'space-between', + justifyContent: 'space-between', + '@media (max-width: 600px)': { + marginTop:'30px', + }, }) const Title = styled.h1({ diff --git a/src/components/Home/Home.tsx b/src/components/Home/Home.tsx new file mode 100644 index 0000000..fcfc194 --- /dev/null +++ b/src/components/Home/Home.tsx @@ -0,0 +1,173 @@ +import React, { useState, useEffect } from 'react'; +import Favorites from '../Favorites/Favorites'; +import HeartIcons from '../../assets/icons/white-heart-icon.png'; +import RedHeart from '../../assets/icons/red-heart-icon.png'; +import searchIcon from '../../assets/icons/search-icon.png'; +import { useSelector, useDispatch } from 'react-redux'; +import { itemSelected, itemRejected } from '../../redux/actions'; +import styled from '@emotion/styled'; + +export default function Home() { + const [searchItem, setSearchItem] = useState(''); + const favoriteImages = useSelector((state) => state) as string[]; + const dispatch = useDispatch(); + const [dogImages, setDogImages] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + useEffect(() => { + setLoading(true); + setError(''); + + fetch('https://dog.ceo/api/breed/hound/images') + .then((res) => res.json()) + .then((res) => { + setLoading(false); + setDogImages(res.message.slice(0, 10)); + }) + .catch(() => { + setLoading(false); + setError('Failed to fetch initial dog images'); + }); + }, []); + + function solve() { + setLoading(true); + setDogImages([]); + setError(''); + + fetch(`https://dog.ceo/api/breed/${searchItem}/images`) + .then((res) => res.json()) + .then((res) => { + if (res.status === 'error') { + setError('No Data Found For The Given Breed Name'); + } else { + setDogImages(res.message.slice(0, 10)); + } + }) + .catch(() => { + setError('An error occurred while fetching data.'); + }) + .finally(() => { + setLoading(false); + }); + } + + return ( +
+ + setSearchItem(e.target.value.toLowerCase())} + /> + + + + {loading &&

Loading...

} + + {error && ( + +

{error}

+
+ )} + + {!error && dogImages.length > 0 && ( + + {dogImages.map((image, idx) => ( + + + { + if (favoriteImages.includes(image)) { + dispatch(itemRejected(image)); + } else { + dispatch(itemSelected(image)); + } + }} + /> + + ))} + + )} + + + + +
+ ); +} + +const SearchContainer = styled.div({ + textAlign: 'center', + marginTop: '30px', + marginBottom: '40px', +}); + +const DefaultMess = styled.div({ + textAlign: 'center', +}); + +const DogPhoto = styled.img({ + width: '160px', + height: '160px' +}) + +const Grid = styled.div({ + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(160px, 1fr))', + gap: '20px', + marginTop: '25px', + marginBottom: '30px', + width: '100%', + placeItems: 'center', +}) + +const Icon = styled.img({ + position: 'absolute', + right: '5px', + bottom: '10px' + +}) + +const ImageContainer = styled.div({ + position:'relative', +}) + +const Input = styled.input({ + // display:'block', + width:'80%', + height : '40px', + paddingLeft: '10px', + backgroundColor: '#F7F7F7', + borderRadius: '5px', + border: 'none' + +}) + +const Button = styled.button({ + width:'105px', + height:'40px', + backgroundColor: '#0794E3', + border: 'none', + borderRadius: '3px', + position: 'relative' +}) + +const SeachIcons = styled.img({ + position: 'absolute', + top: '13px', + left: '11px', + height: '15px', +}) + +const HorizontalLine = styled.hr({ + border: '1px solid #E5E5E5', + marginTop: '30px', + marginBottom: '30px', +}) \ No newline at end of file diff --git a/src/redux/actions.ts b/src/redux/actions.ts index e69de29..532fb11 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -0,0 +1,23 @@ +const Item_Selected = 'ITEM_SELECTED'; +const Item_Removed = 'ITEM_REMOVED'; + +export type Action ={ + type : string, + payload : string +} + +function itemSelected(src:string):Action{ + return{ + type : Item_Selected, + payload : src + } +} + +function itemRejected(src:string):Action{ + return{ + type : Item_Removed, + payload : src + } +} + +export {itemSelected,itemRejected,Item_Removed,Item_Selected}; \ No newline at end of file diff --git a/src/redux/reducer.ts b/src/redux/reducer.ts index be51d22..3492361 100644 --- a/src/redux/reducer.ts +++ b/src/redux/reducer.ts @@ -1,5 +1,17 @@ -export const reducer = (initialState = {}, action) => { +import { Item_Removed,Item_Selected ,Action} from "./actions"; + +type InitialState =string[] + +export const reducer = (initialState:InitialState=[] , action:Action) => { switch (action.type) { + + case Item_Selected : return [...initialState,action.payload]; + + case Item_Removed : + let arr = initialState.filter((it) => it != action.payload); + return arr; + + default: return initialState } diff --git a/src/type.d.ts b/src/type.d.ts new file mode 100644 index 0000000..7ac6874 --- /dev/null +++ b/src/type.d.ts @@ -0,0 +1,5 @@ +declare module '*.png' { + const src: string; + export default src; + } + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2b88693..7d2000a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10214,10 +10214,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.6.4: - version "3.9.9" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674" - integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w== +typescript@4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" + integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== uglify-js@3.4.x: version "3.4.10"