From db08809b514daba4ca3845599e0dd0430e7dece6 Mon Sep 17 00:00:00 2001 From: Kharybin Ruslan Date: Mon, 1 Sep 2025 18:55:58 +0300 Subject: [PATCH 1/3] Lenta-postov --- api.js | 142 +++++++++++++-- components/add-post-page-component.js | 61 ++++++- components/auth-page-component.js | 61 +++++-- components/posts-page-component.js | 223 ++++++++++++++---------- components/upload-image-component.js | 20 ++- components/user-posts-page-component.js | 156 +++++++++++++++++ index.js | 89 ++++++++-- styles.css | 15 ++ 8 files changed, 627 insertions(+), 140 deletions(-) create mode 100644 components/user-posts-page-component.js diff --git a/api.js b/api.js index 123a9e3b6..6470d767f 100644 --- a/api.js +++ b/api.js @@ -1,7 +1,7 @@ // Замени на свой, чтобы получить независимый от других набор данных. // "боевая" версия инстапро лежит в ключе prod const personalKey = "prod"; -const baseHost = "https://webdev-hw-api.vercel.app"; +const baseHost = "https://wedev-api.sky.pro"; const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`; export function getPosts({ token }) { @@ -23,25 +23,48 @@ export function getPosts({ token }) { }); } +export function getUserPosts({ userId, token }) { + // Получаем все посты и фильтруем по userId на клиенте + return getPosts({ token }).then((allPosts) => { + return allPosts.filter((post) => post.user.id === userId); + }); +} + export function registerUser({ login, password, name, imageUrl }) { - return fetch(baseHost + "/api/user", { + const requestBody = { + login, + password, + name, + }; + + // Добавляем imageUrl только если он есть + if (imageUrl) { + requestBody.imageUrl = imageUrl; + } + + return fetch("https://wedev-api.sky.pro/api/user", { method: "POST", - body: JSON.stringify({ - login, - password, - name, - imageUrl, - }), + body: JSON.stringify(requestBody), }).then((response) => { if (response.status === 400) { - throw new Error("Такой пользователь уже существует"); + return response.json().then((errorData) => { + throw new Error("Такой пользователь уже существует"); + }); + } + if (response.status === 201) { + return response.json(); + } + if (!response.ok) { + throw new Error( + `Ошибка при регистрации: ${response.status} ${response.statusText}` + ); } return response.json(); }); } export function loginUser({ login, password }) { - return fetch(baseHost + "/api/user/login", { + return fetch("https://wedev-api.sky.pro/api/user/login", { method: "POST", body: JSON.stringify({ login, @@ -49,7 +72,17 @@ export function loginUser({ login, password }) { }), }).then((response) => { if (response.status === 400) { - throw new Error("Неверный логин или пароль"); + return response.json().then((errorData) => { + throw new Error("Неверный логин или пароль"); + }); + } + if (response.status === 201) { + return response.json(); + } + if (!response.ok) { + throw new Error( + `Ошибка при входе: ${response.status} ${response.statusText}` + ); } return response.json(); }); @@ -60,10 +93,95 @@ export function uploadImage({ file }) { const data = new FormData(); data.append("file", file); - return fetch(baseHost + "/api/upload/image", { + return fetch("https://wedev-api.sky.pro/api/upload/image", { method: "POST", body: data, }).then((response) => { + if (!response.ok) { + throw new Error( + `Ошибка при загрузке изображения: ${response.status} ${response.statusText}` + ); + } + return response.json(); + }); +} + +// Добавить новый пост +export function addPost({ description, imageUrl, token }) { + return fetch(postsHost, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: token, + }, + body: JSON.stringify({ + description, + imageUrl, + }), + }).then((response) => { + if (response.status === 401) { + throw new Error("Нет авторизации"); + } + if (response.status === 400) { + throw new Error("Неверные данные поста"); + } + if (!response.ok) { + throw new Error("Ошибка при добавлении поста"); + } + return response.json(); + }); +} + +// Поставить лайк +export function likePost({ postId, token }) { + 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("Ошибка при постановке лайка"); + } + return response.json(); + }); +} + +// Убрать лайк +export function dislikePost({ postId, token }) { + 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("Ошибка при удалении лайка"); + } + return response.json(); + }); +} + +// Удалить пост +export function deletePost({ postId, token }) { + return fetch(`${postsHost}/${postId}`, { + method: "DELETE", + headers: { + Authorization: token, + }, + }).then((response) => { + if (response.status === 401) { + throw new Error("Нет авторизации"); + } + if (!response.ok) { + throw new Error("Ошибка при удалении поста"); + } return response.json(); }); } diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 8277f093b..8af90467c 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -1,20 +1,71 @@ +import { renderHeaderComponent } from "./header-component.js"; +import { renderUploadImageComponent } from "./upload-image-component.js"; + export function renderAddPostPageComponent({ appEl, onAddPostClick }) { + let imageUrl = ""; + const render = () => { - // @TODO: Реализовать страницу добавления поста const appHtml = `
- Cтраница добавления поста - +
+

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

