From f0c893928b909c8ecb5c921062d8df89dbfa38af Mon Sep 17 00:00:00 2001 From: DmitriySkachkov Date: Sat, 25 Oct 2025 12:01:46 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 +++- api.js | 131 +++++++++++--- components/add-post-page-component.js | 54 +++++- components/auth-page-component.js | 104 +++++------ components/header-component.js | 25 +-- components/loading-page-component.js | 20 +-- components/posts-page-component.js | 229 +++++++++++++++--------- components/upload-image-component.js | 81 +++++---- components/user-posts-page-component.js | 166 +++++++++++++++++ helpers.js | 2 +- index.js | 72 ++++++-- routes.js | 3 +- styles.css | 42 +++++ 13 files changed, 683 insertions(+), 276 deletions(-) create mode 100644 components/user-posts-page-component.js diff --git a/README.md b/README.md index 34719e139..aa5a94720 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,36 @@ MVP социальной сети для обмена фотографиями +## Реализованный функционал + +### Основные функции +- Авторизация и регистрация пользователей с загрузкой аватарок +- Просмотр ленты постов всех пользователей +- Добавление новых постов с изображениями и описанием +- Просмотр постов конкретного пользователя +- Система лайков с мгновенным обновлением +- Форматирование дат постов в относительном формате + +### Технические особенности +- Модульная архитектура приложения +- Компонентный подход к разработке +- Работа с REST API +- Загрузка изображений в облачное хранилище +- Динамическое обновление контента без перезагрузки страницы +- Локальное хранение данных пользователя +- Обработка ошибок и валидация форм + +### Интерфейсные улучшения +- Адаптивный и интуитивный интерфейс +- Информативные сообщения об ошибках +- Состояния загрузки для всех операций +- Интерактивные элементы с hover-эффектами +- Красивое отображение времени публикации + ## Первоначальная оценка -ХХХХ часов +8 часов ## Фактически затраченное время -YYYY часов +10 часов diff --git a/api.js b/api.js index 123a9e3b6..b19ad29ac 100644 --- a/api.js +++ b/api.js @@ -1,6 +1,5 @@ -// Замени на свой, чтобы получить независимый от других набор данных. -// "боевая" версия инстапро лежит в ключе prod -const personalKey = "prod"; + +const personalKey = "dmitriy-skachkov"; const baseHost = "https://webdev-hw-api.vercel.app"; const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`; @@ -15,7 +14,88 @@ export function getPosts({ token }) { if (response.status === 401) { throw new Error("Нет авторизации"); } + if (!response.ok) { + throw new Error(`Ошибка HTTP! статус: ${response.status}`); + } + return response.json(); + }) + .then((data) => { + return data.posts; + }); +} + +export function addPost({ token, description, imageUrl }) { + return fetch(postsHost, { + method: "POST", + body: JSON.stringify({ + description, + imageUrl, + }), + headers: { + Authorization: token, + }, + }) + .then((response) => { + if (response.status === 401) { + throw new Error("Нет авторизации"); + } + if (!response.ok) { + throw new Error(`Ошибка HTTP! статус: ${response.status}`); + } + return response.json(); + }); +} + +export function likePost({ token, postId }) { + return fetch(`${postsHost}/${postId}/like`, { + method: "POST", + headers: { + Authorization: token, + }, + }) + .then((response) => { + if (response.status === 401) { + throw new Error("Нет авторизации"); + } + if (!response.ok) { + throw new Error(`Ошибка HTTP! статус: ${response.status}`); + } + return response.json(); + }); +} + +export function dislikePost({ token, postId }) { + return fetch(`${postsHost}/${postId}/dislike`, { + method: "POST", + headers: { + Authorization: token, + }, + }) + .then((response) => { + if (response.status === 401) { + throw new Error("Нет авторизации"); + } + if (!response.ok) { + throw new Error(`Ошибка HTTP! статус: ${response.status}`); + } + return response.json(); + }); +} +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("Нет авторизации"); + } + if (!response.ok) { + throw new Error(`Ошибка HTTP! статус: ${response.status}`); + } return response.json(); }) .then((data) => { @@ -32,12 +112,16 @@ export function registerUser({ login, password, name, imageUrl }) { name, imageUrl, }), - }).then((response) => { - if (response.status === 400) { - throw new Error("Такой пользователь уже существует"); - } - return response.json(); - }); + }) + .then((response) => { + if (response.status === 400) { + throw new Error("Такой пользователь уже существует"); + } + if (!response.ok) { + throw new Error(`Ошибка HTTP! статус: ${response.status}`); + } + return response.json(); + }); } export function loginUser({ login, password }) { @@ -47,15 +131,18 @@ export function loginUser({ login, password }) { login, password, }), - }).then((response) => { - if (response.status === 400) { - throw new Error("Неверный логин или пароль"); - } - return response.json(); - }); + }) + .then((response) => { + if (response.status === 400) { + throw new Error("Неверный логин или пароль"); + } + if (!response.ok) { + throw new Error(`Ошибка HTTP! статус: ${response.status}`); + } + return response.json(); + }); } -// Загружает картинку в облако, возвращает url загруженной картинки export function uploadImage({ file }) { const data = new FormData(); data.append("file", file); @@ -63,7 +150,11 @@ export function uploadImage({ file }) { return fetch(baseHost + "/api/upload/image", { method: "POST", body: data, - }).then((response) => { - return response.json(); - }); -} + }) + .then((response) => { + if (!response.ok) { + throw new Error(`Ошибка HTTP! статус: ${response.status}`); + } + return response.json(); + }); +} \ No newline at end of file diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 8277f093b..5668167cf 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -1,23 +1,65 @@ +import { renderHeaderComponent } from "./header-component.js"; +import { renderUploadImageComponent } from "./upload-image-component.js"; + export function renderAddPostPageComponent({ appEl, onAddPostClick }) { + let imageUrl = ""; + let description = ""; + const render = () => { - // @TODO: Реализовать страницу добавления поста const appHtml = `
- Cтраница добавления поста - +
+

