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

task-dogbreed-search #69

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
Binary file not shown.
30,273 changes: 30,273 additions & 0 deletions dogbreed-search/package-lock.json

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions dogbreed-search/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "breedsearch",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.15.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Binary file added dogbreed-search/public/favicon.ico
Binary file not shown.
43 changes: 43 additions & 0 deletions dogbreed-search/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
45 changes: 45 additions & 0 deletions dogbreed-search/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* Navbar styles */

.navbar {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #03010b;
padding: 10px 20px; /* Adjust padding as needed */
}

.title {
text-align: center; /* Center the title */
flex-grow: 1; /* Allow the title to expand and take available space */
font-size: 24px;
color: white;
margin: 0; /* Remove margin to fit in the same line */
}

.navbar-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
}

.navbar-item {
margin: 0 10px;
}

.navbar-button {
display: inline-block;
padding: 6px 12px;
background-color: #b2db4b;
color: #0a0101;
text-decoration: none;
border: none;
border-radius: 5px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s ease;
}

.navbar-button:hover {
background-color: #e5dbdb;
}
38 changes: 38 additions & 0 deletions dogbreed-search/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import "./App.css";
import DogImages from "./components/searchBar";
import {Favorites} from "./components/favorite";
import { FavoriteProvider } from "./components/favoritecontext";

function App() {
return (
<Router>
<div>
<nav className="navbar">
<h1 className="title">Search Dog Breed</h1>
<ul className="navbar-list">
<li className="navbar-item">
<Link to="/" className="navbar-button">
Home
</Link>
</li>
<li className="navbar-item">
<Link to="/favorites" className="navbar-button">
Favorites
</Link>
</li>
</ul>
</nav>
<FavoriteProvider>
<Routes>
<Route path="/favorites" element={<Favorites />} />
<Route path="/" element={<DogImages />} />
</Routes>
</FavoriteProvider>
</div>
</Router>
);
}

export default App;
27 changes: 27 additions & 0 deletions dogbreed-search/src/components/favorite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { useFavoriteContext } from './favoritecontext';
import '../style/favorite.css';

function Favorites() {
const { favorites, removeFromFavorites } = useFavoriteContext();

return (
<div className="Favorites">
<h2 className='fav'>Favorites</h2>
<div className="FavoriteList">
{favorites.map((favImage, index) => (
<div key={index} className="FavoriteImage">
<img src={favImage} alt={`Favorite Dog ${index}`} />
<button className="RemoveButton" onClick={() => removeFromFavorites(favImage)}>
Remove
</button>
</div>
))}
</div>
</div>
);
}



export {Favorites};
28 changes: 28 additions & 0 deletions dogbreed-search/src/components/favoritecontext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { createContext, useContext, useState } from 'react';

const FavoriteContext = createContext();

export const useFavoriteContext = () => {
return useContext(FavoriteContext);
};

export function FavoriteProvider({ children }) {
const [favorites, setFavorites] = useState([]);

const addToFavorites = (image) => {
if (!favorites.includes(image)) {
setFavorites([...favorites, image]);
}
};

const removeFromFavorites = (image) => {
const updatedFavorites = favorites.filter((favImage) => favImage !== image);
setFavorites(updatedFavorites);
};

return (
<FavoriteContext.Provider value={{ favorites, addToFavorites, removeFromFavorites }}>
{children}
</FavoriteContext.Provider>
);
}
68 changes: 68 additions & 0 deletions dogbreed-search/src/components/searchBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useState } from 'react';
import { useFavoriteContext } from './favoritecontext';
import '../style/searchBar.css';

function DogImages() {
const [breed, setBreed] = useState('');
const [images, setImages] = useState([]);
const [hoverIndex, setHoverIndex] = useState(null); // Track hovered image
const { addToFavorites } = useFavoriteContext();
const [showPopup, setShowPopup] = useState(false); // State for showing popup

const fetchImages = async () => {
if (breed) {
try {
const response = await fetch(`https://dog.ceo/api/breed/${breed}/images`);
const data = await response.json();

if (data.status === 'success') {
setImages(data.message.slice(0, 10));
setShowPopup(false); // Hide popup on successful fetch
} else {
setShowPopup(true); // Show popup on invalid breed
setImages([]); // Clear images on error
}
} catch (error) {
setShowPopup(true); // Show popup on error
console.error('Error fetching images:', error);
}
}
};

return (
<div className="DogImages">
<div className="SearchBar">
<input
type="text"
placeholder="Search for a breed..."
value={breed}
onChange={(e) => setBreed(e.target.value)}
/>
<button onClick={fetchImages}>Search</button>
</div>
<div className="ImageList">
{images.map((image, index) => (
<div
key={index}
className="ImageContainer"
onMouseEnter={() => setHoverIndex(index)} // Show "Favorite" button on hover
onMouseLeave={() => setHoverIndex(null)} // Hide "Favorite" button when not hovered
>
<img src={image} alt={`Dog ${index}`} />
{hoverIndex === index && (
<button onClick={() => addToFavorites(image)}>Favorite</button>
)}
</div>
))}
</div>
{showPopup && (
<div className="Popup">
<p>Invalid breed entered. Please enter a valid breed name.</p>
<button onClick={() => setShowPopup(false)}>Close</button>
</div>
)}
</div>
);
}

export default DogImages;
13 changes: 13 additions & 0 deletions dogbreed-search/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom/client';

import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);


48 changes: 48 additions & 0 deletions dogbreed-search/src/style/favorite.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

.Favorites {
margin-top: 30px;
}

.Favorites h2 {
margin-bottom: 20px;
}

.FavoriteList {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* Responsive grid */
gap: 20px; /* Space between images */
}

.FavoriteImage {
position: relative;
}

.FavoriteImage img {
max-width: 100%;
max-height: 100%; /* Ensure images fit within the container */
display: block;
margin: 0 auto;
}

.RemoveButton {
display: none; /* Initially hide the "Remove" button */
position: absolute;
top: 50%; /* Position at the center vertically */
left: 50%; /* Position at the center horizontally */
transform: translate(-50%, -50%);
padding: 6px 10px;
background-color: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

.FavoriteImage:hover .RemoveButton {
display: block; /* Show the "Remove" button on hover */
}

.RemoveButton:hover {
background-color: #c82333;
}

Loading