diff --git a/README.md b/README.md
index e69de29b..d2c4da20 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,124 @@
+# Оглавление
+1. [Задание 1](#task_1)
+2. [Задание 2](#task_2)
+
+# Задание 1
+
+Для решения необходимо провести анализ приложения и определить бизнес-функции приложения, руководствуясь принципами Domain-Driven-Design.
+
+## Анализ приложения
+
+Проект Mesto - фронтенд-приложение на React с простым функционалом загрузки фотографий, их отображения и возможностью реакций от зарегистрированных пользователей, таким образом мы имеем следующи фукнции приложения:
+* профиль пользователя
+* галерея фотографий
+* авторизация и аутентификация
+
+Так же стоит отдельно отметить, что выделена функция авторизации и аутентификации, чтобы была возможность развивать профиль как самостоятельную функцию.
+
+## Метод реализации
+
+В качестве метода реализации будет выбрана run time с клиентской компновкой, так у нас будет возможность разрабатывать микрофронты отдельно, а так же осуществлять динамическую lazy-загрузку компонентов.
+
+## Инструмент реализации
+
+В качестве инструмента выбран `Webpack Module Federation`, так как есть опыт работы с данным фреймворком и все части у нас написаны на React. `Single SPA` так же отлично подойдет для решения задачи, в случае наличия опыта у команды и наличия компонентов, реализованных не на React.
+
+## Приложение (Уровень 2)
+
+Структура приложения в папке [microfrontend](frontend/microfrontend)
+
+```text
+/auth
+/gallery
+/profile
+/src
+.babelrc
+.gitignore
+package.json
+webpack.config.js
+```
+
+### auth
+Модуль авторизации и аутентификации пользователя. Отображает страницы регистрации и авторизации, а так же показывает всплывающее окно об успешной авторизации на сайте. Его структура:
+```text
+/auth
+├── /src
+│ ├── /blocks
+│ │ └── /auth-form # стили формы авторизации
+│ ├── /components
+│ │ ├── InfoTooltip.js # компонент всплывающего окна об успешной регистрации
+│ │ ├── Login.js # компонент формы авторизации
+│ │ ├── ProtectedRoute.js # компонент роута проверки авторизации
+│ │ └── Register.js # компонент формы регистрации
+│ │── /utils
+│ │ └── auth.js # утилитарный класс для работы с апи авторизации
+│ └── index.js # точка входа для root приложения
+├── package.json # описание приложения и его зависимостей
+└── webpack.config.js # настройка webpack module federation
+```
+
+### gallery
+Модуль работы с фотографиями - позволяет загружать фотографии, удалять их, а так же ставить и учитывать лайки под ними.
+```text
+/gallery
+├── /src
+│ ├── /blocks
+│ │ ├── /card # стили карточек фотографий и лайков
+│ │ ├── /places # стили мест
+│ │ └── /popup # стили всплывающих окон
+│ ├── /components
+│ │ ├── AddPlacePopup.js # компонент добавления места с указанием фото
+│ │ ├── Card.js # компонент отображения фото и лайков
+│ │ ├── ImagePopup.js # компонент просмотра отдельного фото
+│ │ ├── Main.js # компонент отображения плитки с фото
+│ │ └── PopupWithForm.js # компонент всплывающего окна (используется в AddPlacePopup)
+│ │── /utils
+│ │ └── api.js # утилитарный класс для работы с апи фото
+│ └── index.js # точка входа для root приложения
+├── package.json # описание приложения и его зависимостей
+└── webpack.config.js # настройка webpack module federation
+```
+
+### profile
+Модуль отвечает за профиль пользователя.
+```text
+/gallery
+├── /src
+│ ├── /blocks
+│ │ ├── /popup # стили всплывающих окон
+│ │ └── /profile # стили профиля
+│ ├── /components
+│ │ ├── EditAvatarPopup.js # компонент формы редактирования аватра
+│ │ ├── EditProfilePopup.js # компонент формы редактирования профиля
+│ │ └── PopupWithForm.js # компонент всплывающего окна
+│ │── /utils
+│ │ └── api.js # утилитарный класс для работы с апи фото
+│ └── index.js # точка входа для root приложения
+├── package.json # описание приложения и его зависимостей
+└── webpack.config.js # настройка webpack module federation
+```
+
+### root (каталог src)
+Корневой модуль хост-приложения, который объединяет все микрофронты в один.
+```text
+/
+├── /src
+│ ├── /blocks
+│ │ ├── /content # основной стиль
+│ │ ├── /footer # стиль подвала страницы
+│ │ ├── /header # стиль шапки страницы
+│ │ └── /popup # стили всплывающих окон
+│ ├── /components
+│ │ ├── App.js # компонент основного приложения, собирающего все вместе
+│ │ ├── Footer.js # компонент подвала страницы
+│ │ └── Header.js # компонент шапки страницы
+│ └── index.js # точка входа при запуске и сборки основного фронта
+├── package.json # описание хост приложения и его зависимостей
+└── webpack.config.js # настройка сборки всего фронта
+```
+
+Уровень 3 (запуск готового кода) не выполнялся
+
+# Задание 2
+
+Итоговая диаграмма в файле [arch_template_task2.drawio](arch_template_task2.drawio), вкладка Solution
\ No newline at end of file
diff --git a/arch_template_task2.drawio b/arch_template_task2.drawio
new file mode 100644
index 00000000..2620dfd0
--- /dev/null
+++ b/arch_template_task2.drawio
@@ -0,0 +1,2451 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/microfrontend/.babelrc b/frontend/microfrontend/.babelrc
new file mode 100644
index 00000000..ea5702fa
--- /dev/null
+++ b/frontend/microfrontend/.babelrc
@@ -0,0 +1,7 @@
+{
+ "presets": [
+ ["@babel/preset-react", { "runtime": "automatic" }],
+ "@babel/preset-env"
+ ],
+ "plugins": [["@babel/transform-runtime"]]
+}
diff --git a/frontend/microfrontend/.gitignore b/frontend/microfrontend/.gitignore
new file mode 100644
index 00000000..1f22b9c2
--- /dev/null
+++ b/frontend/microfrontend/.gitignore
@@ -0,0 +1,116 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
diff --git a/frontend/microfrontend/auth/.babelrc b/frontend/microfrontend/auth/.babelrc
new file mode 100644
index 00000000..ea5702fa
--- /dev/null
+++ b/frontend/microfrontend/auth/.babelrc
@@ -0,0 +1,7 @@
+{
+ "presets": [
+ ["@babel/preset-react", { "runtime": "automatic" }],
+ "@babel/preset-env"
+ ],
+ "plugins": [["@babel/transform-runtime"]]
+}
diff --git a/frontend/microfrontend/auth/.gitignore b/frontend/microfrontend/auth/.gitignore
new file mode 100644
index 00000000..1f22b9c2
--- /dev/null
+++ b/frontend/microfrontend/auth/.gitignore
@@ -0,0 +1,116 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
diff --git a/frontend/microfrontend/auth/compilation.config.js b/frontend/microfrontend/auth/compilation.config.js
new file mode 100644
index 00000000..505f191b
--- /dev/null
+++ b/frontend/microfrontend/auth/compilation.config.js
@@ -0,0 +1,31 @@
+const printCompilationMessage = (status, port) => {
+ let messageColor, messageType, browserMessage;
+
+ switch (status) {
+ case "success":
+ messageColor = "\x1b[32m";
+ messageType = "Compiled successfully!";
+ browserMessage = "You can now view";
+ break;
+ case "failure":
+ messageColor = "\x1b[31m";
+ messageType = "Compilation Failed!";
+ browserMessage = "You can't now view";
+ break;
+ case "compiling":
+ messageColor = "\x1b[94m";
+ messageType = "Compiling...";
+ browserMessage = "Compiling the";
+ break;
+ }
+
+ console.log(`\n\n
+ ${messageColor}${messageType}\x1b[0m\n
+ ${browserMessage} \x1b[1mRemote\x1b[0m in the browser.
+ ${messageColor}${messageType}\x1b[0m\n
+ \x1b[1mLocal\x1b[0m: http://localhost:\x1b[1m${port}\x1b[0m
+ \x1b[1mLocal\x1b[0m: http://localhost:\x1b[1m${port}\x1b[0m\n\n
+ `);
+};
+
+module.exports = printCompilationMessage;
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/package.json b/frontend/microfrontend/auth/package.json
new file mode 100644
index 00000000..26b94e67
--- /dev/null
+++ b/frontend/microfrontend/auth/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "auth-micro-front",
+ "version": "1.0.0",
+ "scripts": {
+ "build": "webpack --mode production",
+ "build:dev": "webpack --mode development",
+ "build:start": "cd dist && PORT=8082 npx serve",
+ "start": "webpack serve --mode development",
+ "start:live": "webpack serve --mode development --live-reload --hot"
+ },
+ "license": "MIT",
+ "author": {
+ "name": "Ayrat Belyaev",
+ "email": "email@email.com"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.15.8",
+ "@babel/plugin-transform-runtime": "^7.15.8",
+ "@babel/preset-env": "^7.15.8",
+ "@babel/preset-react": "^7.14.5",
+ "autoprefixer": "^10.1.0",
+ "babel-loader": "^8.2.2",
+ "css-loader": "^6.3.0",
+ "html-webpack-plugin": "^5.3.2",
+ "postcss": "^8.2.1",
+ "postcss-loader": "^4.1.0",
+ "style-loader": "^3.3.0",
+ "webpack": "^5.57.1",
+ "webpack-cli": "^4.10.0",
+ "webpack-dev-server": "^4.3.1",
+ "dotenv-webpack": "^8.0.1"
+ },
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ }
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/App.jsx b/frontend/microfrontend/auth/src/App.jsx
new file mode 100644
index 00000000..82d5ddb4
--- /dev/null
+++ b/frontend/microfrontend/auth/src/App.jsx
@@ -0,0 +1,19 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+
+import "./index.css";
+
+const App = () => (
+
+
Name: gallery-micro-front
+
Framework: react
+
Language: JavaScript
+
CSS: Empty CSS
+
+);
+const rootElement = document.getElementById("app")
+if (!rootElement) throw new Error("Failed to find the root element")
+
+const root = ReactDOM.createRoot(rootElement)
+
+root.render()
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/blocks/auth-form/__button/auth-form__button.css b/frontend/microfrontend/auth/src/blocks/auth-form/__button/auth-form__button.css
new file mode 100644
index 00000000..c3d85651
--- /dev/null
+++ b/frontend/microfrontend/auth/src/blocks/auth-form/__button/auth-form__button.css
@@ -0,0 +1,15 @@
+.auth-form__button {
+ width: 358px;
+ height: 50px;
+ background-color: #fff;
+ border: 0;
+ border-radius: 2px;
+ font-family: Inter, sans-serif;
+ font-size: 18px;
+ line-height: 22px;
+ cursor: pointer;
+}
+
+.auth-form__button:hover {
+ opacity: .85;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/blocks/auth-form/__form/auth-form__form.css b/frontend/microfrontend/auth/src/blocks/auth-form/__form/auth-form__form.css
new file mode 100644
index 00000000..30ebdfe7
--- /dev/null
+++ b/frontend/microfrontend/auth/src/blocks/auth-form/__form/auth-form__form.css
@@ -0,0 +1,6 @@
+.auth-form__form {
+ display: flex;
+ flex-direction: column;
+ min-height: 60vh;
+ justify-content: space-between;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/blocks/auth-form/__input/auth-form__input.css b/frontend/microfrontend/auth/src/blocks/auth-form/__input/auth-form__input.css
new file mode 100644
index 00000000..27c5a510
--- /dev/null
+++ b/frontend/microfrontend/auth/src/blocks/auth-form/__input/auth-form__input.css
@@ -0,0 +1,5 @@
+.auth-form__input {
+ width: 358px;
+ margin-bottom: 30px;
+ display: block;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/blocks/auth-form/__link/auth-form__link.css b/frontend/microfrontend/auth/src/blocks/auth-form/__link/auth-form__link.css
new file mode 100644
index 00000000..ba43bd91
--- /dev/null
+++ b/frontend/microfrontend/auth/src/blocks/auth-form/__link/auth-form__link.css
@@ -0,0 +1,7 @@
+.auth-form__link {
+ color: #fff;
+}
+
+.auth-form__link:hover {
+ opacity: .85;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/blocks/auth-form/__text/auth-form__text.css b/frontend/microfrontend/auth/src/blocks/auth-form/__text/auth-form__text.css
new file mode 100644
index 00000000..c756474c
--- /dev/null
+++ b/frontend/microfrontend/auth/src/blocks/auth-form/__text/auth-form__text.css
@@ -0,0 +1,8 @@
+.auth-form__text {
+ color: #fff;
+ font-family: Inter, sans-serif;
+ text-align: center;
+ font-size: 14px;
+ line-height: 17px;
+ margin-top: 15px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/blocks/auth-form/__textfield/auth-form__textfield.css b/frontend/microfrontend/auth/src/blocks/auth-form/__textfield/auth-form__textfield.css
new file mode 100644
index 00000000..136b1ee5
--- /dev/null
+++ b/frontend/microfrontend/auth/src/blocks/auth-form/__textfield/auth-form__textfield.css
@@ -0,0 +1,16 @@
+.auth-form__textfield {
+ font-family: Inter, sans-serif;
+ background-color: transparent;
+ border: 0;
+ border-bottom: 2px solid #CCCCCC;
+ font-size: 14px;
+ padding: 13px 0;
+ width: 100%;
+ color: #fff;
+}
+
+.auth-form__textfield::-webkit-input-placeholder {color:#CCCCCC;}
+.auth-form__textfield::-moz-placeholder {color:#CCCCCC;}
+.auth-form__textfield:-moz-placeholder {color:#CCCCCC;}
+.auth-form__textfield:-ms-input-placeholder {color:#CCCCCC;}
+.auth-form__textfield::placeholder {color:#CCCCCC;}
diff --git a/frontend/microfrontend/auth/src/blocks/auth-form/__title/auth-form__title.css b/frontend/microfrontend/auth/src/blocks/auth-form/__title/auth-form__title.css
new file mode 100644
index 00000000..ec21e0f9
--- /dev/null
+++ b/frontend/microfrontend/auth/src/blocks/auth-form/__title/auth-form__title.css
@@ -0,0 +1,8 @@
+.auth-form__title {
+ color: #fff;
+ font-weight: 900;
+ font-size: 24px;
+ line-height: 29px;
+ font-family: Inter, sans-serif;
+ text-align: center;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/blocks/auth-form/auth-form.css b/frontend/microfrontend/auth/src/blocks/auth-form/auth-form.css
new file mode 100644
index 00000000..460be902
--- /dev/null
+++ b/frontend/microfrontend/auth/src/blocks/auth-form/auth-form.css
@@ -0,0 +1,15 @@
+@import url('./__title/auth-form__title.css');
+@import url('./__form/auth-form__form.css');
+@import url('./__input/auth-form__input.css');
+@import url('./__textfield/auth-form__textfield.css');
+@import url('./__button/auth-form__button.css');
+@import url('./__text/auth-form__text.css');
+@import url('./__link/auth-form__link.css');
+
+.auth-form {
+ flex-grow: 1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/components/InfoTooltip.js b/frontend/microfrontend/auth/src/components/InfoTooltip.js
new file mode 100644
index 00000000..97d154ad
--- /dev/null
+++ b/frontend/microfrontend/auth/src/components/InfoTooltip.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import SuccessIcon from '../images/success-icon.svg';
+import ErrorIcon from '../images/error-icon.svg';
+
+function InfoTooltip({ isOpen, onClose, status }) {
+ const icon = status === 'success' ? SuccessIcon : ErrorIcon
+ const text = status === 'success' ? "Вы успешно зарегистрировались" :
+ "Что-то пошло не так! Попробуйте ещё раз."
+ return (
+
+ );
+}
+
+export default InfoTooltip;
+
+
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/components/Login.js b/frontend/microfrontend/auth/src/components/Login.js
new file mode 100644
index 00000000..8b4196d1
--- /dev/null
+++ b/frontend/microfrontend/auth/src/components/Login.js
@@ -0,0 +1,39 @@
+import React from 'react';
+
+import '../blocks/login/login.css';
+
+function Login ({ onLogin }){
+ const [email, setEmail] = React.useState('');
+ const [password, setPassword] = React.useState('');
+
+ function handleSubmit(e){
+ e.preventDefault();
+ const userData = {
+ email,
+ password
+ }
+ onLogin(userData);
+ }
+ return (
+
+ )
+}
+
+export default Login;
diff --git a/frontend/microfrontend/auth/src/components/ProtectedRoute.js b/frontend/microfrontend/auth/src/components/ProtectedRoute.js
new file mode 100644
index 00000000..c65ec70d
--- /dev/null
+++ b/frontend/microfrontend/auth/src/components/ProtectedRoute.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import { Route, Redirect } from "react-router-dom";
+
+const ProtectedRoute = ({ component: Component, ...props }) => {
+ return (
+
+ {
+ () => props.loggedIn ? :
+ }
+
+)}
+
+export default ProtectedRoute;
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/components/Register.js b/frontend/microfrontend/auth/src/components/Register.js
new file mode 100644
index 00000000..f72fd4dd
--- /dev/null
+++ b/frontend/microfrontend/auth/src/components/Register.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+function Register ({ onRegister }){
+ const [email, setEmail] = React.useState('');
+ const [password, setPassword] = React.useState('');
+
+ function handleSubmit(e){
+ e.preventDefault();
+ const userData = {
+ email,
+ password
+ }
+ onRegister(userData);
+ }
+ return (
+
+ )
+}
+
+export default Register;
diff --git a/frontend/microfrontend/auth/src/index.css b/frontend/microfrontend/auth/src/index.css
new file mode 100644
index 00000000..20e225c5
--- /dev/null
+++ b/frontend/microfrontend/auth/src/index.css
@@ -0,0 +1,10 @@
+body {
+ font-family: Arial, Helvetica, sans-serif;
+}
+
+.container {
+ font-size: 3rem;
+ margin: auto;
+ max-width: 800px;
+ margin-top: 20px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/src/index.html b/frontend/microfrontend/auth/src/index.html
new file mode 100644
index 00000000..2d13fe8f
--- /dev/null
+++ b/frontend/microfrontend/auth/src/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ auth-micro-front
+
+
+
+
+
+
+
diff --git a/frontend/microfrontend/auth/src/index.js b/frontend/microfrontend/auth/src/index.js
new file mode 100644
index 00000000..fc3cee10
--- /dev/null
+++ b/frontend/microfrontend/auth/src/index.js
@@ -0,0 +1 @@
+import("./App");
diff --git a/frontend/microfrontend/auth/src/utils/auth.js b/frontend/microfrontend/auth/src/utils/auth.js
new file mode 100644
index 00000000..9e33d644
--- /dev/null
+++ b/frontend/microfrontend/auth/src/utils/auth.js
@@ -0,0 +1,43 @@
+const BASE_URL = 'https://auth.nomoreparties.co';
+
+const getResponse = (res) => {
+ return res.ok ? res.json() : Promise.reject(`Ошибка: ${res.status}`);
+}
+
+export const register = (email, password) => {
+ return fetch(`${BASE_URL}/signup`, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({email, password})
+ })
+ .then(getResponse)
+};
+export const login = (email, password) => {
+ return fetch(`${BASE_URL}/signin`, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({email, password})
+ })
+ .then(getResponse)
+ .then((data) => {
+ localStorage.setItem('jwt', data.token)
+ return data;
+ })
+};
+export const checkToken = (token) => {
+ return fetch(`${BASE_URL}/users/me`, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`,
+ }
+ })
+ .then(getResponse)
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/auth/webpack.config.js b/frontend/microfrontend/auth/webpack.config.js
new file mode 100644
index 00000000..7b6bb285
--- /dev/null
+++ b/frontend/microfrontend/auth/webpack.config.js
@@ -0,0 +1,91 @@
+const HtmlWebPackPlugin = require("html-webpack-plugin");
+const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
+const path = require('path');
+const Dotenv = require('dotenv-webpack');
+
+const deps = require("./package.json").dependencies;
+
+const printCompilationMessage = require('./compilation.config.js');
+
+module.exports = (_, argv) => ({
+ output: {
+ publicPath: "http://localhost:8082/",
+ },
+
+ resolve: {
+ extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
+ },
+
+ devServer: {
+ port: 8082,
+ historyApiFallback: true,
+ watchFiles: [path.resolve(__dirname, 'src')],
+ onListening: function (devServer) {
+ const port = devServer.server.address().port
+
+ printCompilationMessage('compiling', port)
+
+ devServer.compiler.hooks.done.tap('OutputMessagePlugin', (stats) => {
+ setImmediate(() => {
+ if (stats.hasErrors()) {
+ printCompilationMessage('failure', port)
+ } else {
+ printCompilationMessage('success', port)
+ }
+ })
+ })
+ }
+ },
+
+ module: {
+ rules: [
+ {
+ test: /\.m?js/,
+ type: "javascript/auto",
+ resolve: {
+ fullySpecified: false,
+ },
+ },
+ {
+ test: /\.(css|s[ac]ss)$/i,
+ use: ["style-loader", "css-loader", "postcss-loader"],
+ },
+ {
+ test: /\.(ts|tsx|js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ },
+ },
+ ],
+ },
+
+ plugins: [
+ new ModuleFederationPlugin({
+ name: "auth_micro_front",
+ filename: "remoteEntry.js",
+ remotes: {},
+ exposes: {
+ './InfoTooltip': './src/components/InfoTooltip.js',
+ './Login': './src/components/Login.js',
+ './ProtectedRoute': './src/components/ProtectedRoute.js',
+ './Register': './src/components/Register.js'
+ },
+ shared: {
+ ...deps,
+ react: {
+ singleton: true,
+ requiredVersion: deps.react,
+ },
+ "react-dom": {
+ singleton: true,
+ requiredVersion: deps["react-dom"],
+ },
+ },
+ }),
+ new HtmlWebPackPlugin({
+ template: "./src/index.html",
+ }),
+ new Dotenv()
+ ],
+});
diff --git a/frontend/microfrontend/compilation.config.js b/frontend/microfrontend/compilation.config.js
new file mode 100644
index 00000000..505f191b
--- /dev/null
+++ b/frontend/microfrontend/compilation.config.js
@@ -0,0 +1,31 @@
+const printCompilationMessage = (status, port) => {
+ let messageColor, messageType, browserMessage;
+
+ switch (status) {
+ case "success":
+ messageColor = "\x1b[32m";
+ messageType = "Compiled successfully!";
+ browserMessage = "You can now view";
+ break;
+ case "failure":
+ messageColor = "\x1b[31m";
+ messageType = "Compilation Failed!";
+ browserMessage = "You can't now view";
+ break;
+ case "compiling":
+ messageColor = "\x1b[94m";
+ messageType = "Compiling...";
+ browserMessage = "Compiling the";
+ break;
+ }
+
+ console.log(`\n\n
+ ${messageColor}${messageType}\x1b[0m\n
+ ${browserMessage} \x1b[1mRemote\x1b[0m in the browser.
+ ${messageColor}${messageType}\x1b[0m\n
+ \x1b[1mLocal\x1b[0m: http://localhost:\x1b[1m${port}\x1b[0m
+ \x1b[1mLocal\x1b[0m: http://localhost:\x1b[1m${port}\x1b[0m\n\n
+ `);
+};
+
+module.exports = printCompilationMessage;
\ No newline at end of file
diff --git a/frontend/microfrontend/gallery/.babelrc b/frontend/microfrontend/gallery/.babelrc
new file mode 100644
index 00000000..ea5702fa
--- /dev/null
+++ b/frontend/microfrontend/gallery/.babelrc
@@ -0,0 +1,7 @@
+{
+ "presets": [
+ ["@babel/preset-react", { "runtime": "automatic" }],
+ "@babel/preset-env"
+ ],
+ "plugins": [["@babel/transform-runtime"]]
+}
diff --git a/frontend/microfrontend/gallery/.gitignore b/frontend/microfrontend/gallery/.gitignore
new file mode 100644
index 00000000..1f22b9c2
--- /dev/null
+++ b/frontend/microfrontend/gallery/.gitignore
@@ -0,0 +1,116 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
diff --git a/frontend/microfrontend/gallery/compilation.config.js b/frontend/microfrontend/gallery/compilation.config.js
new file mode 100644
index 00000000..505f191b
--- /dev/null
+++ b/frontend/microfrontend/gallery/compilation.config.js
@@ -0,0 +1,31 @@
+const printCompilationMessage = (status, port) => {
+ let messageColor, messageType, browserMessage;
+
+ switch (status) {
+ case "success":
+ messageColor = "\x1b[32m";
+ messageType = "Compiled successfully!";
+ browserMessage = "You can now view";
+ break;
+ case "failure":
+ messageColor = "\x1b[31m";
+ messageType = "Compilation Failed!";
+ browserMessage = "You can't now view";
+ break;
+ case "compiling":
+ messageColor = "\x1b[94m";
+ messageType = "Compiling...";
+ browserMessage = "Compiling the";
+ break;
+ }
+
+ console.log(`\n\n
+ ${messageColor}${messageType}\x1b[0m\n
+ ${browserMessage} \x1b[1mRemote\x1b[0m in the browser.
+ ${messageColor}${messageType}\x1b[0m\n
+ \x1b[1mLocal\x1b[0m: http://localhost:\x1b[1m${port}\x1b[0m
+ \x1b[1mLocal\x1b[0m: http://localhost:\x1b[1m${port}\x1b[0m\n\n
+ `);
+};
+
+module.exports = printCompilationMessage;
\ No newline at end of file
diff --git a/frontend/microfrontend/gallery/package.json b/frontend/microfrontend/gallery/package.json
new file mode 100644
index 00000000..995a2225
--- /dev/null
+++ b/frontend/microfrontend/gallery/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "gallery-micro-front",
+ "version": "1.0.0",
+ "scripts": {
+ "build": "webpack --mode production",
+ "build:dev": "webpack --mode development",
+ "build:start": "cd dist && PORT=8082 npx serve",
+ "start": "webpack serve --mode development",
+ "start:live": "webpack serve --mode development --live-reload --hot"
+ },
+ "license": "MIT",
+ "author": {
+ "name": "Ayrat Belyaev",
+ "email": "email@email.com"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.15.8",
+ "@babel/plugin-transform-runtime": "^7.15.8",
+ "@babel/preset-env": "^7.15.8",
+ "@babel/preset-react": "^7.14.5",
+ "autoprefixer": "^10.1.0",
+ "babel-loader": "^8.2.2",
+ "css-loader": "^6.3.0",
+ "html-webpack-plugin": "^5.3.2",
+ "postcss": "^8.2.1",
+ "postcss-loader": "^4.1.0",
+ "style-loader": "^3.3.0",
+ "webpack": "^5.57.1",
+ "webpack-cli": "^4.10.0",
+ "webpack-dev-server": "^4.3.1",
+ "dotenv-webpack": "^8.0.1"
+ },
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ }
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/gallery/src/App.jsx b/frontend/microfrontend/gallery/src/App.jsx
new file mode 100644
index 00000000..4f7f1c89
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/App.jsx
@@ -0,0 +1,19 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+
+import "./index.css";
+
+const App = () => (
+
+
Name: auth-micro-front
+
Framework: react
+
Language: JavaScript
+
CSS: Empty CSS
+
+);
+const rootElement = document.getElementById("app")
+if (!rootElement) throw new Error("Failed to find the root element")
+
+const root = ReactDOM.createRoot(rootElement)
+
+root.render()
\ No newline at end of file
diff --git a/frontend/microfrontend/gallery/src/blocks/card/__delete-button/_hidden/card__delete-button_hidden.css b/frontend/microfrontend/gallery/src/blocks/card/__delete-button/_hidden/card__delete-button_hidden.css
new file mode 100644
index 00000000..629780bc
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/card/__delete-button/_hidden/card__delete-button_hidden.css
@@ -0,0 +1,3 @@
+.card__delete-button_hidden {
+ display: none;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/card/__delete-button/_visible/card__delete-button_visible.css b/frontend/microfrontend/gallery/src/blocks/card/__delete-button/_visible/card__delete-button_visible.css
new file mode 100644
index 00000000..7e69ad82
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/card/__delete-button/_visible/card__delete-button_visible.css
@@ -0,0 +1,3 @@
+.card__delete-button_visible {
+ display: block;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/card/__delete-button/card__delete-button.css b/frontend/microfrontend/gallery/src/blocks/card/__delete-button/card__delete-button.css
new file mode 100644
index 00000000..5b950e4e
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/card/__delete-button/card__delete-button.css
@@ -0,0 +1,22 @@
+.card__delete-button {
+ width: 18px;
+ height: 18px;
+ background: transparent url('../../../images/delete-icon.svg') center no-repeat;
+ background-size: 18px 18px;
+ border: none;
+ cursor: pointer;
+ position: absolute;
+ top: 18px;
+ right: 15px;
+ padding: 0;
+ margin: 0;
+ -webkit-transition: 0.3s;
+ -moz-transition: 0.3s;
+ -ms-transition: 0.3s;
+ -o-transition: 0.3s;
+ transition: 0.3s;
+}
+
+.card__delete-button:hover {
+ opacity: 0.6;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/card/__description/card__description.css b/frontend/microfrontend/gallery/src/blocks/card/__description/card__description.css
new file mode 100644
index 00000000..3c430cd5
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/card/__description/card__description.css
@@ -0,0 +1,7 @@
+.card__description {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: #fff;
+ padding: 25px 20px;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/card/__image/card__image.css b/frontend/microfrontend/gallery/src/blocks/card/__image/card__image.css
new file mode 100644
index 00000000..551129a9
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/card/__image/card__image.css
@@ -0,0 +1,9 @@
+.card__image {
+ min-height: 282px;
+ -webkit-background-size: cover;
+ background-size: cover;
+}
+
+.card__image:hover {
+ cursor: pointer;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/card/__like-button/_is-active/card__like-button_is-active.css b/frontend/microfrontend/gallery/src/blocks/card/__like-button/_is-active/card__like-button_is-active.css
new file mode 100644
index 00000000..9c619d24
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/card/__like-button/_is-active/card__like-button_is-active.css
@@ -0,0 +1,3 @@
+.card__like-button_is-active {
+ background: transparent url('../../../../images/like-active.svg') no-repeat;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/card/__like-button/card__like-button.css b/frontend/microfrontend/gallery/src/blocks/card/__like-button/card__like-button.css
new file mode 100644
index 00000000..2a5c4cc4
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/card/__like-button/card__like-button.css
@@ -0,0 +1,19 @@
+.card__like-button {
+ width: 21px;
+ height: 18px;
+ background: transparent url('../../../images/like-inactive.svg') no-repeat;
+ background-size: 21px 18px;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ margin: 0;
+ -webkit-transition: 0.3s;
+ -moz-transition: 0.3s;
+ -ms-transition: 0.3s;
+ -o-transition: 0.3s;
+ transition: 0.3s;
+}
+
+.card__like-button:hover {
+ opacity: 0.6;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/card/__like-count/card__like-count.css b/frontend/microfrontend/gallery/src/blocks/card/__like-count/card__like-count.css
new file mode 100644
index 00000000..9ae97ccb
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/card/__like-count/card__like-count.css
@@ -0,0 +1,6 @@
+.card__like-count {
+ font-size: 13px;
+ line-height: 16px;
+ text-align: center;
+ margin: 0;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/card/__title/card__title.css b/frontend/microfrontend/gallery/src/blocks/card/__title/card__title.css
new file mode 100644
index 00000000..d899fcc9
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/card/__title/card__title.css
@@ -0,0 +1,9 @@
+.card__title {
+ max-width: 220px;
+ font-size: 24px;
+ line-height: 29px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ margin: 0;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/card/card.css b/frontend/microfrontend/gallery/src/blocks/card/card.css
new file mode 100644
index 00000000..9092ef2f
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/card/card.css
@@ -0,0 +1,18 @@
+@import url('./__description/card__description.css');
+@import url('./__image/card__image.css');
+@import url('./__like-button/card__like-button.css');
+@import url('./__like-button/_is-active/card__like-button_is-active.css');
+@import url('./__delete-button/card__delete-button.css');
+@import url('./__delete-button/_hidden/card__delete-button_hidden.css');
+@import url('./__delete-button/_visible/card__delete-button_visible.css');
+@import url('./__title/card__title.css');
+@import url('./__like-count/card__like-count.css');
+
+.card {
+ font-family: 'Inter', Arial, sans-serif;
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 10px;
+ border-radius: 10px;
+ position: relative;
+ overflow: hidden;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/places/__item/places__item.css b/frontend/microfrontend/gallery/src/blocks/places/__item/places__item.css
new file mode 100644
index 00000000..1d3cbf1b
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/places/__item/places__item.css
@@ -0,0 +1,3 @@
+.places__item {
+
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/places/__list/places__list.css b/frontend/microfrontend/gallery/src/blocks/places/__list/places__list.css
new file mode 100644
index 00000000..99415deb
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/places/__list/places__list.css
@@ -0,0 +1,9 @@
+.places__list {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, 282px);
+ justify-content: center;
+ grid-gap: 20px 18px;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/places/places.css b/frontend/microfrontend/gallery/src/blocks/places/places.css
new file mode 100644
index 00000000..68f1325b
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/places/places.css
@@ -0,0 +1,13 @@
+@import url(./__list/places__list.css);
+@import url(./__item/places__item.css);
+
+.places {
+ font-family: 'Inter', Arial, sans-serif;
+ padding: 15px 0 36px;
+}
+
+@media screen and (max-width: 568px) {
+ .places {
+ padding: 15px 0 18px;
+ }
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__button/_disabled/popup__button_disabled.css b/frontend/microfrontend/gallery/src/blocks/popup/__button/_disabled/popup__button_disabled.css
new file mode 100644
index 00000000..1ad56853
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__button/_disabled/popup__button_disabled.css
@@ -0,0 +1,4 @@
+.popup__button_disabled {
+ opacity: 0.2;
+ pointer-events: none;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__button/popup__button.css b/frontend/microfrontend/gallery/src/blocks/popup/__button/popup__button.css
new file mode 100644
index 00000000..7552a022
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__button/popup__button.css
@@ -0,0 +1,26 @@
+.popup__button {
+ width: 100%;
+ height: 50px;
+ font-size: 18px;
+ line-height: 22px;
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #000;
+ border-radius: 2px;
+ border: none;
+ transition: visibility 0s, background 0.3s;
+ margin-top: 48px;
+}
+
+.popup__button:hover {
+ background: rgba(0, 0, 0, 0.8);
+}
+
+@media screen and (max-width: 568px) {
+ .popup__button {
+ font-size: 14px;
+ line-height: 17px;
+ }
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__caption/popup__caption.css b/frontend/microfrontend/gallery/src/blocks/popup/__caption/popup__caption.css
new file mode 100644
index 00000000..35ef2b88
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__caption/popup__caption.css
@@ -0,0 +1,9 @@
+.popup__caption {
+ font-size: 12px;
+ line-height: 15px;
+ color: #fff;
+ position: absolute;
+ left: 0;
+ top: calc(100% + 10px);
+ margin: 0;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__close/popup__close.css b/frontend/microfrontend/gallery/src/blocks/popup/__close/popup__close.css
new file mode 100644
index 00000000..db3a2db6
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__close/popup__close.css
@@ -0,0 +1,23 @@
+.popup__close {
+ width: 35px;
+ height: 35px;
+ background: transparent url('../../../images/close.svg') center no-repeat;
+ background-size: 35px 35px;
+ border: none;
+ position: absolute;
+ top: -36px;
+ right: -34px;
+ -webkit-transform: rotate(90deg);
+ -moz-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ -o-transform: rotate(90deg);
+ transform: rotate(90deg);
+ transition: visibility 0s, opacity 0.3s;
+ padding: 0;
+ margin: 0;
+ cursor: pointer;
+}
+
+.popup__close:hover {
+ opacity: 0.6;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__content/_content/popup__content_content_image.css b/frontend/microfrontend/gallery/src/blocks/popup/__content/_content/popup__content_content_image.css
new file mode 100644
index 00000000..ad7ff951
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__content/_content/popup__content_content_image.css
@@ -0,0 +1,14 @@
+.popup__content_content_image {
+ max-width: 75vw;
+ max-height: 75vh;
+ height: auto;
+ width: auto;
+ display: flex;
+ background: transparent;
+ -webkit-background-size: cover;
+ background-size: cover;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+ padding: 0;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__content/popup__content.css b/frontend/microfrontend/gallery/src/blocks/popup/__content/popup__content.css
new file mode 100644
index 00000000..668c80eb
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__content/popup__content.css
@@ -0,0 +1,19 @@
+.popup__content {
+ max-width: 430px;
+ width: 100%;
+ min-height: 330px;
+ background-color: #fff;
+ border-radius: 10px;
+ position: relative;
+ box-sizing: border-box;
+ padding: 34px 36px;
+}
+
+@media screen and (max-width: 568px) {
+ .popup__content {
+ width: 100%;
+ max-width: calc(100% - 80px);
+ margin-top: 40px;
+ padding: 30px 20px;
+ }
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__error/_visible/popup__error_visible.css b/frontend/microfrontend/gallery/src/blocks/popup/__error/_visible/popup__error_visible.css
new file mode 100644
index 00000000..4293d5cc
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__error/_visible/popup__error_visible.css
@@ -0,0 +1,3 @@
+.popup__error_visible {
+ opacity: 1;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__error/popup__error.css b/frontend/microfrontend/gallery/src/blocks/popup/__error/popup__error.css
new file mode 100644
index 00000000..017e1dd9
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__error/popup__error.css
@@ -0,0 +1,10 @@
+.popup__error {
+ font-size: 12px;
+ line-height: 15px;
+ color: #FF0000;
+ opacity: 0;
+ position: absolute;
+ top: calc(100% + 5px);
+ left: 0;
+ transition: opacity 0.3s;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__form/popup__form.css b/frontend/microfrontend/gallery/src/blocks/popup/__form/popup__form.css
new file mode 100644
index 00000000..425f78ca
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__form/popup__form.css
@@ -0,0 +1,3 @@
+.popup__form {
+ margin-top: 27px;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__icon/popup__icon.css b/frontend/microfrontend/gallery/src/blocks/popup/__icon/popup__icon.css
new file mode 100644
index 00000000..3ea53b9c
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__icon/popup__icon.css
@@ -0,0 +1,6 @@
+.popup__icon {
+ display: block;
+ margin: auto;
+ width: 120px;
+ height: 120px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__image/popup__image.css b/frontend/microfrontend/gallery/src/blocks/popup/__image/popup__image.css
new file mode 100644
index 00000000..20effa1c
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__image/popup__image.css
@@ -0,0 +1,6 @@
+.popup__image {
+ max-height: 100%;
+ max-width: 100%;
+ object-fit: cover;
+ display: block;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__input/_type/popup__input_type_error.css b/frontend/microfrontend/gallery/src/blocks/popup/__input/_type/popup__input_type_error.css
new file mode 100644
index 00000000..79876daf
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__input/_type/popup__input_type_error.css
@@ -0,0 +1,3 @@
+.popup__input_type_error {
+ border-bottom: 1px solid #FF0000;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__input/popup__input.css b/frontend/microfrontend/gallery/src/blocks/popup/__input/popup__input.css
new file mode 100644
index 00000000..8dc0a28f
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__input/popup__input.css
@@ -0,0 +1,26 @@
+.popup__input {
+ width: 100%;
+ border: 0;
+ border-bottom: 1px solid rgba(0, 0, 0, .2);
+ font-size: 14px;
+ line-height: 18px;
+ box-sizing: border-box;
+ margin-bottom: 10px;
+ padding: 0 0 13px;
+ -webkit-transition: 0.3s;
+ -moz-transition: 0.3s;
+ -ms-transition: 0.3s;
+ -o-transition: 0.3s;
+ transition: border-bottom 0.3s;
+}
+
+.popup__input:last-of-type {
+ margin-bottom: 0;
+}
+
+@media screen and (max-width: 568px) {
+ .popup__title {
+ font-size: 12px;
+ line-height: 15px;
+ }
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__label/popup__label.css b/frontend/microfrontend/gallery/src/blocks/popup/__label/popup__label.css
new file mode 100644
index 00000000..a9122bc5
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__label/popup__label.css
@@ -0,0 +1,5 @@
+.popup__label {
+ position: relative;
+ display: block;
+ padding: 30px 0 0;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__status-message/popup__status-message.css b/frontend/microfrontend/gallery/src/blocks/popup/__status-message/popup__status-message.css
new file mode 100644
index 00000000..577b880a
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__status-message/popup__status-message.css
@@ -0,0 +1,8 @@
+.popup__status-message {
+ font-family: Inter, sans-serif;
+ font-weight: 900;
+ font-size: 24px;
+ line-height: 29px;
+ text-align: center;
+ margin-top: 32px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/__title/popup__title.css b/frontend/microfrontend/gallery/src/blocks/popup/__title/popup__title.css
new file mode 100644
index 00000000..1092b827
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/__title/popup__title.css
@@ -0,0 +1,12 @@
+.popup__title {
+ margin: 0;
+ font-size: 24px;
+ line-height: 30px;
+}
+
+@media screen and (max-width: 568px) {
+ .popup__title {
+ font-size: 18px;
+ line-height: 22px;
+ }
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/_is-opened/popup_is-opened.css b/frontend/microfrontend/gallery/src/blocks/popup/_is-opened/popup_is-opened.css
new file mode 100644
index 00000000..207e395e
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/_is-opened/popup_is-opened.css
@@ -0,0 +1,6 @@
+.popup_is-opened {
+ visibility: visible;
+ opacity: 1;
+ pointer-events: all;
+ transition: visibility 0s, opacity 0.6s;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/_type/popup_type_edit-avatar.css b/frontend/microfrontend/gallery/src/blocks/popup/_type/popup_type_edit-avatar.css
new file mode 100644
index 00000000..b357b631
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/_type/popup_type_edit-avatar.css
@@ -0,0 +1,3 @@
+.popup_type_edit-avatar .popup__content {
+ min-height: auto;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/_type/popup_type_remove-card.css b/frontend/microfrontend/gallery/src/blocks/popup/_type/popup_type_remove-card.css
new file mode 100644
index 00000000..ac639298
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/_type/popup_type_remove-card.css
@@ -0,0 +1,3 @@
+.popup_type_remove-card .popup__content {
+ min-height: auto;
+}
diff --git a/frontend/microfrontend/gallery/src/blocks/popup/popup.css b/frontend/microfrontend/gallery/src/blocks/popup/popup.css
new file mode 100644
index 00000000..c5f3b2cb
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/blocks/popup/popup.css
@@ -0,0 +1,37 @@
+@import url('./__content/popup__content.css');
+@import url('./__content/_content/popup__content_content_image.css');
+@import url('./__close/popup__close.css');
+@import url('./__title/popup__title.css');
+@import url('./__form/popup__form.css');
+@import url('./__input/popup__input.css');
+@import url('./__input/_type/popup__input_type_error.css');
+@import url('./__button/popup__button.css');
+@import url('./__button/_disabled/popup__button_disabled.css');
+@import url('./__caption/popup__caption.css');
+@import url('./__image/popup__image.css');
+@import url('./__label/popup__label.css');
+@import url('./__error/popup__error.css');
+@import url('./__error/_visible/popup__error_visible.css');
+@import url('./_type/popup_type_remove-card.css');
+@import url('./_type/popup_type_edit-avatar.css');
+@import url('./__icon/popup__icon.css');
+@import url('./__status-message/popup__status-message.css');
+
+.popup {
+ font-family: 'Inter', Arial, sans-serif;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: rgba(0, 0, 0, .5);
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ visibility: hidden;
+ opacity: 0;
+ pointer-events: none;
+ user-select: none;
+ transition: visibility 0s 0.6s, opacity 0.6s;
+ z-index: 10;
+}
diff --git a/frontend/microfrontend/gallery/src/components/AddPlacePopup.js b/frontend/microfrontend/gallery/src/components/AddPlacePopup.js
new file mode 100644
index 00000000..90b0a812
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/components/AddPlacePopup.js
@@ -0,0 +1,45 @@
+import React from 'react';
+import PopupWithForm from './PopupWithForm';
+
+function AddPlacePopup({ isOpen, onAddPlace, onClose }) {
+ const [name, setName] = React.useState('');
+ const [link, setLink] = React.useState('');
+
+ function handleNameChange(e) {
+ setName(e.target.value);
+ }
+
+ function handleLinkChange(e) {
+ setLink(e.target.value);
+ }
+
+ function handleSubmit(e) {
+ e.preventDefault();
+
+ onAddPlace({
+ name,
+ link
+ });
+ }
+
+ return (
+
+
+
+
+ );
+}
+
+export default AddPlacePopup;
diff --git a/frontend/microfrontend/gallery/src/components/Card.js b/frontend/microfrontend/gallery/src/components/Card.js
new file mode 100644
index 00000000..a03c38b7
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/components/Card.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import { CurrentUserContext } from '../contexts/CurrentUserContext';
+
+function Card({ card, onCardClick, onCardLike, onCardDelete }) {
+ const cardStyle = { backgroundImage: `url(${card.link})` };
+
+ function handleClick() {
+ onCardClick(card);
+ }
+
+ function handleLikeClick() {
+ onCardLike(card);
+ }
+
+ function handleDeleteClick() {
+ onCardDelete(card);
+ }
+
+ const currentUser = React.useContext(CurrentUserContext);
+
+ const isLiked = card.likes.some(i => i._id === currentUser._id);
+ const cardLikeButtonClassName = `card__like-button ${isLiked && 'card__like-button_is-active'}`;
+
+ const isOwn = card.owner._id === currentUser._id;
+ const cardDeleteButtonClassName = (
+ `card__delete-button ${isOwn ? 'card__delete-button_visible' : 'card__delete-button_hidden'}`
+ );
+
+ return (
+
+
+
+
+
+
+ {card.name}
+
+
+
+
{card.likes.length}
+
+
+
+ );
+}
+
+export default Card;
diff --git a/frontend/microfrontend/gallery/src/components/ImagePopup.js b/frontend/microfrontend/gallery/src/components/ImagePopup.js
new file mode 100644
index 00000000..7bd63a2c
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/components/ImagePopup.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+function ImagePopup({ card, onClose }) {
+ return (
+
+
+
+

+
{card ? card.name : ''}
+
+
+ );
+}
+
+export default ImagePopup;
diff --git a/frontend/microfrontend/gallery/src/components/Main.js b/frontend/microfrontend/gallery/src/components/Main.js
new file mode 100644
index 00000000..d4edc7f5
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/components/Main.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import Card from './Card';
+import { CurrentUserContext } from '../contexts/CurrentUserContext';
+
+function Main({ cards, onEditProfile, onAddPlace, onEditAvatar, onCardClick, onCardLike, onCardDelete }) {
+ const currentUser = React.useContext(CurrentUserContext);
+
+ const imageStyle = { backgroundImage: `url(${currentUser.avatar})` };
+
+ return (
+
+
+
+
+
{currentUser.name}
+
+
{currentUser.about}
+
+
+
+
+
+ {cards.map((card) => (
+
+ ))}
+
+
+
+ );
+}
+
+export default Main;
diff --git a/frontend/microfrontend/gallery/src/components/PopupWithForm.js b/frontend/microfrontend/gallery/src/components/PopupWithForm.js
new file mode 100644
index 00000000..418bd0cf
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/components/PopupWithForm.js
@@ -0,0 +1,26 @@
+import React from 'react';
+
+function PopupWithForm({
+ title,
+ name,
+ isOpen,
+ buttonText = 'Сохранить',
+ onSubmit,
+ onClose,
+ children,
+}) {
+ return (
+
+ );
+}
+
+export default PopupWithForm;
diff --git a/frontend/microfrontend/gallery/src/index.css b/frontend/microfrontend/gallery/src/index.css
new file mode 100644
index 00000000..20e225c5
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/index.css
@@ -0,0 +1,10 @@
+body {
+ font-family: Arial, Helvetica, sans-serif;
+}
+
+.container {
+ font-size: 3rem;
+ margin: auto;
+ max-width: 800px;
+ margin-top: 20px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/gallery/src/index.html b/frontend/microfrontend/gallery/src/index.html
new file mode 100644
index 00000000..a207b28d
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ gallery-micro-front
+
+
+
+
+
+
+
diff --git a/frontend/microfrontend/gallery/src/index.js b/frontend/microfrontend/gallery/src/index.js
new file mode 100644
index 00000000..fc3cee10
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/index.js
@@ -0,0 +1 @@
+import("./App");
diff --git a/frontend/microfrontend/gallery/src/utils/api.js b/frontend/microfrontend/gallery/src/utils/api.js
new file mode 100644
index 00000000..8667b9e9
--- /dev/null
+++ b/frontend/microfrontend/gallery/src/utils/api.js
@@ -0,0 +1,64 @@
+class Api {
+ constructor({ address, token, groupId }) {
+ // стандартная реализация -- объект options
+ this._token = token;
+ this._groupId = groupId;
+ this._address = address;
+
+ // Запросы в примере работы выполняются к старому Api, в новом URL изменены.
+ }
+
+
+ getCardList() {
+ return fetch(`${this._address}/${this._groupId}/cards`, {
+ headers: {
+ authorization: this._token,
+ },
+ })
+ .then(res => res.ok ? res.json() : Promise.reject(`Ошибка: ${res.status}`));
+ }
+
+ addCard({ name, link }) {
+ return fetch(`${this._address}/${this._groupId}/cards`, {
+ method: 'POST',
+ headers: {
+ authorization: this._token,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ name,
+ link,
+ }),
+ })
+ .then(res => res.ok ? res.json() : Promise.reject(`Ошибка: ${res.status}`));
+ }
+
+ removeCard(cardID) {
+ return fetch(`${this._address}/${this._groupId}/cards/${cardID}`, {
+ method: 'DELETE',
+ headers: {
+ authorization: this._token,
+ },
+ })
+ .then(res => res.ok ? res.json() : Promise.reject(`Ошибка: ${res.status}`));
+ }
+ changeLikeCardStatus(cardID, like) {
+ // Обычная реализация: 2 разных метода для удаления и постановки лайка.
+ return fetch(`${this._address}/${this._groupId}/cards/like/${cardID}`, {
+ method: like ? 'PUT' : 'DELETE',
+ headers: {
+ authorization: this._token,
+ 'Content-Type': 'application/json',
+ },
+ })
+ .then(res => res.ok ? res.json() : Promise.reject(`Ошибка: ${res.status}`));
+ }
+}
+
+const api = new Api({
+ address: 'https://nomoreparties.co',
+ groupId: `cohort0`,
+ token: `80a75492-21c5-4330-a02f-308029e94b63`,
+});
+
+export default api;
diff --git a/frontend/microfrontend/gallery/webpack.config.js b/frontend/microfrontend/gallery/webpack.config.js
new file mode 100644
index 00000000..88fa9371
--- /dev/null
+++ b/frontend/microfrontend/gallery/webpack.config.js
@@ -0,0 +1,92 @@
+const HtmlWebPackPlugin = require("html-webpack-plugin");
+const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
+const path = require('path');
+const Dotenv = require('dotenv-webpack');
+
+const deps = require("./package.json").dependencies;
+
+const printCompilationMessage = require('./compilation.config.js');
+
+module.exports = (_, argv) => ({
+ output: {
+ publicPath: "http://localhost:8082/",
+ },
+
+ resolve: {
+ extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
+ },
+
+ devServer: {
+ port: 8082,
+ historyApiFallback: true,
+ watchFiles: [path.resolve(__dirname, 'src')],
+ onListening: function (devServer) {
+ const port = devServer.server.address().port
+
+ printCompilationMessage('compiling', port)
+
+ devServer.compiler.hooks.done.tap('OutputMessagePlugin', (stats) => {
+ setImmediate(() => {
+ if (stats.hasErrors()) {
+ printCompilationMessage('failure', port)
+ } else {
+ printCompilationMessage('success', port)
+ }
+ })
+ })
+ }
+ },
+
+ module: {
+ rules: [
+ {
+ test: /\.m?js/,
+ type: "javascript/auto",
+ resolve: {
+ fullySpecified: false,
+ },
+ },
+ {
+ test: /\.(css|s[ac]ss)$/i,
+ use: ["style-loader", "css-loader", "postcss-loader"],
+ },
+ {
+ test: /\.(ts|tsx|js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ },
+ },
+ ],
+ },
+
+ plugins: [
+ new ModuleFederationPlugin({
+ name: "gallery_micro_front",
+ filename: "remoteEntry.js",
+ remotes: {},
+ exposes: {
+ './AddPlacePopup': './src/components/AddPlacePopup.js',
+ './Card': './src/components/Card.js',
+ './ImagePopup': './src/components/ImagePopup.js',
+ './Main': './src/components/Main.js',
+ './PopupWithForm': './src/components/PopupWithForm.js'
+ },
+ shared: {
+ ...deps,
+ react: {
+ singleton: true,
+ requiredVersion: deps.react,
+ },
+ "react-dom": {
+ singleton: true,
+ requiredVersion: deps["react-dom"],
+ },
+ },
+ }),
+ new HtmlWebPackPlugin({
+ template: "./src/index.html",
+ }),
+ new Dotenv()
+ ],
+});
diff --git a/frontend/microfrontend/package.json b/frontend/microfrontend/package.json
new file mode 100644
index 00000000..33f4849e
--- /dev/null
+++ b/frontend/microfrontend/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "root-micro-front",
+ "version": "1.0.0",
+ "scripts": {
+ "build": "webpack --mode production",
+ "build:dev": "webpack --mode development",
+ "build:start": "cd dist && PORT=8082 npx serve",
+ "start": "webpack serve --mode development",
+ "start:live": "webpack serve --mode development --live-reload --hot"
+ },
+ "license": "MIT",
+ "author": {
+ "name": "Ayrat Belyaev",
+ "email": "email@email.com"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.15.8",
+ "@babel/plugin-transform-runtime": "^7.15.8",
+ "@babel/preset-env": "^7.15.8",
+ "@babel/preset-react": "^7.14.5",
+ "autoprefixer": "^10.1.0",
+ "babel-loader": "^8.2.2",
+ "css-loader": "^6.3.0",
+ "html-webpack-plugin": "^5.3.2",
+ "postcss": "^8.2.1",
+ "postcss-loader": "^4.1.0",
+ "style-loader": "^3.3.0",
+ "webpack": "^5.57.1",
+ "webpack-cli": "^4.10.0",
+ "webpack-dev-server": "^4.3.1",
+ "dotenv-webpack": "^8.0.1"
+ },
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ }
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/profile/.babelrc b/frontend/microfrontend/profile/.babelrc
new file mode 100644
index 00000000..ea5702fa
--- /dev/null
+++ b/frontend/microfrontend/profile/.babelrc
@@ -0,0 +1,7 @@
+{
+ "presets": [
+ ["@babel/preset-react", { "runtime": "automatic" }],
+ "@babel/preset-env"
+ ],
+ "plugins": [["@babel/transform-runtime"]]
+}
diff --git a/frontend/microfrontend/profile/.gitignore b/frontend/microfrontend/profile/.gitignore
new file mode 100644
index 00000000..1f22b9c2
--- /dev/null
+++ b/frontend/microfrontend/profile/.gitignore
@@ -0,0 +1,116 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
diff --git a/frontend/microfrontend/profile/compilation.config.js b/frontend/microfrontend/profile/compilation.config.js
new file mode 100644
index 00000000..505f191b
--- /dev/null
+++ b/frontend/microfrontend/profile/compilation.config.js
@@ -0,0 +1,31 @@
+const printCompilationMessage = (status, port) => {
+ let messageColor, messageType, browserMessage;
+
+ switch (status) {
+ case "success":
+ messageColor = "\x1b[32m";
+ messageType = "Compiled successfully!";
+ browserMessage = "You can now view";
+ break;
+ case "failure":
+ messageColor = "\x1b[31m";
+ messageType = "Compilation Failed!";
+ browserMessage = "You can't now view";
+ break;
+ case "compiling":
+ messageColor = "\x1b[94m";
+ messageType = "Compiling...";
+ browserMessage = "Compiling the";
+ break;
+ }
+
+ console.log(`\n\n
+ ${messageColor}${messageType}\x1b[0m\n
+ ${browserMessage} \x1b[1mRemote\x1b[0m in the browser.
+ ${messageColor}${messageType}\x1b[0m\n
+ \x1b[1mLocal\x1b[0m: http://localhost:\x1b[1m${port}\x1b[0m
+ \x1b[1mLocal\x1b[0m: http://localhost:\x1b[1m${port}\x1b[0m\n\n
+ `);
+};
+
+module.exports = printCompilationMessage;
\ No newline at end of file
diff --git a/frontend/microfrontend/profile/package.json b/frontend/microfrontend/profile/package.json
new file mode 100644
index 00000000..2f282c57
--- /dev/null
+++ b/frontend/microfrontend/profile/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "profile-micro-front",
+ "version": "1.0.0",
+ "scripts": {
+ "build": "webpack --mode production",
+ "build:dev": "webpack --mode development",
+ "build:start": "cd dist && PORT=8082 npx serve",
+ "start": "webpack serve --mode development",
+ "start:live": "webpack serve --mode development --live-reload --hot"
+ },
+ "license": "MIT",
+ "author": {
+ "name": "Ayrat Belyaev",
+ "email": "email@email.com"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.15.8",
+ "@babel/plugin-transform-runtime": "^7.15.8",
+ "@babel/preset-env": "^7.15.8",
+ "@babel/preset-react": "^7.14.5",
+ "autoprefixer": "^10.1.0",
+ "babel-loader": "^8.2.2",
+ "css-loader": "^6.3.0",
+ "html-webpack-plugin": "^5.3.2",
+ "postcss": "^8.2.1",
+ "postcss-loader": "^4.1.0",
+ "style-loader": "^3.3.0",
+ "webpack": "^5.57.1",
+ "webpack-cli": "^4.10.0",
+ "webpack-dev-server": "^4.3.1",
+ "dotenv-webpack": "^8.0.1"
+ },
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ }
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/profile/src/App.jsx b/frontend/microfrontend/profile/src/App.jsx
new file mode 100644
index 00000000..5ee97a70
--- /dev/null
+++ b/frontend/microfrontend/profile/src/App.jsx
@@ -0,0 +1,19 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+
+import "./index.css";
+
+const App = () => (
+
+
Name: profile-micro-front
+
Framework: react
+
Language: JavaScript
+
CSS: Empty CSS
+
+);
+const rootElement = document.getElementById("app")
+if (!rootElement) throw new Error("Failed to find the root element")
+
+const root = ReactDOM.createRoot(rootElement)
+
+root.render()
\ No newline at end of file
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__button/_disabled/popup__button_disabled.css b/frontend/microfrontend/profile/src/blocks/popup/__button/_disabled/popup__button_disabled.css
new file mode 100644
index 00000000..1ad56853
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__button/_disabled/popup__button_disabled.css
@@ -0,0 +1,4 @@
+.popup__button_disabled {
+ opacity: 0.2;
+ pointer-events: none;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__button/popup__button.css b/frontend/microfrontend/profile/src/blocks/popup/__button/popup__button.css
new file mode 100644
index 00000000..7552a022
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__button/popup__button.css
@@ -0,0 +1,26 @@
+.popup__button {
+ width: 100%;
+ height: 50px;
+ font-size: 18px;
+ line-height: 22px;
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #000;
+ border-radius: 2px;
+ border: none;
+ transition: visibility 0s, background 0.3s;
+ margin-top: 48px;
+}
+
+.popup__button:hover {
+ background: rgba(0, 0, 0, 0.8);
+}
+
+@media screen and (max-width: 568px) {
+ .popup__button {
+ font-size: 14px;
+ line-height: 17px;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__caption/popup__caption.css b/frontend/microfrontend/profile/src/blocks/popup/__caption/popup__caption.css
new file mode 100644
index 00000000..35ef2b88
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__caption/popup__caption.css
@@ -0,0 +1,9 @@
+.popup__caption {
+ font-size: 12px;
+ line-height: 15px;
+ color: #fff;
+ position: absolute;
+ left: 0;
+ top: calc(100% + 10px);
+ margin: 0;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__close/popup__close.css b/frontend/microfrontend/profile/src/blocks/popup/__close/popup__close.css
new file mode 100644
index 00000000..db3a2db6
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__close/popup__close.css
@@ -0,0 +1,23 @@
+.popup__close {
+ width: 35px;
+ height: 35px;
+ background: transparent url('../../../images/close.svg') center no-repeat;
+ background-size: 35px 35px;
+ border: none;
+ position: absolute;
+ top: -36px;
+ right: -34px;
+ -webkit-transform: rotate(90deg);
+ -moz-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ -o-transform: rotate(90deg);
+ transform: rotate(90deg);
+ transition: visibility 0s, opacity 0.3s;
+ padding: 0;
+ margin: 0;
+ cursor: pointer;
+}
+
+.popup__close:hover {
+ opacity: 0.6;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__content/_content/popup__content_content_image.css b/frontend/microfrontend/profile/src/blocks/popup/__content/_content/popup__content_content_image.css
new file mode 100644
index 00000000..ad7ff951
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__content/_content/popup__content_content_image.css
@@ -0,0 +1,14 @@
+.popup__content_content_image {
+ max-width: 75vw;
+ max-height: 75vh;
+ height: auto;
+ width: auto;
+ display: flex;
+ background: transparent;
+ -webkit-background-size: cover;
+ background-size: cover;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+ padding: 0;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__content/popup__content.css b/frontend/microfrontend/profile/src/blocks/popup/__content/popup__content.css
new file mode 100644
index 00000000..668c80eb
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__content/popup__content.css
@@ -0,0 +1,19 @@
+.popup__content {
+ max-width: 430px;
+ width: 100%;
+ min-height: 330px;
+ background-color: #fff;
+ border-radius: 10px;
+ position: relative;
+ box-sizing: border-box;
+ padding: 34px 36px;
+}
+
+@media screen and (max-width: 568px) {
+ .popup__content {
+ width: 100%;
+ max-width: calc(100% - 80px);
+ margin-top: 40px;
+ padding: 30px 20px;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__error/_visible/popup__error_visible.css b/frontend/microfrontend/profile/src/blocks/popup/__error/_visible/popup__error_visible.css
new file mode 100644
index 00000000..4293d5cc
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__error/_visible/popup__error_visible.css
@@ -0,0 +1,3 @@
+.popup__error_visible {
+ opacity: 1;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__error/popup__error.css b/frontend/microfrontend/profile/src/blocks/popup/__error/popup__error.css
new file mode 100644
index 00000000..017e1dd9
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__error/popup__error.css
@@ -0,0 +1,10 @@
+.popup__error {
+ font-size: 12px;
+ line-height: 15px;
+ color: #FF0000;
+ opacity: 0;
+ position: absolute;
+ top: calc(100% + 5px);
+ left: 0;
+ transition: opacity 0.3s;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__form/popup__form.css b/frontend/microfrontend/profile/src/blocks/popup/__form/popup__form.css
new file mode 100644
index 00000000..425f78ca
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__form/popup__form.css
@@ -0,0 +1,3 @@
+.popup__form {
+ margin-top: 27px;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__icon/popup__icon.css b/frontend/microfrontend/profile/src/blocks/popup/__icon/popup__icon.css
new file mode 100644
index 00000000..3ea53b9c
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__icon/popup__icon.css
@@ -0,0 +1,6 @@
+.popup__icon {
+ display: block;
+ margin: auto;
+ width: 120px;
+ height: 120px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__image/popup__image.css b/frontend/microfrontend/profile/src/blocks/popup/__image/popup__image.css
new file mode 100644
index 00000000..20effa1c
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__image/popup__image.css
@@ -0,0 +1,6 @@
+.popup__image {
+ max-height: 100%;
+ max-width: 100%;
+ object-fit: cover;
+ display: block;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__input/_type/popup__input_type_error.css b/frontend/microfrontend/profile/src/blocks/popup/__input/_type/popup__input_type_error.css
new file mode 100644
index 00000000..79876daf
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__input/_type/popup__input_type_error.css
@@ -0,0 +1,3 @@
+.popup__input_type_error {
+ border-bottom: 1px solid #FF0000;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__input/popup__input.css b/frontend/microfrontend/profile/src/blocks/popup/__input/popup__input.css
new file mode 100644
index 00000000..8dc0a28f
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__input/popup__input.css
@@ -0,0 +1,26 @@
+.popup__input {
+ width: 100%;
+ border: 0;
+ border-bottom: 1px solid rgba(0, 0, 0, .2);
+ font-size: 14px;
+ line-height: 18px;
+ box-sizing: border-box;
+ margin-bottom: 10px;
+ padding: 0 0 13px;
+ -webkit-transition: 0.3s;
+ -moz-transition: 0.3s;
+ -ms-transition: 0.3s;
+ -o-transition: 0.3s;
+ transition: border-bottom 0.3s;
+}
+
+.popup__input:last-of-type {
+ margin-bottom: 0;
+}
+
+@media screen and (max-width: 568px) {
+ .popup__title {
+ font-size: 12px;
+ line-height: 15px;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__label/popup__label.css b/frontend/microfrontend/profile/src/blocks/popup/__label/popup__label.css
new file mode 100644
index 00000000..a9122bc5
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__label/popup__label.css
@@ -0,0 +1,5 @@
+.popup__label {
+ position: relative;
+ display: block;
+ padding: 30px 0 0;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__status-message/popup__status-message.css b/frontend/microfrontend/profile/src/blocks/popup/__status-message/popup__status-message.css
new file mode 100644
index 00000000..577b880a
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__status-message/popup__status-message.css
@@ -0,0 +1,8 @@
+.popup__status-message {
+ font-family: Inter, sans-serif;
+ font-weight: 900;
+ font-size: 24px;
+ line-height: 29px;
+ text-align: center;
+ margin-top: 32px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/profile/src/blocks/popup/__title/popup__title.css b/frontend/microfrontend/profile/src/blocks/popup/__title/popup__title.css
new file mode 100644
index 00000000..1092b827
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/__title/popup__title.css
@@ -0,0 +1,12 @@
+.popup__title {
+ margin: 0;
+ font-size: 24px;
+ line-height: 30px;
+}
+
+@media screen and (max-width: 568px) {
+ .popup__title {
+ font-size: 18px;
+ line-height: 22px;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/_is-opened/popup_is-opened.css b/frontend/microfrontend/profile/src/blocks/popup/_is-opened/popup_is-opened.css
new file mode 100644
index 00000000..207e395e
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/_is-opened/popup_is-opened.css
@@ -0,0 +1,6 @@
+.popup_is-opened {
+ visibility: visible;
+ opacity: 1;
+ pointer-events: all;
+ transition: visibility 0s, opacity 0.6s;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/_type/popup_type_edit-avatar.css b/frontend/microfrontend/profile/src/blocks/popup/_type/popup_type_edit-avatar.css
new file mode 100644
index 00000000..b357b631
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/_type/popup_type_edit-avatar.css
@@ -0,0 +1,3 @@
+.popup_type_edit-avatar .popup__content {
+ min-height: auto;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/_type/popup_type_remove-card.css b/frontend/microfrontend/profile/src/blocks/popup/_type/popup_type_remove-card.css
new file mode 100644
index 00000000..ac639298
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/_type/popup_type_remove-card.css
@@ -0,0 +1,3 @@
+.popup_type_remove-card .popup__content {
+ min-height: auto;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/popup/popup.css b/frontend/microfrontend/profile/src/blocks/popup/popup.css
new file mode 100644
index 00000000..c5f3b2cb
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/popup/popup.css
@@ -0,0 +1,37 @@
+@import url('./__content/popup__content.css');
+@import url('./__content/_content/popup__content_content_image.css');
+@import url('./__close/popup__close.css');
+@import url('./__title/popup__title.css');
+@import url('./__form/popup__form.css');
+@import url('./__input/popup__input.css');
+@import url('./__input/_type/popup__input_type_error.css');
+@import url('./__button/popup__button.css');
+@import url('./__button/_disabled/popup__button_disabled.css');
+@import url('./__caption/popup__caption.css');
+@import url('./__image/popup__image.css');
+@import url('./__label/popup__label.css');
+@import url('./__error/popup__error.css');
+@import url('./__error/_visible/popup__error_visible.css');
+@import url('./_type/popup_type_remove-card.css');
+@import url('./_type/popup_type_edit-avatar.css');
+@import url('./__icon/popup__icon.css');
+@import url('./__status-message/popup__status-message.css');
+
+.popup {
+ font-family: 'Inter', Arial, sans-serif;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: rgba(0, 0, 0, .5);
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ visibility: hidden;
+ opacity: 0;
+ pointer-events: none;
+ user-select: none;
+ transition: visibility 0s 0.6s, opacity 0.6s;
+ z-index: 10;
+}
diff --git a/frontend/microfrontend/profile/src/blocks/profile/__add-button/profile__add-button.css b/frontend/microfrontend/profile/src/blocks/profile/__add-button/profile__add-button.css
new file mode 100644
index 00000000..06dee3d4
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/profile/__add-button/profile__add-button.css
@@ -0,0 +1,34 @@
+.profile__add-button {
+ width: 150px;
+ height: 50px;
+ background: transparent url("../../../images/add-icon.svg") center no-repeat;
+ background-size: 22px;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+ border: 2px solid #fff;
+ -webkit-transition: 0.3s;
+ -moz-transition: 0.3s;
+ -ms-transition: 0.3s;
+ -o-transition: 0.3s;
+ transition: 0.3s;
+ cursor: pointer;
+ margin-left: auto;
+}
+
+.profile__add-button:hover {
+ opacity: 0.6;
+}
+
+@media screen and (max-width: 740px) {
+ .profile__add-button {
+ width: 50px;
+ height: 50px;
+ }
+}
+
+@media screen and (max-width: 480px) {
+ .profile__add-button {
+ width: 100%;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/blocks/profile/__description/profile__description.css b/frontend/microfrontend/profile/src/blocks/profile/__description/profile__description.css
new file mode 100644
index 00000000..67b0d18b
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/profile/__description/profile__description.css
@@ -0,0 +1,20 @@
+.profile__description {
+ font-size: 18px;
+ line-height: 22px;
+ grid-area: description;
+ margin: 0;
+}
+
+@media screen and (max-width: 568px) {
+ .profile__description {
+ font-size: 14px;
+ line-height: 17px;
+ }
+}
+
+@media screen and (max-width: 480px) {
+ .profile__description {
+ width: 100%;
+ margin: 7px 0 0 0;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/blocks/profile/__edit-button/profile__edit-button.css b/frontend/microfrontend/profile/src/blocks/profile/__edit-button/profile__edit-button.css
new file mode 100644
index 00000000..9816cdf1
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/profile/__edit-button/profile__edit-button.css
@@ -0,0 +1,29 @@
+.profile__edit-button {
+ width: 24px;
+ height: 24px;
+ background: transparent url('../../../images/edit-icon.svg') center no-repeat;
+ background-size: 10px 10px;
+ border: 1px solid #fff;
+ grid-area: button;
+ align-self: center;
+ cursor: pointer;
+ -webkit-transition: 0.3s;
+ -moz-transition: 0.3s;
+ -ms-transition: 0.3s;
+ -o-transition: 0.3s;
+ transition: 0.3s;
+ padding: 0;
+ margin: 0;
+}
+
+.profile__edit-button:hover {
+ opacity: 0.6;
+}
+
+@media screen and (max-width: 480px) {
+ .profile__edit-button {
+ width: 18px;
+ height: 18px;
+ background-size: 8px 8px;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/blocks/profile/__image/profile__image.css b/frontend/microfrontend/profile/src/blocks/profile/__image/profile__image.css
new file mode 100644
index 00000000..a8c43084
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/profile/__image/profile__image.css
@@ -0,0 +1,72 @@
+.profile__image {
+ width: 120px;
+ height: 120px;
+ -webkit-border-radius: 50%;
+ -moz-border-radius: 50%;
+ border-radius: 50%;
+ background-size: cover;
+ background-position: center;
+ position: relative;
+ margin: 0 29px 0 0;
+}
+
+.profile__image:hover {
+ cursor: pointer;
+}
+
+.profile__image::before,
+.profile__image::after {
+ content: '';
+ position: absolute;
+ -webkit-transition: 0.3s;
+ -moz-transition: 0.3s;
+ -ms-transition: 0.3s;
+ -o-transition: 0.3s;
+ transition: 0.3s;
+ pointer-events: none;
+}
+
+.profile__image::before {
+ background: rgba(0, 0, 0, 0);
+ top: 0;
+ right: 0;
+ left: 0;
+ bottom: 0;
+}
+
+.profile__image::after {
+ width: 26px;
+ height: 26px;
+ background-image: url('../../../images/edit-icon.svg');
+ -webkit-background-size: contain;
+ background-size: contain;
+ opacity: 0;
+ top: 50%;
+ left: 50%;
+ -webkit-transform: translate(-50%, -50%);
+ -moz-transform: translate(-50%, -50%);
+ -ms-transform: translate(-50%, -50%);
+ -o-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+}
+
+.profile__image:hover::before {
+ background: rgba(0, 0, 0, 0.8);
+}
+
+.profile__image:hover::after {
+ opacity: 1;
+}
+
+
+@media screen and (max-width: 740px) {
+ .profile__image {
+ margin: 0 10px 0 0;
+ }
+}
+
+@media screen and (max-width: 480px) {
+ .profile__image {
+ margin-right: 0;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/blocks/profile/__info/profile__info.css b/frontend/microfrontend/profile/src/blocks/profile/__info/profile__info.css
new file mode 100644
index 00000000..7aaefee7
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/profile/__info/profile__info.css
@@ -0,0 +1,30 @@
+.profile__info {
+ display: grid;
+ grid-template-areas: "title button"
+ "description description";
+ grid-template-columns: minmax(auto, 295px) auto;
+ grid-gap: 9px 17px;
+}
+
+@media screen and (max-width: 740px) {
+ .profile__info {
+ grid-template-columns: minmax(auto, 228px) auto;
+ grid-gap: 9px 5px;
+ }
+}
+
+@media screen and (max-width: 568px) {
+ .profile__info {
+ grid-template-columns: minmax(auto, 195px) auto;
+ }
+}
+
+@media screen and (max-width: 480px) {
+ .profile__info {
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ margin: 26px 0 33px 0;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/blocks/profile/__title/profile__title.css b/frontend/microfrontend/profile/src/blocks/profile/__title/profile__title.css
new file mode 100644
index 00000000..4c7e2ddf
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/profile/__title/profile__title.css
@@ -0,0 +1,30 @@
+.profile__title {
+ font-size: 42px;
+ line-height: 48px;
+ font-weight: 400;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ grid-area: title;
+ margin: 0;
+}
+
+@media screen and (max-width: 740px) {
+ .profile__title {
+ font-size: 32px;
+ line-height: 38px;
+ }
+}
+
+@media screen and (max-width: 568px) {
+ .profile__title {
+ font-size: 27px;
+ line-height: 33px;
+ }
+}
+
+@media screen and (max-width: 480px) {
+ .profile__title {
+ min-width: 201px;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/blocks/profile/profile.css b/frontend/microfrontend/profile/src/blocks/profile/profile.css
new file mode 100644
index 00000000..01d738e7
--- /dev/null
+++ b/frontend/microfrontend/profile/src/blocks/profile/profile.css
@@ -0,0 +1,22 @@
+@import url('./__description/profile__description.css');
+@import url('./__add-button/profile__add-button.css');
+@import url('./__edit-button/profile__edit-button.css');
+@import url('./__info/profile__info.css');
+@import url('./__title/profile__title.css');
+@import url('./__image/profile__image.css');
+
+.profile {
+ font-family: 'Inter', Arial, sans-serif;
+ color: #fff;
+ display: flex;
+ align-items: center;
+ padding: 36px 0;
+}
+
+@media screen and (max-width: 480px) {
+ .profile {
+ padding: 43px 0;
+ flex-direction: column;
+ text-align: center;
+ }
+}
diff --git a/frontend/microfrontend/profile/src/components/EditAvatarPopup.js b/frontend/microfrontend/profile/src/components/EditAvatarPopup.js
new file mode 100644
index 00000000..6fdb564b
--- /dev/null
+++ b/frontend/microfrontend/profile/src/components/EditAvatarPopup.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import PopupWithForm from './PopupWithForm';
+
+function EditAvatarPopup({ isOpen, onUpdateAvatar, onClose }) {
+ const inputRef = React.useRef();
+
+ function handleSubmit(e) {
+ e.preventDefault();
+
+ onUpdateAvatar({
+ avatar: inputRef.current.value,
+ });
+ }
+
+ return (
+
+
+
+
+ );
+}
+
+export default EditAvatarPopup;
diff --git a/frontend/microfrontend/profile/src/components/EditProfilePopup.js b/frontend/microfrontend/profile/src/components/EditProfilePopup.js
new file mode 100644
index 00000000..9efd9545
--- /dev/null
+++ b/frontend/microfrontend/profile/src/components/EditProfilePopup.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import PopupWithForm from './PopupWithForm';
+import { CurrentUserContext } from '../contexts/CurrentUserContext';
+
+function EditProfilePopup({ isOpen, onUpdateUser, onClose }) {
+ const [name, setName] = React.useState('');
+ const [description, setDescription] = React.useState('');
+
+ function handleNameChange(e) {
+ setName(e.target.value);
+ }
+
+ function handleDescriptionChange(e) {
+ setDescription(e.target.value);
+ }
+
+ const currentUser = React.useContext(CurrentUserContext);
+
+ React.useEffect(() => {
+ if (currentUser) {
+ setName(currentUser.name);
+ setDescription(currentUser.about);
+ }
+ }, [currentUser]);
+
+ function handleSubmit(e) {
+ e.preventDefault();
+
+ onUpdateUser({
+ name,
+ about: description,
+ });
+ }
+
+ return (
+
+
+
+
+ );
+}
+
+export default EditProfilePopup;
diff --git a/frontend/microfrontend/profile/src/components/PopupWithForm.js b/frontend/microfrontend/profile/src/components/PopupWithForm.js
new file mode 100644
index 00000000..418bd0cf
--- /dev/null
+++ b/frontend/microfrontend/profile/src/components/PopupWithForm.js
@@ -0,0 +1,26 @@
+import React from 'react';
+
+function PopupWithForm({
+ title,
+ name,
+ isOpen,
+ buttonText = 'Сохранить',
+ onSubmit,
+ onClose,
+ children,
+}) {
+ return (
+
+ );
+}
+
+export default PopupWithForm;
diff --git a/frontend/microfrontend/profile/src/index.css b/frontend/microfrontend/profile/src/index.css
new file mode 100644
index 00000000..20e225c5
--- /dev/null
+++ b/frontend/microfrontend/profile/src/index.css
@@ -0,0 +1,10 @@
+body {
+ font-family: Arial, Helvetica, sans-serif;
+}
+
+.container {
+ font-size: 3rem;
+ margin: auto;
+ max-width: 800px;
+ margin-top: 20px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/profile/src/index.html b/frontend/microfrontend/profile/src/index.html
new file mode 100644
index 00000000..79cadc53
--- /dev/null
+++ b/frontend/microfrontend/profile/src/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ profile-micro-front
+
+
+
+
+
+
+
diff --git a/frontend/microfrontend/profile/src/index.js b/frontend/microfrontend/profile/src/index.js
new file mode 100644
index 00000000..fc3cee10
--- /dev/null
+++ b/frontend/microfrontend/profile/src/index.js
@@ -0,0 +1 @@
+import("./App");
diff --git a/frontend/microfrontend/profile/src/utils/api.js b/frontend/microfrontend/profile/src/utils/api.js
new file mode 100644
index 00000000..aae69352
--- /dev/null
+++ b/frontend/microfrontend/profile/src/utils/api.js
@@ -0,0 +1,55 @@
+class Api {
+ constructor({ address, token, groupId }) {
+ // стандартная реализация -- объект options
+ this._token = token;
+ this._groupId = groupId;
+ this._address = address;
+
+ // Запросы в примере работы выполняются к старому Api, в новом URL изменены.
+ }
+ getUserInfo() {
+ return fetch(`${this._address}/${this._groupId}/users/me`, {
+ headers: {
+ authorization: this._token,
+ },
+ })
+ .then(res => res.ok ? res.json() : Promise.reject(`Ошибка: ${res.status}`));
+ }
+
+ setUserInfo({ name, about }) {
+ return fetch(`${this._address}/${this._groupId}/users/me`, {
+ method: 'PATCH',
+ headers: {
+ authorization: this._token,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ name,
+ about,
+ }),
+ })
+ .then(res => res.ok ? res.json() : Promise.reject(`Ошибка: ${res.status}`));
+ }
+
+ setUserAvatar({ avatar }) {
+ return fetch(`${this._address}/${this._groupId}/users/me/avatar`, {
+ method: 'PATCH',
+ headers: {
+ authorization: this._token,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ avatar,
+ }),
+ })
+ .then(res => res.ok ? res.json() : Promise.reject(`Ошибка: ${res.status}`));
+ }
+}
+
+const api = new Api({
+ address: 'https://nomoreparties.co',
+ groupId: `cohort0`,
+ token: `80a75492-21c5-4330-a02f-308029e94b63`,
+});
+
+export default api;
diff --git a/frontend/microfrontend/profile/webpack.config.js b/frontend/microfrontend/profile/webpack.config.js
new file mode 100644
index 00000000..53d68d86
--- /dev/null
+++ b/frontend/microfrontend/profile/webpack.config.js
@@ -0,0 +1,86 @@
+const HtmlWebPackPlugin = require("html-webpack-plugin");
+const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
+const path = require('path');
+const Dotenv = require('dotenv-webpack');
+
+const deps = require("./package.json").dependencies;
+
+const printCompilationMessage = require('./compilation.config.js');
+
+module.exports = (_, argv) => ({
+ output: {
+ publicPath: "http://localhost:8082/",
+ },
+
+ resolve: {
+ extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
+ },
+
+ devServer: {
+ port: 8082,
+ historyApiFallback: true,
+ watchFiles: [path.resolve(__dirname, 'src')],
+ onListening: function (devServer) {
+ const port = devServer.server.address().port
+
+ printCompilationMessage('compiling', port)
+
+ devServer.compiler.hooks.done.tap('OutputMessagePlugin', (stats) => {
+ setImmediate(() => {
+ if (stats.hasErrors()) {
+ printCompilationMessage('failure', port)
+ } else {
+ printCompilationMessage('success', port)
+ }
+ })
+ })
+ }
+ },
+
+ module: {
+ rules: [
+ {
+ test: /\.m?js/,
+ type: "javascript/auto",
+ resolve: {
+ fullySpecified: false,
+ },
+ },
+ {
+ test: /\.(css|s[ac]ss)$/i,
+ use: ["style-loader", "css-loader", "postcss-loader"],
+ },
+ {
+ test: /\.(ts|tsx|js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ },
+ },
+ ],
+ },
+
+ plugins: [
+ new ModuleFederationPlugin({
+ name: "profile_micro_front",
+ filename: "remoteEntry.js",
+ remotes: {},
+ exposes: {},
+ shared: {
+ ...deps,
+ react: {
+ singleton: true,
+ requiredVersion: deps.react,
+ },
+ "react-dom": {
+ singleton: true,
+ requiredVersion: deps["react-dom"],
+ },
+ },
+ }),
+ new HtmlWebPackPlugin({
+ template: "./src/index.html",
+ }),
+ new Dotenv()
+ ],
+});
diff --git a/frontend/microfrontend/src/blocks/content/content.css b/frontend/microfrontend/src/blocks/content/content.css
new file mode 100644
index 00000000..84ac9e4d
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/content/content.css
@@ -0,0 +1,4 @@
+.content {
+ flex-shrink: 0;
+ flex-grow: 1;
+}
diff --git a/frontend/microfrontend/src/blocks/footer/__copyright/footer__copyright.css b/frontend/microfrontend/src/blocks/footer/__copyright/footer__copyright.css
new file mode 100644
index 00000000..5941b681
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/footer/__copyright/footer__copyright.css
@@ -0,0 +1,13 @@
+.footer__copyright {
+ font-size: 18px;
+ line-height: 22px;
+ color: #545454;
+ margin: 0;
+}
+
+@media screen and (max-width: 568px) {
+ .footer__copyright {
+ font-size: 14px;
+ line-height: 17px;
+ }
+}
diff --git a/frontend/microfrontend/src/blocks/footer/footer.css b/frontend/microfrontend/src/blocks/footer/footer.css
new file mode 100644
index 00000000..9455f1b2
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/footer/footer.css
@@ -0,0 +1,13 @@
+@import url('./__copyright/footer__copyright.css');
+
+.footer {
+ font-family: 'Inter', Arial, sans-serif;
+ flex-shrink: 0;
+ padding: 30px 0 60px;
+}
+
+@media screen and (max-width: 568px) {
+ .footer {
+ padding: 30px 0 36px;
+ }
+}
diff --git a/frontend/microfrontend/src/blocks/header/__auth-link/header__auth-link.css b/frontend/microfrontend/src/blocks/header/__auth-link/header__auth-link.css
new file mode 100644
index 00000000..18a11e1a
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/header/__auth-link/header__auth-link.css
@@ -0,0 +1,10 @@
+.header__auth-link {
+ font-size: 18px;
+ line-height: 22px;
+ color: #FFFFFF;
+ text-decoration: none;
+}
+
+.header__auth-link:hover {
+ opacity: .85;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/src/blocks/header/__logo/header__logo.css b/frontend/microfrontend/src/blocks/header/__logo/header__logo.css
new file mode 100644
index 00000000..2fec6e16
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/header/__logo/header__logo.css
@@ -0,0 +1,13 @@
+.header__logo {
+ width: 142px;
+ height: 33px;
+ object-fit: contain;
+}
+
+@media screen and (max-width: 480px) {
+ .header__logo {
+ width: 104px;
+ height: 24px;
+ margin: 0 0 0 7px;
+ }
+}
diff --git a/frontend/microfrontend/src/blocks/header/__logout/header__logout.css b/frontend/microfrontend/src/blocks/header/__logout/header__logout.css
new file mode 100644
index 00000000..77bad6e7
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/header/__logout/header__logout.css
@@ -0,0 +1,8 @@
+.header__logout {
+ font-size: 18px;
+ line-height: 22px;
+ color: #A9A9A9;
+ background-color: transparent;
+ border: 0;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/src/blocks/header/__user/header__user.css b/frontend/microfrontend/src/blocks/header/__user/header__user.css
new file mode 100644
index 00000000..8b0b29d1
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/header/__user/header__user.css
@@ -0,0 +1,7 @@
+.header__user {
+ color: #fff;
+ font-weight: 500;
+ font-size: 18px;
+ line-height: 22px;
+ margin-right: 24px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/src/blocks/header/__wrapper/header__wrapper.css b/frontend/microfrontend/src/blocks/header/__wrapper/header__wrapper.css
new file mode 100644
index 00000000..3470dc44
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/header/__wrapper/header__wrapper.css
@@ -0,0 +1,3 @@
+.header__wrapper {
+ display: flex;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/src/blocks/header/header.css b/frontend/microfrontend/src/blocks/header/header.css
new file mode 100644
index 00000000..80611d86
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/header/header.css
@@ -0,0 +1,39 @@
+@import url('./__logo/header__logo.css');
+@import url('./__auth-link/header__auth-link.css');
+@import url('./__wrapper/header__wrapper.css');
+@import url('./__user/header__user.css');
+@import url('./__logout/header__logout.css');
+
+.header {
+ min-height: 120px;
+ font-family: 'Inter', Arial, sans-serif;
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ justify-content: space-between;
+}
+
+.header::before {
+ content: '';
+ width: 100%;
+ height: 1px;
+ opacity: 0.7;
+ background: #545454;
+ position: absolute;
+ left: 50%;
+ -webkit-transform: translateX(-50%);
+ -moz-transform: translateX(-50%);
+ -ms-transform: translateX(-50%);
+ -o-transform: translateX(-50%);
+ transform: translateX(-50%);
+ bottom: 0;
+}
+
+@media screen and (max-width: 480px) {
+ .header {
+ min-height: 85px;
+ }
+ .header::before {
+ width: calc(100% + 40px);
+ }
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__button/_disabled/popup__button_disabled.css b/frontend/microfrontend/src/blocks/popup/__button/_disabled/popup__button_disabled.css
new file mode 100644
index 00000000..1ad56853
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__button/_disabled/popup__button_disabled.css
@@ -0,0 +1,4 @@
+.popup__button_disabled {
+ opacity: 0.2;
+ pointer-events: none;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__button/popup__button.css b/frontend/microfrontend/src/blocks/popup/__button/popup__button.css
new file mode 100644
index 00000000..7552a022
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__button/popup__button.css
@@ -0,0 +1,26 @@
+.popup__button {
+ width: 100%;
+ height: 50px;
+ font-size: 18px;
+ line-height: 22px;
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #000;
+ border-radius: 2px;
+ border: none;
+ transition: visibility 0s, background 0.3s;
+ margin-top: 48px;
+}
+
+.popup__button:hover {
+ background: rgba(0, 0, 0, 0.8);
+}
+
+@media screen and (max-width: 568px) {
+ .popup__button {
+ font-size: 14px;
+ line-height: 17px;
+ }
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__caption/popup__caption.css b/frontend/microfrontend/src/blocks/popup/__caption/popup__caption.css
new file mode 100644
index 00000000..35ef2b88
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__caption/popup__caption.css
@@ -0,0 +1,9 @@
+.popup__caption {
+ font-size: 12px;
+ line-height: 15px;
+ color: #fff;
+ position: absolute;
+ left: 0;
+ top: calc(100% + 10px);
+ margin: 0;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__close/popup__close.css b/frontend/microfrontend/src/blocks/popup/__close/popup__close.css
new file mode 100644
index 00000000..db3a2db6
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__close/popup__close.css
@@ -0,0 +1,23 @@
+.popup__close {
+ width: 35px;
+ height: 35px;
+ background: transparent url('../../../images/close.svg') center no-repeat;
+ background-size: 35px 35px;
+ border: none;
+ position: absolute;
+ top: -36px;
+ right: -34px;
+ -webkit-transform: rotate(90deg);
+ -moz-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ -o-transform: rotate(90deg);
+ transform: rotate(90deg);
+ transition: visibility 0s, opacity 0.3s;
+ padding: 0;
+ margin: 0;
+ cursor: pointer;
+}
+
+.popup__close:hover {
+ opacity: 0.6;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__content/_content/popup__content_content_image.css b/frontend/microfrontend/src/blocks/popup/__content/_content/popup__content_content_image.css
new file mode 100644
index 00000000..ad7ff951
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__content/_content/popup__content_content_image.css
@@ -0,0 +1,14 @@
+.popup__content_content_image {
+ max-width: 75vw;
+ max-height: 75vh;
+ height: auto;
+ width: auto;
+ display: flex;
+ background: transparent;
+ -webkit-background-size: cover;
+ background-size: cover;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+ padding: 0;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__content/popup__content.css b/frontend/microfrontend/src/blocks/popup/__content/popup__content.css
new file mode 100644
index 00000000..668c80eb
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__content/popup__content.css
@@ -0,0 +1,19 @@
+.popup__content {
+ max-width: 430px;
+ width: 100%;
+ min-height: 330px;
+ background-color: #fff;
+ border-radius: 10px;
+ position: relative;
+ box-sizing: border-box;
+ padding: 34px 36px;
+}
+
+@media screen and (max-width: 568px) {
+ .popup__content {
+ width: 100%;
+ max-width: calc(100% - 80px);
+ margin-top: 40px;
+ padding: 30px 20px;
+ }
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__error/_visible/popup__error_visible.css b/frontend/microfrontend/src/blocks/popup/__error/_visible/popup__error_visible.css
new file mode 100644
index 00000000..4293d5cc
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__error/_visible/popup__error_visible.css
@@ -0,0 +1,3 @@
+.popup__error_visible {
+ opacity: 1;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__error/popup__error.css b/frontend/microfrontend/src/blocks/popup/__error/popup__error.css
new file mode 100644
index 00000000..017e1dd9
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__error/popup__error.css
@@ -0,0 +1,10 @@
+.popup__error {
+ font-size: 12px;
+ line-height: 15px;
+ color: #FF0000;
+ opacity: 0;
+ position: absolute;
+ top: calc(100% + 5px);
+ left: 0;
+ transition: opacity 0.3s;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__form/popup__form.css b/frontend/microfrontend/src/blocks/popup/__form/popup__form.css
new file mode 100644
index 00000000..425f78ca
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__form/popup__form.css
@@ -0,0 +1,3 @@
+.popup__form {
+ margin-top: 27px;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__icon/popup__icon.css b/frontend/microfrontend/src/blocks/popup/__icon/popup__icon.css
new file mode 100644
index 00000000..3ea53b9c
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__icon/popup__icon.css
@@ -0,0 +1,6 @@
+.popup__icon {
+ display: block;
+ margin: auto;
+ width: 120px;
+ height: 120px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/src/blocks/popup/__image/popup__image.css b/frontend/microfrontend/src/blocks/popup/__image/popup__image.css
new file mode 100644
index 00000000..20effa1c
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__image/popup__image.css
@@ -0,0 +1,6 @@
+.popup__image {
+ max-height: 100%;
+ max-width: 100%;
+ object-fit: cover;
+ display: block;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__input/_type/popup__input_type_error.css b/frontend/microfrontend/src/blocks/popup/__input/_type/popup__input_type_error.css
new file mode 100644
index 00000000..79876daf
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__input/_type/popup__input_type_error.css
@@ -0,0 +1,3 @@
+.popup__input_type_error {
+ border-bottom: 1px solid #FF0000;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__input/popup__input.css b/frontend/microfrontend/src/blocks/popup/__input/popup__input.css
new file mode 100644
index 00000000..8dc0a28f
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__input/popup__input.css
@@ -0,0 +1,26 @@
+.popup__input {
+ width: 100%;
+ border: 0;
+ border-bottom: 1px solid rgba(0, 0, 0, .2);
+ font-size: 14px;
+ line-height: 18px;
+ box-sizing: border-box;
+ margin-bottom: 10px;
+ padding: 0 0 13px;
+ -webkit-transition: 0.3s;
+ -moz-transition: 0.3s;
+ -ms-transition: 0.3s;
+ -o-transition: 0.3s;
+ transition: border-bottom 0.3s;
+}
+
+.popup__input:last-of-type {
+ margin-bottom: 0;
+}
+
+@media screen and (max-width: 568px) {
+ .popup__title {
+ font-size: 12px;
+ line-height: 15px;
+ }
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__label/popup__label.css b/frontend/microfrontend/src/blocks/popup/__label/popup__label.css
new file mode 100644
index 00000000..a9122bc5
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__label/popup__label.css
@@ -0,0 +1,5 @@
+.popup__label {
+ position: relative;
+ display: block;
+ padding: 30px 0 0;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/__status-message/popup__status-message.css b/frontend/microfrontend/src/blocks/popup/__status-message/popup__status-message.css
new file mode 100644
index 00000000..577b880a
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__status-message/popup__status-message.css
@@ -0,0 +1,8 @@
+.popup__status-message {
+ font-family: Inter, sans-serif;
+ font-weight: 900;
+ font-size: 24px;
+ line-height: 29px;
+ text-align: center;
+ margin-top: 32px;
+}
\ No newline at end of file
diff --git a/frontend/microfrontend/src/blocks/popup/__title/popup__title.css b/frontend/microfrontend/src/blocks/popup/__title/popup__title.css
new file mode 100644
index 00000000..1092b827
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/__title/popup__title.css
@@ -0,0 +1,12 @@
+.popup__title {
+ margin: 0;
+ font-size: 24px;
+ line-height: 30px;
+}
+
+@media screen and (max-width: 568px) {
+ .popup__title {
+ font-size: 18px;
+ line-height: 22px;
+ }
+}
diff --git a/frontend/microfrontend/src/blocks/popup/_is-opened/popup_is-opened.css b/frontend/microfrontend/src/blocks/popup/_is-opened/popup_is-opened.css
new file mode 100644
index 00000000..207e395e
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/_is-opened/popup_is-opened.css
@@ -0,0 +1,6 @@
+.popup_is-opened {
+ visibility: visible;
+ opacity: 1;
+ pointer-events: all;
+ transition: visibility 0s, opacity 0.6s;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/_type/popup_type_edit-avatar.css b/frontend/microfrontend/src/blocks/popup/_type/popup_type_edit-avatar.css
new file mode 100644
index 00000000..b357b631
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/_type/popup_type_edit-avatar.css
@@ -0,0 +1,3 @@
+.popup_type_edit-avatar .popup__content {
+ min-height: auto;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/_type/popup_type_remove-card.css b/frontend/microfrontend/src/blocks/popup/_type/popup_type_remove-card.css
new file mode 100644
index 00000000..ac639298
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/_type/popup_type_remove-card.css
@@ -0,0 +1,3 @@
+.popup_type_remove-card .popup__content {
+ min-height: auto;
+}
diff --git a/frontend/microfrontend/src/blocks/popup/popup.css b/frontend/microfrontend/src/blocks/popup/popup.css
new file mode 100644
index 00000000..c5f3b2cb
--- /dev/null
+++ b/frontend/microfrontend/src/blocks/popup/popup.css
@@ -0,0 +1,37 @@
+@import url('./__content/popup__content.css');
+@import url('./__content/_content/popup__content_content_image.css');
+@import url('./__close/popup__close.css');
+@import url('./__title/popup__title.css');
+@import url('./__form/popup__form.css');
+@import url('./__input/popup__input.css');
+@import url('./__input/_type/popup__input_type_error.css');
+@import url('./__button/popup__button.css');
+@import url('./__button/_disabled/popup__button_disabled.css');
+@import url('./__caption/popup__caption.css');
+@import url('./__image/popup__image.css');
+@import url('./__label/popup__label.css');
+@import url('./__error/popup__error.css');
+@import url('./__error/_visible/popup__error_visible.css');
+@import url('./_type/popup_type_remove-card.css');
+@import url('./_type/popup_type_edit-avatar.css');
+@import url('./__icon/popup__icon.css');
+@import url('./__status-message/popup__status-message.css');
+
+.popup {
+ font-family: 'Inter', Arial, sans-serif;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: rgba(0, 0, 0, .5);
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ visibility: hidden;
+ opacity: 0;
+ pointer-events: none;
+ user-select: none;
+ transition: visibility 0s 0.6s, opacity 0.6s;
+ z-index: 10;
+}
diff --git a/frontend/microfrontend/src/components/App.js b/frontend/microfrontend/src/components/App.js
new file mode 100644
index 00000000..f950da2f
--- /dev/null
+++ b/frontend/microfrontend/src/components/App.js
@@ -0,0 +1,234 @@
+import React from "react";
+import { Route, useHistory, Switch } from "react-router-dom";
+import Header from "./Header";
+import Main from "./Main";
+import Footer from "./Footer";
+import PopupWithForm from "./PopupWithForm";
+import ImagePopup from "./ImagePopup";
+import api from "../utils/api";
+import { CurrentUserContext } from "../contexts/CurrentUserContext";
+import EditProfilePopup from "./EditProfilePopup";
+import EditAvatarPopup from "./EditAvatarPopup";
+import AddPlacePopup from "./AddPlacePopup";
+import Register from "./Register";
+import Login from "./Login";
+import InfoTooltip from "./InfoTooltip";
+import ProtectedRoute from "./ProtectedRoute";
+import * as auth from "../utils/auth.js";
+
+function App() {
+ const [isEditProfilePopupOpen, setIsEditProfilePopupOpen] =
+ React.useState(false);
+ const [isAddPlacePopupOpen, setIsAddPlacePopupOpen] = React.useState(false);
+ const [isEditAvatarPopupOpen, setIsEditAvatarPopupOpen] =
+ React.useState(false);
+ const [selectedCard, setSelectedCard] = React.useState(null);
+ const [cards, setCards] = React.useState([]);
+
+ // В корневом компоненте App создана стейт-переменная currentUser. Она используется в качестве значения для провайдера контекста.
+ const [currentUser, setCurrentUser] = React.useState({});
+
+ const [isInfoToolTipOpen, setIsInfoToolTipOpen] = React.useState(false);
+ const [tooltipStatus, setTooltipStatus] = React.useState("");
+
+ const [isLoggedIn, setIsLoggedIn] = React.useState(false);
+ //В компоненты добавлены новые стейт-переменные: email — в компонент App
+ const [email, setEmail] = React.useState("");
+
+ const history = useHistory();
+
+ // Запрос к API за информацией о пользователе и массиве карточек выполняется единожды, при монтировании.
+ React.useEffect(() => {
+ api
+ .getAppInfo()
+ .then(([cardData, userData]) => {
+ setCurrentUser(userData);
+ setCards(cardData);
+ })
+ .catch((err) => console.log(err));
+ }, []);
+
+ // при монтировании App описан эффект, проверяющий наличие токена и его валидности
+ React.useEffect(() => {
+ const token = localStorage.getItem("jwt");
+ if (token) {
+ auth
+ .checkToken(token)
+ .then((res) => {
+ setEmail(res.data.email);
+ setIsLoggedIn(true);
+ history.push("/");
+ })
+ .catch((err) => {
+ localStorage.removeItem("jwt");
+ console.log(err);
+ });
+ }
+ }, [history]);
+
+ function handleEditProfileClick() {
+ setIsEditProfilePopupOpen(true);
+ }
+
+ function handleAddPlaceClick() {
+ setIsAddPlacePopupOpen(true);
+ }
+
+ function handleEditAvatarClick() {
+ setIsEditAvatarPopupOpen(true);
+ }
+
+ function closeAllPopups() {
+ setIsEditProfilePopupOpen(false);
+ setIsAddPlacePopupOpen(false);
+ setIsEditAvatarPopupOpen(false);
+ setIsInfoToolTipOpen(false);
+ setSelectedCard(null);
+ }
+
+ function handleCardClick(card) {
+ setSelectedCard(card);
+ }
+
+ function handleUpdateUser(userUpdate) {
+ api
+ .setUserInfo(userUpdate)
+ .then((newUserData) => {
+ setCurrentUser(newUserData);
+ closeAllPopups();
+ })
+ .catch((err) => console.log(err));
+ }
+
+ function handleUpdateAvatar(avatarUpdate) {
+ api
+ .setUserAvatar(avatarUpdate)
+ .then((newUserData) => {
+ setCurrentUser(newUserData);
+ closeAllPopups();
+ })
+ .catch((err) => console.log(err));
+ }
+
+ function handleCardLike(card) {
+ const isLiked = card.likes.some((i) => i._id === currentUser._id);
+ api
+ .changeLikeCardStatus(card._id, !isLiked)
+ .then((newCard) => {
+ setCards((cards) =>
+ cards.map((c) => (c._id === card._id ? newCard : c))
+ );
+ })
+ .catch((err) => console.log(err));
+ }
+
+ function handleCardDelete(card) {
+ api
+ .removeCard(card._id)
+ .then(() => {
+ setCards((cards) => cards.filter((c) => c._id !== card._id));
+ })
+ .catch((err) => console.log(err));
+ }
+
+ function handleAddPlaceSubmit(newCard) {
+ api
+ .addCard(newCard)
+ .then((newCardFull) => {
+ setCards([newCardFull, ...cards]);
+ closeAllPopups();
+ })
+ .catch((err) => console.log(err));
+ }
+
+ function onRegister({ email, password }) {
+ auth
+ .register(email, password)
+ .then((res) => {
+ setTooltipStatus("success");
+ setIsInfoToolTipOpen(true);
+ history.push("/signin");
+ })
+ .catch((err) => {
+ setTooltipStatus("fail");
+ setIsInfoToolTipOpen(true);
+ });
+ }
+
+ function onLogin({ email, password }) {
+ auth
+ .login(email, password)
+ .then((res) => {
+ setIsLoggedIn(true);
+ setEmail(email);
+ history.push("/");
+ })
+ .catch((err) => {
+ setTooltipStatus("fail");
+ setIsInfoToolTipOpen(true);
+ });
+ }
+
+ function onSignOut() {
+ // при вызове обработчика onSignOut происходит удаление jwt
+ localStorage.removeItem("jwt");
+ setIsLoggedIn(false);
+ // После успешного вызова обработчика onSignOut происходит редирект на /signin
+ history.push("/signin");
+ }
+
+ return (
+ // В компонент App внедрён контекст через CurrentUserContext.Provider
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/frontend/microfrontend/src/components/Footer.js b/frontend/microfrontend/src/components/Footer.js
new file mode 100644
index 00000000..c59e9907
--- /dev/null
+++ b/frontend/microfrontend/src/components/Footer.js
@@ -0,0 +1,13 @@
+import React from 'react';
+
+function Footer() {
+ return (
+
+ );
+}
+
+export default Footer;
diff --git a/frontend/microfrontend/src/components/Header.js b/frontend/microfrontend/src/components/Header.js
new file mode 100644
index 00000000..abee0d7c
--- /dev/null
+++ b/frontend/microfrontend/src/components/Header.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Route, Link } from 'react-router-dom';
+import logoPath from '../images/logo.svg';
+
+// В корневом компоненте App описаны обработчики: onRegister, onLogin и onSignOut. Эти обработчики переданы в соответствующие компоненты: Register.js, Login.js, Header.js
+function Header ({onSignOut, email }) {
+ function handleSignOut(){
+ onSignOut();
+ }
+ return (
+
+
+
+
+
+
+ Войти
+
+
+ Регистрация
+
+
+ )
+}
+
+export default Header;
diff --git a/frontend/microfrontend/src/index.css b/frontend/microfrontend/src/index.css
new file mode 100644
index 00000000..8fb4ffe3
--- /dev/null
+++ b/frontend/microfrontend/src/index.css
@@ -0,0 +1,12 @@
+@import url('./vendor/normalize.css');
+@import url('./vendor/fonts.css');
+@import url('./blocks/page/page.css');
+@import url('./blocks/header/header.css');
+@import url('./blocks/content/content.css');
+@import url('./blocks/footer/footer.css');
+@import url('./blocks/profile/profile.css');
+@import url('./blocks/places/places.css');
+@import url('./blocks/card/card.css');
+@import url('./blocks/popup/popup.css');
+@import url('./blocks/popup/_is-opened/popup_is-opened.css');
+@import url('./blocks/auth-form/auth-form.css');
diff --git a/frontend/microfrontend/src/index.js b/frontend/microfrontend/src/index.js
new file mode 100644
index 00000000..8b0459b8
--- /dev/null
+++ b/frontend/microfrontend/src/index.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import './index.css';
+import App from './components/App';
+import * as serviceWorker from './serviceWorker';
+import { BrowserRouter } from "react-router-dom";
+
+ReactDOM.render(
+
+
+
+
+ ,
+ document.getElementById('root')
+);
+
+// If you want your app to work offline and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: https://bit.ly/CRA-PWA
+serviceWorker.unregister();
diff --git a/frontend/microfrontend/src/logo.svg b/frontend/microfrontend/src/logo.svg
new file mode 100644
index 00000000..6b60c104
--- /dev/null
+++ b/frontend/microfrontend/src/logo.svg
@@ -0,0 +1,7 @@
+
diff --git a/frontend/microfrontend/src/serviceWorker.js b/frontend/microfrontend/src/serviceWorker.js
new file mode 100644
index 00000000..b04b771a
--- /dev/null
+++ b/frontend/microfrontend/src/serviceWorker.js
@@ -0,0 +1,141 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read https://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+ window.location.hostname === 'localhost' ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === '[::1]' ||
+ // 127.0.0.0/8 are considered localhost for IPv4.
+ window.location.hostname.match(
+ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+ )
+);
+
+export function register(config) {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config);
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log(
+ 'This web app is being served cache-first by a service ' +
+ 'worker. To learn more, visit https://bit.ly/CRA-PWA'
+ );
+ });
+ } else {
+ // Is not localhost. Just register service worker
+ registerValidSW(swUrl, config);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl, config) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ if (installingWorker == null) {
+ return;
+ }
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the updated precached content has been fetched,
+ // but the previous service worker will still serve the older
+ // content until all client tabs are closed.
+ console.log(
+ 'New content is available and will be used when all ' +
+ 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
+ );
+
+ // Execute callback
+ if (config && config.onUpdate) {
+ config.onUpdate(registration);
+ }
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.');
+
+ // Execute callback
+ if (config && config.onSuccess) {
+ config.onSuccess(registration);
+ }
+ }
+ }
+ };
+ };
+ })
+ .catch(error => {
+ console.error('Error during service worker registration:', error);
+ });
+}
+
+function checkValidServiceWorker(swUrl, config) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl, {
+ headers: { 'Service-Worker': 'script' },
+ })
+ .then(response => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ const contentType = response.headers.get('content-type');
+ if (
+ response.status === 404 ||
+ (contentType != null && contentType.indexOf('javascript') === -1)
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl, config);
+ }
+ })
+ .catch(() => {
+ console.log(
+ 'No internet connection found. App is running in offline mode.'
+ );
+ });
+}
+
+export function unregister() {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready
+ .then(registration => {
+ registration.unregister();
+ })
+ .catch(error => {
+ console.error(error.message);
+ });
+ }
+}
diff --git a/frontend/microfrontend/src/setupTests.js b/frontend/microfrontend/src/setupTests.js
new file mode 100644
index 00000000..74b1a275
--- /dev/null
+++ b/frontend/microfrontend/src/setupTests.js
@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom/extend-expect';
diff --git a/frontend/microfrontend/webpack.config.js b/frontend/microfrontend/webpack.config.js
new file mode 100644
index 00000000..1dfbf303
--- /dev/null
+++ b/frontend/microfrontend/webpack.config.js
@@ -0,0 +1,93 @@
+const HtmlWebPackPlugin = require("html-webpack-plugin");
+const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
+const path = require('path');
+const Dotenv = require('dotenv-webpack');
+
+const deps = require("./package.json").dependencies;
+
+const printCompilationMessage = require('./compilation.config.js');
+
+module.exports = (_, argv) => ({
+ output: {
+ publicPath: "http://localhost:8080/",
+ },
+
+ resolve: {
+ extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
+ },
+
+ devServer: {
+ port: 8080,
+ historyApiFallback: true,
+ watchFiles: [path.resolve(__dirname, 'src')],
+ onListening: function (devServer) {
+ const port = devServer.server.address().port
+
+ printCompilationMessage('compiling', port)
+
+ devServer.compiler.hooks.done.tap('OutputMessagePlugin', (stats) => {
+ setImmediate(() => {
+ if (stats.hasErrors()) {
+ printCompilationMessage('failure', port)
+ } else {
+ printCompilationMessage('success', port)
+ }
+ })
+ })
+ }
+ },
+
+ module: {
+ rules: [
+ {
+ test: /\.m?js/,
+ type: "javascript/auto",
+ resolve: {
+ fullySpecified: false,
+ },
+ },
+ {
+ test: /\.(css|s[ac]ss)$/i,
+ use: ["style-loader", "css-loader", "postcss-loader"],
+ },
+ {
+ test: /\.(ts|tsx|js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ },
+ },
+ ],
+ },
+
+ plugins: [
+ new ModuleFederationPlugin({
+ // имя корневого приложения
+ name: "root",
+ filename: "remoteEntry.js",
+ // подключение микрофронтов
+ remotes: {
+ 'auth':'auth@http://localhost:8081/remoteEntry.js',
+ 'gallery':'gallery@http://localhost:8082/remoteEntry.js',
+ 'profile':'profile@http://localhost:8083/remoteEntry.js'
+ },
+ exposes: {},
+ // шаренные либы
+ shared: {
+ ...deps,
+ react: {
+ singleton: true,
+ requiredVersion: deps.react,
+ },
+ "react-dom": {
+ singleton: true,
+ requiredVersion: deps["react-dom"],
+ },
+ },
+ }),
+ new HtmlWebPackPlugin({
+ template: "./src/index.html",
+ }),
+ new Dotenv()
+ ],
+});
\ No newline at end of file