From 4c451e9e90dc50ad53e4d250e529d32a88658a05 Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Mon, 2 Jun 2025 18:43:26 +0400 Subject: [PATCH 01/14] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B8=20pretti?= =?UTF-8?q?er,=20ESlinit,=20Webpack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .prettierrc.yaml | 2 + api.js | 102 +- components/add-post-page-component.js | 24 +- components/auth-page-component.js | 264 +-- components/header-component.js | 80 +- components/loading-page-component.js | 36 +- components/posts-page-component.js | 52 +- components/upload-image-component.js | 88 +- eslint.config.mjs | 19 + helpers.js | 14 +- index.html | 24 +- index.js | 209 +- package-lock.json | 2573 +++++++++++++++++++++++++ package.json | 30 + routes.js | 10 +- webpack.config.js | 7 + 17 files changed, 3089 insertions(+), 447 deletions(-) create mode 100644 .gitignore create mode 100644 .prettierrc.yaml create mode 100644 eslint.config.mjs create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..c85804c96 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/dist/ \ No newline at end of file diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 000000000..0fe5deb5b --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,2 @@ +tabWidth: 4 +singleQuote: true diff --git a/api.js b/api.js index 123a9e3b6..9239c41a5 100644 --- a/api.js +++ b/api.js @@ -1,69 +1,69 @@ // Замени на свой, чтобы получить независимый от других набор данных. // "боевая" версия инстапро лежит в ключе prod -const personalKey = "prod"; -const baseHost = "https://webdev-hw-api.vercel.app"; +const personalKey = 'prod'; +const baseHost = 'https://webdev-hw-api.vercel.app'; const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`; export function getPosts({ token }) { - return fetch(postsHost, { - method: "GET", - headers: { - Authorization: token, - }, - }) - .then((response) => { - if (response.status === 401) { - throw new Error("Нет авторизации"); - } - - return response.json(); + return fetch(postsHost, { + method: 'GET', + headers: { + Authorization: token, + }, }) - .then((data) => { - return data.posts; - }); + .then((response) => { + if (response.status === 401) { + throw new Error('Нет авторизации'); + } + + return response.json(); + }) + .then((data) => { + return data.posts; + }); } export function registerUser({ login, password, name, imageUrl }) { - return fetch(baseHost + "/api/user", { - method: "POST", - body: JSON.stringify({ - login, - password, - name, - imageUrl, - }), - }).then((response) => { - if (response.status === 400) { - throw new Error("Такой пользователь уже существует"); - } - return response.json(); - }); + return fetch(baseHost + '/api/user', { + method: 'POST', + body: JSON.stringify({ + login, + password, + name, + imageUrl, + }), + }).then((response) => { + if (response.status === 400) { + throw new Error('Такой пользователь уже существует'); + } + return response.json(); + }); } export function loginUser({ login, password }) { - return fetch(baseHost + "/api/user/login", { - method: "POST", - body: JSON.stringify({ - login, - password, - }), - }).then((response) => { - if (response.status === 400) { - throw new Error("Неверный логин или пароль"); - } - return response.json(); - }); + return fetch(baseHost + '/api/user/login', { + method: 'POST', + body: JSON.stringify({ + login, + password, + }), + }).then((response) => { + if (response.status === 400) { + throw new Error('Неверный логин или пароль'); + } + return response.json(); + }); } // Загружает картинку в облако, возвращает url загруженной картинки export function uploadImage({ file }) { - const data = new FormData(); - data.append("file", file); + const data = new FormData(); + data.append('file', file); - return fetch(baseHost + "/api/upload/image", { - method: "POST", - body: data, - }).then((response) => { - return response.json(); - }); + return fetch(baseHost + '/api/upload/image', { + method: 'POST', + body: data, + }).then((response) => { + return response.json(); + }); } diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 8277f093b..99038fc85 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -1,7 +1,7 @@ export function renderAddPostPageComponent({ appEl, onAddPostClick }) { - const render = () => { - // @TODO: Реализовать страницу добавления поста - const appHtml = ` + const render = () => { + // @TODO: Реализовать страницу добавления поста + const appHtml = `
Cтраница добавления поста @@ -9,15 +9,15 @@ export function renderAddPostPageComponent({ appEl, onAddPostClick }) {
`; - appEl.innerHTML = appHtml; + appEl.innerHTML = appHtml; - document.getElementById("add-button").addEventListener("click", () => { - onAddPostClick({ - description: "Описание картинки", - imageUrl: "https://image.png", - }); - }); - }; + document.getElementById('add-button').addEventListener('click', () => { + onAddPostClick({ + description: 'Описание картинки', + imageUrl: 'https://image.png', + }); + }); + }; - render(); + render(); } diff --git a/components/auth-page-component.js b/components/auth-page-component.js index 0e93530ec..3db0490a9 100644 --- a/components/auth-page-component.js +++ b/components/auth-page-component.js @@ -1,6 +1,6 @@ -import { loginUser, registerUser } from "../api.js"; -import { renderHeaderComponent } from "./header-component.js"; -import { renderUploadImageComponent } from "./upload-image-component.js"; +import { loginUser, registerUser } from '../api.js'; +import { renderHeaderComponent } from './header-component.js'; +import { renderUploadImageComponent } from './upload-image-component.js'; /** * Компонент страницы авторизации. @@ -12,57 +12,57 @@ import { renderUploadImageComponent } from "./upload-image-component.js"; * Принимает объект пользователя в качестве аргумента. */ export function renderAuthPageComponent({ appEl, setUser }) { - /** - * Флаг, указывающий текущий режим формы. - * Если `true`, форма находится в режиме входа. Если `false`, в режиме регистрации. - * @type {boolean} - */ - let isLoginMode = true; - - /** - * URL изображения, загруженного пользователем при регистрации. - * Используется только в режиме регистрации. - * @type {string} - */ - let imageUrl = ""; - - /** - * Рендерит форму авторизации или регистрации. - * В зависимости от значения `isLoginMode` отображает соответствующий интерфейс. - */ - const renderForm = () => { - const appHtml = ` + /** + * Флаг, указывающий текущий режим формы. + * Если `true`, форма находится в режиме входа. Если `false`, в режиме регистрации. + * @type {boolean} + */ + let isLoginMode = true; + + /** + * URL изображения, загруженного пользователем при регистрации. + * Используется только в режиме регистрации. + * @type {string} + */ + let imageUrl = ''; + + /** + * Рендерит форму авторизации или регистрации. + * В зависимости от значения `isLoginMode` отображает соответствующий интерфейс. + */ + const renderForm = () => { + const appHtml = `

${ - isLoginMode - ? "Вход в Instapro" - : "Регистрация в Instapro" + isLoginMode + ? 'Вход в Instapro' + : 'Регистрация в Instapro' }

${ - !isLoginMode - ? ` + !isLoginMode + ? `
` - : "" + : '' }
@@ -70,103 +70,111 @@ export function renderAuthPageComponent({ appEl, setUser }) {
`; - appEl.innerHTML = appHtml; - - /** - * Устанавливает сообщение об ошибке в форме. - * @param {string} message - Текст сообщения об ошибке. - */ - const setError = (message) => { - appEl.querySelector(".form-error").textContent = message; - }; - - // Рендерим заголовок страницы - renderHeaderComponent({ - element: document.querySelector(".header-container"), - }); - - // Если режим регистрации, рендерим компонент загрузки изображения - const uploadImageContainer = appEl.querySelector(".upload-image-container"); - if (uploadImageContainer) { - renderUploadImageComponent({ - element: uploadImageContainer, - onImageUrlChange(newImageUrl) { - imageUrl = newImageUrl; - }, - }); - } - - // Обработка клика на кнопку входа/регистрации - document.getElementById("login-button").addEventListener("click", () => { - setError(""); - - if (isLoginMode) { - // Обработка входа - const login = document.getElementById("login-input").value; - const password = document.getElementById("password-input").value; - - if (!login) { - alert("Введите логин"); - return; - } - - if (!password) { - alert("Введите пароль"); - return; - } - - loginUser({ login, password }) - .then((user) => { - setUser(user.user); - }) - .catch((error) => { - console.warn(error); - setError(error.message); - }); - } else { - // Обработка регистрации - const login = document.getElementById("login-input").value; - const name = document.getElementById("name-input").value; - const password = document.getElementById("password-input").value; - - if (!name) { - alert("Введите имя"); - return; - } - - if (!login) { - alert("Введите логин"); - return; - } - - if (!password) { - alert("Введите пароль"); - return; + appEl.innerHTML = appHtml; + + /** + * Устанавливает сообщение об ошибке в форме. + * @param {string} message - Текст сообщения об ошибке. + */ + const setError = (message) => { + appEl.querySelector('.form-error').textContent = message; + }; + + // Рендерим заголовок страницы + renderHeaderComponent({ + element: document.querySelector('.header-container'), + }); + + // Если режим регистрации, рендерим компонент загрузки изображения + const uploadImageContainer = appEl.querySelector( + '.upload-image-container', + ); + if (uploadImageContainer) { + renderUploadImageComponent({ + element: uploadImageContainer, + onImageUrlChange(newImageUrl) { + imageUrl = newImageUrl; + }, + }); } - if (!imageUrl) { - alert("Не выбрана фотография"); - return; - } + // Обработка клика на кнопку входа/регистрации + document + .getElementById('login-button') + .addEventListener('click', () => { + setError(''); + + if (isLoginMode) { + // Обработка входа + const login = document.getElementById('login-input').value; + const password = + document.getElementById('password-input').value; + + if (!login) { + alert('Введите логин'); + return; + } + + if (!password) { + alert('Введите пароль'); + return; + } + + loginUser({ login, password }) + .then((user) => { + setUser(user.user); + }) + .catch((error) => { + console.warn(error); + setError(error.message); + }); + } else { + // Обработка регистрации + const login = document.getElementById('login-input').value; + const name = document.getElementById('name-input').value; + const password = + document.getElementById('password-input').value; + + if (!name) { + alert('Введите имя'); + return; + } + + if (!login) { + alert('Введите логин'); + return; + } + + if (!password) { + alert('Введите пароль'); + return; + } + + if (!imageUrl) { + alert('Не выбрана фотография'); + return; + } + + registerUser({ login, password, name, imageUrl }) + .then((user) => { + setUser(user.user); + }) + .catch((error) => { + console.warn(error); + setError(error.message); + }); + } + }); + + // Обработка переключения режима (вход ↔ регистрация) + document + .getElementById('toggle-button') + .addEventListener('click', () => { + isLoginMode = !isLoginMode; + renderForm(); // Перерисовываем форму с новым режимом + }); + }; - registerUser({ login, password, name, imageUrl }) - .then((user) => { - setUser(user.user); - }) - .catch((error) => { - console.warn(error); - setError(error.message); - }); - } - }); - - // Обработка переключения режима (вход ↔ регистрация) - document.getElementById("toggle-button").addEventListener("click", () => { - isLoginMode = !isLoginMode; - renderForm(); // Перерисовываем форму с новым режимом - }); - }; - - // Инициализация формы - renderForm(); + // Инициализация формы + renderForm(); } diff --git a/components/header-component.js b/components/header-component.js index e733a39dc..b1019c1ec 100644 --- a/components/header-component.js +++ b/components/header-component.js @@ -1,63 +1,63 @@ -import { goToPage, logout, user } from "../index.js"; -import { ADD_POSTS_PAGE, AUTH_PAGE, POSTS_PAGE } from "../routes.js"; +import { goToPage, logout, user } from '../index.js'; +import { ADD_POSTS_PAGE, AUTH_PAGE, POSTS_PAGE } from '../routes.js'; /** * Компонент заголовка страницы. * Этот компонент отображает шапку страницы с логотипом, кнопкой добавления постов/входа и кнопкой выхода (если пользователь авторизован). - * + * * @param {HTMLElement} params.element - HTML-элемент, в который будет рендериться заголовок. * @returns {HTMLElement} Возвращает элемент заголовка после рендеринга. */ export function renderHeaderComponent({ element }) { - /** - * Рендерит содержимое заголовка. - */ - element.innerHTML = ` + /** + * Рендерит содержимое заголовка. + */ + element.innerHTML = ` `; - /** - * Обработчик клика по кнопке "Добавить пост"/"Войти". - * Если пользователь авторизован, перенаправляет на страницу добавления постов. - * Если пользователь не авторизован, перенаправляет на страницу авторизации. - */ - element - .querySelector(".add-or-login-button") - .addEventListener("click", () => { - if (user) { - goToPage(ADD_POSTS_PAGE); - } else { - goToPage(AUTH_PAGE); - } - }); + /** + * Обработчик клика по кнопке "Добавить пост"/"Войти". + * Если пользователь авторизован, перенаправляет на страницу добавления постов. + * Если пользователь не авторизован, перенаправляет на страницу авторизации. + */ + element + .querySelector('.add-or-login-button') + .addEventListener('click', () => { + if (user) { + goToPage(ADD_POSTS_PAGE); + } else { + goToPage(AUTH_PAGE); + } + }); - /** - * Обработчик клика по логотипу. - * Перенаправляет на страницу с постами. - */ - element.querySelector(".logo").addEventListener("click", () => { - goToPage(POSTS_PAGE); - }); + /** + * Обработчик клика по логотипу. + * Перенаправляет на страницу с постами. + */ + element.querySelector('.logo').addEventListener('click', () => { + goToPage(POSTS_PAGE); + }); - /** - * Обработчик клика по кнопке "Выйти". - * Если кнопка существует (т.е. пользователь авторизован), вызывает функцию `logout`. - */ - element.querySelector(".logout-button")?.addEventListener("click", logout); + /** + * Обработчик клика по кнопке "Выйти". + * Если кнопка существует (т.е. пользователь авторизован), вызывает функцию `logout`. + */ + element.querySelector('.logout-button')?.addEventListener('click', logout); - return element; + return element; } diff --git a/components/loading-page-component.js b/components/loading-page-component.js index c93522573..f42f40748 100644 --- a/components/loading-page-component.js +++ b/components/loading-page-component.js @@ -1,20 +1,20 @@ -import { renderHeaderComponent } from "./header-component.js"; +import { renderHeaderComponent } from './header-component.js'; /** * Компонент страницы загрузки. * Этот компонент отображает страницу с индикатором загрузки и заголовком. * Используется для отображения промежуточного состояния, пока выполняется загрузка данных или другой процесс. - * + * * @param {HTMLElement} params.appEl - Корневой элемент приложения, в который будет рендериться страница загрузки. * @param {Object} params.user - Объект пользователя, содержащий данные о текущем авторизованном пользователе (если он есть). * @param {Function} params.goToPage - Функция для навигации по страницам. */ export function renderLoadingPageComponent({ appEl, user, goToPage }) { - /** - * HTML-разметка страницы загрузки. - * Содержит контейнер заголовка и индикатор загрузки. - */ - const appHtml = ` + /** + * HTML-разметка страницы загрузки. + * Содержит контейнер заголовка и индикатор загрузки. + */ + const appHtml = `
@@ -22,16 +22,16 @@ export function renderLoadingPageComponent({ appEl, user, goToPage }) {
`; - // Устанавливаем разметку в корневой элемент приложения - appEl.innerHTML = appHtml; + // Устанавливаем разметку в корневой элемент приложения + appEl.innerHTML = appHtml; - /** - * Рендеринг заголовка с использованием компонента `renderHeaderComponent`. - * Передаются данные пользователя и функция навигации. - */ - renderHeaderComponent({ - user, - element: document.querySelector(".header-container"), - goToPage, - }); + /** + * Рендеринг заголовка с использованием компонента `renderHeaderComponent`. + * Передаются данные пользователя и функция навигации. + */ + renderHeaderComponent({ + user, + element: document.querySelector('.header-container'), + goToPage, + }); } diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 662ccdbdc..86df890b8 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -1,19 +1,20 @@ -import { USER_POSTS_PAGE } from "../routes.js"; -import { renderHeaderComponent } from "./header-component.js"; -import { posts, goToPage } from "../index.js"; +import { USER_POSTS_PAGE } from '../routes.js'; +import { renderHeaderComponent } from './header-component.js'; +import { posts, goToPage } from '../index.js'; export function renderPostsPageComponent({ appEl }) { - // @TODO: реализовать рендер постов из api - console.log("Актуальный список постов:", posts); + // @TODO: реализовать рендер постов из api + console.log('Актуальный список постов:', posts); - /** - * @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" - * можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow - */ - const appHtml = ` + /** + * @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" + * можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow + */ + const appHtml = `
    +
  • @@ -38,13 +39,12 @@ export function renderPostsPageComponent({ appEl }) { 19 минут назад

  • +
  • Варварва Н.

    - -
    @@ -64,13 +64,12 @@ export function renderPostsPageComponent({ appEl }) { 3 часа назад

  • +
  • Варварва Н.

    -
    - - +
@@ -90,20 +89,21 @@ export function renderPostsPageComponent({ appEl }) { 8 дней назад

+
`; - appEl.innerHTML = appHtml; + appEl.innerHTML = appHtml; - renderHeaderComponent({ - element: document.querySelector(".header-container"), - }); - - for (let userEl of document.querySelectorAll(".post-header")) { - userEl.addEventListener("click", () => { - goToPage(USER_POSTS_PAGE, { - userId: userEl.dataset.userId, - }); + renderHeaderComponent({ + element: document.querySelector('.header-container'), }); - } + + for (let userEl of document.querySelectorAll('.post-header')) { + userEl.addEventListener('click', () => { + goToPage(USER_POSTS_PAGE, { + userId: userEl.dataset.userId, + }); + }); + } } diff --git a/components/upload-image-component.js b/components/upload-image-component.js index e2cdf2018..f5df68c02 100644 --- a/components/upload-image-component.js +++ b/components/upload-image-component.js @@ -1,4 +1,4 @@ -import { uploadImage } from "../api.js"; +import { uploadImage } from '../api.js'; /** * Компонент загрузки изображения. @@ -10,30 +10,30 @@ import { uploadImage } from "../api.js"; * Принимает один аргумент - новый URL изображения или пустую строку. */ export function renderUploadImageComponent({ element, onImageUrlChange }) { - /** - * URL текущего изображения. - * Изначально пуст, пока пользователь не загрузит изображение. - * @type {string} - */ - let imageUrl = ""; + /** + * URL текущего изображения. + * Изначально пуст, пока пользователь не загрузит изображение. + * @type {string} + */ + let imageUrl = ''; - /** - * Функция рендеринга компонента. - * Отображает интерфейс компонента в зависимости от состояния: - * либо форма выбора файла, либо превью загруженного изображения с кнопкой замены. - */ - const render = () => { - element.innerHTML = ` + /** + * Функция рендеринга компонента. + * Отображает интерфейс компонента в зависимости от состояния: + * либо форма выбора файла, либо превью загруженного изображения с кнопкой замены. + */ + const render = () => { + element.innerHTML = `
${ - imageUrl - ? ` + imageUrl + ? `
Загруженное изображение
` - : ` + : `
diff --git a/components/posts-page-component.js b/components/posts-page-component.js index b62b5491c..beb248220 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -5,12 +5,12 @@ import { formatDistanceToNow } from 'date-fns'; import { ru } from 'date-fns/locale'; export function renderPostsPageComponent({ appEl }) { - // @TODO: реализовать рендер постов из api - console.log('Актуальный список постов:', posts); + let appHtml = ''; - const appHtml = posts - .map((post) => { - return ` + if (posts.length > 0) { + appHtml = posts + .map((post) => { + return `
`; - }) - .join(''); + }) + .join(''); + } else { + appHtml = ` +
+
+
+ `; + } appEl.innerHTML = appHtml; diff --git a/index.js b/index.js index db6be61d7..777004859 100644 --- a/index.js +++ b/index.js @@ -110,9 +110,8 @@ const renderApp = () => { if (page === ADD_POSTS_PAGE) { return renderAddPostPageComponent({ appEl, - onAddPostClick({ description, imageUrl }) { - // @TODO: реализовать добавление поста в API - console.log('Добавляю пост...', { description, imageUrl }); + token: getToken(), + onAddPostClick() { goToPage(POSTS_PAGE); }, }); diff --git a/ui-kit.css b/ui-kit.css index 2e050fe70..f5334bf01 100644 --- a/ui-kit.css +++ b/ui-kit.css @@ -4,159 +4,162 @@ */ @font-face { - font-family: "StratosSkyeng"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("./assets/fonts/stratosskyengweb-regular.woff2") format("woff2"); + font-family: 'StratosSkyeng'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('./assets/fonts/stratosskyengweb-regular.woff2') format('woff2'); } @font-face { - font-family: "StratosSkyeng"; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url("./assets/fonts/stratosskyengweb-medium.woff2") format("woff2"); + font-family: 'StratosSkyeng'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('./assets/fonts/stratosskyengweb-medium.woff2') format('woff2'); } html { - /* скрывает скролбары в firefox */ - scrollbar-width: none; + /* скрывает скролбары в firefox */ + scrollbar-width: none; } body { - margin: 0; - padding: 0; - font-family: StratosSkyeng; - overflow: auto; - font-size: 14px; - line-height: 18px; + margin: 0; + padding: 0; + font-family: StratosSkyeng; + overflow: auto; + font-size: 14px; + line-height: 18px; } /* скрывает скролбары в chrome */ body::-webkit-scrollbar { - display: none; + display: none; } p { - margin: 0; + margin: 0; } .button, .secondary-button { - display: block; - padding: 10px 13px 13px; - font-size: 18px; - line-height: 22px; - border-radius: 5px; - background-color: #565eef; - color: #ffffff; - border: none; + display: block; + padding: 10px 13px 13px; + font-size: 18px; + line-height: 22px; + border-radius: 5px; + background-color: #565eef; + color: #ffffff; + border: none; } .link-button { - display: inline; - border: 0; - padding: 0; - background: none; - color: #565eef; - cursor: pointer; + display: inline; + border: 0; + padding: 0; + background: none; + color: #565eef; + cursor: pointer; } .button:hover { - background-color: #6d73ff; + background-color: #6d73ff; } -.button[disabled="true"], -.secondary-button[disabled="true"] { - background-color: #f4f5f6; - color: rgba(153, 153, 153, 0.6); - pointer-events: none; +.button[disabled='true'], +.secondary-button[disabled='true'] { + background-color: #f4f5f6; + color: rgba(153, 153, 153, 0.6); + pointer-events: none; } .button:active { - background-color: #4f56ce; + background-color: #4f56ce; } .secondary-button { - background-color: #edecff; - color: #565eef; + background-color: #edecff; + color: #565eef; } .secondary-button:hover { - background-color: #f2eeff; + background-color: #f2eeff; } .secondary-button:active { - background-color: #d8d7ff; + background-color: #d8d7ff; } .input { - height: 36px; - box-sizing: border-box; - padding: 15px; - font-size: 14px; - line-height: 18px; - color: rgb(4, 18, 27); - border: none; - border-radius: 5px; - background-color: #e8e8e8; + height: 36px; + box-sizing: border-box; + padding: 15px; + font-size: 14px; + line-height: 18px; + color: rgb(4, 18, 27); + border: none; + border-radius: 5px; + background-color: #e8e8e8; +} +.input_area { + height: auto; } .input .-error { - background-color: #fff; + background-color: #fff; } .input:focus { - background-color: #fff; + background-color: #fff; } .textarea { - padding: 15px; - resize: none; - width: 100%; - height: 100px; + padding: 15px; + resize: none; + width: 100%; + height: 100px; } strong { - font-weight: 500; + font-weight: 500; } /** Анимация загрузки **/ .loader { - display: inline-block; - position: relative; - width: 80px; - height: 80px; + display: inline-block; + position: relative; + width: 80px; + height: 80px; } .loader div { - display: inline-block; - position: absolute; - left: 8px; - width: 16px; - background: #000; - animation: loader 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite; + display: inline-block; + position: absolute; + left: 8px; + width: 16px; + background: #000; + animation: loader 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite; } .loader div:nth-child(1) { - left: 8px; - animation-delay: -0.24s; + left: 8px; + animation-delay: -0.24s; } .loader div:nth-child(2) { - left: 32px; - animation-delay: -0.12s; + left: 32px; + animation-delay: -0.12s; } .loader div:nth-child(3) { - left: 56px; - animation-delay: 0; + left: 56px; + animation-delay: 0; } @keyframes loader { - 0% { - top: 8px; - height: 64px; - } - 50%, - 100% { - top: 24px; - height: 32px; - } + 0% { + top: 8px; + height: 64px; + } + 50%, + 100% { + top: 24px; + height: 32px; + } } From ded2b4102c88d3447ac3367cbaecc58b8d8f94ec Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Tue, 3 Jun 2025 20:13:51 +0400 Subject: [PATCH 04/14] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BE=D1=82=D1=80=D0=B8=D1=81=D0=BE=D0=B2=D0=BA=D1=83?= =?UTF-8?q?=20=D0=BF=D0=BE=D1=81=D1=82=D0=BE=D0=B2=20=D0=BE=D0=BF=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BB=D1=91=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.js | 22 ++- components/add-post-page-component.js | 10 +- components/user-post-page-component.js | 62 +++++++ index.js | 38 ++++- styles.css | 216 +++++++++++++------------ 5 files changed, 229 insertions(+), 119 deletions(-) create mode 100644 components/user-post-page-component.js diff --git a/api.js b/api.js index 2bcd844bf..19c31ea36 100644 --- a/api.js +++ b/api.js @@ -23,7 +23,27 @@ export function getPosts({ token }) { }); } -export function addPost(token, description, imageUrl) { +export function getUserPosts({ token }, userId) { + return fetch(`${postsHost}/user-posts/${userId}`, { + method: 'GET', + headers: { + Authorization: token, + }, + }) + .then((response) => { + if (response.status === 401) { + throw new Error('Нет авторизации'); + } + + return response.json(); + }) + .then((data) => { + console.log(data.posts); + return data.posts; + }); +} + +export function addPost({ token }, description, imageUrl) { return fetch(postsHost, { method: 'POST', headers: { diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index c21e0091e..8f83dac41 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -1,8 +1,7 @@ -import { addPost } from '../api.js'; import { renderHeaderComponent } from './header-component.js'; import { renderUploadImageComponent } from './upload-image-component.js'; -export function renderAddPostPageComponent({ appEl, token, onAddPostClick }) { +export function renderAddPostPageComponent({ appEl, onAddPostClick }) { let imageUrl = ''; const render = () => { @@ -60,9 +59,12 @@ export function renderAddPostPageComponent({ appEl, token, onAddPostClick }) { alert('Выберите фото'); return; } - addPost(token, description.value, imageUrl); + // addPost(token, description.value, imageUrl); - onAddPostClick(); + onAddPostClick({ + description: description.value, + imageUrl: imageUrl, + }); }); }; diff --git a/components/user-post-page-component.js b/components/user-post-page-component.js new file mode 100644 index 000000000..6a9f477ad --- /dev/null +++ b/components/user-post-page-component.js @@ -0,0 +1,62 @@ +import { renderHeaderComponent } from './header-component.js'; +import { posts } from '../index.js'; +import { formatDistanceToNow } from 'date-fns'; +import { ru } from 'date-fns/locale'; + +export function renderUserPostsPageComponent({ appEl }) { + window.scrollTo(0, 0); + + let appHtml = ''; + + if (posts.length > 0) { + appHtml = posts + .map((post) => { + return ` +
+
+
    + +
  • +
    + +

    ${post.user.name}

    +
    +
    + +
    +
    + +

    + Нравится: ${post.likes.length} +

    +
    +

    + ${post.user.name} + ${post.description} +

    + +
  • + +
+
+ `; + }) + .join(''); + } else { + appHtml = ` +
+
+
+ `; + } + + appEl.innerHTML = appHtml; + + renderHeaderComponent({ + element: document.querySelector('.header-container'), + }); +} diff --git a/index.js b/index.js index 777004859..9a2e999d7 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import { getPosts } from './api.js'; +import { addPost, getPosts, getUserPosts } from './api.js'; import { renderAddPostPageComponent } from './components/add-post-page-component.js'; import { renderAuthPageComponent } from './components/auth-page-component.js'; import { @@ -15,6 +15,7 @@ import { removeUserFromLocalStorage, saveUserToLocalStorage, } from './helpers.js'; +import { renderUserPostsPageComponent } from './components/user-post-page-component.js'; export let user = getUserFromLocalStorage(); export let page = null; @@ -68,10 +69,24 @@ export const goToPage = (newPage, data) => { if (newPage === USER_POSTS_PAGE) { // @@TODO: реализовать получение постов юзера из API - console.log('Открываю страницу пользователя: ', data.userId); - page = USER_POSTS_PAGE; - posts = []; - return renderApp(); + page = LOADING_PAGE; + renderApp(); + + return getUserPosts({ token: getToken() }, data.userId) + .then((newPosts) => { + page = USER_POSTS_PAGE; + posts = newPosts; + renderApp(); + }) + .catch((error) => { + console.error(error); + goToPage(POSTS_PAGE); + }); + + // console.log('Открываю страницу пользователя: ', data.userId); + // page = USER_POSTS_PAGE; + // posts = []; + // return renderApp(); } page = newPage; @@ -111,7 +126,9 @@ const renderApp = () => { return renderAddPostPageComponent({ appEl, token: getToken(), - onAddPostClick() { + onAddPostClick({ description, imageUrl }) { + // @TODO: реализовать добавление поста в API + addPost({ token: getToken() }, description, imageUrl); goToPage(POSTS_PAGE); }, }); @@ -125,8 +142,13 @@ const renderApp = () => { if (page === USER_POSTS_PAGE) { // @TODO: реализовать страницу с фотографиями отдельного пользвателя - appEl.innerHTML = 'Здесь будет страница фотографий пользователя'; - return; + // appEl.innerHTML = 'Здесь будет страница фотографий пользователя'; + // return; + console.log('yes'); + + return renderUserPostsPageComponent({ + appEl, + }); } }; diff --git a/styles.css b/styles.css index 57bc08b16..550e78910 100644 --- a/styles.css +++ b/styles.css @@ -1,198 +1,202 @@ .page-container { - padding: 0 20px; + padding: 0 20px; } .page-header { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid #e0e0e0; - padding: 20px 16px; - margin-left: -20px; - margin-right: -20px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #e0e0e0; + padding: 20px 16px; + margin-left: -20px; + margin-right: -20px; } .logo { - margin: 0; - font-size: 28px; - cursor: pointer; - width: 130px; + margin: 0; + font-size: 28px; + cursor: pointer; + width: 130px; } .header-container { - margin-bottom: 8px; + margin-bottom: 8px; } .header-button { - padding: 0; - border: none; - background-color: transparent; - font-size: 14px; - line-height: 18px; - font-weight: 500; - cursor: pointer; + padding: 0; + border: none; + background-color: transparent; + font-size: 14px; + line-height: 18px; + font-weight: 500; + cursor: pointer; } .logout-button { - width: 130px; - text-align: right; + width: 130px; + text-align: right; } .posts { - display: flex; - flex-direction: column; - list-style: none; - padding: 0; - margin: 0; + display: flex; + flex-direction: column; + list-style: none; + padding: 0; + margin: 0; } .post-header { - display: flex; - flex-direction: row; - align-items: center; - margin-bottom: 10px; - cursor: pointer; + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 10px; + cursor: pointer; +} + +.post-header_user { + cursor: auto; } .post-header__user-image { - width: 40px; - height: 40px; - object-fit: cover; - border-radius: 40px; - margin-right: 10px; + width: 40px; + height: 40px; + object-fit: cover; + border-radius: 40px; + margin-right: 10px; } .post-image-container { - margin-left: -20px; - margin-right: -20px; - height: 500px; - display: flex; - justify-content: center; - background-color: #e0e0e0; + margin-left: -20px; + margin-right: -20px; + height: 500px; + display: flex; + justify-content: center; + background-color: #e0e0e0; } .post-image { - width: 100%; - height: 100%; - max-width: 500px; - object-fit: cover; + width: 100%; + height: 100%; + max-width: 500px; + object-fit: cover; } .post-likes { - display: flex; - flex-direction: row; - align-items: center; + display: flex; + flex-direction: row; + align-items: center; } .like-button { - border: none; - background-color: transparent; - padding: 8px; - padding-left: 0px; - padding-bottom: 5px; - cursor: pointer; + border: none; + background-color: transparent; + padding: 8px; + padding-left: 0px; + padding-bottom: 5px; + cursor: pointer; } .user-name { - font-weight: 500; + font-weight: 500; } .post-text { - font-size: 14px; - line-height: 18px; - margin-top: 5px; + font-size: 14px; + line-height: 18px; + margin-top: 5px; } .post-date { - color: #8a8a8a; + color: #8a8a8a; } .post + .post { - margin-top: 20px; + margin-top: 20px; } .posts-user-header { - display: flex; - flex-direction: row; - align-items: center; - margin-bottom: 12px; + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 12px; } .posts-user-header__user-image { - width: 70px; - height: 70px; - object-fit: cover; - border-radius: 40px; - margin-right: 10px; + width: 70px; + height: 70px; + object-fit: cover; + border-radius: 40px; + margin-right: 10px; } .posts-user-header__user-name { - font-size: 28px; - line-height: 35px; + font-size: 28px; + line-height: 35px; } .loading-page { - display: flex; - justify-content: center; - margin-top: 100px; + display: flex; + justify-content: center; + margin-top: 100px; } .add-post-sign { - background-image: url("data:image/svg+xml,%3Csvg id='Layer_1' data-name='Layer 1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 122.88 122.88'%3E%3Ctitle%3Eadd%3C/title%3E%3Cpath d='M61.44,0A61.46,61.46,0,1,1,18,18,61.25,61.25,0,0,1,61.44,0ZM88.6,56.82v9.24a4,4,0,0,1-4,4H70V84.62a4,4,0,0,1-4,4H56.82a4,4,0,0,1-4-4V70H38.26a4,4,0,0,1-4-4V56.82a4,4,0,0,1,4-4H52.84V38.26a4,4,0,0,1,4-4h9.24a4,4,0,0,1,4,4V52.84H84.62a4,4,0,0,1,4,4Zm8.83-31.37a50.92,50.92,0,1,0,14.9,36,50.78,50.78,0,0,0-14.9-36Z'/%3E%3C/svg%3E"); - height: 30px; - width: 30px; + background-image: url("data:image/svg+xml,%3Csvg id='Layer_1' data-name='Layer 1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 122.88 122.88'%3E%3Ctitle%3Eadd%3C/title%3E%3Cpath d='M61.44,0A61.46,61.46,0,1,1,18,18,61.25,61.25,0,0,1,61.44,0ZM88.6,56.82v9.24a4,4,0,0,1-4,4H70V84.62a4,4,0,0,1-4,4H56.82a4,4,0,0,1-4-4V70H38.26a4,4,0,0,1-4-4V56.82a4,4,0,0,1,4-4H52.84V38.26a4,4,0,0,1,4-4h9.24a4,4,0,0,1,4,4V52.84H84.62a4,4,0,0,1,4,4Zm8.83-31.37a50.92,50.92,0,1,0,14.9,36,50.78,50.78,0,0,0-14.9-36Z'/%3E%3C/svg%3E"); + height: 30px; + width: 30px; } .form { - display: flex; - flex-direction: column; - width: 100%; + display: flex; + flex-direction: column; + width: 100%; } .form-title { - font-size: 28px; - line-height: 35px; - text-align: center; + font-size: 28px; + line-height: 35px; + text-align: center; } .form-inputs { - display: flex; - flex-direction: column; - gap: 10px; + display: flex; + flex-direction: column; + gap: 10px; } .form-error { - color: red; + color: red; } .form-footer { - margin-top: 50px; - display: flex; - flex-direction: column; - gap: 10px; + margin-top: 50px; + display: flex; + flex-direction: column; + gap: 10px; } .form-footer-title { - text-align: center; + text-align: center; } .file-upload-image-conrainer { - display: flex; - align-items: center; + display: flex; + align-items: center; } .file-upload-image { - width: 100px; - height: 100px; - object-fit: cover; - margin-right: 10px; - border: 1px solid gray; - border-radius: 5px; + width: 100px; + height: 100px; + object-fit: cover; + margin-right: 10px; + border: 1px solid gray; + border-radius: 5px; } .file-upload-label { - display: inline-block; - box-sizing: border-box; - width: 100%; - text-align: center; + display: inline-block; + box-sizing: border-box; + width: 100%; + text-align: center; } From c7c98b0b1a43a121986c65888fed01c7b8c76da9 Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Wed, 4 Jun 2025 18:52:45 +0400 Subject: [PATCH 05/14] =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BB=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20=D0=BB=D0=B0=D0=B9=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.js | 22 +++++++++++++++++- components/add-post-page-component.js | 1 - components/posts-page-component.js | 31 +++++++++++++++++++++++--- components/user-post-page-component.js | 31 +++++++++++++++++++++++--- index.js | 30 ++++++++++--------------- 5 files changed, 89 insertions(+), 26 deletions(-) diff --git a/api.js b/api.js index 19c31ea36..275e6178c 100644 --- a/api.js +++ b/api.js @@ -38,7 +38,6 @@ export function getUserPosts({ token }, userId) { return response.json(); }) .then((data) => { - console.log(data.posts); return data.posts; }); } @@ -103,3 +102,24 @@ export function uploadImage({ file }) { return response.json(); }); } + +export function likePost({ token }, postId, isLiked) { + const flag = isLiked ? 'dislike' : 'like'; + + return fetch(`${postsHost}/${postId}/${flag}`, { + method: 'POST', + headers: { + Authorization: token, + }, + }) + .then((response) => { + if (response.status === 401) { + throw new Error('Нет авторизации'); + } + + return response.json(); + }) + .then((data) => { + return data.post; + }); +} diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 8f83dac41..4da9b67e7 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -59,7 +59,6 @@ export function renderAddPostPageComponent({ appEl, onAddPostClick }) { alert('Выберите фото'); return; } - // addPost(token, description.value, imageUrl); onAddPostClick({ description: description.value, diff --git a/components/posts-page-component.js b/components/posts-page-component.js index beb248220..bd03a13d3 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -3,13 +3,14 @@ import { renderHeaderComponent } from './header-component.js'; import { posts, goToPage } from '../index.js'; import { formatDistanceToNow } from 'date-fns'; import { ru } from 'date-fns/locale'; +import { likePost } from '../api.js'; -export function renderPostsPageComponent({ appEl }) { +export function renderPostsPageComponent({ appEl }, token) { let appHtml = ''; if (posts.length > 0) { appHtml = posts - .map((post) => { + .map((post, index) => { return `
@@ -24,7 +25,7 @@ export function renderPostsPageComponent({ appEl }) {
-

@@ -66,4 +67,28 @@ export function renderPostsPageComponent({ appEl }) { }); }); } + + for (let btnElem of document.querySelectorAll('.like-button')) { + btnElem.addEventListener('click', () => { + const index = btnElem.dataset.postIndex; + const img = btnElem.querySelector('img'); + const countLikesElem = + document.querySelectorAll('.post-likes-text')[index]; + + likePost({ token }, posts[index].id, posts[index].isLiked).then( + (data) => { + posts[index].likes = data.likes; + posts[index].isLiked = data.isLiked; + + if (posts[index].isLiked) { + img.src = './assets/images/like-active.svg'; + } else { + img.src = './assets/images/like-not-active.svg'; + } + + countLikesElem.innerHTML = `Нравится: ${posts[index].likes.length}`; + }, + ); + }); + } } diff --git a/components/user-post-page-component.js b/components/user-post-page-component.js index 6a9f477ad..6436e7223 100644 --- a/components/user-post-page-component.js +++ b/components/user-post-page-component.js @@ -2,15 +2,16 @@ import { renderHeaderComponent } from './header-component.js'; import { posts } from '../index.js'; import { formatDistanceToNow } from 'date-fns'; import { ru } from 'date-fns/locale'; +import { likePost } from '../api.js'; -export function renderUserPostsPageComponent({ appEl }) { +export function renderUserPostsPageComponent({ appEl }, token) { window.scrollTo(0, 0); let appHtml = ''; if (posts.length > 0) { appHtml = posts - .map((post) => { + .map((post, index) => { return `

@@ -25,7 +26,7 @@ export function renderUserPostsPageComponent({ appEl }) {
-

@@ -59,4 +60,28 @@ export function renderUserPostsPageComponent({ appEl }) { renderHeaderComponent({ element: document.querySelector('.header-container'), }); + + for (let btnElem of document.querySelectorAll('.like-button')) { + btnElem.addEventListener('click', () => { + const index = btnElem.dataset.postIndex; + const img = btnElem.querySelector('img'); + const countLikesElem = + document.querySelectorAll('.post-likes-text')[index]; + + likePost({ token }, posts[index].id, posts[index].isLiked).then( + (data) => { + posts[index].likes = data.likes; + posts[index].isLiked = data.isLiked; + + if (posts[index].isLiked) { + img.src = './assets/images/like-active.svg'; + } else { + img.src = './assets/images/like-not-active.svg'; + } + + countLikesElem.innerHTML = `Нравится: ${posts[index].likes.length}`; + }, + ); + }); + } } diff --git a/index.js b/index.js index 9a2e999d7..1d754695e 100644 --- a/index.js +++ b/index.js @@ -68,7 +68,6 @@ export const goToPage = (newPage, data) => { } if (newPage === USER_POSTS_PAGE) { - // @@TODO: реализовать получение постов юзера из API page = LOADING_PAGE; renderApp(); @@ -82,11 +81,6 @@ export const goToPage = (newPage, data) => { console.error(error); goToPage(POSTS_PAGE); }); - - // console.log('Открываю страницу пользователя: ', data.userId); - // page = USER_POSTS_PAGE; - // posts = []; - // return renderApp(); } page = newPage; @@ -127,7 +121,6 @@ const renderApp = () => { appEl, token: getToken(), onAddPostClick({ description, imageUrl }) { - // @TODO: реализовать добавление поста в API addPost({ token: getToken() }, description, imageUrl); goToPage(POSTS_PAGE); }, @@ -135,20 +128,21 @@ const renderApp = () => { } if (page === POSTS_PAGE) { - return renderPostsPageComponent({ - appEl, - }); + return renderPostsPageComponent( + { + appEl, + }, + getToken(), + ); } if (page === USER_POSTS_PAGE) { - // @TODO: реализовать страницу с фотографиями отдельного пользвателя - // appEl.innerHTML = 'Здесь будет страница фотографий пользователя'; - // return; - console.log('yes'); - - return renderUserPostsPageComponent({ - appEl, - }); + return renderUserPostsPageComponent( + { + appEl, + }, + getToken(), + ); } }; From 549778b8b263d08107181654dbf4973a3e773386 Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Wed, 4 Jun 2025 21:02:56 +0400 Subject: [PATCH 06/14] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B0=D0=B4=D0=B0=D0=BF=D1=82=D0=B8=D0=B2=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=B8=D0=B7=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.js | 2 +- components/add-post-page-component.js | 3 ++- components/header-component.js | 2 +- helpers.js | 4 ++++ styles.css | 15 ++++++++++++++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/api.js b/api.js index 275e6178c..86b1f31f1 100644 --- a/api.js +++ b/api.js @@ -1,6 +1,6 @@ // Замени на свой, чтобы получить независимый от других набор данных. // "боевая" версия инстапро лежит в ключе prod -const personalKey = 'avrusskov-test'; +const personalKey = 'avrusskov-test1'; const baseHost = 'https://webdev-hw-api.vercel.app'; const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`; diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 4da9b67e7..33590e095 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -1,3 +1,4 @@ +import { replaceHtmlTags } from '../helpers.js'; import { renderHeaderComponent } from './header-component.js'; import { renderUploadImageComponent } from './upload-image-component.js'; @@ -61,7 +62,7 @@ export function renderAddPostPageComponent({ appEl, onAddPostClick }) { } onAddPostClick({ - description: description.value, + description: replaceHtmlTags(description.value), imageUrl: imageUrl, }); }); diff --git a/components/header-component.js b/components/header-component.js index 672b5fab5..d70e38efa 100644 --- a/components/header-component.js +++ b/components/header-component.js @@ -24,7 +24,7 @@ export function renderHeaderComponent({ element }) { ${ user - ? `

+ ? `

${user.name}

` diff --git a/helpers.js b/helpers.js index ae7a4a3b4..7b4649f9f 100644 --- a/helpers.js +++ b/helpers.js @@ -13,3 +13,7 @@ export function getUserFromLocalStorage(user) { export function removeUserFromLocalStorage(user) { window.localStorage.removeItem('user'); } + +export function replaceHtmlTags(text) { + return text.replaceAll('<', '˂').replaceAll('>', '˃'); +} diff --git a/styles.css b/styles.css index 550e78910..38e67661b 100644 --- a/styles.css +++ b/styles.css @@ -23,6 +23,12 @@ margin-bottom: 8px; } +.header__user-container { + display: flex; + flex-direction: row; + gap: 10px; +} + .header-button { padding: 0; border: none; @@ -34,7 +40,7 @@ } .logout-button { - width: 130px; + /* width: 130px; */ text-align: right; } @@ -200,3 +206,10 @@ width: 100%; text-align: center; } + +@media (max-width: 500px) { + .post-image-container { + width: 100vw; + height: auto; + } +} From 04784a1271a6c83ddd1d49a1a0e6bbfd698ce44d Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Thu, 5 Jun 2025 15:19:06 +0400 Subject: [PATCH 07/14] =?UTF-8?q?=D1=83=D0=BA=D0=B0=D0=B7=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=20readme=20=D0=BD=D0=BE=D0=B2=D1=83=D1=8E=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D1=87=D1=83=20=D0=B8=20=D1=81=D1=80=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 34719e139..d5facf7ad 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # Instapro MVP социальной сети для обмена фотографиями +Добавить функионал удаления собственных комментариев. ## Первоначальная оценка -ХХХХ часов +72 часа ## Фактически затраченное время From b8f93022f9d2d263877690d3a6276eb060ed7282 Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Fri, 6 Jun 2025 19:56:08 +0400 Subject: [PATCH 08/14] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8E=20=D1=83?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.js | 13 +++++ assets/images/3dots.png | Bin 0 -> 1063 bytes components/posts-page-component.js | 74 +++++++++++++++++++++++------ styles.css | 12 +++++ 4 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 assets/images/3dots.png diff --git a/api.js b/api.js index 86b1f31f1..9a0263dd9 100644 --- a/api.js +++ b/api.js @@ -58,6 +58,19 @@ export function addPost({ token }, description, imageUrl) { }); } +export function deletePost({ token }, postId) { + return fetch(`${postsHost}/${postId}`, { + method: 'DELETE', + headers: { Authorization: token }, + }).then((response) => { + if (response.status === 401) { + throw new Error('Нет авторизации'); + } + + return; + }); +} + export function registerUser({ login, password, name, imageUrl }) { return fetch(baseHost + '/api/user', { method: 'POST', diff --git a/assets/images/3dots.png b/assets/images/3dots.png new file mode 100644 index 0000000000000000000000000000000000000000..bdfc310faa8d03aee7eef28d9567a44b06ef7877 GIT binary patch literal 1063 zcmV+?1laqDP)M`h;zhg{L!5(Pq6;#OBT?>j-iLLqcQ zG8zE@000000001JGmdRu9E;q>6Yf&}hlw?HR$IvA+q}vX?xNl(TQ>4lPAPX`?{a9( zS2=K(_8#lbfX1x3i+hJpM?hmfxyuXp;S6ZZg1f*_`<{Tt?72$}6ZHl(CgLtKNJ&Uo zau*q5MhIxkjJwPL8&bjzcbWb&Qb1!e?n1rA#Dp<-q2AhuqfJZhLVZyl6?ak}7_>WS zMe5!3Oip;_F4a@tko4h$yHrmLazd%Y?r<0dww3z5Nms%56uDi3!Kth5Fl)0&cm>4A9-}t`pt)E(ie&?jnOw{)vMBg%x*C zzasZW`q!x^;EB7yV3GTNx|Q0%sELk_fL{s%UI%a8Ew|;IySO*$YG+7oUbINtF5^C0kC#jZ+ z(I-)2zaDdIHqkcu6irtz^M|Xos*LTcg{7_xZpwBY^Y}X@Gl{O1U&2>>-MLqLOI_hz z)3ssy<6Ca|*HHie zQ2)JD|MO7)m`4A&bO7%b`ri-rzvB;}PouxrHh_NV0D9i~=Pd>>_cVa{!vV|zkMz&u zj|Q+7W2ApQ&5iOkQ2+n{0000000000Ad2oiXtnPntbH$N_4|2i-;-VazWVCVMu^-; z%@pz@sz0Md{W&xG&kFh>B>dqnGKBs!yVRe%rvL0b_2(bzKf_Z0Ii&i}lGT46u>S9W z@h!LfYuMGiXp-8_IFl2exl8plBPW#FS)_emSrOaH=x(w-TXGlbjk;}w)J-Gk15>HP z(yQ0Wigp|BG6T@=rc&>uHzgrp$z5a+$^%2}qvCGg8*tBEVyM2ssnj89jk~Wewh2|| z+WtfGrXMxkoo~tA?k8FM0QDqzEYpyDH}d6jcNS_ldEPY2N^KxIjbob^$0D~EmGJ-o h00000003a%{s0oqBexiJOY{H$002ovPDHLkV1lTT0r>y` literal 0 HcmV?d00001 diff --git a/components/posts-page-component.js b/components/posts-page-component.js index bd03a13d3..a82660df3 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -1,9 +1,9 @@ import { USER_POSTS_PAGE } from '../routes.js'; import { renderHeaderComponent } from './header-component.js'; -import { posts, goToPage } from '../index.js'; +import { posts, goToPage, user } from '../index.js'; import { formatDistanceToNow } from 'date-fns'; import { ru } from 'date-fns/locale'; -import { likePost } from '../api.js'; +import { deletePost, likePost } from '../api.js'; export function renderPostsPageComponent({ appEl }, token) { let appHtml = ''; @@ -18,26 +18,34 @@ export function renderPostsPageComponent({ appEl }, token) {
  • - -

    ${post.user.name}

    +
    + +

    ${post.user.name}

    +
    + + + +
    - +
    - -

    - Нравится: ${post.likes.length} -

    + +

    + Нравится: ${post.likes.length} +

    - ${post.user.name} - ${post.description} + ${post.user.name} + ${post.description}

  • @@ -91,4 +99,42 @@ export function renderPostsPageComponent({ appEl }, token) { ); }); } + + for (let btnMenu of document.querySelectorAll('.post-header__menu-btn')) { + btnMenu.addEventListener('click', (event) => { + event.stopPropagation(); + + const index = btnMenu.dataset.index; + + if (posts[index].user.id === user._id) { + console.log('user confirm'); + + const menu = document.getElementById(`menu-${index}`); + menu.style.display = + menu.style.display === 'none' ? 'flex' : 'none'; + } else { + console.log('user not confirm'); + } + }); + } + + for (let btnDelete of document.querySelectorAll('.post-header__delete')) { + btnDelete.addEventListener('click', (event) => { + event.stopPropagation(); + + if (user) { + const index = btnDelete.dataset.index; + const isConfirm = confirm('Вы подтверждаете удаление?'); + if (isConfirm) { + deletePost({ token }, posts[index].id); + } + } + }); + } +} + +{ + /* */ } diff --git a/styles.css b/styles.css index 38e67661b..68cd3d4f6 100644 --- a/styles.css +++ b/styles.css @@ -55,6 +55,7 @@ .post-header { display: flex; flex-direction: row; + justify-content: space-between; align-items: center; margin-bottom: 10px; cursor: pointer; @@ -128,6 +129,12 @@ margin-bottom: 12px; } +.post-header__user { + display: flex; + flex-direction: row; + align-items: center; +} + .posts-user-header__user-image { width: 70px; height: 70px; @@ -141,6 +148,11 @@ line-height: 35px; } +.post-header__menu-btn { + width: 30px; + height: 30px; +} + .loading-page { display: flex; justify-content: center; From ea9f77f44ee4bfe4010b5e747f1f163299697519 Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Sat, 7 Jun 2025 13:09:25 +0400 Subject: [PATCH 09/14] =?UTF-8?q?=D0=9D=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83=D1=82=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BF=D1=80=D0=B8=20=D1=83?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=BF=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.js | 2 +- components/add-post-page-component.js | 1 - components/posts-page-component.js | 30 ++++++---------- components/user-post-page-component.js | 48 ++++++++++++++++++++++++-- index.js | 34 ++++++++++++++++-- routes.js | 1 + styles.css | 23 ++++++++++++ 7 files changed, 112 insertions(+), 27 deletions(-) diff --git a/api.js b/api.js index 9a0263dd9..abd7d3855 100644 --- a/api.js +++ b/api.js @@ -1,6 +1,6 @@ // Замени на свой, чтобы получить независимый от других набор данных. // "боевая" версия инстапро лежит в ключе prod -const personalKey = 'avrusskov-test1'; +const personalKey = 'avrusskov'; const baseHost = 'https://webdev-hw-api.vercel.app'; const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`; diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 33590e095..467bb74a3 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -60,7 +60,6 @@ export function renderAddPostPageComponent({ appEl, onAddPostClick }) { alert('Выберите фото'); return; } - onAddPostClick({ description: replaceHtmlTags(description.value), imageUrl: imageUrl, diff --git a/components/posts-page-component.js b/components/posts-page-component.js index a82660df3..47e219ecf 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -1,9 +1,9 @@ -import { USER_POSTS_PAGE } from '../routes.js'; +import { DELETE_POST_PAGE, USER_POSTS_PAGE } from '../routes.js'; import { renderHeaderComponent } from './header-component.js'; import { posts, goToPage, user } from '../index.js'; import { formatDistanceToNow } from 'date-fns'; import { ru } from 'date-fns/locale'; -import { deletePost, likePost } from '../api.js'; +import { likePost } from '../api.js'; export function renderPostsPageComponent({ appEl }, token) { let appHtml = ''; @@ -22,11 +22,11 @@ export function renderPostsPageComponent({ appEl }, token) {

    ${post.user.name}

    - - - -
    @@ -107,18 +107,16 @@ export function renderPostsPageComponent({ appEl }, token) { const index = btnMenu.dataset.index; if (posts[index].user.id === user._id) { - console.log('user confirm'); - const menu = document.getElementById(`menu-${index}`); menu.style.display = menu.style.display === 'none' ? 'flex' : 'none'; - } else { - console.log('user not confirm'); } }); } - for (let btnDelete of document.querySelectorAll('.post-header__delete')) { + for (let btnDelete of document.querySelectorAll( + '.post-header__btn-delete', + )) { btnDelete.addEventListener('click', (event) => { event.stopPropagation(); @@ -126,15 +124,9 @@ export function renderPostsPageComponent({ appEl }, token) { const index = btnDelete.dataset.index; const isConfirm = confirm('Вы подтверждаете удаление?'); if (isConfirm) { - deletePost({ token }, posts[index].id); + goToPage(DELETE_POST_PAGE, { postId: posts[index].id }); } } }); } } - -{ - /* */ -} diff --git a/components/user-post-page-component.js b/components/user-post-page-component.js index 6436e7223..bd67eea78 100644 --- a/components/user-post-page-component.js +++ b/components/user-post-page-component.js @@ -1,8 +1,9 @@ import { renderHeaderComponent } from './header-component.js'; -import { posts } from '../index.js'; +import { goToPage, posts, user } from '../index.js'; import { formatDistanceToNow } from 'date-fns'; import { ru } from 'date-fns/locale'; import { likePost } from '../api.js'; +import { DELETE_POST_PAGE } from '../routes.js'; export function renderUserPostsPageComponent({ appEl }, token) { window.scrollTo(0, 0); @@ -19,8 +20,16 @@ export function renderUserPostsPageComponent({ appEl }, token) {
  • - -

    ${post.user.name}

    +
    + +

    ${post.user.name}

    +
    +
    + + +
    @@ -84,4 +93,37 @@ export function renderUserPostsPageComponent({ appEl }, token) { ); }); } + + for (let btnMenu of document.querySelectorAll('.post-header__menu-btn')) { + btnMenu.addEventListener('click', (event) => { + event.stopPropagation(); + + const index = btnMenu.dataset.index; + + if (posts[index].user.id === user._id) { + const menu = document.getElementById(`menu-${index}`); + menu.style.display = + menu.style.display === 'none' ? 'flex' : 'none'; + } + }); + } + + for (let btnDelete of document.querySelectorAll( + '.post-header__btn-delete', + )) { + btnDelete.addEventListener('click', (event) => { + event.stopPropagation(); + + if (user) { + const index = btnDelete.dataset.index; + const isConfirm = confirm('Вы подтверждаете удаление?'); + if (isConfirm) { + goToPage(DELETE_POST_PAGE, { + postId: posts[index].id, + userId: posts[index].user.id, + }); + } + } + }); + } } diff --git a/index.js b/index.js index 1d754695e..3307d94b9 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,10 @@ -import { addPost, getPosts, getUserPosts } from './api.js'; +import { addPost, deletePost, getPosts, getUserPosts } from './api.js'; import { renderAddPostPageComponent } from './components/add-post-page-component.js'; import { renderAuthPageComponent } from './components/auth-page-component.js'; import { ADD_POSTS_PAGE, AUTH_PAGE, + DELETE_POST_PAGE, LOADING_PAGE, POSTS_PAGE, USER_POSTS_PAGE, @@ -43,6 +44,7 @@ export const goToPage = (newPage, data) => { ADD_POSTS_PAGE, USER_POSTS_PAGE, LOADING_PAGE, + DELETE_POST_PAGE, ].includes(newPage) ) { if (newPage === ADD_POSTS_PAGE) { @@ -73,9 +75,35 @@ export const goToPage = (newPage, data) => { return getUserPosts({ token: getToken() }, data.userId) .then((newPosts) => { - page = USER_POSTS_PAGE; posts = newPosts; - renderApp(); + + if (posts.length > 0) { + page = USER_POSTS_PAGE; + renderApp(); + } else { + page = POSTS_PAGE; + goToPage(page); + } + }) + .catch((error) => { + console.error(error); + goToPage(POSTS_PAGE); + }); + } + + if (newPage === DELETE_POST_PAGE) { + page = LOADING_PAGE; + renderApp(); + + return deletePost({ token: getToken() }, data.postId) + .then(() => { + if (data.userId) { + page = USER_POSTS_PAGE; + goToPage(USER_POSTS_PAGE, { userId: data.userId }); + } else { + page = POSTS_PAGE; + goToPage(POSTS_PAGE); + } }) .catch((error) => { console.error(error); diff --git a/routes.js b/routes.js index 4ee2413f3..2fe9aa459 100644 --- a/routes.js +++ b/routes.js @@ -4,3 +4,4 @@ export const USER_POSTS_PAGE = 'user-posts'; export const AUTH_PAGE = 'auth'; export const ADD_POSTS_PAGE = 'add-post'; export const LOADING_PAGE = 'loading'; +export const DELETE_POST_PAGE = 'delete'; diff --git a/styles.css b/styles.css index 68cd3d4f6..b3e723d8c 100644 --- a/styles.css +++ b/styles.css @@ -153,6 +153,29 @@ height: 30px; } +.post-header__user-menu { + position: relative; + cursor: pointer; +} + +.post-header__menu { + position: absolute; + top: 2px; + right: 25px; +} + +.post-header__btn-delete { + display: block; + padding: 5px 5px 5px; + font-size: 18px; + line-height: 22px; + border-radius: 5px; + background-color: #565eef; + color: #ffffff; + border: none; + cursor: pointer; +} + .loading-page { display: flex; justify-content: center; From 3941988bd3da5b321099cc555e9ebba9925eebd7 Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Sat, 7 Jun 2025 13:17:30 +0400 Subject: [PATCH 10/14] =?UTF-8?q?=D0=97=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=84=D0=B0=D0=B9=D0=BB=20ReadMe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d5facf7ad..db2c278a3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # Instapro MVP социальной сети для обмена фотографиями -Добавить функионал удаления собственных комментариев. + +1. Добавить функионал удаления собственных постов. + Удалять можно как в общей ленте постов, так и при просмотре конкретного пользователя. + Удалять посты другого пользователя нельзя, кнопка удалить не активна. ## Первоначальная оценка @@ -9,4 +12,4 @@ MVP социальной сети для обмена фотографиями ## Фактически затраченное время -YYYY часов +40 часов From c293915e81adde32f279e99c3d3cb9ee090785e4 Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Sat, 7 Jun 2025 13:20:28 +0400 Subject: [PATCH 11/14] Update README.md From fcb08c7c52302c0718690fc13d426d1a4f81cdb7 Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Sat, 7 Jun 2025 13:20:40 +0400 Subject: [PATCH 12/14] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index db2c278a3..1fffa0b83 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ MVP социальной сети для обмена фотографиями 1. Добавить функионал удаления собственных постов. + Удалять можно как в общей ленте постов, так и при просмотре конкретного пользователя. + Удалять посты другого пользователя нельзя, кнопка удалить не активна. ## Первоначальная оценка From 77751a3b1f936babae36bea4e8171b78d8ff6900 Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Sun, 8 Jun 2025 18:16:14 +0400 Subject: [PATCH 13/14] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83=20?= =?UTF-8?q?=D0=BD=D0=B0=20HTML=20=D0=B4=D0=BB=D1=8F=20=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D1=8B=20=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8/=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/auth-page-component.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/components/auth-page-component.js b/components/auth-page-component.js index 3db0490a9..729e596b4 100644 --- a/components/auth-page-component.js +++ b/components/auth-page-component.js @@ -1,6 +1,7 @@ import { loginUser, registerUser } from '../api.js'; import { renderHeaderComponent } from './header-component.js'; import { renderUploadImageComponent } from './upload-image-component.js'; +import { replaceHtmlTags } from '../helpers.js'; /** * Компонент страницы авторизации. @@ -119,8 +120,8 @@ export function renderAuthPageComponent({ appEl, setUser }) { alert('Введите пароль'); return; } - - loginUser({ login, password }) + console.log(replaceHtmlTags(login)); + loginUser({ login: replaceHtmlTags(login), password }) .then((user) => { setUser(user.user); }) @@ -155,7 +156,12 @@ export function renderAuthPageComponent({ appEl, setUser }) { return; } - registerUser({ login, password, name, imageUrl }) + registerUser({ + login: replaceHtmlTags(login), + password, + name: replaceHtmlTags(name), + imageUrl, + }) .then((user) => { setUser(user.user); }) From 1032794112ae5701c40533b6da2ea6fb0c57fe75 Mon Sep 17 00:00:00 2001 From: OlmerOnline Date: Mon, 9 Jun 2025 10:35:16 +0400 Subject: [PATCH 14/14] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD=D1=83=20HTML=20=D1=82?= =?UTF-8?q?=D0=B5=D0=B3=D0=BE=D0=B2=20=D0=B2=20=D1=80=D0=B5=D0=BD=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/add-post-page-component.js | 2 +- components/auth-page-component.js | 9 ++------- components/header-component.js | 5 +++-- components/posts-page-component.js | 7 ++++--- components/user-post-page-component.js | 7 ++++--- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 467bb74a3..838d2de91 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -61,7 +61,7 @@ export function renderAddPostPageComponent({ appEl, onAddPostClick }) { return; } onAddPostClick({ - description: replaceHtmlTags(description.value), + description: description.value, imageUrl: imageUrl, }); }); diff --git a/components/auth-page-component.js b/components/auth-page-component.js index 729e596b4..4405a450d 100644 --- a/components/auth-page-component.js +++ b/components/auth-page-component.js @@ -121,7 +121,7 @@ export function renderAuthPageComponent({ appEl, setUser }) { return; } console.log(replaceHtmlTags(login)); - loginUser({ login: replaceHtmlTags(login), password }) + loginUser({ login, password }) .then((user) => { setUser(user.user); }) @@ -156,12 +156,7 @@ export function renderAuthPageComponent({ appEl, setUser }) { return; } - registerUser({ - login: replaceHtmlTags(login), - password, - name: replaceHtmlTags(name), - imageUrl, - }) + registerUser({ login, password, name, imageUrl }) .then((user) => { setUser(user.user); }) diff --git a/components/header-component.js b/components/header-component.js index d70e38efa..f1be6e440 100644 --- a/components/header-component.js +++ b/components/header-component.js @@ -1,3 +1,4 @@ +import { replaceHtmlTags } from '../helpers.js'; import { goToPage, logout, user } from '../index.js'; import { ADD_POSTS_PAGE, AUTH_PAGE, POSTS_PAGE } from '../routes.js'; @@ -25,8 +26,8 @@ export function renderHeaderComponent({ element }) { ${ user ? `
    -

    ${user.name}

    - +

    ${replaceHtmlTags(user.name)}

    +
    ` : '' } diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 47e219ecf..2485839be 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -4,6 +4,7 @@ import { posts, goToPage, user } from '../index.js'; import { formatDistanceToNow } from 'date-fns'; import { ru } from 'date-fns/locale'; import { likePost } from '../api.js'; +import { replaceHtmlTags } from '../helpers.js'; export function renderPostsPageComponent({ appEl }, token) { let appHtml = ''; @@ -20,7 +21,7 @@ export function renderPostsPageComponent({ appEl }, token) {
    -

    ${post.user.name}

    +

    ${replaceHtmlTags(post.user.name)}

    @@ -41,8 +42,8 @@ export function renderPostsPageComponent({ appEl }, token) {

    - ${post.user.name} - ${post.description} + ${replaceHtmlTags(post.user.name)} + ${replaceHtmlTags(post.description)}

    -

    ${post.user.name}

    +

    ${replaceHtmlTags(post.user.name)}

    @@ -43,8 +44,8 @@ export function renderUserPostsPageComponent({ appEl }, token) {

    - ${post.user.name} - ${post.description} + ${replaceHtmlTags(post.user.name)} + ${replaceHtmlTags(post.description)}