Skip to content

Commit

Permalink
commit
Browse files Browse the repository at this point in the history
  • Loading branch information
RalucaElenaB committed Nov 19, 2023
1 parent 6c8bfbc commit e17b0c9
Show file tree
Hide file tree
Showing 17 changed files with 1,178 additions and 60 deletions.
423 changes: 387 additions & 36 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.2",
"notiflix": "^3.2.6",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-loader-spinner": "^5.4.5",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.3"
},
Expand Down
23 changes: 23 additions & 0 deletions src/components/API/Api.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import axios from 'axios';

const BASE_URL = 'https://pixabay.com/api/';
const API_KEY = '40728648-1b398202c5fa26f14327021d8';

const getImages = async (search, page) => {
const response = await axios.get(BASE_URL, {
method: 'get',
params: {
key: API_KEY,
q: search,
image_type: 'photo',
orientation: 'horizontal',
per_page: 12,
page: page,
},
});
console.log(response.data);
console.log(response.data.hits);
return response;
};

export default getImages;
200 changes: 184 additions & 16 deletions src/components/App.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,184 @@
export const App = () => {
return (
<div
style={{
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: 40,
color: '#010101',
}}
>
React homework template!!!!
</div>
);
};
import React, { Component } from 'react';
import Notify from 'notiflix/build/notiflix-notify-aio';
import getImages from './API/Api';
import Searchbar from './Searchbar/Searchbar';
import ImageGallery from './ImageGallery/ImageGallery';
import Button from './Button/Button';
import Modal from './Modal/Modal';
import Loader from './Loader/Loader';

export default class App extends Component {
constructor(props) {
super(props);
this.state = {
images: [],
query: '',
page: 1,
loading: false,
openModal: false,
largeImageURL: '',
loadMore: false,
toTop: false,
};

// Ref for scrolling to bottom
this.bottomRef = React.createRef();
}

// Scroll to bottom with load more
scrollToBottom = () => {
this.bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
};

handleSubmit = query => {
this.setState({
loading: true,
images: [],
query: query,
page: 1,
});
};

handleOpenModal = url => {
this.setState({
openModal: true,
largeImageURL: url,
});
};

handleLoadMore = () => {
this.setState(prevState => ({
page: prevState.page + 1,
loading: true,
loadMore: true,
toTop: true,
}));
};

backToTop = () => {
window.scrollTo({
left: 0,
top: 0,
behavior: 'smooth',
});
};

handleModalClose = () => {
this.setState({
openModal: false,
largeImageURL: '',
});
};

componentDidMount() {
this.getGallery();
}

componentDidUpdate(prevProps, prevState) {
if (
prevState.query !== this.state.query ||
prevState.page !== this.state.page
) {
this.getGallery();
}
}

getGallery = async () => {
const { query, page } = this.state;

if (!query) return;

try {
const response = await getImages(query, page);
let imageData = response.data;
let imageCount = imageData.hits.length;
let imageTotal = imageData.totalHits;
let totalPages = Math.round(imageTotal / imageCount);

if (imageCount === 0) {
this.setState({
images: [],
loadMore: false,
toTop: false,
});
Notify.failure(
`Sorry, there are no images matching your search query: ${query}. Please try again.`
);
return;
}

const newState = { images: [...this.state.images, ...imageData.hits] };

if (imageCount < 12) {
this.setState({ ...newState, toTop: true });
if (page === 1) {
this.setState({
loadMore: false,
toTop: false,
});
Notify.success(
`Maximum search value found, there are ${imageCount} images.`
);
}
} else {
this.setState({
...newState,
loadMore: page !== totalPages,
});
if (page >= 2 && page <= 41) {
this.setState({
loadMore: true,
toTop: true,
});
} else if (page === 42) {
this.setState({
toTop: true,
loadMore: false,
});
} else if (imageTotal > 12) {
this.setState({
loading: true,
loadMore: true,
toTop: true,
});
}
}
} finally {
this.setState({
loading: false,
});
}
};

render() {
const { images, loading, loadMore, toTop, openModal, largeImageURL } =
this.state;

return (
<div>
<Searchbar onSubmit={this.handleSubmit} />

<ImageGallery
images={images}
openModal={this.handleOpenModal}
loadMore={this.handleLoadMore}
/>
<div ref={this.bottomRef}></div>
{''}
{loading && <Loader />}
<div className="Center-buttons">
{loadMore && (
<Button clickHandler={this.handleLoadMore} text="Load More" />
)}
{toTop && <Button clickHandler={this.backToTop} text="To Top" />}
</div>
{openModal && (
<Modal
largeImageURL={largeImageURL}
modalClose={this.handleModalClose}
/>
)}
</div>
);
}
}
31 changes: 31 additions & 0 deletions src/components/Button/Button.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { string, func } from 'prop-types';
import './Button.modules.css';