+
+
+ +
+ +
+
`; appEl.innerHTML = appHtml; + // Рендерим заголовок страницы + renderHeaderComponent({ + element: document.querySelector(".header-container"), + }); + + // Рендерим компонент загрузки изображения + const uploadImageContainer = appEl.querySelector(".upload-image-container"); + renderUploadImageComponent({ + element: uploadImageContainer, + onImageUrlChange(newImageUrl) { + imageUrl = newImageUrl; + }, + }); + + // Функция для отображения ошибок + const setError = (message) => { + appEl.querySelector(".form-error").textContent = message; + }; + document.getElementById("add-button").addEventListener("click", () => { + setError(""); + + const description = document + .getElementById("description-input") + .value.trim(); + + if (!description) { + setError("Введите описание фотографии"); + return; + } + + if (!imageUrl) { + setError("Не выбрана фотография"); + return; + } + onAddPostClick({ - description: "Описание картинки", - imageUrl: "https://image.png", + description, + imageUrl, }); }); }; diff --git a/components/auth-page-component.js b/components/auth-page-component.js index 0e93530ec..fe82640e5 100644 --- a/components/auth-page-component.js +++ b/components/auth-page-component.js @@ -102,16 +102,22 @@ export function renderAuthPageComponent({ appEl, setUser }) { if (isLoginMode) { // Обработка входа - const login = document.getElementById("login-input").value; + const login = document.getElementById("login-input").value.trim(); const password = document.getElementById("password-input").value; + // Проверяем, заполнены ли поля + if (!login && !password) { + setError("Введите логин и пароль"); + return; + } + if (!login) { - alert("Введите логин"); + setError("Введите логин"); return; } if (!password) { - alert("Введите пароль"); + setError("Введите пароль"); return; } @@ -120,41 +126,68 @@ export function renderAuthPageComponent({ appEl, setUser }) { 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 login = document.getElementById("login-input").value.trim(); + const name = document.getElementById("name-input").value.trim(); const password = document.getElementById("password-input").value; + // Проверяем, заполнены ли обязательные поля + const emptyFields = []; + if (!name) { - alert("Введите имя"); - return; + emptyFields.push("имя"); } if (!login) { - alert("Введите логин"); - return; + emptyFields.push("логин"); } if (!password) { - alert("Введите пароль"); + emptyFields.push("пароль"); + } + + // Если есть пустые поля, показываем общую ошибку + if (emptyFields.length > 0) { + if (emptyFields.length === 1) { + setError(`Введите ${emptyFields[0]}`); + } else if (emptyFields.length === 2) { + setError(`Введите ${emptyFields[0]} и ${emptyFields[1]}`); + } else { + setError( + `Введите ${emptyFields.slice(0, -1).join(", ")} и ${ + emptyFields[emptyFields.length - 1] + }` + ); + } + return; + } + + // Проверяем длину логина + if (login.length < 3) { + setError("Логин должен содержать минимум 3 символа"); return; } - if (!imageUrl) { - alert("Не выбрана фотография"); + // Проверяем длину пароля + if (password.length < 6) { + setError("Пароль должен содержать минимум 6 символов"); return; } + // Убираем обязательность изображения для тестирования + // if (!imageUrl) { + // setError("Не выбрана фотография"); + // return; + // } + registerUser({ login, password, name, imageUrl }) .then((user) => { setUser(user.user); }) .catch((error) => { - console.warn(error); setError(error.message); }); } diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 662ccdbdc..0863d71a0 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -1,97 +1,75 @@ 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, deletePost } from "../api.js"; export function renderPostsPageComponent({ appEl }) { - // @TODO: реализовать рендер постов из api - console.log("Актуальный список постов:", posts); + // Функция для форматирования даты + const formatDate = (dateString) => { + const date = new Date(dateString); + const now = new Date(); + const diffInMinutes = Math.floor((now - date) / (1000 * 60)); + + if (diffInMinutes < 1) return "только что"; + if (diffInMinutes < 60) return `${diffInMinutes} минут назад`; + + const diffInHours = Math.floor(diffInMinutes / 60); + if (diffInHours < 24) return `${diffInHours} часов назад`; + + const diffInDays = Math.floor(diffInHours / 24); + if (diffInDays < 7) return `${diffInDays} дней назад`; + + return date.toLocaleDateString("ru-RU"); + }; + + // Генерируем HTML для постов + const postsHtml = posts + .map( + (post) => ` +
  • +
    + +

    ${post.user.name}

    +
    +
    + +
    +
    + +

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

    +
    +

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

    + + ${ + user && user.id === post.user.id + ? ` + + ` + : "" + } +
  • + ` + ) + .join(""); - /** - * @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" - * можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow - */ const appHtml = ` -
    -
    - -
    `; +
    +
    + +
    `; appEl.innerHTML = appHtml; @@ -99,6 +77,7 @@ export function renderPostsPageComponent({ appEl }) { element: document.querySelector(".header-container"), }); + // Обработчики для клика по пользователю for (let userEl of document.querySelectorAll(".post-header")) { userEl.addEventListener("click", () => { goToPage(USER_POSTS_PAGE, { @@ -106,4 +85,72 @@ export function renderPostsPageComponent({ appEl }) { }); }); } + + // Обработчики для лайков + for (let likeButton of document.querySelectorAll(".like-button")) { + likeButton.addEventListener("click", () => { + const postId = likeButton.dataset.postId; + const post = posts.find((p) => p.id === postId); + + if (!user) { + alert("Для лайка нужно авторизоваться"); + return; + } + + if (post.isLiked) { + // Убираем лайк + dislikePost({ postId, token: `Bearer ${user.token}` }) + .then((response) => { + // Обновляем пост в списке + const index = posts.findIndex((p) => p.id === postId); + if (index !== -1) { + posts[index] = response.post; + renderPostsPageComponent({ appEl }); + } + }) + .catch((error) => { + console.error(error); + alert(error.message); + }); + } else { + // Ставим лайк + likePost({ postId, token: `Bearer ${user.token}` }) + .then((response) => { + // Обновляем пост в списке + const index = posts.findIndex((p) => p.id === postId); + if (index !== -1) { + posts[index] = response.post; + renderPostsPageComponent({ appEl }); + } + }) + .catch((error) => { + console.error(error); + alert(error.message); + }); + } + }); + } + + // Обработчики для удаления постов + for (let deleteButton of document.querySelectorAll(".delete-button")) { + deleteButton.addEventListener("click", () => { + const postId = deleteButton.dataset.postId; + + if (confirm("Вы уверены, что хотите удалить этот пост?")) { + deletePost({ postId, token: `Bearer ${user.token}` }) + .then(() => { + // Удаляем пост из списка + const index = posts.findIndex((p) => p.id === postId); + if (index !== -1) { + posts.splice(index, 1); + renderPostsPageComponent({ appEl }); + } + }) + .catch((error) => { + console.error(error); + alert(error.message); + }); + } + }); + } } diff --git a/components/upload-image-component.js b/components/upload-image-component.js index e2cdf2018..a07727b82 100644 --- a/components/upload-image-component.js +++ b/components/upload-image-component.js @@ -19,7 +19,7 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { /** * Функция рендеринга компонента. - * Отображает интерфейс компонента в зависимости от состояния: + * Отображает интерфейс компонента в зависимости от состояния: * либо форма выбора файла, либо превью загруженного изображения с кнопкой замены. */ const render = () => { @@ -55,13 +55,19 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { 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(); // Перерисовываем компонент с новым состоянием - }); + uploadImage({ file }) + .then(({ fileUrl }) => { + imageUrl = fileUrl; // Сохраняем URL загруженного изображения + onImageUrlChange(imageUrl); // Уведомляем о изменении URL изображения + render(); // Перерисовываем компонент с новым состоянием + }) + .catch((error) => { + alert(error.message); + labelEl.removeAttribute("disabled"); + labelEl.textContent = "Выберите фото"; + }); } }); diff --git a/components/user-posts-page-component.js b/components/user-posts-page-component.js new file mode 100644 index 000000000..a8f4f9d5e --- /dev/null +++ b/components/user-posts-page-component.js @@ -0,0 +1,156 @@ +import { USER_POSTS_PAGE } from "../routes.js"; +import { renderHeaderComponent } from "./header-component.js"; +import { posts, goToPage, user } from "../index.js"; +import { likePost, dislikePost, deletePost } from "../api.js"; + +export function renderUserPostsPageComponent({ appEl, userId }) { + // Функция для форматирования даты + const formatDate = (dateString) => { + const date = new Date(dateString); + const now = new Date(); + const diffInMinutes = Math.floor((now - date) / (1000 * 60)); + + if (diffInMinutes < 1) return "только что"; + if (diffInMinutes < 60) return `${diffInMinutes} минут назад`; + + const diffInHours = Math.floor(diffInMinutes / 60); + if (diffInHours < 24) return `${diffInHours} часов назад`; + + const diffInDays = Math.floor(diffInHours / 24); + if (diffInDays < 7) return `${diffInDays} дней назад`; + + return date.toLocaleDateString("ru-RU"); + }; + + // Генерируем HTML для постов + const postsHtml = posts + .map( + (post) => ` +
  • +
    + +

    ${post.user.name}

    +
    +
    + +
    +
    + +

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

    +
    +

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

    + + ${ + user && user.id === post.user.id + ? ` + + ` + : "" + } +
  • + ` + ) + .join(""); + + const 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, + }); + }); + } + + // Обработчики для лайков + for (let likeButton of document.querySelectorAll(".like-button")) { + likeButton.addEventListener("click", () => { + const postId = likeButton.dataset.postId; + const post = posts.find((p) => p.id === postId); + + if (!user) { + alert("Для лайка нужно авторизоваться"); + return; + } + + if (post.isLiked) { + // Убираем лайк + dislikePost({ postId, token: `Bearer ${user.token}` }) + .then((response) => { + // Обновляем пост в списке + const index = posts.findIndex((p) => p.id === postId); + if (index !== -1) { + posts[index] = response.post; + renderUserPostsPageComponent({ appEl, userId }); + } + }) + .catch((error) => { + console.error(error); + alert(error.message); + }); + } else { + // Ставим лайк + likePost({ postId, token: `Bearer ${user.token}` }) + .then((response) => { + // Обновляем пост в списке + const index = posts.findIndex((p) => p.id === postId); + if (index !== -1) { + posts[index] = response.post; + renderUserPostsPageComponent({ appEl, userId }); + } + }) + .catch((error) => { + console.error(error); + alert(error.message); + }); + } + }); + } + + // Обработчики для удаления постов + for (let deleteButton of document.querySelectorAll(".delete-button")) { + deleteButton.addEventListener("click", () => { + const postId = deleteButton.dataset.postId; + + if (confirm("Вы уверены, что хотите удалить этот пост?")) { + deletePost({ postId, token: `Bearer ${user.token}` }) + .then(() => { + // Удаляем пост из списка + const index = posts.findIndex((p) => p.id === postId); + if (index !== -1) { + posts.splice(index, 1); + renderUserPostsPageComponent({ appEl, userId }); + } + }) + .catch((error) => { + console.error(error); + alert(error.message); + }); + } + }); + } +} diff --git a/index.js b/index.js index 7f1817c75..c657900d4 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 { @@ -9,6 +9,7 @@ import { USER_POSTS_PAGE, } from "./routes.js"; import { renderPostsPageComponent } from "./components/posts-page-component.js"; +import { renderUserPostsPageComponent } from "./components/user-posts-page-component.js"; import { renderLoadingPageComponent } from "./components/loading-page-component.js"; import { getUserFromLocalStorage, @@ -19,12 +20,32 @@ import { export let user = getUserFromLocalStorage(); export let page = null; export let posts = []; +export let pageData = null; const getToken = () => { const token = user ? `Bearer ${user.token}` : undefined; return token; }; +// Проверяем авторизацию при загрузке приложения +const checkAuth = () => { + if (user && user.token) { + // Если есть токен, проверяем его валидность, делая запрос к API + return getPosts({ token: getToken() }) + .then(() => { + // Если запрос прошел успешно, пользователь авторизован + return true; + }) + .catch(() => { + // Если запрос не прошел, удаляем пользователя из localStorage + user = null; + removeUserFromLocalStorage(); + return false; + }); + } + return Promise.resolve(false); +}; + export const logout = () => { user = null; removeUserFromLocalStorage(); @@ -35,6 +56,7 @@ export const logout = () => { * Включает страницу приложения */ export const goToPage = (newPage, data) => { + pageData = data; if ( [ POSTS_PAGE, @@ -62,16 +84,40 @@ export const goToPage = (newPage, data) => { }) .catch((error) => { console.error(error); - goToPage(POSTS_PAGE); + if (error.message === "Нет авторизации") { + // Если нет авторизации, переходим на страницу авторизации + goToPage(AUTH_PAGE); + } else { + // Если другая ошибка, показываем пустую страницу постов + page = POSTS_PAGE; + posts = []; + renderApp(); + } }); } if (newPage === USER_POSTS_PAGE) { - // @@TODO: реализовать получение постов юзера из API - console.log("Открываю страницу пользователя: ", data.userId); - page = USER_POSTS_PAGE; - posts = []; - return renderApp(); + page = LOADING_PAGE; + renderApp(); + + return getUserPosts({ userId: data.userId, token: getToken() }) + .then((userPosts) => { + page = USER_POSTS_PAGE; + posts = userPosts; + renderApp(); + }) + .catch((error) => { + console.error(error); + if (error.message === "Нет авторизации") { + // Если нет авторизации, переходим на страницу авторизации + goToPage(AUTH_PAGE); + } else { + // Если другая ошибка, показываем пустую страницу постов + page = USER_POSTS_PAGE; + posts = []; + renderApp(); + } + }); } page = newPage; @@ -110,9 +156,16 @@ const renderApp = () => { return renderAddPostPageComponent({ appEl, onAddPostClick({ description, imageUrl }) { - // @TODO: реализовать добавление поста в API - console.log("Добавляю пост...", { description, imageUrl }); - goToPage(POSTS_PAGE); + addPost({ description, imageUrl, token: getToken() }) + .then((response) => { + // Добавляем новый пост в начало списка + posts.unshift(response.post); + goToPage(POSTS_PAGE); + }) + .catch((error) => { + console.error(error); + alert(error.message); + }); }, }); } @@ -124,10 +177,18 @@ const renderApp = () => { } if (page === USER_POSTS_PAGE) { - // @TODO: реализовать страницу с фотографиями отдельного пользвателя - appEl.innerHTML = "Здесь будет страница фотографий пользователя"; - return; + return renderUserPostsPageComponent({ + appEl, + userId: pageData?.userId, + }); } }; -goToPage(POSTS_PAGE); +// Инициализация приложения с проверкой авторизации +checkAuth().then((isAuthenticated) => { + if (isAuthenticated) { + goToPage(POSTS_PAGE); + } else { + goToPage(AUTH_PAGE); + } +}); diff --git a/styles.css b/styles.css index 57bc08b16..a23289da5 100644 --- a/styles.css +++ b/styles.css @@ -196,3 +196,18 @@ width: 100%; text-align: center; } + +.delete-button { + background-color: #ff4444; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + margin-top: 10px; +} + +.delete-button:hover { + background-color: #cc0000; +} From f62d5974638c173f2b0e78d6ee3bc6d3c2c5ef90 Mon Sep 17 00:00:00 2001 From: Kharybin Ruslan Date: Mon, 1 Sep 2025 19:21:33 +0300 Subject: [PATCH 2/3] finished project --- api.js | 50 +++++-- components/header-component.js | 25 +++- components/posts-page-component.js | 31 ++-- components/user-posts-page-component.js | 31 ++-- index.js | 9 +- styles.css | 185 ++++++++++++++++++++++++ 6 files changed, 294 insertions(+), 37 deletions(-) diff --git a/api.js b/api.js index 6470d767f..cba2d1bd1 100644 --- a/api.js +++ b/api.js @@ -24,10 +24,22 @@ export function getPosts({ token }) { } export function getUserPosts({ userId, token }) { - // Получаем все посты и фильтруем по userId на клиенте - return getPosts({ token }).then((allPosts) => { - return allPosts.filter((post) => post.user.id === 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) => { + return data.posts; + }); } export function registerUser({ login, password, name, imageUrl }) { @@ -108,22 +120,40 @@ export function uploadImage({ file }) { // Добавить новый пост export function addPost({ description, imageUrl, token }) { + const requestBody = { + description, + imageUrl, + }; + + console.log("API запрос:", { + url: postsHost, + method: "POST", + headers: { + Authorization: token, + }, + body: requestBody, + }); + return fetch(postsHost, { method: "POST", headers: { - "Content-Type": "application/json", Authorization: token, }, - body: JSON.stringify({ - description, - imageUrl, - }), + body: JSON.stringify(requestBody), }).then((response) => { + console.log("Ответ API:", response.status, response.statusText); + if (response.status === 401) { throw new Error("Нет авторизации"); } if (response.status === 400) { - throw new Error("Неверные данные поста"); + return response.json().then((errorData) => { + console.log("Детали ошибки 400:", errorData); + throw new Error("Неверные данные поста"); + }); + } + if (response.status === 201) { + return response.json(); } if (!response.ok) { throw new Error("Ошибка при добавлении поста"); diff --git a/components/header-component.js b/components/header-component.js index e733a39dc..cb1b111b2 100644 --- a/components/header-component.js +++ b/components/header-component.js @@ -4,7 +4,7 @@ import { ADD_POSTS_PAGE, AUTH_PAGE, POSTS_PAGE } from "../routes.js"; /** * Компонент заголовка страницы. * Этот компонент отображает шапку страницы с логотипом, кнопкой добавления постов/входа и кнопкой выхода (если пользователь авторизован). - * + * * @param {HTMLElement} params.element - HTML-элемент, в который будет рендериться заголовок. * @returns {HTMLElement} Возвращает элемент заголовка после рендеринга. */ @@ -14,17 +14,19 @@ export function renderHeaderComponent({ element }) { */ element.innerHTML = ` @@ -46,11 +48,20 @@ export function renderHeaderComponent({ element }) { }); /** - * Обработчик клика по логотипу. + * Обработчик клика и нажатия клавиш по логотипу. * Перенаправляет на страницу с постами. */ - element.querySelector(".logo").addEventListener("click", () => { + const logoElement = element.querySelector(".logo"); + const handleLogoAction = () => { goToPage(POSTS_PAGE); + }; + + logoElement.addEventListener("click", handleLogoAction); + logoElement.addEventListener("keydown", (event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + handleLogoAction(); + } }); /** diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 0863d71a0..5e92f1782 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -3,6 +3,13 @@ import { renderHeaderComponent } from "./header-component.js"; import { posts, goToPage, user } from "../index.js"; import { likePost, dislikePost, deletePost } from "../api.js"; +// Функция для экранирования HTML +function escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; +} + export function renderPostsPageComponent({ appEl }) { // Функция для форматирования даты const formatDate = (dateString) => { @@ -27,26 +34,30 @@ export function renderPostsPageComponent({ appEl }) { .map( (post) => `
  • -
    - -

    ${post.user.name}

    +
    + Фото пользователя +

    ${escapeHtml(post.user.name)}

    - + Фото поста
    -

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

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

  • -
    - -

    ${post.user.name}

    +
    + Фото пользователя +

    ${escapeHtml(post.user.name)}

    - + Фото поста
    -

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

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