Добавить пост

+
+
Загрузка изображения...
+ + +
+
`; appEl.innerHTML = appHtml; + renderHeaderComponent({ + element: document.querySelector(".header-container"), + }); + + renderUploadImageComponent({ + element: document.querySelector(".upload-image-container"), + onImageUrlChange: (newImageUrl) => { + imageUrl = newImageUrl; + }, + }); + + const descriptionInput = document.getElementById("description-input"); + descriptionInput.addEventListener("input", (e) => { + description = e.target.value; + }); + document.getElementById("add-button").addEventListener("click", () => { + if (!imageUrl) { + alert("Не выбрана фотография"); + return; + } + + if (!description.trim()) { + alert("Добавьте описание фотографии"); + return; + } + onAddPostClick({ - description: "Описание картинки", - imageUrl: "https://image.png", + description: description.trim(), + imageUrl: imageUrl, }); }); }; render(); -} +} \ No newline at end of file diff --git a/components/auth-page-component.js b/components/auth-page-component.js index 0e93530ec..26e50bd10 100644 --- a/components/auth-page-component.js +++ b/components/auth-page-component.js @@ -2,51 +2,23 @@ import { loginUser, registerUser } from "../api.js"; import { renderHeaderComponent } from "./header-component.js"; import { renderUploadImageComponent } from "./upload-image-component.js"; -/** - * Компонент страницы авторизации. - * Этот компонент предоставляет пользователю интерфейс для входа в систему или регистрации. - * Форма переключается между режимами "Вход" и "Регистрация". - * - * @param {HTMLElement} params.appEl - Корневой элемент приложения, в который будет рендериться страница. - * @param {Function} params.setUser - Функция, вызываемая при успешной авторизации или регистрации. - * Принимает объект пользователя в качестве аргумента. - */ export function renderAuthPageComponent({ appEl, setUser }) { - /** - * Флаг, указывающий текущий режим формы. - * Если `true`, форма находится в режиме входа. Если `false`, в режиме регистрации. - * @type {boolean} - */ let isLoginMode = true; - - /** - * URL изображения, загруженного пользователем при регистрации. - * Используется только в режиме регистрации. - * @type {string} - */ let imageUrl = ""; - /** - * Рендерит форму авторизации или регистрации. - * В зависимости от значения `isLoginMode` отображает соответствующий интерфейс. - */ const renderForm = () => { const appHtml = `

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