const Button = props => {
const { text, type, clickHandler } = props;

return (
<div className="Center-buttons">
<button
className="Button"
type={type}
onClick={clickHandler}
aria-label={text}
>
{text}
</button>
</div>
);
};

Button.defaultProps = {
type: 'button',
text: 'click me',
};

Button.propTypes = {
type: string,
text: string,
};

export default Button;
31 changes: 31 additions & 0 deletions src/components/Button/Button.modules.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.Button {
padding: 8px 16px;
border-radius: 2px;
background-color: #3f51b5;
transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
text-align: center;
display: inline-block;
color: #fff;
border: 0;
text-decoration: none;
cursor: pointer;
font-family: inherit;
font-size: 18px;
line-height: 24px;
font-style: normal;
font-weight: 500;
min-width: 180px;
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2),
0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
}

.Button:hover,
.Button:focus {
background-color: #303f9f;
}

.Center-buttons {
display: flex;
flex-direction: column;
align-items: center;
}
27 changes: 27 additions & 0 deletions src/components/ImageGallery/ImageGallery.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { array, func } from 'prop-types';
import ImageGalleryItem from 'components/ImageGalleryItem/ImageGalleryItem';
import './ImageGallery.modules.css';

const ImageGallery = props => {
const { images, openModal } = props;
return (
<ul className="ImageGallery">
{images.map(({ id, webformatURL, largeImageURL, tags }) => (
<ImageGalleryItem
key={id}
webformatURL={webformatURL}
largeImageURL={largeImageURL}
openModal={openModal}
tags={tags}
/>
))}
</ul>
);
};

ImageGallery.propTypes = {
images: array.isRequired,
openModal: func.isRequired,
};

export default ImageGallery;
12 changes: 12 additions & 0 deletions src/components/ImageGallery/ImageGallery.modules.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.ImageGallery {
display: grid;
max-width: calc(100vw - 48px);
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
grid-gap: 16px;
margin-top: 0;
margin-bottom: 0;
padding: 0;
list-style: none;
margin-left: auto;
margin-right: auto;
}
32 changes: 32 additions & 0 deletions src/components/ImageGalleryItem/ImageGalleryItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { string, func } from 'prop-types';
import './ImageGalleryItem.modules.css';

const ImageGalleryItem = props => {
const { webformatURL, tags, largeImageURL, openModal } = props;

return (
<li>
<img
className="ImageGalleryItem-image"
src={webformatURL}
alt={tags}
largeImage={largeImageURL}
onClick={() => openModal(largeImageURL)}
/>
</li>
);
};

ImageGalleryItem.defaultProps = {
largeImageURL: 'https://picsum.photos/100%/260',
webformatURL: 'https://picsum.photos/100%/260',
};

ImageGalleryItem.propTypes = {
largeImageURL: string,
webformatURL: string,
tags: string.isRequired,
openModal: func.isRequired,
};

export default ImageGalleryItem;
17 changes: 17 additions & 0 deletions src/components/ImageGalleryItem/ImageGalleryItem.modules.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.ImageGalleryItem {
border-radius: 2px;
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12);
}

.ImageGalleryItem-image {
width: 100%;
height: 260px;
object-fit: cover;
transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
}

.ImageGalleryItem-image:hover {
transform: scale(1.03);
cursor: zoom-in;
}
Loading

0 comments on commit e17b0c9

Please sign in to comment.