├── .prettierignore ├── frontend ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── manifest.json │ ├── favicon.svg │ └── index.html ├── src │ ├── blocks │ │ ├── popup │ │ │ ├── __container │ │ │ │ ├── popup__container.css │ │ │ │ └── _type │ │ │ │ │ └── popup__container_type_form.css │ │ │ ├── _type │ │ │ │ └── popup_type_img.css │ │ │ ├── __figure-wrapper │ │ │ │ └── popup__figure-wrapper.css │ │ │ ├── _opened │ │ │ │ └── popup_opened.css │ │ │ ├── __status-icon │ │ │ │ ├── popup__status-icon.css │ │ │ │ └── _type │ │ │ │ │ ├── popup__status-icon_type_fail.css │ │ │ │ │ └── popup__status-icon_type_success.css │ │ │ ├── __img │ │ │ │ └── popup__img.css │ │ │ ├── __img-caption │ │ │ │ └── popup__img-caption.css │ │ │ ├── __status-text │ │ │ │ └── popup__status-text.css │ │ │ ├── __title │ │ │ │ └── popup__title.css │ │ │ ├── popup.css │ │ │ ├── __status-wrapper │ │ │ │ └── popup__status-wrapper.css │ │ │ └── __btn-close │ │ │ │ └── popup__btn-close.css │ │ ├── preloader │ │ │ ├── _active │ │ │ │ └── preloader_active.css │ │ │ └── preloader.css │ │ ├── form │ │ │ ├── __input-error │ │ │ │ ├── _active │ │ │ │ │ └── form__input-error_active.css │ │ │ │ └── form__input-error.css │ │ │ ├── __input │ │ │ │ ├── _type │ │ │ │ │ └── form__input_type_error.css │ │ │ │ ├── _place │ │ │ │ │ └── form__input_place_authorization.css │ │ │ │ └── form__input.css │ │ │ ├── _type │ │ │ │ └── form_type_card-delete-confirmation.css │ │ │ ├── _place │ │ │ │ └── form_place_authorization.css │ │ │ ├── form.css │ │ │ └── __btn-submit │ │ │ │ ├── _place │ │ │ │ └── form__btn-submit_place_authorization.css │ │ │ │ └── form__btn-submit.css │ │ ├── hamburger │ │ │ ├── _active │ │ │ │ └── hamburger_active.css │ │ │ ├── __user-email │ │ │ │ └── hamburger__user-email.css │ │ │ ├── hamburger.css │ │ │ └── __btn-sign-out │ │ │ │ └── hamburger__btn-sign-out.css │ │ ├── card │ │ │ ├── __like-wrapper │ │ │ │ └── card__like-wrapper.css │ │ │ ├── __img │ │ │ │ └── card__img.css │ │ │ ├── __btn-like │ │ │ │ ├── _active │ │ │ │ │ └── card__btn-like_active.css │ │ │ │ └── card__btn-like.css │ │ │ ├── __like-counter │ │ │ │ └── card__like-counter.css │ │ │ ├── __caption │ │ │ │ └── card__caption.css │ │ │ ├── __title │ │ │ │ └── card__title.css │ │ │ ├── card.css │ │ │ └── __btn-del │ │ │ │ └── card__btn-del.css │ │ ├── footer │ │ │ ├── footer.css │ │ │ └── __copyright │ │ │ │ └── footer__copyright.css │ │ ├── header │ │ │ ├── __logo │ │ │ │ └── header__logo.css │ │ │ ├── __btn-hamburger │ │ │ │ ├── _type │ │ │ │ │ └── header__btn-hamburger_type_close.css │ │ │ │ └── header__btn-hamburger.css │ │ │ ├── __nav │ │ │ │ └── header__nav.css │ │ │ ├── __user-email │ │ │ │ └── header__user-email.css │ │ │ ├── __link │ │ │ │ └── header__link.css │ │ │ ├── header.css │ │ │ └── __btn-sign-out │ │ │ │ └── header__btn-sign-out.css │ │ ├── profile │ │ │ ├── __avatar │ │ │ │ └── profile__avatar.css │ │ │ ├── __wrapper │ │ │ │ └── profile__wrapper.css │ │ │ ├── __user-edit │ │ │ │ └── profile__user-edit.css │ │ │ ├── __user-wrapper │ │ │ │ └── profile__user-wrapper.css │ │ │ ├── profile.css │ │ │ ├── __user-about │ │ │ │ └── profile__user-about.css │ │ │ ├── __btn-add │ │ │ │ └── profile__btn-add.css │ │ │ ├── __user-name │ │ │ │ └── profile__user-name.css │ │ │ ├── __btn-edit │ │ │ │ └── profile__btn-edit.css │ │ │ └── __btn-avatar-edit │ │ │ │ └── profile__btn-avatar-edit.css │ │ ├── authorization │ │ │ ├── __link │ │ │ │ └── authorization__link.css │ │ │ ├── __text │ │ │ │ └── authorization__text.css │ │ │ ├── __wrapper │ │ │ │ └── authorization__wrapper.css │ │ │ ├── authorization.css │ │ │ └── __title │ │ │ │ └── authorization__title.css │ │ ├── page │ │ │ ├── __content │ │ │ │ └── page__content.css │ │ │ └── page.css │ │ ├── cards │ │ │ ├── cards.css │ │ │ └── __wrapper │ │ │ │ └── cards__wrapper.css │ │ ├── not-found │ │ │ ├── not-found.css │ │ │ ├── __title │ │ │ │ └── not-found__title.css │ │ │ ├── __description │ │ │ │ └── not-found__description.css │ │ │ └── __link │ │ │ │ └── not-found__link.css │ │ ├── logo │ │ │ └── logo.css │ │ └── content │ │ │ └── content.css │ ├── fonts │ │ ├── Inter-Black.woff │ │ ├── Inter-Black.woff2 │ │ ├── Inter-Medium.woff │ │ ├── Inter-Medium.woff2 │ │ ├── Inter-Regular.woff │ │ └── Inter-Regular.woff2 │ ├── contexts │ │ └── CurrentUserContext.js │ ├── images │ │ ├── hamburger.svg │ │ ├── profile-add.svg │ │ ├── avatar-edit.svg │ │ ├── profile-edit.svg │ │ ├── close-icon.svg │ │ ├── card-like-active.svg │ │ ├── success.svg │ │ ├── fail.svg │ │ ├── card-like.svg │ │ ├── trash.svg │ │ ├── preloader.svg │ │ ├── logo-light.svg │ │ └── logo-dark.svg │ ├── components │ │ ├── Preloader.js │ │ ├── Footer.js │ │ ├── ProtectedRoute.js │ │ ├── HamburgerMenu.js │ │ ├── Header.js │ │ ├── NotFound.js │ │ ├── ImagePopup.js │ │ ├── AppLayout.js │ │ ├── PopupWithForm.js │ │ ├── DeleteCardPopup.js │ │ ├── Form.js │ │ ├── AuthScreen.js │ │ ├── InfoTooltip.js │ │ ├── Popup.js │ │ ├── NavBar.js │ │ ├── Card.js │ │ ├── EditAvatarPopup.js │ │ ├── Main.js │ │ ├── Login.js │ │ ├── Register.js │ │ ├── AddPlacePopup.js │ │ ├── EditProfilePopup.js │ │ └── App.js │ ├── setupTests.js │ ├── reportWebVitals.js │ ├── index.js │ ├── vendor │ │ ├── font.css │ │ └── normalize.css │ ├── utils │ │ ├── useValidation.js │ │ └── api.js │ └── index.css ├── .gitignore └── package.json ├── screenshot └── mesto_1.png ├── backend ├── .eslintrc ├── middlewares │ ├── limiter.js │ ├── errors.js │ ├── cors.js │ ├── logger.js │ └── auth.js ├── controllers │ ├── notFound.js │ ├── cards.js │ └── users.js ├── .gitignore ├── routes │ ├── notFound.js │ ├── signin.js │ ├── index.js │ ├── signup.js │ ├── users.js │ └── cards.js ├── utils │ ├── config.js │ └── constants.js ├── errors │ ├── incorrectDataError.js │ ├── conflictError.js │ ├── forbiddenError.js │ ├── notFoundError.js │ └── authorizationError.js ├── .editorconfig ├── models │ ├── card.js │ └── user.js ├── package.json └── app.js ├── .github └── workflows │ └── tests.yml └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.* -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__container/popup__container.css: -------------------------------------------------------------------------------- 1 | .popup__container { 2 | position: relative; 3 | } -------------------------------------------------------------------------------- /frontend/src/blocks/preloader/_active/preloader_active.css: -------------------------------------------------------------------------------- 1 | .preloader_active { 2 | visibility: visible; 3 | } -------------------------------------------------------------------------------- /screenshot/mesto_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjorn86/react-mesto-api-full-gha/HEAD/screenshot/mesto_1.png -------------------------------------------------------------------------------- /frontend/src/blocks/popup/_type/popup_type_img.css: -------------------------------------------------------------------------------- 1 | .popup_type_img { 2 | background-color: rgba(0, 0, 0, .9); 3 | } -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjorn86/react-mesto-api-full-gha/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__figure-wrapper/popup__figure-wrapper.css: -------------------------------------------------------------------------------- 1 | .popup__figure-wrapper { 2 | margin: 0; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/blocks/form/__input-error/_active/form__input-error_active.css: -------------------------------------------------------------------------------- 1 | .form__input-error_active { 2 | opacity: 1; 3 | } -------------------------------------------------------------------------------- /frontend/src/blocks/popup/_opened/popup_opened.css: -------------------------------------------------------------------------------- 1 | .popup_opened { 2 | visibility: visible; 3 | opacity: 1; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/blocks/hamburger/_active/hamburger_active.css: -------------------------------------------------------------------------------- 1 | .hamburger_active { 2 | position: relative; 3 | top: 0; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__status-icon/popup__status-icon.css: -------------------------------------------------------------------------------- 1 | .popup__status-icon { 2 | width: 120px; 3 | height: 120px; 4 | } -------------------------------------------------------------------------------- /frontend/src/blocks/form/__input/_type/form__input_type_error.css: -------------------------------------------------------------------------------- 1 | .form__input_type_error { 2 | border-bottom-color: #FF0000; 3 | } -------------------------------------------------------------------------------- /frontend/src/fonts/Inter-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjorn86/react-mesto-api-full-gha/HEAD/frontend/src/fonts/Inter-Black.woff -------------------------------------------------------------------------------- /frontend/src/blocks/form/_type/form_type_card-delete-confirmation.css: -------------------------------------------------------------------------------- 1 | .form_type_card-delete-confirmation { 2 | padding-top: 14px; 3 | } -------------------------------------------------------------------------------- /frontend/src/fonts/Inter-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjorn86/react-mesto-api-full-gha/HEAD/frontend/src/fonts/Inter-Black.woff2 -------------------------------------------------------------------------------- /frontend/src/fonts/Inter-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjorn86/react-mesto-api-full-gha/HEAD/frontend/src/fonts/Inter-Medium.woff -------------------------------------------------------------------------------- /frontend/src/fonts/Inter-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjorn86/react-mesto-api-full-gha/HEAD/frontend/src/fonts/Inter-Medium.woff2 -------------------------------------------------------------------------------- /frontend/src/fonts/Inter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjorn86/react-mesto-api-full-gha/HEAD/frontend/src/fonts/Inter-Regular.woff -------------------------------------------------------------------------------- /frontend/src/fonts/Inter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bjorn86/react-mesto-api-full-gha/HEAD/frontend/src/fonts/Inter-Regular.woff2 -------------------------------------------------------------------------------- /backend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "rules": { 4 | "no-underscore-dangle": ["error", { "allow": ["_id"] }] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/blocks/card/__like-wrapper/card__like-wrapper.css: -------------------------------------------------------------------------------- 1 | .card__like-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/blocks/footer/footer.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | font-family: "Inter", "Arial", sans-serif; 3 | max-width: 100%; 4 | flex-shrink: 0; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/blocks/header/__logo/header__logo.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 896px) { 2 | .header__logo { 3 | margin-left: 27px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/contexts/CurrentUserContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | // CURRENT USER CONTEXT 4 | export const CurrentUserContext = createContext(); 5 | -------------------------------------------------------------------------------- /frontend/src/blocks/card/__img/card__img.css: -------------------------------------------------------------------------------- 1 | .card__img { 2 | width: 282px; 3 | height: 282px; 4 | object-fit: cover; 5 | cursor: pointer; 6 | color: #fff; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/blocks/profile/__avatar/profile__avatar.css: -------------------------------------------------------------------------------- 1 | .profile__avatar { 2 | width: 100%; 3 | height: 100%; 4 | object-fit: cover; 5 | border-radius: 50%; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/images/hamburger.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__img/popup__img.css: -------------------------------------------------------------------------------- 1 | .popup__img { 2 | max-width: 75vw; 3 | max-height: 75vh; 4 | object-fit: cover; 5 | vertical-align: bottom; 6 | color: #fff; 7 | } 8 | -------------------------------------------------------------------------------- /backend/middlewares/limiter.js: -------------------------------------------------------------------------------- 1 | // IMPORT PACKAGES 2 | const rateLimit = require('express-rate-limit'); 3 | 4 | module.exports = rateLimit({ 5 | windowMs: 15 * 60 * 1000, 6 | max: 100, 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__status-icon/_type/popup__status-icon_type_fail.css: -------------------------------------------------------------------------------- 1 | .popup__status-icon_type_fail { 2 | background: url('../../../../images/fail.svg') center center/cover no-repeat; 3 | } -------------------------------------------------------------------------------- /frontend/src/blocks/card/__btn-like/_active/card__btn-like_active.css: -------------------------------------------------------------------------------- 1 | .card__btn-like_active { 2 | background: url('../../../../images/card-like-active.svg') center center/contain no-repeat; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__status-icon/_type/popup__status-icon_type_success.css: -------------------------------------------------------------------------------- 1 | .popup__status-icon_type_success { 2 | background: url('../../../../images/success.svg') center center/cover no-repeat; 3 | } -------------------------------------------------------------------------------- /frontend/src/images/profile-add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/blocks/card/__like-counter/card__like-counter.css: -------------------------------------------------------------------------------- 1 | .card__like-counter { 2 | margin: 0; 3 | font-weight: 400; 4 | font-size: 13px; 5 | line-height: 1.23; 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__img-caption/popup__img-caption.css: -------------------------------------------------------------------------------- 1 | .popup__img-caption { 2 | margin-top: 10px; 3 | font-weight: 400; 4 | font-size: 12px; 5 | line-height: 1.25; 6 | color: #fff; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/blocks/authorization/__link/authorization__link.css: -------------------------------------------------------------------------------- 1 | .authorization__link { 2 | text-decoration: none; 3 | transition: opacity .6s; 4 | } 5 | 6 | .authorization__link:hover { 7 | opacity: 0.6; 8 | } -------------------------------------------------------------------------------- /frontend/src/blocks/hamburger/__user-email/hamburger__user-email.css: -------------------------------------------------------------------------------- 1 | .hamburger__user-email { 2 | margin: 0; 3 | font-weight: 500; 4 | font-size: 18px; 5 | line-height: 1.22; 6 | color: #ffffff; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/images/avatar-edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/images/profile-edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/blocks/header/__btn-hamburger/_type/header__btn-hamburger_type_close.css: -------------------------------------------------------------------------------- 1 | .header__btn-hamburger_type_close { 2 | background: url('../../../../images/close-icon.svg') center center/20px no-repeat, transparent; 3 | } -------------------------------------------------------------------------------- /frontend/src/blocks/page/__content/page__content.css: -------------------------------------------------------------------------------- 1 | .page__content { 2 | min-width: 320px; 3 | max-width: 880px; 4 | min-height: 100vh; 5 | margin: 0 auto; 6 | display: flex; 7 | flex-direction: column; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/blocks/authorization/__text/authorization__text.css: -------------------------------------------------------------------------------- 1 | .authorization__text { 2 | margin: 15px 0 0; 3 | font-weight: 400; 4 | font-size: 14px; 5 | line-height: 1.21; 6 | text-align: center; 7 | color: #fff; 8 | } -------------------------------------------------------------------------------- /frontend/src/blocks/card/__caption/card__caption.css: -------------------------------------------------------------------------------- 1 | .card__caption { 2 | height: 80px; 3 | display: flex; 4 | justify-content: space-between; 5 | align-items: center; 6 | padding: 0 20px 0 21px; 7 | background-color: #fff; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/blocks/cards/cards.css: -------------------------------------------------------------------------------- 1 | .cards { 2 | font-family: "Inter", "Arial", sans-serif; 3 | max-width: 100%; 4 | } 5 | 6 | @media screen and (max-width: 896px) { 7 | .cards { 8 | padding: 0 19px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/blocks/form/_place/form_place_authorization.css: -------------------------------------------------------------------------------- 1 | .form_place_authorization { 2 | padding: 50px 0 0; 3 | } 4 | 5 | @media screen and (max-width: 544px) { 6 | .form_place_authorization { 7 | padding-top: 40px; 8 | } 9 | } -------------------------------------------------------------------------------- /frontend/src/blocks/header/__nav/header__nav.css: -------------------------------------------------------------------------------- 1 | .header__nav { 2 | display: flex; 3 | align-items: center; 4 | } 5 | 6 | @media screen and (max-width: 896px) { 7 | .header__nav { 8 | padding-right: 30px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/Preloader.js: -------------------------------------------------------------------------------- 1 | // PRELOADER COMPONENT 2 | function Preloader({ isActive }) { 3 | return ( 4 |
5 | ); 6 | } 7 | 8 | export default Preloader; 9 | -------------------------------------------------------------------------------- /backend/controllers/notFound.js: -------------------------------------------------------------------------------- 1 | // IMPORT ERRORS 2 | const NotFoundError = require('../errors/notFoundError'); 3 | 4 | // NOT FOUNDED ROUTE 5 | module.exports.notFound = (req, res, next) => { 6 | next(new NotFoundError('Указан несуществующий URL')); 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/blocks/card/__title/card__title.css: -------------------------------------------------------------------------------- 1 | .card__title { 2 | margin: 0; 3 | font-weight: 900; 4 | font-size: 24px; 5 | line-height: 1.21; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | overflow: hidden; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/images/close-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/blocks/not-found/not-found.css: -------------------------------------------------------------------------------- 1 | .not-found { 2 | font-family: "Inter", "Arial", sans-serif; 3 | max-width: 100%; 4 | padding: 60px 19px; 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | flex-grow: 1; 9 | } 10 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directory 7 | node_modules 8 | 9 | # Optional npm cache directory 10 | .npm 11 | 12 | # Optional REPL history 13 | .DS_Store 14 | .idea 15 | .vscode 16 | 17 | # Env variables 18 | *.env -------------------------------------------------------------------------------- /frontend/src/blocks/form/__input-error/form__input-error.css: -------------------------------------------------------------------------------- 1 | .form__input-error { 2 | display: block; 3 | min-height: 25px; 4 | margin-top: 5px; 5 | opacity: 0; 6 | font-weight: 400; 7 | font-size: 12px; 8 | line-height: 1.04; 9 | color: #FF0000; 10 | } -------------------------------------------------------------------------------- /frontend/src/blocks/card/card.css: -------------------------------------------------------------------------------- 1 | .card { 2 | font-family: "Inter", "Arial", sans-serif; 3 | width: 282px; 4 | height: 362px; 5 | position: relative; 6 | display: flex; 7 | flex-direction: column; 8 | border-radius: 10px; 9 | overflow: hidden; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/blocks/page/page.css: -------------------------------------------------------------------------------- 1 | .page { 2 | min-height: 100vh; 3 | max-width: 100vw; 4 | background-color: #000; 5 | padding-bottom: 60px; 6 | } 7 | 8 | @media screen and (max-width: 544px) { 9 | .page { 10 | padding-bottom: 36px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /frontend/src/blocks/form/__input/_place/form__input_place_authorization.css: -------------------------------------------------------------------------------- 1 | .form__input_place_authorization { 2 | border-bottom: 2px solid #CCC; 3 | background-color: transparent; 4 | color: #fff; 5 | } 6 | 7 | .form__input_place_authorization::placeholder { 8 | color: #CCC; 9 | } -------------------------------------------------------------------------------- /frontend/src/blocks/form/__input/form__input.css: -------------------------------------------------------------------------------- 1 | .form__input { 2 | width: 100%; 3 | height: 27px; 4 | padding: 0; 5 | border: none; 6 | border-bottom: 1px solid rgba(0, 0, 0, .2); 7 | outline: none; 8 | font-weight: 400; 9 | font-size: 14px; 10 | line-height: 1.21; 11 | } -------------------------------------------------------------------------------- /backend/routes/notFound.js: -------------------------------------------------------------------------------- 1 | // IMPORT PACKAGES 2 | const router = require('express').Router(); 3 | 4 | // IMPORT CONTROLLERS 5 | const { notFound } = require('../controllers/notFound'); 6 | 7 | // NOT FOUNDED ROUTE 8 | router.all('/*', notFound); 9 | 10 | // MODULE EXPORT 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /frontend/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | // FOOTER COMPONENT 2 | function Footer() { 3 | return ( 4 | 7 | ); 8 | } 9 | 10 | export default Footer; 11 | -------------------------------------------------------------------------------- /frontend/src/blocks/authorization/__wrapper/authorization__wrapper.css: -------------------------------------------------------------------------------- 1 | .authorization__wrapper { 2 | max-width: 358px; 3 | margin: 0 auto; 4 | } 5 | 6 | @media screen and (max-width: 544px) { 7 | .authorization__wrapper { 8 | padding-left: 30px; 9 | padding-right: 30px; 10 | } 11 | } -------------------------------------------------------------------------------- /frontend/src/blocks/logo/logo.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | width: 142px; 3 | height: 33px; 4 | background: url('../../images/logo-light.svg') center center/contain no-repeat; 5 | } 6 | 7 | @media screen and (max-width: 544px) { 8 | .logo { 9 | width: 104px; 10 | height: 25px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/blocks/profile/__wrapper/profile__wrapper.css: -------------------------------------------------------------------------------- 1 | .profile__wrapper { 2 | display: flex; 3 | gap: 30px; 4 | } 5 | 6 | @media screen and (max-width: 544px) { 7 | .profile__wrapper { 8 | gap: 26px; 9 | flex-direction: column; 10 | align-items: center; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/blocks/authorization/authorization.css: -------------------------------------------------------------------------------- 1 | .authorization { 2 | font-family: "Inter", "Arial", sans-serif; 3 | max-width: 100%; 4 | padding: 60px 0; 5 | flex-grow: 1; 6 | } 7 | 8 | @media screen and (max-width: 544px) { 9 | .authorization { 10 | padding-top: 40px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/blocks/profile/__user-edit/profile__user-edit.css: -------------------------------------------------------------------------------- 1 | .profile__user-edit { 2 | max-width: 100%; 3 | display: flex; 4 | align-items: baseline; 5 | gap: 18px; 6 | } 7 | 8 | @media screen and (max-width: 544px) { 9 | .profile__user-edit { 10 | position: relative; 11 | gap: 10px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/utils/config.js: -------------------------------------------------------------------------------- 1 | // DEFAULT VALUES 2 | const MODE_PRODUCTION = 'production'; 3 | const DEV_KEY = 'dev-secret-key'; 4 | const DEFAULT_PORT = 3000; 5 | const DEFAULT_DATABASE = 'mongodb://localhost:27017/mestodb'; 6 | 7 | module.exports = { 8 | MODE_PRODUCTION, 9 | DEV_KEY, 10 | DEFAULT_PORT, 11 | DEFAULT_DATABASE, 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/blocks/not-found/__title/not-found__title.css: -------------------------------------------------------------------------------- 1 | .not-found__title { 2 | margin: 0; 3 | font-weight: 900; 4 | font-size: 32px; 5 | line-height: 1.21; 6 | text-align: center; 7 | color: #fff; 8 | } 9 | 10 | @media screen and (max-width: 544px) { 11 | .not-found__title { 12 | font-size: 24px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/blocks/preloader/preloader.css: -------------------------------------------------------------------------------- 1 | .preloader { 2 | font-family: "Inter", "Arial", sans-serif; 3 | width: 100%; 4 | height: 100%; 5 | position: fixed; 6 | visibility: hidden; 7 | left: 0; 8 | top: 0; 9 | background: url("../../images/preloader.svg") center center/auto no-repeat, rgb(0, 0, 0); 10 | } 11 | -------------------------------------------------------------------------------- /backend/errors/incorrectDataError.js: -------------------------------------------------------------------------------- 1 | const { BAD_REQUEST_ERROR_CODE } = require('../utils/constants'); 2 | 3 | // AUTHORIZATION ERROR 4 | class IncorrectDataError extends Error { 5 | constructor(message) { 6 | super(message); 7 | this.statusCode = BAD_REQUEST_ERROR_CODE; 8 | } 9 | } 10 | 11 | module.exports = IncorrectDataError; 12 | -------------------------------------------------------------------------------- /backend/errors/conflictError.js: -------------------------------------------------------------------------------- 1 | // IMPORT VARIABLES 2 | const { CONFLICT_ERROR_CODE } = require('../utils/constants'); 3 | 4 | // AUTHORIZATION ERROR 5 | class ConflictError extends Error { 6 | constructor(message) { 7 | super(message); 8 | this.statusCode = CONFLICT_ERROR_CODE; 9 | } 10 | } 11 | 12 | module.exports = ConflictError; 13 | -------------------------------------------------------------------------------- /backend/errors/forbiddenError.js: -------------------------------------------------------------------------------- 1 | // IMPORT VARIABLES 2 | const { FORBIDDEN_ERROR_CODE } = require('../utils/constants'); 3 | 4 | // AUTHORIZATION ERROR 5 | class ForbiddenError extends Error { 6 | constructor(message) { 7 | super(message); 8 | this.statusCode = FORBIDDEN_ERROR_CODE; 9 | } 10 | } 11 | 12 | module.exports = ForbiddenError; 13 | -------------------------------------------------------------------------------- /backend/errors/notFoundError.js: -------------------------------------------------------------------------------- 1 | // IMPORT VARIABLES 2 | const { NOT_FOUND_ERROR_CODE } = require('../utils/constants'); 3 | 4 | // AUTHORIZATION ERROR 5 | class NotFoundError extends Error { 6 | constructor(message) { 7 | super(message); 8 | this.statusCode = NOT_FOUND_ERROR_CODE; 9 | } 10 | } 11 | 12 | module.exports = NotFoundError; 13 | -------------------------------------------------------------------------------- /frontend/src/blocks/form/form.css: -------------------------------------------------------------------------------- 1 | .form { 2 | box-sizing: border-box; 3 | -webkit-box-sizing: border-box; 4 | -moz-box-sizing: border-box; 5 | padding: 46px 36px 37px; 6 | display: flex; 7 | flex-direction: column; 8 | } 9 | 10 | @media screen and (max-width: 544px) { 11 | .form { 12 | padding: 73px 22px 25px; 13 | } 14 | } -------------------------------------------------------------------------------- /frontend/src/blocks/header/__user-email/header__user-email.css: -------------------------------------------------------------------------------- 1 | .header__user-email { 2 | margin: 0; 3 | padding-right: 24px; 4 | font-weight: 500; 5 | font-size: 18px; 6 | line-height: 1.22; 7 | color: #ffffff; 8 | } 9 | 10 | @media screen and (max-width: 544px) { 11 | .header__user-email { 12 | display: none; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/blocks/not-found/__description/not-found__description.css: -------------------------------------------------------------------------------- 1 | .not-found__description { 2 | margin: 30px 0 0; 3 | font-weight: 400; 4 | font-size: 16px; 5 | line-height: 1.21; 6 | text-align: center; 7 | color: #fff; 8 | } 9 | 10 | @media screen and (max-width: 544px) { 11 | .not-found__description { 12 | font-size: 14px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__status-text/popup__status-text.css: -------------------------------------------------------------------------------- 1 | .popup__status-text { 2 | margin: 32px 0 0; 3 | font-weight: 900; 4 | font-size: 24px; 5 | line-height: 1.21; 6 | text-align: center; 7 | } 8 | 9 | @media screen and (max-width: 544px) { 10 | .popup__status-text { 11 | margin-top: 40px; 12 | font-size: 20px; 13 | } 14 | } -------------------------------------------------------------------------------- /backend/errors/authorizationError.js: -------------------------------------------------------------------------------- 1 | // IMPORT VARIABLES 2 | const { UNAUTHORIZED_ERROR_CODE } = require('../utils/constants'); 3 | 4 | // AUTHORIZATION ERROR 5 | class AuthorizationError extends Error { 6 | constructor(message) { 7 | super(message); 8 | this.statusCode = UNAUTHORIZED_ERROR_CODE; 9 | } 10 | } 11 | 12 | module.exports = AuthorizationError; 13 | -------------------------------------------------------------------------------- /frontend/src/blocks/card/__btn-like/card__btn-like.css: -------------------------------------------------------------------------------- 1 | .card__btn-like { 2 | width: 22px; 3 | height: 19px; 4 | padding: 0; 5 | cursor: pointer; 6 | border: none; 7 | background: url('../../../images/card-like.svg') center center/contain no-repeat; 8 | transition: opacity .6s; 9 | } 10 | 11 | .card__btn-like:hover { 12 | opacity: .5; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/blocks/content/content.css: -------------------------------------------------------------------------------- 1 | .content { 2 | max-width: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | gap: 50px; 6 | padding: 40px 0 66px; 7 | flex-grow: 1; 8 | } 9 | 10 | @media screen and (max-width: 544px) { 11 | .content { 12 | padding-top: 42px; 13 | padding-bottom: 48px; 14 | gap: 36px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/blocks/hamburger/hamburger.css: -------------------------------------------------------------------------------- 1 | .hamburger { 2 | font-family: "Inter", "Arial", sans-serif; 3 | position: absolute; 4 | top: -143px; 5 | max-width: 100%; 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | gap: 18px; 10 | padding: 40px 0 40px; 11 | border-bottom: 1px solid rgba(84, 84, 84, 0.7); 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/components/ProtectedRoute.js: -------------------------------------------------------------------------------- 1 | import { Navigate } from "react-router-dom"; 2 | 3 | // 4 | const ProtectedRouteElement = ({ element: Component, ...props }) => { 5 | return props.loggedIn ? ( 6 | 7 | ) : ( 8 | 9 | ); 10 | }; 11 | 12 | export default ProtectedRouteElement; 13 | -------------------------------------------------------------------------------- /frontend/src/images/card-like-active.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/blocks/authorization/__title/authorization__title.css: -------------------------------------------------------------------------------- 1 | .authorization__title { 2 | margin: 0; 3 | font-weight: 900; 4 | font-size: 24px; 5 | line-height: 1.21; 6 | color: #fff; 7 | text-align: center; 8 | } 9 | 10 | @media screen and (max-width: 544px) { 11 | .authorization__title { 12 | font-size: 20px; 13 | line-height: 1.2; 14 | } 15 | } -------------------------------------------------------------------------------- /frontend/src/blocks/profile/__user-wrapper/profile__user-wrapper.css: -------------------------------------------------------------------------------- 1 | .profile__user-wrapper { 2 | max-width: 336px; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: center; 6 | gap: 8px; 7 | } 8 | 9 | @media screen and (max-width: 544px) { 10 | .profile__user-wrapper { 11 | max-width: 220px; 12 | align-items: center; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Mesto", 3 | "name": "Mesto", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__container/_type/popup__container_type_form.css: -------------------------------------------------------------------------------- 1 | .popup__container_type_form { 2 | width: 100%; 3 | max-width: 430px; 4 | background-color: #fff; 5 | box-shadow: 0px 0px 25px rgba(0, 0, 0, 0.15); 6 | border-radius: 10px; 7 | } 8 | 9 | @media screen and (max-width: 544px) { 10 | .popup__container_type_form { 11 | margin: 0 19px; 12 | } 13 | } -------------------------------------------------------------------------------- /frontend/src/blocks/profile/profile.css: -------------------------------------------------------------------------------- 1 | .profile { 2 | font-family: "Inter", "Arial", sans-serif; 3 | max-width: 100%; 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | } 8 | 9 | @media screen and (max-width: 896px) { 10 | .profile { 11 | padding: 0 19px; 12 | flex-direction: column; 13 | gap: 32px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/blocks/header/__link/header__link.css: -------------------------------------------------------------------------------- 1 | .header__link { 2 | font-weight: 400; 3 | font-size: 18px; 4 | line-height: 1.21; 5 | color: #fff; 6 | text-decoration: none; 7 | transition: opacity .6s; 8 | } 9 | 10 | .header__link:hover { 11 | opacity: 0.6; 12 | } 13 | 14 | @media screen and (max-width: 544px) { 15 | .header__link { 16 | font-size: 14px; 17 | } 18 | } -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__title/popup__title.css: -------------------------------------------------------------------------------- 1 | .popup__title { 2 | margin: 0; 3 | padding: 34px 36px 0; 4 | font-weight: 900; 5 | font-size: 24px; 6 | line-height: 1.21; 7 | } 8 | 9 | @media screen and (max-width: 544px) { 10 | .popup__title { 11 | padding-top: 25px; 12 | padding-right: 22px; 13 | padding-left: 22px; 14 | font-size: 18px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/blocks/cards/__wrapper/cards__wrapper.css: -------------------------------------------------------------------------------- 1 | .cards__wrapper { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fit, minmax(282px, 1fr)); 4 | justify-items: center; 5 | gap: 20px 17px; 6 | list-style: none; 7 | margin: 0 auto; 8 | padding: 0; 9 | } 10 | 11 | @media screen and (max-width: 896px) { 12 | .cards__wrapper { 13 | max-width: 581px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/popup.css: -------------------------------------------------------------------------------- 1 | .popup { 2 | font-family: "Inter", "Arial", sans-serif; 3 | width: 100%; 4 | height: 100%; 5 | position: fixed; 6 | left: 0; 7 | top: 0; 8 | visibility: hidden; 9 | opacity: 0; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | background-color: rgba(0, 0, 0, .5); 14 | transition: visibility .6s, opacity .6s; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/images/success.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/blocks/card/__btn-del/card__btn-del.css: -------------------------------------------------------------------------------- 1 | .card__btn-del { 2 | width: 18px; 3 | height: 19px; 4 | position: absolute; 5 | top: 20px; 6 | right: 20px; 7 | padding: 0; 8 | cursor: pointer; 9 | border: none; 10 | background: url('../../../images/trash.svg') center center/contain no-repeat; 11 | transition: opacity .6s; 12 | } 13 | 14 | .card__btn-del:hover { 15 | opacity: .6; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/blocks/not-found/__link/not-found__link.css: -------------------------------------------------------------------------------- 1 | .not-found__link { 2 | margin: 15px 0 0; 3 | font-weight: 400; 4 | font-size: 16px; 5 | line-height: 1.21; 6 | text-decoration: none; 7 | color: #fff; 8 | transition: opacity 0.6s; 9 | } 10 | 11 | .not-found__link:hover { 12 | opacity: 0.6; 13 | } 14 | 15 | @media screen and (max-width: 544px) { 16 | .not-found__link { 17 | font-size: 14px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__status-wrapper/popup__status-wrapper.css: -------------------------------------------------------------------------------- 1 | .popup__status-wrapper { 2 | box-sizing: border-box; 3 | -webkit-box-sizing: border-box; 4 | -moz-box-sizing: border-box; 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | padding: 60px 36px; 9 | } 10 | 11 | @media screen and (max-width: 544px) { 12 | .popup__status-wrapper { 13 | padding: 50px 18px; 14 | } 15 | } -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/src/blocks/header/header.css: -------------------------------------------------------------------------------- 1 | .header { 2 | font-family: "Inter", "Arial", sans-serif; 3 | max-width: 100%; 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | padding: 45px 0 41px; 8 | border-bottom: 1px solid rgba(84, 84, 84, 0.7); 9 | } 10 | 11 | @media screen and (max-width: 544px) { 12 | .header { 13 | padding-top: 28px; 14 | padding-bottom: 30px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/blocks/footer/__copyright/footer__copyright.css: -------------------------------------------------------------------------------- 1 | .footer__copyright { 2 | margin: 0; 3 | font-weight: 400; 4 | font-size: 18px; 5 | line-height: 1.21; 6 | color: #545454; 7 | } 8 | 9 | @media screen and (max-width: 896px) { 10 | .footer__copyright { 11 | padding-left: 19px; 12 | } 13 | } 14 | 15 | @media screen and (max-width: 544px) { 16 | .footer__copyright { 17 | font-size: 14px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /frontend/src/blocks/hamburger/__btn-sign-out/hamburger__btn-sign-out.css: -------------------------------------------------------------------------------- 1 | .hamburger__btn-sign-out { 2 | width: 56px; 3 | height: 22px; 4 | background-color: transparent; 5 | padding: 0; 6 | border: none; 7 | cursor: pointer; 8 | font-weight: 400; 9 | font-size: 18px; 10 | line-height: 1.22; 11 | color: #a9a9a9; 12 | transition: opacity 0.6s; 13 | } 14 | 15 | .hamburger__btn-sign-out:hover { 16 | opacity: 0.6; 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/images/fail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/routes/signin.js: -------------------------------------------------------------------------------- 1 | // IMPORT PACKAGES 2 | const router = require('express').Router(); 3 | const { celebrate, Joi } = require('celebrate'); 4 | 5 | // IMPORT CONTROLLERS 6 | const { login } = require('../controllers/users'); 7 | 8 | // LOGIN ROUTE 9 | router.post('/', celebrate({ 10 | body: Joi.object().keys({ 11 | email: Joi.string().required().email(), 12 | password: Joi.string().required(), 13 | }), 14 | }), login); 15 | 16 | // MODULE EXPORT 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /backend/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | // IMPORT VARIABLES 2 | const { DEFAULT_ERROR_CODE } = require('../utils/constants'); 3 | 4 | // ERRORS MIDDLEWARE 5 | module.exports = (err, req, res, next) => { 6 | const statusCode = err.statusCode || DEFAULT_ERROR_CODE; 7 | const errorMessage = statusCode === DEFAULT_ERROR_CODE ? `Произошла неизвестная ошибка ${err.name}: ${err.message}` : err.message; 8 | res.status(statusCode).send({ 9 | message: errorMessage, 10 | }); 11 | return next(); 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/blocks/profile/__user-about/profile__user-about.css: -------------------------------------------------------------------------------- 1 | .profile__user-about { 2 | max-width: 85%; 3 | margin: 0; 4 | font-weight: 400; 5 | font-size: 18px; 6 | line-height: 1.21; 7 | color: #fff; 8 | text-overflow: ellipsis; 9 | white-space: nowrap; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 544px) { 14 | .profile__user-about { 15 | max-width: 90%; 16 | font-size: 14px; 17 | text-align: center; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/components/HamburgerMenu.js: -------------------------------------------------------------------------------- 1 | // HAMBURGER MENU COMPONENT 2 | function HamburgerMenu({ email, isOpen, onLogOut }) { 3 | return ( 4 |
5 |

{email || ""}

6 | 13 |
14 | ); 15 | } 16 | 17 | export default HamburgerMenu; 18 | -------------------------------------------------------------------------------- /frontend/src/components/Header.js: -------------------------------------------------------------------------------- 1 | // IMPORT COMPONENTS 2 | import NavBar from "./NavBar"; 3 | 4 | // HEADER COMPONENT 5 | function Header({ email, onHamburgerClick, isOpen, onLogOut }) { 6 | return ( 7 |
8 |
9 | 15 |
16 | ); 17 | } 18 | 19 | export default Header; 20 | -------------------------------------------------------------------------------- /frontend/src/blocks/header/__btn-hamburger/header__btn-hamburger.css: -------------------------------------------------------------------------------- 1 | .header__btn-hamburger { 2 | display: none; 3 | width: 24px; 4 | height: 22px; 5 | background: url("../../../images/hamburger.svg") center center/contain no-repeat, transparent; 6 | padding: 0; 7 | border: none; 8 | cursor: pointer; 9 | transition: opacity 0.6s; 10 | } 11 | 12 | .header__btn-hamburger:hover { 13 | opacity: 0.6; 14 | } 15 | 16 | @media screen and (max-width: 544px) { 17 | .header__btn-hamburger { 18 | display: block; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/blocks/header/__btn-sign-out/header__btn-sign-out.css: -------------------------------------------------------------------------------- 1 | .header__btn-sign-out { 2 | width: 56px; 3 | height: 22px; 4 | background-color: transparent; 5 | padding: 0; 6 | border: none; 7 | cursor: pointer; 8 | font-weight: 400; 9 | font-size: 18px; 10 | line-height: 1.22; 11 | color: #a9a9a9; 12 | transition: opacity 0.6s; 13 | } 14 | 15 | .header__btn-sign-out:hover { 16 | opacity: 0.6; 17 | } 18 | 19 | @media screen and (max-width: 544px) { 20 | .header__btn-sign-out { 21 | display: none; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/components/NotFound.js: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | // NOT FOUND COMPONENT 4 | function NotFound() { 5 | return ( 6 |
7 |

404 - Страница не найдена

8 |

9 | Извините, страница которую вы ищите не найдена. 10 |

11 | 12 | Вернуться на главную 13 | 14 |
15 | ); 16 | } 17 | 18 | export default NotFound; 19 | -------------------------------------------------------------------------------- /frontend/src/images/card-like.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/blocks/profile/__btn-add/profile__btn-add.css: -------------------------------------------------------------------------------- 1 | .profile__btn-add { 2 | min-width: 150px; 3 | min-height: 50px; 4 | padding: 0; 5 | cursor: pointer; 6 | border: 2px solid #fff; 7 | border-radius: 2px; 8 | background: url('../../../images/profile-add.svg') center center/22px no-repeat; 9 | transition: opacity .6s; 10 | } 11 | 12 | .profile__btn-add:hover { 13 | opacity: .6; 14 | } 15 | 16 | @media screen and (max-width: 544px) { 17 | .profile__btn-add { 18 | min-width: 282px; 19 | background-size: 16px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/components/ImagePopup.js: -------------------------------------------------------------------------------- 1 | import Popup from "./Popup"; 2 | 3 | // IMAGE POPUP COMPONENT 4 | function ImagePopup({ card, onClose }) { 5 | return ( 6 | 11 |
12 | {card?.name} 13 |
{card?.name}
14 |
15 |
16 | ); 17 | } 18 | 19 | export default ImagePopup; 20 | -------------------------------------------------------------------------------- /frontend/src/images/trash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/blocks/profile/__user-name/profile__user-name.css: -------------------------------------------------------------------------------- 1 | .profile__user-name { 2 | width: 100%; 3 | margin: 0; 4 | font-weight: 500; 5 | font-size: 42px; 6 | line-height: 1.14; 7 | color: #fff; 8 | text-overflow: ellipsis; 9 | white-space: nowrap; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 896px) { 14 | .profile__user-name { 15 | font-size: 34px; 16 | } 17 | } 18 | 19 | @media screen and (max-width: 544px) { 20 | .profile__user-name { 21 | font-size: 27px; 22 | line-height: 1.21; 23 | text-align: center; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/blocks/popup/__btn-close/popup__btn-close.css: -------------------------------------------------------------------------------- 1 | .popup__btn-close { 2 | min-width: 32px; 3 | min-height: 32px; 4 | top: -40px; 5 | right: -40px; 6 | position: absolute; 7 | padding: 0; 8 | border: none; 9 | cursor: pointer; 10 | background: url('../../../images/close-icon.svg') center center/contain no-repeat; 11 | transition: opacity .6s; 12 | } 13 | 14 | .popup__btn-close:hover { 15 | opacity: .6; 16 | } 17 | 18 | @media screen and (max-width: 544px) { 19 | .popup__btn-close { 20 | min-width: 20px; 21 | min-height: 20px; 22 | top: -36px; 23 | right: 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/blocks/profile/__btn-edit/profile__btn-edit.css: -------------------------------------------------------------------------------- 1 | .profile__btn-edit { 2 | display: inline-flex; 3 | min-width: 24px; 4 | min-height: 24px; 5 | padding: 0; 6 | cursor: pointer; 7 | border: 1px solid #fff; 8 | background: url('../../../images/profile-edit.svg') center center/10px no-repeat; 9 | transition: opacity .6s; 10 | } 11 | 12 | .profile__btn-edit:hover { 13 | opacity: .6; 14 | } 15 | 16 | @media screen and (max-width: 544px) { 17 | .profile__btn-edit { 18 | position: absolute; 19 | min-width: 18px; 20 | min-height: 18px; 21 | top: 8px; 22 | right: -28px; 23 | background-size: 7.5px; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/routes/index.js: -------------------------------------------------------------------------------- 1 | // IMPORT PACKAGES 2 | const rootRouter = require('express').Router(); 3 | 4 | // IMPORT ROUTES 5 | const signin = require('./signin'); 6 | const signup = require('./signup'); 7 | const users = require('./users'); 8 | const cards = require('./cards'); 9 | const notFound = require('./notFound'); 10 | 11 | // IMPORT MIDDLEWARES 12 | const auth = require('../middlewares/auth'); 13 | 14 | // ROUTES METHODS 15 | rootRouter.use('/signin', signin); 16 | rootRouter.use('/signup', signup); 17 | rootRouter.use('/users', auth, users); 18 | rootRouter.use('/cards', auth, cards); 19 | rootRouter.use('*', auth, notFound); 20 | 21 | // EXPORT ROUTES 22 | module.exports = rootRouter; 23 | -------------------------------------------------------------------------------- /frontend/src/blocks/form/__btn-submit/_place/form__btn-submit_place_authorization.css: -------------------------------------------------------------------------------- 1 | .form__btn-submit_place_authorization { 2 | margin-top: 186px; 3 | background-color: #fff; 4 | color: #000; 5 | } 6 | 7 | .form__btn-submit_place_authorization:hover { 8 | opacity: 0.85; 9 | } 10 | 11 | .form__btn-submit_place_authorization:disabled { 12 | background-color: #CCC; 13 | border: 1px solid rgba(255, 255, 255, .2); 14 | color: rgba(0, 0, 0, .4); 15 | pointer-events: none; 16 | } 17 | 18 | @media screen and (max-width: 544px) { 19 | .form__btn-submit_place_authorization { 20 | margin-top: 143px; 21 | font-size: 16px; 22 | line-height: 1.19; 23 | } 24 | } -------------------------------------------------------------------------------- /frontend/src/blocks/profile/__btn-avatar-edit/profile__btn-avatar-edit.css: -------------------------------------------------------------------------------- 1 | .profile__btn-avatar-edit { 2 | width: 120px; 3 | height: 120px; 4 | position: relative; 5 | padding: 0; 6 | border: none; 7 | border-radius: 50%; 8 | cursor: pointer; 9 | background-color: transparent; 10 | } 11 | 12 | .profile__btn-avatar-edit::before { 13 | content: ''; 14 | width: 100%; 15 | height: 100%; 16 | position: absolute; 17 | top: 0; 18 | right: 0; 19 | background: url("../../../images/avatar-edit.svg") center center/auto no-repeat, rgb(0, 0, 0); 20 | opacity: 0; 21 | transition: opacity .6s; 22 | } 23 | 24 | .profile__btn-avatar-edit:hover::before { 25 | opacity: 0.8; 26 | } 27 | -------------------------------------------------------------------------------- /backend/routes/signup.js: -------------------------------------------------------------------------------- 1 | // IMPORT PACKAGES 2 | const router = require('express').Router(); 3 | const { celebrate, Joi } = require('celebrate'); 4 | 5 | // IMPORT CONTROLLERS 6 | const { createUser } = require('../controllers/users'); 7 | 8 | // IMPORT VARIABLES 9 | const { LINK_REGEXP } = require('../utils/constants'); 10 | 11 | // LOGIN ROUTE 12 | router.post('/', celebrate({ 13 | body: Joi.object().keys({ 14 | name: Joi.string().min(2).max(30), 15 | about: Joi.string().min(2).max(30), 16 | avatar: Joi.string().regex(LINK_REGEXP), 17 | email: Joi.string().required().email(), 18 | password: Joi.string().required(), 19 | }), 20 | }), createUser); 21 | 22 | // MODULE EXPORT 23 | module.exports = router; 24 | -------------------------------------------------------------------------------- /backend/middlewares/cors.js: -------------------------------------------------------------------------------- 1 | // CORS VARIABLES 2 | const { ALLOWED_CORS, DEFAULT_ALLOWED_METHODS } = require('../utils/constants'); 3 | 4 | // CORS MIDDLEWARE 5 | module.exports = (req, res, next) => { 6 | const { origin } = req.headers; 7 | const { method } = req; 8 | const requestHeaders = req.headers['access-control-request-headers']; 9 | if (ALLOWED_CORS.includes(origin)) { 10 | res.header('Access-Control-Allow-Origin', origin); 11 | res.header('Access-Control-Allow-Credentials', true); 12 | } 13 | if (method === 'OPTIONS') { 14 | res.header('Access-Control-Allow-Methods', DEFAULT_ALLOWED_METHODS); 15 | res.header('Access-Control-Allow-Headers', requestHeaders); 16 | return res.end(); 17 | } 18 | return next(); 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { BrowserRouter } from "react-router-dom"; 4 | import "./index.css"; 5 | import App from "./components/App"; 6 | import reportWebVitals from "./reportWebVitals"; 7 | 8 | const root = ReactDOM.createRoot(document.getElementById("root")); 9 | root.render( 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | 17 | // If you want to start measuring performance in your app, pass a function 18 | // to log results (for example: reportWebVitals(console.log)) 19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 20 | reportWebVitals(); 21 | -------------------------------------------------------------------------------- /frontend/src/vendor/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Inter; 3 | font-display: swap; 4 | src: url("../fonts/Inter-Regular.woff2") format("woff2"), url("../fonts/Inter-Regular.woff") format("woff"); 5 | font-weight: 400; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: Inter; 11 | font-display: swap; 12 | src: url("../fonts/Inter-Medium.woff2") format("woff2"), url("../fonts/Inter-Medium.woff") format("woff"); 13 | font-weight: 500; 14 | font-style: normal; 15 | } 16 | 17 | @font-face { 18 | font-family: Inter; 19 | font-display: swap; 20 | src: url("../fonts/Inter-Black.woff2") format("woff2"), url("../fonts/Inter-Black.woff") format("woff"); 21 | font-weight: 900; 22 | font-style: normal; 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/components/AppLayout.js: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | 3 | // IMPORT COMPONENTS 4 | import Footer from "./Footer"; 5 | import HamburgerMenu from "./HamburgerMenu"; 6 | import Header from "./Header"; 7 | 8 | // APP LAYOUT COMPONENT 9 | function AppLayout({ email, isOpen, onHamburgerClick, onLogOut }) { 10 | return ( 11 | <> 12 | 17 |
23 | 24 |