${ !isLoginMode ? ` -
+
Выберите фото профиля
` : "" @@ -72,20 +44,26 @@ export function renderAuthPageComponent({ appEl, setUser }) { appEl.innerHTML = appHtml; - /** - * Устанавливает сообщение об ошибке в форме. - * @param {string} message - Текст сообщения об ошибке. - */ const setError = (message) => { - appEl.querySelector(".form-error").textContent = message; + const errorEl = appEl.querySelector(".form-error"); + errorEl.textContent = message; + }; + + const setLoading = (isLoading) => { + const button = appEl.querySelector("#login-button"); + if (isLoading) { + button.disabled = true; + button.textContent = "Загрузка..."; + } else { + button.disabled = false; + button.textContent = isLoginMode ? "Войти" : "Зарегистрироваться"; + } }; - // Рендерим заголовок страницы renderHeaderComponent({ element: document.querySelector(".header-container"), }); - // Если режим регистрации, рендерим компонент загрузки изображения const uploadImageContainer = appEl.querySelector(".upload-image-container"); if (uploadImageContainer) { renderUploadImageComponent({ @@ -96,22 +74,23 @@ export function renderAuthPageComponent({ appEl, setUser }) { }); } - // Обработка клика на кнопку входа/регистрации document.getElementById("login-button").addEventListener("click", () => { setError(""); + setLoading(true); - if (isLoginMode) { - // Обработка входа - const login = document.getElementById("login-input").value; - const password = document.getElementById("password-input").value; + const login = document.getElementById("login-input").value.trim(); + const password = document.getElementById("password-input").value; + if (isLoginMode) { if (!login) { - alert("Введите логин"); + setError("Введите логин"); + setLoading(false); return; } if (!password) { - alert("Введите пароль"); + setError("Введите пароль"); + setLoading(false); return; } @@ -122,30 +101,38 @@ export function renderAuthPageComponent({ appEl, setUser }) { .catch((error) => { console.warn(error); setError(error.message); + setLoading(false); }); } else { - // Обработка регистрации - const login = document.getElementById("login-input").value; - const name = document.getElementById("name-input").value; - const password = document.getElementById("password-input").value; + const name = document.getElementById("name-input").value.trim(); if (!name) { - alert("Введите имя"); + setError("Введите имя"); + setLoading(false); return; } if (!login) { - alert("Введите логин"); + setError("Введите логин"); + setLoading(false); return; } if (!password) { - alert("Введите пароль"); + setError("Введите пароль"); + setLoading(false); return; } if (!imageUrl) { - alert("Не выбрана фотография"); + setError("Не выбрана фотография профиля"); + setLoading(false); + return; + } + + if (password.length < 3) { + setError("Пароль должен содержать минимум 3 символа"); + setLoading(false); return; } @@ -156,17 +143,22 @@ export function renderAuthPageComponent({ appEl, setUser }) { .catch((error) => { console.warn(error); setError(error.message); + setLoading(false); }); } }); - // Обработка переключения режима (вход ↔ регистрация) document.getElementById("toggle-button").addEventListener("click", () => { isLoginMode = !isLoginMode; - renderForm(); // Перерисовываем форму с новым режимом + renderForm(); }); + + // Автофокус на первом поле + const firstInput = appEl.querySelector('input'); + if (firstInput) { + firstInput.focus(); + } }; - // Инициализация формы renderForm(); -} +} \ No newline at end of file diff --git a/components/header-component.js b/components/header-component.js index e733a39dc..7280156b8 100644 --- a/components/header-component.js +++ b/components/header-component.js @@ -1,17 +1,7 @@ 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 .querySelector(".add-or-login-button") .addEventListener("click", () => { @@ -45,19 +30,11 @@ export function renderHeaderComponent({ element }) { } }); - /** - * Обработчик клика по логотипу. - * Перенаправляет на страницу с постами. - */ element.querySelector(".logo").addEventListener("click", () => { goToPage(POSTS_PAGE); }); - /** - * Обработчик клика по кнопке "Выйти". - * Если кнопка существует (т.е. пользователь авторизован), вызывает функцию `logout`. - */ element.querySelector(".logout-button")?.addEventListener("click", logout); return element; -} +} \ No newline at end of file diff --git a/components/loading-page-component.js b/components/loading-page-component.js index c93522573..704fd9449 100644 --- a/components/loading-page-component.js +++ b/components/loading-page-component.js @@ -1,19 +1,6 @@ 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 = `
@@ -22,16 +9,11 @@ export function renderLoadingPageComponent({ appEl, user, goToPage }) {
`; - // Устанавливаем разметку в корневой элемент приложения appEl.innerHTML = appHtml; - /** - * Рендеринг заголовка с использованием компонента `renderHeaderComponent`. - * Передаются данные пользователя и функция навигации. - */ renderHeaderComponent({ user, element: document.querySelector(".header-container"), goToPage, }); -} +} \ No newline at end of file diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 662ccdbdc..5b11469c3 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -1,97 +1,89 @@ 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 { likePost, dislikePost } from "../api.js"; export function renderPostsPageComponent({ appEl }) { - // @TODO: реализовать рендер постов из api console.log("Актуальный список постов:", posts); - /** - * @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" - * можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow - */ + const formatDate = (createdAt) => { + const now = new Date(); + const postDate = new Date(createdAt); + const diffInSeconds = Math.floor((now - postDate) / 1000); + + if (diffInSeconds < 60) { + return 'только что'; + } else if (diffInSeconds < 3600) { + const minutes = Math.floor(diffInSeconds / 60); + return `${minutes} ${getMinutesText(minutes)} назад`; + } else if (diffInSeconds < 86400) { + const hours = Math.floor(diffInSeconds / 3600); + return `${hours} ${getHoursText(hours)} назад`; + } else if (diffInSeconds < 2592000) { + const days = Math.floor(diffInSeconds / 86400); + return `${days} ${getDaysText(days)} назад`; + } else { + const months = Math.floor(diffInSeconds / 2592000); + return `${months} ${getMonthsText(months)} назад`; + } + }; + + const getMinutesText = (minutes) => { + if (minutes === 1) return 'минуту'; + if (minutes >= 2 && minutes <= 4) return 'минуты'; + return 'минут'; + }; + + const getHoursText = (hours) => { + if (hours === 1) return 'час'; + if (hours >= 2 && hours <= 4) return 'часа'; + return 'часов'; + }; + + const getDaysText = (days) => { + if (days === 1) return 'день'; + if (days >= 2 && days <= 4) return 'дня'; + return 'дней'; + }; + + const getMonthsText = (months) => { + if (months === 1) return 'месяц'; + if (months >= 2 && months <= 4) return 'месяца'; + return 'месяцев'; + }; + const appHtml = ` -
-
-
    -
  • -
    - -

    Иван Иваныч

    -
    -
    - -
    -
    - -

    - Нравится: 2 -

    -
    -

    - Иван Иваныч - Ромашка, ромашка... -

    - -
  • -
  • -
    - -

    Варварва Н.

    -
    - - -
    - -
    -
    - -

    - Нравится: 35 -

    -
    -

    - Варварва Н. - Нарисовала картину, посмотрите какая красивая -

    - -
  • -
  • -
    - -

    Варварва Н.

    -
    - - -
    - -
    -
    - -

    - Нравится: 0 -

    -
    -

    - Варварва Н. - Голова -

    - -
  • -
-
`; +
+
+
    + ${posts.map(post => ` +
  • +
    + +

    ${post.user.name}

    +
    +
    + +
    +
    + +

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

    +
    +

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

    + +
  • + `).join('')} +
+
`; appEl.innerHTML = appHtml; @@ -106,4 +98,65 @@ export function renderPostsPageComponent({ appEl }) { }); }); } -} + + for (let likeButton of document.querySelectorAll(".like-button")) { + likeButton.addEventListener("click", () => { + const postId = likeButton.dataset.postId; + + if (!user) { + alert("Для лайков нужно авторизоваться"); + return; + } + + // Находим пост в массиве posts + const postIndex = posts.findIndex(p => p.id === postId); + if (postIndex === -1) { + console.error("Пост не найден:", postId); + alert("Ошибка: пост не найден"); + return; + } + + const post = posts[postIndex]; + const token = `Bearer ${user.token}`; + + console.log("Текущий пост:", post); + console.log("isLiked:", post.isLiked); + console.log("Лайки:", post.likes); + + // Определяем текущее состояние лайка + const isCurrentlyLiked = post.isLiked; + + if (isCurrentlyLiked) { + // Убираем лайк + console.log("Убираем лайк с поста:", postId); + dislikePost({ token, postId }) + .then((updatedPost) => { + console.log("Лайк убран, обновленный пост:", updatedPost); + // Обновляем пост в массиве + posts[postIndex] = updatedPost; + // Перерисовываем компонент + renderPostsPageComponent({ appEl }); + }) + .catch((error) => { + console.error("Ошибка при снятии лайка:", error); + alert("Не удалось снять лайк: " + error.message); + }); + } else { + // Ставим лайк + console.log("Ставим лайк на пост:", postId); + likePost({ token, postId }) + .then((updatedPost) => { + console.log("Лайк поставлен, обновленный пост:", updatedPost); + // Обновляем пост в массиве + posts[postIndex] = updatedPost; + // Перерисовываем компонент + renderPostsPageComponent({ appEl }); + }) + .catch((error) => { + console.error("Ошибка при установке лайка:", error); + alert("Не удалось поставить лайк: " + error.message); + }); + } + }); + } +} \ No newline at end of file diff --git a/components/upload-image-component.js b/components/upload-image-component.js index e2cdf2018..46829d61e 100644 --- a/components/upload-image-component.js +++ b/components/upload-image-component.js @@ -1,27 +1,9 @@ import { uploadImage } from "../api.js"; -/** - * Компонент загрузки изображения. - * Этот компонент позволяет пользователю загружать изображение и отображать его превью. - * Если изображение уже загружено, пользователь может заменить его. - * - * @param {HTMLElement} params.element - HTML-элемент, в который будет рендериться компонент. - * @param {Function} params.onImageUrlChange - Функция, вызываемая при изменении URL изображения. - * Принимает один аргумент - новый URL изображения или пустую строку. - */ export function renderUploadImageComponent({ element, onImageUrlChange }) { - /** - * URL текущего изображения. - * Изначально пуст, пока пользователь не загрузит изображение. - * @type {string} - */ let imageUrl = ""; + let isLoading = false; - /** - * Функция рендеринга компонента. - * Отображает интерфейс компонента в зависимости от состояния: - * либо форма выбора файла, либо превью загруженного изображения с кнопкой замены. - */ const render = () => { element.innerHTML = `
@@ -30,51 +12,72 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { ? `
Загруженное изображение - +
` : ` -
`; - // Обработчик выбора файла const fileInputElement = element.querySelector(".file-upload-input"); fileInputElement?.addEventListener("change", () => { const file = fileInputElement.files[0]; if (file) { - const labelEl = document.querySelector(".file-upload-label"); - labelEl.setAttribute("disabled", true); - labelEl.textContent = "Загружаю файл..."; - - // Загружаем изображение с помощью API - uploadImage({ file }).then(({ fileUrl }) => { - imageUrl = fileUrl; // Сохраняем URL загруженного изображения - onImageUrlChange(imageUrl); // Уведомляем о изменении URL изображения - render(); // Перерисовываем компонент с новым состоянием - }); + // Проверка типа файла + if (!file.type.startsWith('image/')) { + alert("Пожалуйста, выберите файл изображения (JPG, PNG, GIF)"); + return; + } + + // Проверка размера файла (максимум 5MB) + if (file.size > 5 * 1024 * 1024) { + alert("Файл слишком большой. Максимальный размер: 5MB"); + return; + } + + isLoading = true; + render(); + + uploadImage({ file }) + .then(({ fileUrl }) => { + imageUrl = fileUrl; + isLoading = false; + onImageUrlChange(imageUrl); + render(); + }) + .catch((error) => { + console.error("Ошибка загрузки изображения:", error); + alert("Не удалось загрузить изображение. Попробуйте еще раз."); + isLoading = false; + render(); + }); } }); - // Обработчик удаления изображения element .querySelector(".file-upload-remove-button") ?.addEventListener("click", () => { - imageUrl = ""; // Сбрасываем URL изображения - onImageUrlChange(imageUrl); // Уведомляем об изменении URL изображения - render(); // Перерисовываем компонент + if (!isLoading) { + imageUrl = ""; + onImageUrlChange(imageUrl); + render(); + } }); }; - // Инициализация компонента render(); -} +} \ No newline at end of file diff --git a/components/user-posts-page-component.js b/components/user-posts-page-component.js new file mode 100644 index 000000000..603d9c2c2 --- /dev/null +++ b/components/user-posts-page-component.js @@ -0,0 +1,166 @@ +import { renderHeaderComponent } from "./header-component.js"; +import { posts, goToPage, user } from "../index.js"; +import { likePost, dislikePost } from "../api.js"; + +export function renderUserPostsPageComponent({ appEl, userId }) { + const userPosts = posts.filter(post => post.user.id === userId); + + if (userPosts.length === 0) { + appEl.innerHTML = ` +
+
+

У пользователя пока нет постов

+
+ `; + renderHeaderComponent({ + element: document.querySelector(".header-container"), + }); + return; + } + + const userData = userPosts[0].user; + + const formatDate = (createdAt) => { + const now = new Date(); + const postDate = new Date(createdAt); + const diffInSeconds = Math.floor((now - postDate) / 1000); + + if (diffInSeconds < 60) { + return 'только что'; + } else if (diffInSeconds < 3600) { + const minutes = Math.floor(diffInSeconds / 60); + return `${minutes} ${getMinutesText(minutes)} назад`; + } else if (diffInSeconds < 86400) { + const hours = Math.floor(diffInSeconds / 3600); + return `${hours} ${getHoursText(hours)} назад`; + } else if (diffInSeconds < 2592000) { + const days = Math.floor(diffInSeconds / 86400); + return `${days} ${getDaysText(days)} назад`; + } else { + const months = Math.floor(diffInSeconds / 2592000); + return `${months} ${getMonthsText(months)} назад`; + } + }; + + const getMinutesText = (minutes) => { + if (minutes === 1) return 'минуту'; + if (minutes >= 2 && minutes <= 4) return 'минуты'; + return 'минут'; + }; + + const getHoursText = (hours) => { + if (hours === 1) return 'час'; + if (hours >= 2 && hours <= 4) return 'часа'; + return 'часов'; + }; + + const getDaysText = (days) => { + if (days === 1) return 'день'; + if (days >= 2 && days <= 4) return 'дня'; + return 'дней'; + }; + + const getMonthsText = (months) => { + if (months === 1) return 'месяц'; + if (months >= 2 && months <= 4) return 'месяца'; + return 'месяцев'; + }; + + const appHtml = ` +
+
+
+ +

${userData.name}

+
+
    + ${userPosts.map(post => ` +
  • +
    + +
    +
    + +

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

    +
    +

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

    + +
  • + `).join('')} +
+
`; + + appEl.innerHTML = appHtml; + + renderHeaderComponent({ + element: document.querySelector(".header-container"), + }); + + for (let likeButton of document.querySelectorAll(".like-button")) { + likeButton.addEventListener("click", () => { + const postId = likeButton.dataset.postId; + + if (!user) { + alert("Для лайков нужно авторизоваться"); + return; + } + + // Находим пост в основном массиве posts + const postIndex = posts.findIndex(p => p.id === postId); + if (postIndex === -1) { + console.error("Пост не найден:", postId); + alert("Ошибка: пост не найден"); + return; + } + + const post = posts[postIndex]; + const token = `Bearer ${user.token}`; + + console.log("Текущий пост на странице пользователя:", post); + console.log("isLiked:", post.isLiked); + + const isCurrentlyLiked = post.isLiked; + + if (isCurrentlyLiked) { + // Убираем лайк + console.log("Убираем лайк с поста:", postId); + dislikePost({ token, postId }) + .then((updatedPost) => { + console.log("Лайк убран, обновленный пост:", updatedPost); + // Обновляем пост в основном массиве + posts[postIndex] = updatedPost; + // Перерисовываем страницу + renderUserPostsPageComponent({ appEl, userId }); + }) + .catch((error) => { + console.error("Ошибка при снятии лайка:", error); + alert("Не удалось снять лайк: " + error.message); + }); + } else { + // Ставим лайк + console.log("Ставим лайк на пост:", postId); + likePost({ token, postId }) + .then((updatedPost) => { + console.log("Лайк поставлен, обновленный пост:", updatedPost); + // Обновляем пост в основном массиве + posts[postIndex] = updatedPost; + // Перерисовываем страницу + renderUserPostsPageComponent({ appEl, userId }); + }) + .catch((error) => { + console.error("Ошибка при установке лайка:", error); + alert("Не удалось поставить лайк: " + error.message); + }); + } + }); + } +} \ No newline at end of file diff --git a/helpers.js b/helpers.js index b47d3b777..7dc7fa2a3 100644 --- a/helpers.js +++ b/helpers.js @@ -12,4 +12,4 @@ export function getUserFromLocalStorage(user) { export function removeUserFromLocalStorage(user) { window.localStorage.removeItem("user"); -} +} \ No newline at end of file diff --git a/index.js b/index.js index 7f1817c75..3fcf3a498 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import { getPosts } from "./api.js"; +import { getPosts, addPost, getUserPosts } from "./api.js"; import { renderAddPostPageComponent } from "./components/add-post-page-component.js"; import { renderAuthPageComponent } from "./components/auth-page-component.js"; import { @@ -10,6 +10,7 @@ import { } from "./routes.js"; import { renderPostsPageComponent } from "./components/posts-page-component.js"; import { renderLoadingPageComponent } from "./components/loading-page-component.js"; +import { renderUserPostsPageComponent } from "./components/user-posts-page-component.js"; import { getUserFromLocalStorage, removeUserFromLocalStorage, @@ -31,9 +32,6 @@ export const logout = () => { goToPage(POSTS_PAGE); }; -/** - * Включает страницу приложения - */ export const goToPage = (newPage, data) => { if ( [ @@ -45,7 +43,6 @@ export const goToPage = (newPage, data) => { ].includes(newPage) ) { if (newPage === ADD_POSTS_PAGE) { - /* Если пользователь не авторизован, то отправляем его на страницу авторизации перед добавлением поста */ page = user ? ADD_POSTS_PAGE : AUTH_PAGE; return renderApp(); } @@ -61,17 +58,35 @@ export const goToPage = (newPage, data) => { renderApp(); }) .catch((error) => { - console.error(error); - goToPage(POSTS_PAGE); + console.error("Ошибка загрузки постов:", error); + page = POSTS_PAGE; + posts = []; + renderApp(); }); } if (newPage === USER_POSTS_PAGE) { - // @@TODO: реализовать получение постов юзера из API - console.log("Открываю страницу пользователя: ", data.userId); - page = USER_POSTS_PAGE; - posts = []; - return renderApp(); + if (!data?.userId) { + console.error("Не передан userId для страницы пользователя"); + goToPage(POSTS_PAGE); + return; + } + + page = LOADING_PAGE; + renderApp(); + + return getUserPosts({ token: getToken(), userId: data.userId }) + .then((newPosts) => { + page = USER_POSTS_PAGE; + posts = newPosts; + renderApp(); + }) + .catch((error) => { + console.error("Ошибка загрузки постов пользователя:", error); + page = USER_POSTS_PAGE; + posts = []; + renderApp(); + }); } page = newPage; @@ -85,6 +100,11 @@ export const goToPage = (newPage, data) => { const renderApp = () => { const appEl = document.getElementById("app"); + if (!appEl) { + console.error("Элемент app не найден"); + return; + } + if (page === LOADING_PAGE) { return renderLoadingPageComponent({ appEl, @@ -110,9 +130,19 @@ const renderApp = () => { return renderAddPostPageComponent({ appEl, onAddPostClick({ description, imageUrl }) { - // @TODO: реализовать добавление поста в API - console.log("Добавляю пост...", { description, imageUrl }); - goToPage(POSTS_PAGE); + addPost({ + token: getToken(), + description, + imageUrl + }) + .then((newPost) => { + posts.unshift(newPost); + goToPage(POSTS_PAGE); + }) + .catch((error) => { + console.error("Ошибка при добавлении поста:", error); + alert("Не удалось добавить пост. Попробуйте еще раз."); + }); }, }); } @@ -124,10 +154,14 @@ const renderApp = () => { } if (page === USER_POSTS_PAGE) { - // @TODO: реализовать страницу с фотографиями отдельного пользвателя - appEl.innerHTML = "Здесь будет страница фотографий пользователя"; - return; + return renderUserPostsPageComponent({ + appEl, + userId: data?.userId, + }); } }; -goToPage(POSTS_PAGE); +// Запускаем приложение +document.addEventListener('DOMContentLoaded', () => { + goToPage(POSTS_PAGE); +}); \ No newline at end of file diff --git a/routes.js b/routes.js index 725f8eb1a..f7521ea5b 100644 --- a/routes.js +++ b/routes.js @@ -1,6 +1,5 @@ -/* Файл со списком страниц приложения */ export const POSTS_PAGE = "posts"; 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 LOADING_PAGE = "loading"; \ No newline at end of file diff --git a/styles.css b/styles.css index 57bc08b16..535f2749f 100644 --- a/styles.css +++ b/styles.css @@ -196,3 +196,45 @@ width: 100%; text-align: center; } + +.file-upload-hint { + font-size: 12px; + color: #666; + margin-top: 5px; + text-align: center; +} + +.upload-progress { + color: #565eef; + font-size: 14px; + margin-top: 10px; + text-align: center; +} + +.file-upload-label[disabled], +.file-upload-remove-button[disabled] { + opacity: 0.6; + cursor: not-allowed; +} + +.form-error { + color: #ff3333; + font-size: 14px; + margin: 10px 0; + min-height: 20px; + text-align: center; +} + +.file-upload-image { + max-width: 200px; + max-height: 200px; + border-radius: 8px; + margin-bottom: 10px; +} + +.file-upload-image-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; +} \ No newline at end of file From cafc2015d32f27440312b61b6d840f793c231161 Mon Sep 17 00:00:00 2001 From: DmitriySkachkov Date: Wed, 5 Nov 2025 10:58:11 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.js | 10 +- components/auth-page-component.js | 1 - components/posts-page-component.js | 145 +++++++----------------- components/user-posts-page-component.js | 145 +++++++----------------- helpers.js | 69 +++++++++++ index.js | 9 +- 6 files changed, 163 insertions(+), 216 deletions(-) diff --git a/api.js b/api.js index b19ad29ac..dc08332ff 100644 --- a/api.js +++ b/api.js @@ -57,8 +57,11 @@ export function likePost({ token, postId }) { if (response.status === 401) { throw new Error("Нет авторизации"); } + if (response.status === 404) { + throw new Error("Пост не найден"); + } if (!response.ok) { - throw new Error(`Ошибка HTTP! статус: ${response.status}`); + throw new Error(`Ошибка лайка! статус: ${response.status}`); } return response.json(); }); @@ -75,8 +78,11 @@ export function dislikePost({ token, postId }) { if (response.status === 401) { throw new Error("Нет авторизации"); } + if (response.status === 404) { + throw new Error("Пост не найден"); + } if (!response.ok) { - throw new Error(`Ошибка HTTP! статус: ${response.status}`); + throw new Error(`Ошибка дизлайка! статус: ${response.status}`); } return response.json(); }); diff --git a/components/auth-page-component.js b/components/auth-page-component.js index 26e50bd10..4e72f6a64 100644 --- a/components/auth-page-component.js +++ b/components/auth-page-component.js @@ -153,7 +153,6 @@ export function renderAuthPageComponent({ appEl, setUser }) { renderForm(); }); - // Автофокус на первом поле const firstInput = appEl.querySelector('input'); if (firstInput) { firstInput.focus(); diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 5b11469c3..c33a82aaa 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -2,61 +2,21 @@ import { USER_POSTS_PAGE } from "../routes.js"; import { renderHeaderComponent } from "./header-component.js"; import { posts, goToPage, user } from "../index.js"; import { likePost, dislikePost } from "../api.js"; +import { formatDate, initLikeButtonHandlers } from "../helpers.js"; export function renderPostsPageComponent({ appEl }) { console.log("Актуальный список постов:", posts); - const formatDate = (createdAt) => { - const now = new Date(); - const postDate = new Date(createdAt); - const diffInSeconds = Math.floor((now - postDate) / 1000); - - if (diffInSeconds < 60) { - return 'только что'; - } else if (diffInSeconds < 3600) { - const minutes = Math.floor(diffInSeconds / 60); - return `${minutes} ${getMinutesText(minutes)} назад`; - } else if (diffInSeconds < 86400) { - const hours = Math.floor(diffInSeconds / 3600); - return `${hours} ${getHoursText(hours)} назад`; - } else if (diffInSeconds < 2592000) { - const days = Math.floor(diffInSeconds / 86400); - return `${days} ${getDaysText(days)} назад`; - } else { - const months = Math.floor(diffInSeconds / 2592000); - return `${months} ${getMonthsText(months)} назад`; - } - }; - - const getMinutesText = (minutes) => { - if (minutes === 1) return 'минуту'; - if (minutes >= 2 && minutes <= 4) return 'минуты'; - return 'минут'; - }; - - const getHoursText = (hours) => { - if (hours === 1) return 'час'; - if (hours >= 2 && hours <= 4) return 'часа'; - return 'часов'; - }; - - const getDaysText = (days) => { - if (days === 1) return 'день'; - if (days >= 2 && days <= 4) return 'дня'; - return 'дней'; - }; - - const getMonthsText = (months) => { - if (months === 1) return 'месяц'; - if (months >= 2 && months <= 4) return 'месяца'; - return 'месяцев'; - }; - const appHtml = `
    - ${posts.map(post => ` + ${posts.map(post => { + if (!post || !post.user) { + console.error("Некорректный пост:", post); + return ''; + } + return `
  • @@ -70,7 +30,7 @@ export function renderPostsPageComponent({ appEl }) { ${post.isLiked ? 'Убрать лайк' : 'Поставить лайк'}

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

    @@ -81,7 +41,7 @@ export function renderPostsPageComponent({ appEl }) { ${formatDate(post.createdAt)}

  • - `).join('')} + `}).join('')}
`; @@ -99,64 +59,37 @@ export function renderPostsPageComponent({ appEl }) { }); } - for (let likeButton of document.querySelectorAll(".like-button")) { - likeButton.addEventListener("click", () => { - const postId = likeButton.dataset.postId; - - if (!user) { - alert("Для лайков нужно авторизоваться"); - return; - } - - // Находим пост в массиве posts - const postIndex = posts.findIndex(p => p.id === postId); - if (postIndex === -1) { - console.error("Пост не найден:", postId); - alert("Ошибка: пост не найден"); - return; - } - - const post = posts[postIndex]; + initLikeButtonHandlers({ + appEl, + posts, + user, + onLikeUpdate: async (postId, postIndex) => { const token = `Bearer ${user.token}`; + const post = posts[postIndex]; - console.log("Текущий пост:", post); - console.log("isLiked:", post.isLiked); - console.log("Лайки:", post.likes); - - // Определяем текущее состояние лайка - const isCurrentlyLiked = post.isLiked; - - if (isCurrentlyLiked) { - // Убираем лайк - console.log("Убираем лайк с поста:", postId); - dislikePost({ token, postId }) - .then((updatedPost) => { - console.log("Лайк убран, обновленный пост:", updatedPost); - // Обновляем пост в массиве - posts[postIndex] = updatedPost; - // Перерисовываем компонент - renderPostsPageComponent({ appEl }); - }) - .catch((error) => { - console.error("Ошибка при снятии лайка:", error); - alert("Не удалось снять лайк: " + error.message); - }); - } else { - // Ставим лайк - console.log("Ставим лайк на пост:", postId); - likePost({ token, postId }) - .then((updatedPost) => { - console.log("Лайк поставлен, обновленный пост:", updatedPost); - // Обновляем пост в массиве - posts[postIndex] = updatedPost; - // Перерисовываем компонент - renderPostsPageComponent({ appEl }); - }) - .catch((error) => { - console.error("Ошибка при установке лайка:", error); - alert("Не удалось поставить лайк: " + error.message); - }); + try { + let response; + if (post.isLiked) { + response = await dislikePost({ token, postId }); + } else { + response = await likePost({ token, postId }); + } + + console.log("Ответ от API:", response); + + if (response && response.post) { + posts[postIndex] = response.post; + } else if (response) { + posts[postIndex] = response; + } else { + throw new Error("Пустой ответ от сервера"); + } + + renderPostsPageComponent({ appEl }); + } catch (error) { + console.error("Ошибка при лайке:", error); + alert("Не удалось обновить лайк: " + error.message); } - }); - } + } + }); } \ No newline at end of file diff --git a/components/user-posts-page-component.js b/components/user-posts-page-component.js index 603d9c2c2..ab3f964ad 100644 --- a/components/user-posts-page-component.js +++ b/components/user-posts-page-component.js @@ -1,9 +1,10 @@ import { renderHeaderComponent } from "./header-component.js"; import { posts, goToPage, user } from "../index.js"; import { likePost, dislikePost } from "../api.js"; +import { formatDate, initLikeButtonHandlers } from "../helpers.js"; export function renderUserPostsPageComponent({ appEl, userId }) { - const userPosts = posts.filter(post => post.user.id === userId); + const userPosts = posts.filter(post => post && post.user && post.user.id === userId); if (userPosts.length === 0) { appEl.innerHTML = ` @@ -20,52 +21,6 @@ export function renderUserPostsPageComponent({ appEl, userId }) { const userData = userPosts[0].user; - const formatDate = (createdAt) => { - const now = new Date(); - const postDate = new Date(createdAt); - const diffInSeconds = Math.floor((now - postDate) / 1000); - - if (diffInSeconds < 60) { - return 'только что'; - } else if (diffInSeconds < 3600) { - const minutes = Math.floor(diffInSeconds / 60); - return `${minutes} ${getMinutesText(minutes)} назад`; - } else if (diffInSeconds < 86400) { - const hours = Math.floor(diffInSeconds / 3600); - return `${hours} ${getHoursText(hours)} назад`; - } else if (diffInSeconds < 2592000) { - const days = Math.floor(diffInSeconds / 86400); - return `${days} ${getDaysText(days)} назад`; - } else { - const months = Math.floor(diffInSeconds / 2592000); - return `${months} ${getMonthsText(months)} назад`; - } - }; - - const getMinutesText = (minutes) => { - if (minutes === 1) return 'минуту'; - if (minutes >= 2 && minutes <= 4) return 'минуты'; - return 'минут'; - }; - - const getHoursText = (hours) => { - if (hours === 1) return 'час'; - if (hours >= 2 && hours <= 4) return 'часа'; - return 'часов'; - }; - - const getDaysText = (days) => { - if (days === 1) return 'день'; - if (days >= 2 && days <= 4) return 'дня'; - return 'дней'; - }; - - const getMonthsText = (months) => { - if (months === 1) return 'месяц'; - if (months >= 2 && months <= 4) return 'месяца'; - return 'месяцев'; - }; - const appHtml = `
@@ -74,7 +29,12 @@ export function renderUserPostsPageComponent({ appEl, userId }) {

${userData.name}

    - ${userPosts.map(post => ` + ${userPosts.map(post => { + if (!post || !post.user) { + console.error("Некорректный пост:", post); + return ''; + } + return `
  • @@ -84,7 +44,7 @@ export function renderUserPostsPageComponent({ appEl, userId }) { ${post.isLiked ? 'Убрать лайк' : 'Поставить лайк'}

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

    @@ -95,7 +55,7 @@ export function renderUserPostsPageComponent({ appEl, userId }) { ${formatDate(post.createdAt)}

  • - `).join('')} + `}).join('')}
`; @@ -105,62 +65,37 @@ export function renderUserPostsPageComponent({ appEl, userId }) { element: document.querySelector(".header-container"), }); - for (let likeButton of document.querySelectorAll(".like-button")) { - likeButton.addEventListener("click", () => { - const postId = likeButton.dataset.postId; - - if (!user) { - alert("Для лайков нужно авторизоваться"); - return; - } - - // Находим пост в основном массиве posts - const postIndex = posts.findIndex(p => p.id === postId); - if (postIndex === -1) { - console.error("Пост не найден:", postId); - alert("Ошибка: пост не найден"); - return; - } - - const post = posts[postIndex]; + initLikeButtonHandlers({ + appEl, + posts, + user, + onLikeUpdate: async (postId, postIndex) => { const token = `Bearer ${user.token}`; + const post = posts[postIndex]; - console.log("Текущий пост на странице пользователя:", post); - console.log("isLiked:", post.isLiked); - - const isCurrentlyLiked = post.isLiked; - - if (isCurrentlyLiked) { - // Убираем лайк - console.log("Убираем лайк с поста:", postId); - dislikePost({ token, postId }) - .then((updatedPost) => { - console.log("Лайк убран, обновленный пост:", updatedPost); - // Обновляем пост в основном массиве - posts[postIndex] = updatedPost; - // Перерисовываем страницу - renderUserPostsPageComponent({ appEl, userId }); - }) - .catch((error) => { - console.error("Ошибка при снятии лайка:", error); - alert("Не удалось снять лайк: " + error.message); - }); - } else { - // Ставим лайк - console.log("Ставим лайк на пост:", postId); - likePost({ token, postId }) - .then((updatedPost) => { - console.log("Лайк поставлен, обновленный пост:", updatedPost); - // Обновляем пост в основном массиве - posts[postIndex] = updatedPost; - // Перерисовываем страницу - renderUserPostsPageComponent({ appEl, userId }); - }) - .catch((error) => { - console.error("Ошибка при установке лайка:", error); - alert("Не удалось поставить лайк: " + error.message); - }); + try { + let response; + if (post.isLiked) { + response = await dislikePost({ token, postId }); + } else { + response = await likePost({ token, postId }); + } + + console.log("Ответ от API:", response); + + if (response && response.post) { + posts[postIndex] = response.post; + } else if (response) { + posts[postIndex] = response; + } else { + throw new Error("Пустой ответ от сервера"); + } + + renderUserPostsPageComponent({ appEl, userId }); + } catch (error) { + console.error("Ошибка при лайке:", error); + alert("Не удалось обновить лайк: " + error.message); } - }); - } + } + }); } \ No newline at end of file diff --git a/helpers.js b/helpers.js index 7dc7fa2a3..fb2479ab1 100644 --- a/helpers.js +++ b/helpers.js @@ -12,4 +12,73 @@ export function getUserFromLocalStorage(user) { export function removeUserFromLocalStorage(user) { window.localStorage.removeItem("user"); +} + +export function formatDate(createdAt) { + const now = new Date(); + const postDate = new Date(createdAt); + const diffInSeconds = Math.floor((now - postDate) / 1000); + + if (diffInSeconds < 60) { + return 'только что'; + } else if (diffInSeconds < 3600) { + const minutes = Math.floor(diffInSeconds / 60); + return `${minutes} ${getMinutesText(minutes)} назад`; + } else if (diffInSeconds < 86400) { + const hours = Math.floor(diffInSeconds / 3600); + return `${hours} ${getHoursText(hours)} назад`; + } else if (diffInSeconds < 2592000) { + const days = Math.floor(diffInSeconds / 86400); + return `${days} ${getDaysText(days)} назад`; + } else { + const months = Math.floor(diffInSeconds / 2592000); + return `${months} ${getMonthsText(months)} назад`; + } +} + +function getMinutesText(minutes) { + if (minutes === 1) return 'минуту'; + if (minutes >= 2 && minutes <= 4) return 'минуты'; + return 'минут'; +} + +function getHoursText(hours) { + if (hours === 1) return 'час'; + if (hours >= 2 && hours <= 4) return 'часа'; + return 'часов'; +} + +function getDaysText(days) { + if (days === 1) return 'день'; + if (days >= 2 && days <= 4) return 'дня'; + return 'дней'; +} + +function getMonthsText(months) { + if (months === 1) return 'месяц'; + if (months >= 2 && months <= 4) return 'месяца'; + return 'месяцев'; +} + +export function initLikeButtonHandlers({ appEl, posts, user, onLikeUpdate }) { + const likeButtons = appEl.querySelectorAll(".like-button"); + + likeButtons.forEach(likeButton => { + likeButton.addEventListener("click", async () => { + const postId = likeButton.dataset.postId; + + if (!user) { + alert("Для лайков нужно авторизоваться"); + return; + } + + const postIndex = posts.findIndex(p => p.id === postId); + if (postIndex === -1) { + console.error("Пост не найден:", postId); + return; + } + + await onLikeUpdate(postId, postIndex); + }); + }); } \ No newline at end of file diff --git a/index.js b/index.js index 3fcf3a498..6343e75df 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,8 @@ export let user = getUserFromLocalStorage(); export let page = null; export let posts = []; +let data = {}; + const getToken = () => { const token = user ? `Bearer ${user.token}` : undefined; return token; @@ -32,7 +34,11 @@ export const logout = () => { goToPage(POSTS_PAGE); }; -export const goToPage = (newPage, data) => { +export const goToPage = (newPage, newData) => { + if (newData) { + data = newData; + } + if ( [ POSTS_PAGE, @@ -161,7 +167,6 @@ const renderApp = () => { } }; -// Запускаем приложение document.addEventListener('DOMContentLoaded', () => { goToPage(POSTS_PAGE); }); \ No newline at end of file