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 ( +
+
+
+ +
+ +

{text}

+
+
+
+
+ ); +} + +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 ? 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 ( +
    +
    +
    + +

    {title}

    + {children} + +
    +
    +
    + ); +} + +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 ( +
    +
    +
    + +

    {title}

    + {children} + +
    +
    +
    + ); +} + +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 ( +
    +

    + © 2021 Mesto Russia +

    +
    + ); +} + +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 ( +
    + Логотип проекта Mesto + +
    +

    { email }

    + +
    +
    + + Войти + + + Регистрация + +
    + ) +} + +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