From dfbef2293c1076a25fe48279b6bdae30307ce34a Mon Sep 17 00:00:00 2001 From: Maxim_Parfenev Date: Sat, 3 Jan 2026 11:46:41 +0300 Subject: [PATCH 1/8] =?UTF-8?q?=D0=9D=D1=83=D0=BB=D0=B5=D0=B2=D0=BE=D0=B9?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82=20=D0=9A=D1=83=D1=80?= =?UTF-8?q?=D1=81=D0=BE=D0=B2=D0=BE=D0=B9=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=8B,=20=D0=B8=D1=81=D1=85=D0=BE=D0=B4=D0=BD=D0=B0=D1=8F=20?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BA=D0=B0=20=D1=84=D0=BE=D1=80=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BE=D1=82=20Mielilon/webdev-cw-instapro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From e17bea54c95d92ec64322c83d97342e6639aff22 Mon Sep 17 00:00:00 2001 From: Maxim_Parfenev Date: Sat, 3 Jan 2026 11:49:14 +0300 Subject: [PATCH 2/8] =?UTF-8?q?=D0=9D=D1=83=D0=BB=D0=B5=D0=B2=D0=BE=D0=B9?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82=20=D0=9A=D1=83=D1=80?= =?UTF-8?q?=D1=81=D0=BE=D0=B2=D0=BE=D0=B9=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=8B,=20=D0=B8=D1=81=D1=85=D0=BE=D0=B4=D0=BD=D0=B0=D1=8F=20?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BA=D0=B0=20=D1=84=D0=BE=D1=80=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BE=D1=82=20Mielilon/webdev-cw-instapro?= 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..669612ab9 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ MVP социальной сети для обмена фотографиями ## Первоначальная оценка -ХХХХ часов +Много много часов ## Фактически затраченное время YYYY часов + From 1d1f2e824465f7230dfe75ff1465f92b2c6729fe Mon Sep 17 00:00:00 2001 From: Maxim_Parfenev Date: Mon, 5 Jan 2026 11:40:29 +0300 Subject: [PATCH 3/8] =?UTF-8?q?=D0=A0=D0=B0=D0=B7=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=BB=D1=81=D1=8F=20=D0=B2=20=D0=BA=D0=BE=D0=B4=D0=B5,=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=81=D1=82=D1=80=D0=BE=D0=B8=D0=BB=20=D0=BF=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD.=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=20=D0=BF=D0=B5=D1=80=D0=B2=D1=8B=D0=B9=20TOD?= =?UTF-8?q?O=20-=20=20=D1=80=D0=B5=D0=BD=D0=B4=D0=B5=D1=80=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D0=B2=20=D0=B8=D0=B7=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 85 +++++++++++++++++++++ components/posts-page-component.js | 118 +++++++++-------------------- 2 files changed, 119 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 669612ab9..c8c48c13c 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,88 @@ MVP социальной сети для обмена фотографиями YYYY часов +Сhecklist @TODO + +index.js +1) // @@TODO: реализовать получение постов юзера из API + +2) // @TODO: реализовать добавление поста в API + +3) // @TODO: реализовать страницу с фотографиями отдельного пользвателя + +add-post-page-component.js +4) // @TODO: Реализовать страницу добавления поста + +posts-page-component.js +5) // @TODO: реализовать рендер постов из api - ГОТОВО + +6) @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow + +Анализ архитектуры работы и последовательности действий + +Структура проекта +Project/ +|- api.js - работа с сервером (запросы GET, POST, загрузка файлов). +|- favicon.ico +|- helpers.js - вспомогательные функции (работа с LocalStorage для сохранения данных пользователя) +|- index.html - оболочка +|- index.js - управление рендером и навигацией @TODO #1-3 +|- README.md +|- routes.js - маршрутизатор +|- styles.css +|- ui-kit.css - стили кнопок инпутов и шрифтов +|- assets/ +| |- fonts/ +| | |- stratosskyengweb-medium.woff2 +| | |- stratosskyengweb-regular.woff2 +| |- images/ +| |- like-active.svg +| |- like-not-active.svg +|── components/ + |- add-post-page-component.js - создание нового поста @TODO #4 + |- auth-page-component.js - вход и регистрация + |- header-component.js - шапка (навигация, логин, кнопки входа и выхода) + |- loading-page-component.js - спинер загрузки данных с сервера + |- posts-page-component.js - главная страница, рендер постов @TODO #5 и 6 + |- upload-image-component.js - загрузка фоток + + КАК ЭТО РАБОТАЕТ + +1) index.html запускает index.js. +2) он в свою очередь определяет, на какой странице мы находимся (переменная page). Это либо: +- AUTH_PAGE - страница авторизации. +- ADD_POSTS_PAGE - добавление поста, +- POSTS_PAGE, основная страница ну или +- USER_POSTS_PAGE - профиль пользователя. +3) Если мы на POSTS_PAGE или USER_POSTS_PAGE то нужны данные пользователя или сами посты, тогда вызывается функция из api.js. +Пока мы ее ждем запускается LOADING_PAGE - спинер +4) Когда мы их получили, index.js вызывает нужный нам component/ +- AUTH_PAGE - renderAuthPageComponent +- ADD_POSTS_PAGE - renderAddPostPageComponent @TODO #2 +- POSTS_PAGE - renderPostsPageComponent +- USER_POSTS_PAGE - здесь заглушка @TODO #1 и 3 +5) И он уже генерирует HTML-строку и вставляет её в appEl.innerHTML внутри оболочки index.html. + +ИТАК ПЛАН РАБОТЫ +1) Пока лента статична я не увижу динамику Оживить ленту в posts-page-component.js +@TODO: реализовать рендер постов из api + +Удаляем статичный HTML сохранив шаблон post. Добавляем map для связи с массивом posts который приходит из index.js и превращаем массив в строку HTML. + + + + + + +2) Профиль пользователя: +index.js +- @TODO: реализовать получение постов юзера из API +- @TODO: реализовать страницу с фотографиями отдельного пользвателя + +3) Интерфейс создания поста: +add-post-page-component.js +@TODO: Реализовать страницу добавления поста + +4) Теперь публикация. Возвращаемся в index.js - @TODO: реализовать добавление поста в API + +5) И в конце когда все будет работать мы можем внести правку в posts-page-component.js @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 662ccdbdc..3ece07276 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -1,104 +1,54 @@ import { USER_POSTS_PAGE } from "../routes.js"; import { renderHeaderComponent } from "./header-component.js"; +// Имортируем массив данных 'posts' и функцию навигации 'goToPage' import { posts, goToPage } from "../index.js"; export function renderPostsPageComponent({ appEl }) { - // @TODO: реализовать рендер постов из api - console.log("Актуальный список постов:", posts); + // Удоляем статику + const postsHtml = posts.map((post) => { + return ` +
  • +
    + +

    ${post.user.name}

    +
    +
    + +
    +
    + +

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

    +
    +

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

    + +
  • `; + }).join(""); // Превращаем массив строк в одну сплошную HTML-строку - /** - * @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" - * можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow - */ + // Вставляем сгенерированный список в общую оболочку страницы const appHtml = `
      -
    • -
      - -

      Иван Иваныч

      -
      -
      - -
      -
      - -

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

      -
      -

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

      - -
    • -
    • -
      - -

      Варварва Н.

      -
      - - -
      - -
      -
      - -

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

      -
      -

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

      - -
    • -
    • -
      - -

      Варварва Н.

      -
      - - -
      - -
      -
      - -

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

      -
      -

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

      - -
    • -
    + ${postsHtml}
    `; appEl.innerHTML = appHtml; + // Шапка одна для всех страниц renderHeaderComponent({ element: document.querySelector(".header-container"), }); + // Обработчик кликов на заголовки постов + // goToPage при клике на заголовок дожны попасть в профиль автора for (let userEl of document.querySelectorAll(".post-header")) { userEl.addEventListener("click", () => { goToPage(USER_POSTS_PAGE, { @@ -106,4 +56,4 @@ export function renderPostsPageComponent({ appEl }) { }); }); } -} +} \ No newline at end of file From 92f0967d6e5c55197c66b07b7705a72966f23fdf Mon Sep 17 00:00:00 2001 From: Maxim_Parfenev Date: Mon, 5 Jan 2026 23:09:22 +0300 Subject: [PATCH 4/8] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=BE=20-=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=20=D1=8E=D0=B7=D0=B5=D1=80=D0=B0=20=D0=B8=D0=B7=20API=20-=20?= =?UTF-8?q?=D1=80=D0=B5=D0=BD=D0=B4=D0=B5=D1=80=20=D0=BF=D1=80=D0=BE=D1=84?= =?UTF-8?q?=D0=B8=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++------- api.js | 19 +++++++++++++++++++ index.js | 41 ++++++++++++++++++++++++++++++++--------- 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c8c48c13c..b00f77fef 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ YYYY часов Сhecklist @TODO index.js -1) // @@TODO: реализовать получение постов юзера из API +1) // @@TODO: реализовать получение постов юзера из API ГОТОВО 2) // @TODO: реализовать добавление поста в API -3) // @TODO: реализовать страницу с фотографиями отдельного пользвателя +3) // @TODO: реализовать страницу с фотографиями отдельного пользвателя ГОТОВО add-post-page-component.js 4) // @TODO: Реализовать страницу добавления поста @@ -78,16 +78,16 @@ Project/ Удаляем статичный HTML сохранив шаблон post. Добавляем map для связи с массивом posts который приходит из index.js и превращаем массив в строку HTML. - - - - - 2) Профиль пользователя: index.js - @TODO: реализовать получение постов юзера из API - @TODO: реализовать страницу с фотографиями отдельного пользвателя +Добявляем в api.js функцию getUserPosts для получения постов от конкретного пользователя и отправляем ее в index.js + +Меняем логику получения постов для USER_POSTS_PAGE + + 3) Интерфейс создания поста: add-post-page-component.js @TODO: Реализовать страницу добавления поста diff --git a/api.js b/api.js index 123a9e3b6..0ae4e70b1 100644 --- a/api.js +++ b/api.js @@ -23,6 +23,25 @@ export function getPosts({ token }) { }); } +// Функция получения постов конкретного пользователя +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) => { + return data.posts; + }); +} + export function registerUser({ login, password, name, imageUrl }) { return fetch(baseHost + "/api/user", { method: "POST", diff --git a/index.js b/index.js index 7f1817c75..783dbf6f9 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ -import { getPosts } from "./api.js"; +// Импортируем новую функцию getUserPosts +import { getPosts, getUserPosts } from "./api.js"; import { renderAddPostPageComponent } from "./components/add-post-page-component.js"; import { renderAuthPageComponent } from "./components/auth-page-component.js"; import { @@ -67,11 +68,31 @@ export const goToPage = (newPage, data) => { } if (newPage === USER_POSTS_PAGE) { - // @@TODO: реализовать получение постов юзера из API - console.log("Открываю страницу пользователя: ", data.userId); - page = USER_POSTS_PAGE; - posts = []; - return renderApp(); + // @TODO: реализовать получение постов юзера из API + + // Включаем спин + page = LOADING_PAGE; + renderApp(); + + // Вызываем API. ID пользователя мы берем из объекта data, + // который передается в goToPage при клике на аватарку + return getUserPosts({ + token: getToken(), + userId: data.userId + }) + .then((newPosts) => { + // Обновляем массив постов из API + posts = newPosts; + // Меняем статус страницы на "Профиль пользователя" + page = USER_POSTS_PAGE; + // И рисуем приложение с новыми данными + renderApp(); + }) + // Если ошибка то возвращаемся на главную страницу + .catch((error) => { + console.error(error); + goToPage(POSTS_PAGE); + }); } page = newPage; @@ -124,9 +145,11 @@ const renderApp = () => { } if (page === USER_POSTS_PAGE) { - // @TODO: реализовать страницу с фотографиями отдельного пользвателя - appEl.innerHTML = "Здесь будет страница фотографий пользователя"; - return; + // @TODO: реализовать страницу с фотографиями отдельного пользователя + // Меняем заглушку на реальную ленту + return renderPostsPageComponent({ + appEl, + }); } }; From efe79cf1fbaf41f7083c5e5b0803debae27d79b0 Mon Sep 17 00:00:00 2001 From: Maxim_Parfenev Date: Tue, 6 Jan 2026 11:58:39 +0300 Subject: [PATCH 5/8] =?UTF-8?q?=D0=A1=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86?= =?UTF-8?q?=D0=B0=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=20=D0=B3=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0.=20=D0=A2=D0=B0=D0=BA=20=D0=B6=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=8F=D0=B2=D0=B8=D0=BB=D0=B8=D1=81=D1=8C=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B=20=D0=B2=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0=20=D0=B8=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 ++++- components/add-post-page-component.js | 66 +++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b00f77fef..7162c41dd 100644 --- a/README.md +++ b/README.md @@ -87,11 +87,20 @@ index.js Меняем логику получения постов для USER_POSTS_PAGE - 3) Интерфейс создания поста: add-post-page-component.js @TODO: Реализовать страницу добавления поста +- Добавляем разметку страницы добавления поста +- Добовляем шапку +- И форму выбора фото с валидацией +- И О Чудо активировались страницы входа и регистрации + + + 4) Теперь публикация. Возвращаемся в index.js - @TODO: реализовать добавление поста в API 5) И в конце когда все будет работать мы можем внести правку в posts-page-component.js @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow + + + diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 8277f093b..a6b6e996d 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -1,20 +1,70 @@ +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"); + + if (uploadImageContainer) { + renderUploadImageComponent({ + element: uploadImageContainer, + onImageUrlChange(newImageUrl) { + // Сохраняем ссылку из загрузки + imageUrl = newImageUrl; + }, + }); + } + + // Сбор данных и вызов callback-функции document.getElementById("add-button").addEventListener("click", () => { + const description = document.getElementById("description-input").value; + + // Валидация: картинка выбрана, описание заполнено + if (!imageUrl) { + alert("Пожалуйста, выберите фото"); + return; + } + if (!description) { + alert("Пожалуйста, добавьте описание"); + return; + } + + // Если всё ок, вызываем функцию из index.js onAddPostClick({ - description: "Описание картинки", - imageUrl: "https://image.png", + description: description, + imageUrl: imageUrl, }); }); }; From a64496e6cb93f3ce34abb9b7cb5916c60c876f12 Mon Sep 17 00:00:00 2001 From: Maxim_Parfenev Date: Wed, 7 Jan 2026 18:18:41 +0300 Subject: [PATCH 6/8] =?UTF-8?q?@TODO:=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B0=20=D0=B2=20API=20=D0=93=D0=9E=D0=A2=D0=9E=D0=92=D0=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++++++--- api.js | 25 +++++++++++++++++++++---- index.js | 24 +++++++++++++++++++++--- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7162c41dd..8c5f81f17 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,12 @@ YYYY часов index.js 1) // @@TODO: реализовать получение постов юзера из API ГОТОВО -2) // @TODO: реализовать добавление поста в API +2) // @TODO: реализовать добавление поста в API ГОТОВО 3) // @TODO: реализовать страницу с фотографиями отдельного пользвателя ГОТОВО add-post-page-component.js -4) // @TODO: Реализовать страницу добавления поста +4) // @TODO: Реализовать страницу добавления поста posts-page-component.js 5) // @TODO: реализовать рендер постов из api - ГОТОВО @@ -96,9 +96,12 @@ add-post-page-component.js - И форму выбора фото с валидацией - И О Чудо активировались страницы входа и регистрации +4) Теперь публикация. Возвращаемся в index.js - @TODO: реализовать добавление поста в API + +В api добавляем функцию создания поста addPost +В index добавляем импорт из нее и вызываем функцию в ADD_POSTS_PAGE -4) Теперь публикация. Возвращаемся в index.js - @TODO: реализовать добавление поста в API 5) И в конце когда все будет работать мы можем внести правку в posts-page-component.js @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow diff --git a/api.js b/api.js index 0ae4e70b1..a7aca7802 100644 --- a/api.js +++ b/api.js @@ -1,9 +1,28 @@ // Замени на свой, чтобы получить независимый от других набор данных. -// "боевая" версия инстапро лежит в ключе prod const personalKey = "prod"; const baseHost = "https://webdev-hw-api.vercel.app"; const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`; +// Функция создания поста +export function addPost({ token, description, imageUrl }) { + return fetch(postsHost, { + method: "POST", + body: JSON.stringify({ + description, + imageUrl, + }), + headers: { + Authorization: token, + }, + }).then((response) => { + if (response.status === 400) { + throw new Error("Заполните описание и выберите картинку"); + } + return response.json(); + }); +} + +// Функция получения постов конкретного пользователя export function getPosts({ token }) { return fetch(postsHost, { method: "GET", @@ -15,7 +34,6 @@ export function getPosts({ token }) { if (response.status === 401) { throw new Error("Нет авторизации"); } - return response.json(); }) .then((data) => { @@ -23,7 +41,6 @@ export function getPosts({ token }) { }); } -// Функция получения постов конкретного пользователя export function getUserPosts({ token, userId }) { return fetch(postsHost + "/user-posts/" + userId, { method: "GET", @@ -85,4 +102,4 @@ export function uploadImage({ file }) { }).then((response) => { return response.json(); }); -} +} \ No newline at end of file diff --git a/index.js b/index.js index 783dbf6f9..a8ef3c57c 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ // Импортируем новую функцию getUserPosts -import { getPosts, getUserPosts } from "./api.js"; +import { getPosts, getUserPosts, addPost } from "./api.js"; import { renderAddPostPageComponent } from "./components/add-post-page-component.js"; import { renderAuthPageComponent } from "./components/auth-page-component.js"; import { @@ -132,8 +132,26 @@ const renderApp = () => { appEl, onAddPostClick({ description, imageUrl }) { // @TODO: реализовать добавление поста в API - console.log("Добавляю пост...", { description, imageUrl }); - goToPage(POSTS_PAGE); + page = LOADING_PAGE; + renderApp(); + + // Вызываем API для добавления поста + addPost({ + token: getToken(), + description, + imageUrl, + }) + .then(() => { + // Возвращаемся в ленту + goToPage(POSTS_PAGE); + }) + .catch((error) => { + // Если ошибка то возвращаем форму и выводим ошибку + console.error(error); + alert(error.message); + page = ADD_POSTS_PAGE; + renderApp(); + }); }, }); } From 7260e0c8691fc53863d9ac8a7cd9844534679876 Mon Sep 17 00:00:00 2001 From: Maxim_Parfenev Date: Wed, 7 Jan 2026 23:27:02 +0300 Subject: [PATCH 7/8] =?UTF-8?q?@TODO:=20=D1=84=D0=B8=D0=BD=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=9F=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B8=D0=BB=20=D0=B1=D0=B8=D0=B1=D0=BB=D0=B8=D0=BE=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BB=D0=BE=D0=BA?= =?UTF-8?q?=D0=BE=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=B0=D1=82=D1=8B=20=D0=BF?= =?UTF-8?q?=D1=83=D0=B1=D0=BB=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++--- components/posts-page-component.js | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8c5f81f17..c8446d27e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ index.js 3) // @TODO: реализовать страницу с фотографиями отдельного пользвателя ГОТОВО add-post-page-component.js -4) // @TODO: Реализовать страницу добавления поста +4) // @TODO: Реализовать страницу добавления поста ГОТОВО posts-page-component.js 5) // @TODO: реализовать рендер постов из api - ГОТОВО @@ -105,5 +105,4 @@ add-post-page-component.js 5) И в конце когда все будет работать мы можем внести правку в posts-page-component.js @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow - - +Подключил библиотеки для локолизации и форматирования даты публикации \ No newline at end of file diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 3ece07276..05804398a 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -2,10 +2,26 @@ import { USER_POSTS_PAGE } from "../routes.js"; import { renderHeaderComponent } from "./header-component.js"; // Имортируем массив данных 'posts' и функцию навигации 'goToPage' import { posts, goToPage } from "../index.js"; +// Подключаем импорт из библиотеки date-fns для локализации +import formatDistanceToNow from + "https://cdn.jsdelivr.net/npm/date-fns@2.29.3/esm/formatDistanceToNow/index.js"; + +import ru from + "https://cdn.jsdelivr.net/npm/date-fns@2.29.3/esm/locale/ru/index.js"; export function renderPostsPageComponent({ appEl }) { // Удоляем статику const postsHtml = posts.map((post) => { + + // Вычисляем сколько времени прошло с момента создания поста до текущего + const createDate = post.createdAt + ? formatDistanceToNow(new Date(post.createdAt), { + addSuffix: true, + locale: ru, + }) + : "только что"; + + return `
  • @@ -17,7 +33,7 @@ export function renderPostsPageComponent({ appEl }) {

    Нравится: ${post.likes.length} @@ -28,7 +44,8 @@ export function renderPostsPageComponent({ appEl }) { ${post.description}

    + ${createDate} +

  • `; }).join(""); // Превращаем массив строк в одну сплошную HTML-строку @@ -37,7 +54,8 @@ export function renderPostsPageComponent({ appEl }) {
      - ${postsHtml}
    + ${postsHtml} +
    `; appEl.innerHTML = appHtml; From bd469d87898593859a668b7e9f9e3c03c5567e3c Mon Sep 17 00:00:00 2001 From: Maxim_Parfenev Date: Thu, 8 Jan 2026 02:51:32 +0300 Subject: [PATCH 8/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BB=D0=B0=D0=B9=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=B0?= =?UTF-8?q?=D0=B2=D1=82=D0=BE=D0=B8=D1=80=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++++++++-- api.js | 17 +++++++++++ components/posts-page-component.js | 45 +++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c8446d27e..26c8b4ddf 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ add-post-page-component.js posts-page-component.js 5) // @TODO: реализовать рендер постов из api - ГОТОВО -6) @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow +6) @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow ГОТОВО Анализ архитектуры работы и последовательности действий @@ -105,4 +105,14 @@ add-post-page-component.js 5) И в конце когда все будет работать мы можем внести правку в posts-page-component.js @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow -Подключил библиотеки для локолизации и форматирования даты публикации \ No newline at end of file +Подключил библиотеки для локолизации и форматирования даты публикации + +ФИНАЛ Доп задание - Лайки +Лайки можно ставить и удалять +api добавим функции POST и DELETE + +Импортируем функции в posts-page-component +Добовляем обработчик кликов +если не авторизован то ошибка в алерт + +ГОТОВО \ No newline at end of file diff --git a/api.js b/api.js index a7aca7802..4fa16545e 100644 --- a/api.js +++ b/api.js @@ -91,6 +91,23 @@ export function loginUser({ login, password }) { }); } +// Ставим лайк +export function likePost({ token, postId }) { + return fetch(`${postsHost}/${postId}/like`, { + method: "POST", + headers: { Authorization: token } + }).then((response) => response.json()); +} + +// Удаляем лайк +export function dislikePost({ token, postId }) { + return fetch(`${postsHost}/${postId}/dislike`, { + method: "POST", // Странно но удаление тоже через POST + headers: { Authorization: token } + }).then((response) => response.json()); +} + + // Загружает картинку в облако, возвращает url загруженной картинки export function uploadImage({ file }) { const data = new FormData(); diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 05804398a..3768bd68f 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -1,7 +1,8 @@ import { USER_POSTS_PAGE } from "../routes.js"; import { renderHeaderComponent } from "./header-component.js"; // Имортируем массив данных 'posts' и функцию навигации 'goToPage' -import { posts, goToPage } from "../index.js"; +import { posts, user, goToPage } from "../index.js"; +import { likePost, dislikePost } from "../api.js"; // Подключаем импорт из библиотеки date-fns для локализации import formatDistanceToNow from "https://cdn.jsdelivr.net/npm/date-fns@2.29.3/esm/formatDistanceToNow/index.js"; @@ -65,6 +66,48 @@ export function renderPostsPageComponent({ appEl }) { element: document.querySelector(".header-container"), }); + // Обработчик кликов на лайки + for (let button of document.querySelectorAll(".like-button")) { + button.addEventListener("click", () => { + if (!user) { + alert("Чтобы поставить лайк, нужна аторизация"); + return; + } + + const postId = button.dataset.postId; + const post = posts.find((p) => p.id === postId); + + // Если лайк есть то удаляем или ставим + const apiFunction = post.isLiked ? dislikePost : likePost; + + const oldIsLiked = post.isLiked; + const oldLikes = [...post.likes]; + + // Перерисовываем страницу + renderPostsPageComponent({ appEl }); + + // Отправляем запрос на сервер + apiFunction({ + token: `Bearer ${user.token}`, + postId, + }) + .then((data) => { + const updatedPost = data.post || data; + // Сохраняем данные с сервера + post.likes = updatedPost.likes || []; + post.isLiked = updatedPost.isLiked; + // И опять перерисовываем + renderPostsPageComponent({ appEl }); + }) + .catch((error) => { + console.error("Ошибка при работе с лайком:", error); + // Если ошибка + renderPostsPageComponent({ appEl }); + alert("Не удалось обработать лайк."); + }); + }); + } + // Обработчик кликов на заголовки постов // goToPage при клике на заголовок дожны попасть в профиль автора for (let userEl of document.querySelectorAll(".post-header")) {