From 23b43568d6cb2da782961c97bbffda900e2c0502 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 20:34:29 +0500 Subject: [PATCH 001/202] Update api.js --- api.js | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/api.js b/api.js index 123a9e3b6..a00b363f7 100644 --- a/api.js +++ b/api.js @@ -55,7 +55,7 @@ export function loginUser({ login, password }) { }); } -// Загружает картинку в облако, возвращает url загруженной картинки + export function uploadImage({ file }) { const data = new FormData(); data.append("file", file); @@ -67,3 +67,75 @@ export function uploadImage({ file }) { return response.json(); }); } + + +export function addPost({ token, description, imageUrl }) { + return fetch(postsHost, { + method: "POST", + headers: { + Authorization: token, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + description, + imageUrl, + }), + }).then((response) => { + if (response.status === 401) { + throw new Error("Нет авторизации"); + } + if (response.status === 400) { + throw new Error("Не переданы обязательные данные"); + } + 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("Нет авторизации"); + } + return response.json(); + }) + .then((data) => { + return data.posts; + }); +} + + +export function likePost({ token, postId }) { + return fetch(`${postsHost}/${postId}/like`, { + method: "POST", + headers: { + Authorization: token, + }, + }).then((response) => { + if (response.status === 401) { + throw new Error("Нет авторизации"); + } + 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("Нет авторизации"); + } + return response.json(); + }); +} From 8fbca97542519317e7ac94fab7470ad05ae106a9 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 20:37:27 +0500 Subject: [PATCH 002/202] Update index.js --- index.js | 101 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 7f1817c75..09059893a 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,10 @@ -import { getPosts } from "./api.js"; +import { + getPosts, + addPost, + getUserPosts, + likePost, + dislikePost +} from "./api.js"; import { renderAddPostPageComponent } from "./components/add-post-page-component.js"; import { renderAuthPageComponent } from "./components/auth-page-component.js"; import { @@ -31,9 +37,9 @@ export const logout = () => { goToPage(POSTS_PAGE); }; -/** - * Включает страницу приложения - */ + + + export const goToPage = (newPage, data) => { if ( [ @@ -45,7 +51,7 @@ export const goToPage = (newPage, data) => { ].includes(newPage) ) { if (newPage === ADD_POSTS_PAGE) { - /* Если пользователь не авторизован, то отправляем его на страницу авторизации перед добавлением поста */ + page = user ? ADD_POSTS_PAGE : AUTH_PAGE; return renderApp(); } @@ -67,11 +73,19 @@ export const goToPage = (newPage, data) => { } if (newPage === USER_POSTS_PAGE) { - // @@TODO: реализовать получение постов юзера из API - console.log("Открываю страницу пользователя: ", data.userId); - page = USER_POSTS_PAGE; - posts = []; - return renderApp(); + page = LOADING_PAGE; + renderApp(); + + return getUserPosts({ token: getToken(), userId: data.userId }) + .then((userPosts) => { + page = USER_POSTS_PAGE; + posts = userPosts; + renderApp(); + }) + .catch((error) => { + console.error(error); + goToPage(POSTS_PAGE); + }); } page = newPage; @@ -83,6 +97,31 @@ export const goToPage = (newPage, data) => { throw new Error("страницы не существует"); }; + +export const toggleLike = (postId, isLiked) => { + const token = getToken(); + if (!token) { + goToPage(AUTH_PAGE); + return Promise.resolve(); + } + + const likePromise = isLiked ? dislikePost : likePost; + + return likePromise({ token, postId }) + .then((responseData) => { + + const postIndex = posts.findIndex(post => post.id === postId); + if (postIndex !== -1) { + posts[postIndex] = responseData.post; + } + renderApp(); + }) + .catch((error) => { + console.error("Ошибка при работе с лайком:", error); + alert("Не удалось выполнить действие"); + }); +}; + const renderApp = () => { const appEl = document.getElementById("app"); if (page === LOADING_PAGE) { @@ -110,9 +149,30 @@ const renderApp = () => { return renderAddPostPageComponent({ appEl, onAddPostClick({ description, imageUrl }) { - // @TODO: реализовать добавление поста в API - console.log("Добавляю пост...", { description, imageUrl }); - goToPage(POSTS_PAGE); + const token = getToken(); + if (!token) { + goToPage(AUTH_PAGE); + return; + } + + if (!description.trim()) { + alert("Введите описание поста"); + return; + } + + if (!imageUrl) { + alert("Загрузите изображение"); + return; + } + + addPost({ token, description, imageUrl }) + .then(() => { + goToPage(POSTS_PAGE); + }) + .catch((error) => { + console.error("Ошибка при добавлении поста:", error); + alert("Не удалось добавить пост: " + error.message); + }); }, }); } @@ -120,13 +180,22 @@ const renderApp = () => { if (page === POSTS_PAGE) { return renderPostsPageComponent({ appEl, + posts, + user, + goToPage, + toggleLike, }); } if (page === USER_POSTS_PAGE) { - // @TODO: реализовать страницу с фотографиями отдельного пользвателя - appEl.innerHTML = "Здесь будет страница фотографий пользователя"; - return; + return renderPostsPageComponent({ + appEl, + posts, + user, + goToPage, + toggleLike, + isUserPostsPage: true, + }); } }; From 9f465d7d0e379462952bd00542041cb64fee0f1c Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 20:45:51 +0500 Subject: [PATCH 003/202] Update api.js --- api.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api.js b/api.js index a00b363f7..d40c3506b 100644 --- a/api.js +++ b/api.js @@ -1,5 +1,4 @@ -// Замени на свой, чтобы получить независимый от других набор данных. -// "боевая" версия инстапро лежит в ключе prod + const personalKey = "prod"; const baseHost = "https://webdev-hw-api.vercel.app"; const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`; From 4a92adf7c67e293fc0c5879ba0d3359bd01ca577 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 20:47:14 +0500 Subject: [PATCH 004/202] Update index.js --- index.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 09059893a..eea646094 100644 --- a/index.js +++ b/index.js @@ -37,9 +37,6 @@ export const logout = () => { goToPage(POSTS_PAGE); }; - - - export const goToPage = (newPage, data) => { if ( [ @@ -51,7 +48,7 @@ export const goToPage = (newPage, data) => { ].includes(newPage) ) { if (newPage === ADD_POSTS_PAGE) { - + page = user ? ADD_POSTS_PAGE : AUTH_PAGE; return renderApp(); } @@ -109,16 +106,18 @@ export const toggleLike = (postId, isLiked) => { return likePromise({ token, postId }) .then((responseData) => { - + const postIndex = posts.findIndex(post => post.id === postId); if (postIndex !== -1) { posts[postIndex] = responseData.post; } + + renderApp(); }) .catch((error) => { console.error("Ошибка при работе с лайком:", error); - alert("Не удалось выполнить действие"); + alert("Не удалось выполнить действие: " + error.message); }); }; From 942a65101b42fe6b482c98f4a562c7ca2d139247 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:01:39 +0500 Subject: [PATCH 005/202] Update index.js --- index.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index eea646094..1a138e927 100644 --- a/index.js +++ b/index.js @@ -37,6 +37,9 @@ export const logout = () => { goToPage(POSTS_PAGE); }; +/** + * Включает страницу приложения + */ export const goToPage = (newPage, data) => { if ( [ @@ -48,7 +51,7 @@ export const goToPage = (newPage, data) => { ].includes(newPage) ) { if (newPage === ADD_POSTS_PAGE) { - + /* Если пользователь не авторизован, то отправляем его на страницу авторизации перед добавлением поста */ page = user ? ADD_POSTS_PAGE : AUTH_PAGE; return renderApp(); } @@ -59,6 +62,7 @@ export const goToPage = (newPage, data) => { return getPosts({ token: getToken() }) .then((newPosts) => { + console.log("Посты из API:", newPosts); // Для отладки page = POSTS_PAGE; posts = newPosts; renderApp(); @@ -94,7 +98,7 @@ export const goToPage = (newPage, data) => { throw new Error("страницы не существует"); }; - +// Функция для обработки лайков export const toggleLike = (postId, isLiked) => { const token = getToken(); if (!token) { @@ -106,13 +110,13 @@ export const toggleLike = (postId, isLiked) => { return likePromise({ token, postId }) .then((responseData) => { - + // Обновляем конкретный пост в массиве posts const postIndex = posts.findIndex(post => post.id === postId); if (postIndex !== -1) { posts[postIndex] = responseData.post; } - + // Также обновляем страницу для отображения изменений renderApp(); }) .catch((error) => { From 480a59ec82da2ae5682833a1bee32e171b76452e Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:03:27 +0500 Subject: [PATCH 006/202] Update posts-page-component.js --- components/posts-page-component.js | 170 ++++++++++++++--------------- 1 file changed, 79 insertions(+), 91 deletions(-) diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 662ccdbdc..5f81de629 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -1,97 +1,68 @@ import { USER_POSTS_PAGE } from "../routes.js"; import { renderHeaderComponent } from "./header-component.js"; -import { posts, goToPage } from "../index.js"; +import { formatDate } from "../date-utils.js"; -export function renderPostsPageComponent({ appEl }) { - // @TODO: реализовать рендер постов из api - console.log("Актуальный список постов:", posts); +export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleLike, isUserPostsPage = false }) { + + let pageHeader = ''; + if (isUserPostsPage && posts.length > 0) { + const firstPost = posts[0]; + pageHeader = ` +
+ +
${firstPost.user.name}
+
+ `; + } + + const postsHtml = posts.map((post) => { + const isLiked = post.isLiked; + const likeImage = isLiked + ? "./assets/images/like-active.svg" + : "./assets/images/like-not-active.svg"; + + + const imageSrc = post.imageUrl && post.imageUrl.trim() !== '' + ? post.imageUrl + : "./assets/images/default-image.jpg"; + + return ` +
  • +
    + +

    ${post.user.name}

    +
    +
    + +
    +
    + +

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

    +
    +

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

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

      Иван Иваныч

      -
      -
      - -
      -
      - -

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

      -
      -

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

      - -
    • -
    • -
      - -

      Варварва Н.

      -
      - - -
      - -
      -
      - -

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

      -
      -

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

      - -
    • -
    • -
      - -

      Варварва Н.

      -
      - - -
      - -
      -
      - -

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

      -
      -

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

      - -
    • -
    -
    `; +
    +
    + ${pageHeader} +
      + ${postsHtml || '

      Постов пока нет

      '} +
    +
    + `; appEl.innerHTML = appHtml; @@ -99,11 +70,28 @@ export function renderPostsPageComponent({ appEl }) { element: document.querySelector(".header-container"), }); - for (let userEl of document.querySelectorAll(".post-header")) { + + document.querySelectorAll(".post-header").forEach(userEl => { userEl.addEventListener("click", () => { goToPage(USER_POSTS_PAGE, { userId: userEl.dataset.userId, }); }); - } + }); + + + document.querySelectorAll('.like-button').forEach(button => { + button.addEventListener('click', (event) => { + event.stopPropagation(); + const postId = button.dataset.postId; + const isLiked = button.dataset.isLiked === 'true'; + + + button.dataset.isLiked = !isLiked; + + toggleLike(postId, isLiked); + }); + }); + + } From d57a7c9e47abb3679a70c9898b94de700249d4fb Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:12:03 +0500 Subject: [PATCH 007/202] Update posts-page-component.js --- components/posts-page-component.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 5f81de629..c9063d329 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -1,6 +1,6 @@ import { USER_POSTS_PAGE } from "../routes.js"; import { renderHeaderComponent } from "./header-component.js"; -import { formatDate } from "../date-utils.js"; + export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleLike, isUserPostsPage = false }) { @@ -48,8 +48,8 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL ${post.description || ''}

    + ${post.createdAt} +

    `; }).join(""); From f9d4c456a92810f69d0c7aa67b3edc5bb73a002e Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:14:48 +0500 Subject: [PATCH 008/202] Update posts-page-component.js --- components/posts-page-component.js | 97 ++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 11 deletions(-) diff --git a/components/posts-page-component.js b/components/posts-page-component.js index c9063d329..2f0bccd2b 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -2,8 +2,83 @@ import { USER_POSTS_PAGE } from "../routes.js"; import { renderHeaderComponent } from "./header-component.js"; -export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleLike, isUserPostsPage = false }) { +function simpleFormatDate(dateString) { + try { + const date = new Date(dateString); + const now = new Date(); + const timeDiff = now - date; + const minutes = Math.floor(timeDiff / (1000 * 60)); + const hours = Math.floor(timeDiff / (1000 * 60 * 60)); + const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); + + if (minutes < 1) { + return "менее минуты назад"; + } else if (minutes < 60) { + return `${minutes} ${getMinutesWord(minutes)} назад`; + } else if (hours < 24) { + return `${hours} ${getHoursWord(hours)} назад`; + } else { + return `${days} ${getDaysWord(days)} назад`; + } + } catch (e) { + return "недавно"; + } +} + + +function getMinutesWord(minutes) { + const lastDigit = minutes % 10; + const lastTwoDigits = minutes % 100; + + if (lastTwoDigits >= 11 && lastTwoDigits <= 19) { + return "минут"; + } + + switch (lastDigit) { + case 1: return "минуту"; + case 2: + case 3: + case 4: return "минуты"; + default: return "минут"; + } +} + +function getHoursWord(hours) { + const lastDigit = hours % 10; + const lastTwoDigits = hours % 100; + + if (lastTwoDigits >= 11 && lastTwoDigits <= 19) { + return "часов"; + } + + switch (lastDigit) { + case 1: return "час"; + case 2: + case 3: + case 4: return "часа"; + default: return "часов"; + } +} + +function getDaysWord(days) { + const lastDigit = days % 10; + const lastTwoDigits = days % 100; + + if (lastTwoDigits >= 11 && lastTwoDigits <= 19) { + return "дней"; + } + switch (lastDigit) { + case 1: return "день"; + case 2: + case 3: + case 4: return "дня"; + default: return "дней"; + } +} + +export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleLike, isUserPostsPage = false }) { + let pageHeader = ''; if (isUserPostsPage && posts.length > 0) { const firstPost = posts[0]; @@ -21,7 +96,7 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL ? "./assets/images/like-active.svg" : "./assets/images/like-not-active.svg"; - + const imageSrc = post.imageUrl && post.imageUrl.trim() !== '' ? post.imageUrl : "./assets/images/default-image.jpg"; @@ -48,8 +123,8 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL ${post.description || ''}

    + ${simpleFormatDate(post.createdAt)} +

    `; }).join(""); @@ -70,7 +145,7 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL element: document.querySelector(".header-container"), }); - + document.querySelectorAll(".post-header").forEach(userEl => { userEl.addEventListener("click", () => { goToPage(USER_POSTS_PAGE, { @@ -79,19 +154,19 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL }); }); - + document.querySelectorAll('.like-button').forEach(button => { button.addEventListener('click', (event) => { - event.stopPropagation(); + event.stopPropagation(); // Предотвращаем всплытие события const postId = button.dataset.postId; const isLiked = button.dataset.isLiked === 'true'; - + button.dataset.isLiked = !isLiked; - toggleLike(postId, isLiked); + if (typeof toggleLike === 'function') { + toggleLike(postId, isLiked); + } }); }); - - } From ad11cd449bba3958635cfa088ca85ff92244cecc Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:25:02 +0500 Subject: [PATCH 009/202] Update add-post-page-component.js --- components/add-post-page-component.js | 76 +++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 8277f093b..5fe8b0b82 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -1,20 +1,78 @@ +import { uploadImage } from "../api.js"; +import { renderUploadImageComponent } from "./upload-image-component.js"; +import { renderHeaderComponent } from "./header-component.js"; + export function renderAddPostPageComponent({ appEl, onAddPostClick }) { + let imageUrl = ""; + const render = () => { - // @TODO: Реализовать страницу добавления поста const appHtml = ` -
    -
    - Cтраница добавления поста - -
    - `; +
    +
    +
    +

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

    +
    +
    +
    +
    + +
    +
    + +
    +
    + `; appEl.innerHTML = appHtml; + + renderHeaderComponent({ + element: document.querySelector(".header-container"), + }); + + + renderUploadImageComponent({ + element: document.getElementById("upload-image-conrainer"), + onImageUpload: (imageFile) => { + document.getElementById("add-button").setAttribute("disabled", true); + + uploadImage({ file: imageFile }) + .then(({ imageUrl: uploadedImageUrl }) => { + imageUrl = uploadedImageUrl; + document.getElementById("add-button").removeAttribute("disabled"); + }) + .catch((error) => { + console.error("Ошибка загрузки изображения:", error); + document.getElementById("form-error").textContent = "Ошибка загрузки изображения"; + document.getElementById("add-button").removeAttribute("disabled"); + }); + }, + }); + + document.getElementById("add-button").addEventListener("click", () => { + const description = document.getElementById("post-description").value; + + + if (!description.trim()) { + document.getElementById("form-error").textContent = "Введите описание поста"; + return; + } + + if (!imageUrl) { + document.getElementById("form-error").textContent = "Загрузите изображение"; + return; + } + + onAddPostClick({ - description: "Описание картинки", - imageUrl: "https://image.png", + description: description.trim(), + imageUrl, }); }); }; From 271aa779675a0d2a8780cd5c71e0387ade2fc2ed Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:26:22 +0500 Subject: [PATCH 010/202] Update styles.css --- styles.css | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/styles.css b/styles.css index 57bc08b16..1f6fbab25 100644 --- a/styles.css +++ b/styles.css @@ -196,3 +196,88 @@ width: 100%; text-align: center; } + + +.form { + display: flex; + flex-direction: column; + width: 100%; + max-width: 500px; + margin: 0 auto; + padding: 20px; +} + +.form-title { + font-size: 28px; + line-height: 35px; + text-align: center; + margin-bottom: 20px; +} + +.form-inputs { + display: flex; + flex-direction: column; + gap: 15px; +} + +.form-textarea { + width: 100%; + min-height: 100px; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + font-family: inherit; + resize: vertical; +} + +.form-error { + color: red; + font-size: 14px; + text-align: center; +} + +.form-footer { + margin-top: 20px; + display: flex; + justify-content: center; +} + +.button { + background-color: #0095f6; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-weight: 500; + font-size: 16px; +} + +.button:disabled { + background-color: #b2dffc; + cursor: not-allowed; +} + +.button:hover:not(:disabled) { + background-color: #0081d1; +} + +.upload-image-container { + margin-bottom: 15px; +} + + +.uploaded-image-container { + display: flex; + align-items: center; + margin-bottom: 15px; +} + +.uploaded-image { + width: 100px; + height: 100px; + object-fit: cover; + margin-right: 10px; + border: 1px solid gray; + border-radius: 5px; +} From eea767d8cb038b613e71f435cca988f592883db7 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:27:34 +0500 Subject: [PATCH 011/202] Update index.js --- index.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 1a138e927..6fbdd5c96 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,8 @@ import { addPost, getUserPosts, likePost, - dislikePost + dislikePost, + uploadImage } from "./api.js"; import { renderAddPostPageComponent } from "./components/add-post-page-component.js"; import { renderAuthPageComponent } from "./components/auth-page-component.js"; @@ -37,9 +38,7 @@ export const logout = () => { goToPage(POSTS_PAGE); }; -/** - * Включает страницу приложения - */ + export const goToPage = (newPage, data) => { if ( [ @@ -51,7 +50,7 @@ export const goToPage = (newPage, data) => { ].includes(newPage) ) { if (newPage === ADD_POSTS_PAGE) { - /* Если пользователь не авторизован, то отправляем его на страницу авторизации перед добавлением поста */ + page = user ? ADD_POSTS_PAGE : AUTH_PAGE; return renderApp(); } @@ -62,7 +61,7 @@ export const goToPage = (newPage, data) => { return getPosts({ token: getToken() }) .then((newPosts) => { - console.log("Посты из API:", newPosts); // Для отладки + console.log("Посты из API:", newPosts); page = POSTS_PAGE; posts = newPosts; renderApp(); @@ -98,7 +97,7 @@ export const goToPage = (newPage, data) => { throw new Error("страницы не существует"); }; -// Функция для обработки лайков + export const toggleLike = (postId, isLiked) => { const token = getToken(); if (!token) { @@ -110,13 +109,13 @@ export const toggleLike = (postId, isLiked) => { return likePromise({ token, postId }) .then((responseData) => { - // Обновляем конкретный пост в массиве posts + const postIndex = posts.findIndex(post => post.id === postId); if (postIndex !== -1) { posts[postIndex] = responseData.post; } - // Также обновляем страницу для отображения изменений + renderApp(); }) .catch((error) => { @@ -158,6 +157,7 @@ const renderApp = () => { return; } + if (!description.trim()) { alert("Введите описание поста"); return; @@ -168,8 +168,10 @@ const renderApp = () => { return; } + addPost({ token, description, imageUrl }) .then(() => { + goToPage(POSTS_PAGE); }) .catch((error) => { From f4cea14667bd4997e6166c8491ed3b5cdc2bd852 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:43:16 +0500 Subject: [PATCH 012/202] Update loading-page-component.js --- components/loading-page-component.js | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/components/loading-page-component.js b/components/loading-page-component.js index c93522573..45b6e3890 100644 --- a/components/loading-page-component.js +++ b/components/loading-page-component.js @@ -1,34 +1,23 @@ 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 = `
    -
    +
    +
    +

    Загружаем данные...

    +
    `; - // Устанавливаем разметку в корневой элемент приложения + appEl.innerHTML = appHtml; - /** - * Рендеринг заголовка с использованием компонента `renderHeaderComponent`. - * Передаются данные пользователя и функция навигации. - */ + renderHeaderComponent({ user, element: document.querySelector(".header-container"), From f054e95e008ffb368648ddc352a1cc48fcde4b9b Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:44:31 +0500 Subject: [PATCH 013/202] Update styles.css --- styles.css | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) diff --git a/styles.css b/styles.css index 1f6fbab25..176241db7 100644 --- a/styles.css +++ b/styles.css @@ -281,3 +281,331 @@ border: 1px solid gray; border-radius: 5px; } + +.page-container { + padding: 0 20px; +} + +.page-header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #e0e0e0; + padding: 20px 16px; + margin-left: -20px; + margin-right: -20px; +} + +.logo { + margin: 0; + font-size: 28px; + cursor: pointer; + width: 130px; +} + +.header-container { + margin-bottom: 8px; +} + +.header-button { + padding: 0; + border: none; + background-color: transparent; + font-size: 14px; + line-height: 18px; + font-weight: 500; + cursor: pointer; +} + +.logout-button { + width: 130px; + text-align: right; +} + +.posts { + display: flex; + flex-direction: column; + list-style: none; + padding: 0; + margin: 0; +} + +.post-header { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 10px; + cursor: pointer; +} + +.post-header__user-image { + width: 40px; + height: 40px; + object-fit: cover; + border-radius: 40px; + margin-right: 10px; +} + +.post-image-container { + margin-left: -20px; + margin-right: -20px; + height: 500px; + display: flex; + justify-content: center; + background-color: #e0e0e0; +} + +.post-image { + width: 100%; + height: 100%; + max-width: 500px; + object-fit: cover; +} + +.post-likes { + display: flex; + flex-direction: row; + align-items: center; +} + +.like-button { + border: none; + background-color: transparent; + padding: 8px; + padding-left: 0px; + padding-bottom: 5px; + cursor: pointer; + transition: transform 0.2s ease; +} + +.like-button:active { + transform: scale(1.2); +} + +.user-name { + font-weight: 500; +} + +.post-text { + font-size: 14px; + line-height: 18px; + margin-top: 5px; +} + +.post-date { + color: #8a8a8a; +} + +.post + .post { + margin-top: 20px; +} + +.posts-user-header { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 12px; +} + +.posts-user-header__user-image { + width: 70px; + height: 70px; + object-fit: cover; + border-radius: 40px; + margin-right: 10px; +} + +.posts-user-header__user-name { + font-size: 28px; + line-height: 35px; +} + +.loading-page { + display: flex; + justify-content: center; + margin-top: 100px; +} + +.add-post-sign { + background-image: url("data:image/svg+xml,%3Csvg id='Layer_1' data-name='Layer 1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 122.88 122.88'%3E%3Ctitle%3Eadd%3C/title%3E%3Cpath d='M61.44,0A61.46,61.46,0,1,1,18,18,61.25,61.25,0,0,1,61.44,0ZM88.6,56.82v9.24a4,4,0,0,1-4,4H70V84.62a4,4,0,0,1-4,4H56.82a4,4,0,0,1-4-4V70H38.26a4,4,0,0,1-4-4V56.82a4,4,0,0,1,4-4H52.84V38.26a4,4,0,0,1,4-4h9.24a4,4,0,0,1,4,4V52.84H84.62a4,4,0,0,1,4,4Zm8.83-31.37a50.92,50.92,0,1,0,14.9,36,50.78,50.78,0,0,0-14.9-36Z'/%3E%3C/svg%3E"); + height: 30px; + width: 30px; +} + +.form { + display: flex; + flex-direction: column; + width: 100%; +} + +.form-title { + font-size: 28px; + line-height: 35px; + text-align: center; +} + +.form-inputs { + display: flex; + flex-direction: column; + gap: 10px; +} + +.form-error { + color: red; +} + +.form-footer { + margin-top: 50px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.form-footer-title { + text-align: center; +} + +.file-upload-image-conrainer { + display: flex; + align-items: center; +} + +.file-upload-image { + width: 100px; + height: 100px; + object-fit: cover; + margin-right: 10px; + border: 1px solid gray; + border-radius: 5px; +} + +.file-upload-label { + display: inline-block; + box-sizing: border-box; + width: 100%; + text-align: center; +} + + + + +.page-container { + opacity: 1; + transition: opacity 0.3s ease; +} + + +.loading-spinner { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; +} + +.spinner { + width: 50px; + height: 50px; + border: 5px solid #f3f3f3; + border-top: 5px solid #0095f6; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + + +.post { + animation: fadeIn 0.5s ease; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + + +.like-button.liked { + animation: pulse 0.3s ease; +} + +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.3); } + 100% { transform: scale(1); } +} + + +.form-error { + padding: 10px; + border-radius: 4px; + background-color: #ffebee; + border-left: 4px solid #f44336; + animation: shake 0.5s ease; + margin-top: 10px; +} + +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-5px); } + 75% { transform: translateX(5px); } +} + + +.button { + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.button:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0,0,0,0.15); +} + +.button:active { + transform: translateY(0); +} + + +.page-transition { + opacity: 0; + transform: translateY(20px); + transition: all 0.3s ease; +} + +.page-transition.active { + opacity: 1; + transform: translateY(0); +} + + +.form-textarea { + width: 100%; + min-height: 100px; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + font-family: inherit; + resize: vertical; +} + +.button { + background-color: #0095f6; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-weight: 500; +} + +.button:disabled { + background-color: #b2dffc; + cursor: not-allowed; +} + +.upload-image-container { + margin-bottom: 15px; +} From 0c074cf37e1681214b3e0560f092ba7296aa2d7b Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:45:48 +0500 Subject: [PATCH 014/202] Update posts-page-component.js --- components/posts-page-component.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 2f0bccd2b..4c5e37ea5 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -96,7 +96,7 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL ? "./assets/images/like-active.svg" : "./assets/images/like-not-active.svg"; - + const imageSrc = post.imageUrl && post.imageUrl.trim() !== '' ? post.imageUrl : "./assets/images/default-image.jpg"; @@ -145,7 +145,7 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL element: document.querySelector(".header-container"), }); - + document.querySelectorAll(".post-header").forEach(userEl => { userEl.addEventListener("click", () => { goToPage(USER_POSTS_PAGE, { @@ -154,14 +154,20 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL }); }); - + document.querySelectorAll('.like-button').forEach(button => { button.addEventListener('click', (event) => { - event.stopPropagation(); // Предотвращаем всплытие события + event.stopPropagation(); + + + button.classList.add('liked'); + setTimeout(() => { + button.classList.remove('liked'); + }, 300); + const postId = button.dataset.postId; const isLiked = button.dataset.isLiked === 'true'; - button.dataset.isLiked = !isLiked; if (typeof toggleLike === 'function') { @@ -169,4 +175,13 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL } }); }); + + + const container = document.querySelector('.page-container'); + if (container) { + container.classList.add('page-transition'); + setTimeout(() => { + container.classList.add('active'); + }, 10); + } } From 772b60260499e451772127ef551706081a53e863 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:46:45 +0500 Subject: [PATCH 015/202] Update index.js --- index.js | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 6fbdd5c96..c03358d42 100644 --- a/index.js +++ b/index.js @@ -39,6 +39,30 @@ export const logout = () => { }; +const smoothPageTransition = (callback) => { + const appEl = document.getElementById("app"); + const container = appEl.querySelector('.page-container'); + + if (container) { + container.classList.remove('active'); + setTimeout(() => { + callback(); + setTimeout(() => { + const newContainer = appEl.querySelector('.page-container'); + if (newContainer) { + newContainer.classList.add('page-transition'); + setTimeout(() => { + newContainer.classList.add('active'); + }, 10); + } + }, 10); + }, 300); + } else { + callback(); + } +}; + + export const goToPage = (newPage, data) => { if ( [ @@ -50,7 +74,7 @@ export const goToPage = (newPage, data) => { ].includes(newPage) ) { if (newPage === ADD_POSTS_PAGE) { - + page = user ? ADD_POSTS_PAGE : AUTH_PAGE; return renderApp(); } @@ -101,7 +125,7 @@ export const goToPage = (newPage, data) => { export const toggleLike = (postId, isLiked) => { const token = getToken(); if (!token) { - goToPage(AUTH_PAGE); + smoothPageTransition(() => goToPage(AUTH_PAGE)); return Promise.resolve(); } @@ -115,7 +139,7 @@ export const toggleLike = (postId, isLiked) => { posts[postIndex] = responseData.post; } - + renderApp(); }) .catch((error) => { @@ -140,7 +164,7 @@ const renderApp = () => { setUser: (newUser) => { user = newUser; saveUserToLocalStorage(user); - goToPage(POSTS_PAGE); + smoothPageTransition(() => goToPage(POSTS_PAGE)); }, user, goToPage, @@ -153,11 +177,11 @@ const renderApp = () => { onAddPostClick({ description, imageUrl }) { const token = getToken(); if (!token) { - goToPage(AUTH_PAGE); + smoothPageTransition(() => goToPage(AUTH_PAGE)); return; } - + if (!description.trim()) { alert("Введите описание поста"); return; @@ -168,11 +192,11 @@ const renderApp = () => { return; } - + addPost({ token, description, imageUrl }) .then(() => { - - goToPage(POSTS_PAGE); + + smoothPageTransition(() => goToPage(POSTS_PAGE)); }) .catch((error) => { console.error("Ошибка при добавлении поста:", error); From 6e59ac771bac245e7458bf5c9443e2451e80515b Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:54:50 +0500 Subject: [PATCH 016/202] Update README.md --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 34719e139..cfad9f3e6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,18 @@ # Instapro MVP социальной сети для обмена фотографиями - +Удалось реализовать : +Анимации интерфейса +Плавные переходы между страницами +Анимация лайков при нажатии +Анимация ошибок формы +Индикаторы загрузки +Спиннер загрузки с анимацией +Визуальная обратная связь при действиях пользователя ## Первоначальная оценка -ХХХХ часов +18 часов ## Фактически затраченное время -YYYY часов +10 часов From 6b830a69d1a28046b0b8569d9d9c779fc300dc27 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Thu, 31 Jul 2025 21:56:58 +0500 Subject: [PATCH 017/202] Update index.js --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index c03358d42..a42d9ac7e 100644 --- a/index.js +++ b/index.js @@ -38,7 +38,6 @@ export const logout = () => { goToPage(POSTS_PAGE); }; - const smoothPageTransition = (callback) => { const appEl = document.getElementById("app"); const container = appEl.querySelector('.page-container'); From 5344bfb8391ae43894be3b668e8cb474607d9cd9 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Sun, 3 Aug 2025 19:24:27 +0500 Subject: [PATCH 018/202] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit добавлено изображение --- assets/images/default-image.jpg | Bin 0 -> 4964 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/images/default-image.jpg diff --git a/assets/images/default-image.jpg b/assets/images/default-image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c2e6d817b8c8e134562fc8369b0e6eb407cb0cea GIT binary patch literal 4964 zcmZvVcRUo1|Htp_m3?GJT-+U(J<7bSBUyL$C_9u*5!ve;*)!vCIpc7WQ54C{-gITh zC7F?iU;Xj<{`Gsl-;ej>{dm0Iuh;v0{(Kq0Zm5UU0|0>l0Pt@C&X)jj07g1`b}r6; zu!Gpy{ucYc`L97B9v;wNE(%GCT;vA}fIy1b)F|6Kt8p3`uM(sIgbo6&LI@xL6M!7XQA z-qcMG5<_GM(EgP!a{#FR@mK#(22ycQi*m}+XmedQ z&cRD3Elji%GjOIY?q!rb(l}t*_5z?KZ;#KGvHRD!Q(|jVe{+t){8jHG1u8 z)mlUEvTj^~-SJa5^YM4|XMWcW?=jAFyY$&!>gdpVg^y+q2!g8i4STh4Ilt}Jvlwm@ zKW@x=l0LO~BcL6*FbYjZwMzO0q!vi$R0@#k=beS;Xa}9*#5qIFpAKdoRqKnzJisYux9M;7q;N| zg--r+K&wnJ)2a8ozh9S&S#6)jQ7om?xK8ODKvS`}-6o@uUXv6(V0p2T^ig;&)S4@& zF*OFBlAQXLk)mfRPcvrfOgqTW=VK*G!s&DOgwTlXG`mzn3x3=T+7B1?D(gac;QRN; zH1+AyBDn4$JA$`VlI@S3mqaUu`G~yX%^ zP6~N;IsHX6x}ZmiKbz<4!SPi-TkDuGje7l>rVoJ&a@Z3m#VPNR!^Ftiz$z3O_lxim z8!p@z#)@hBV!9W~9D45@plc-L-gdnBShoaPcNfX!7Ig0}#rjvvoAev~ZB}Gw0+d-$ zP)2{V;I+eJt%)x~9&FvHxn|wqnSB0f^3tKFWvfHpUgH@ioy^Lz>p8xQgm)SfHz$=n ze;JFX=W9QfP25i(xrmP)BRNxSFJ*8x4 zQIO?adciR+=+3cY1$g^b$o=cXOy7_bXr~-RSA_v=zyKNmN2hd&^Gqj9~V>#FqWg zcP$XOuYvZCtO$Ni(k5U_&?I=0T)mfzd%{;e?U3CO_Jq!sDO6o0%+8|RaAj})kD-*! z1JbLuA7ZdNuO{HVSBGx#Gf->SQ{aU@p-(kuJmt%a{hxfCe-9T@@uZ(xnVwK9)VYKu zJi2vOyRqI)92AYlOYGIde+oE$cS4ro7soM^uas%!Z`i`M))zQXT1=3!EUA7bV4;y{ zDa4^rD91x@Z_RW_sJ-hDgI$yPyl0PbgA&J?QkeGw&qjrGon7ZM$!u%7nMy9i{m_iP# zCJ$-P;_S^q;aci5>FdTbAek7D8kcOEWx*kIaZYwt)R3RteOIv)LZoMw|JC_k<)Y!T z4NHn{+LN+i!x(<-11&F(`#d)L%117x7Gj_7Jj!X_`<`}U-La+4rbB16#Oq`*<@r{t zXF_lAU~`oKGBc%16nuO4R)fO%)lK75yI; zQE#);q@HC`mt?$(&X&4EMtEPiUUU*aCvpGdo3N^~c`i|_VZ9J?=m5>9_Zn~GeA4cZ zR%s}jBaF9hLVhC-@9=JfAfr}Z+trMioLN;TA-iPBC~IB^^Ki*)HzxcTzZ%x12US52 zO?Kndo&ymK|Q8>Jzh=W$DfF-JriZ8 z!YS|cA*s;)e$6=(0ncX`6@9!5@TwN_AW6h99`t@_x4@`eF1`q>hi9!O|nhhtc~EhS##l4$^iVsnBAZ zQSJlONS4~-I4klDL}_;pzi(@t*Vq{#RAi@)O1Ba-}#+E159jmzp!jyGmI zD>Tw?L!VhH$T(nIO_yE_?$th#Gf#etj*Q*HxqUyAr0>}!-Z?&v*~hE3jD$8hCNuIH zZE*{p1GFl{1XXEKpNT42r90VVIWV+ClI3jK#YDW+)s9+gh0Ta=oH04PB4N}5b-(#jE*_Y20EPeKZYcX z2|(EN6@Y!VA`cGU&fkT76&!C5IHsX(8}I#B&r#NpnM58)7*tN~z@o?NFUw)KjXoW2OhYQ{c%Oi1<9{oVpH%Xim@JX8%Bo zS0(HjNh>Z`E{MDLz+iU(5{O+X72r(-=kUh9N`fw=yfOJjT`)*84Sdb7)rH5F2 zx4rTiKGJY)#mK|Dv4uQGD=`GimI)V9<#%6gtpC4 z66H?hvQd=sDSWPVV@563CZ#?~C0btgWsHe4Xg`r#9}?hp9CUL3baXTQfO^J4pP(Th zn?`0IpV3gZJ{j0Tbm5g=u6jshOvuEZMIa!0e%}TWIA;GJe$BvML9y*%IhnO|^t8Z* zhlVHkmFP7_lLe=xLx!?TH>P11ibv~Lr1+CP0mZaTgS$p4V{^CceRyPhJBy59NB661 z3pYP*WkA-YI=L|X^kv4?-f*pBgkODGpVd4$3v?S$Tf`t5xbq#s#gf4$4s}oFsR^5Z z&o;zVfYVojf{~(G0QQD!G=|5y_X?H7=3*Ew*W!EL%ZXyfrcb*Wq?eir^Hu0z_bW##4Q$*#Mf#C|<-&r7fFhUGE4qCn9hH{ZJQ%3k5f8 zg(dq`Ao={;mn9j7M_w!RDcx&iM0g(of6GPq)K|L=B_w>?W-BCFm+eoAws>cMeXRcNsEl5XKe@Ji+x-kS3P-b}CC$w5n=9WpCRTyI?1_XobxS@Tf>fQpx$8u^M(kcKs4d1oqI*UoRT* zj8D!7Vb3!0c5k%%HD_+3K2nmNyTBx_)Al5#LU=#?ggjqS1d!Bdbq?U|L(M`H04b6a znHlB-&o^Qhq+!_;Hq=YJm8zM_Z*XP4qp!1eyX85;bhGVJ9_;hk2uJeIheB*jpM;S$ z;L%p+XK5+^dIBY#bGFwAAf)Q#vCnOkMC^Rg+eY-zH_kD%vkqu74Dy@kr-rbvaa9x4}i}HIq)%=By>+&x&1WDMiC(=;4+ERk=4LF_F!x^D2Ub0!abk*3uyj5U^8~XFD35e` z>3LtxGCR%n#2*UmE6#H2t@`Z$yo7#<-&dDf9f5oAXz`q2n+n4e>w|J#vr*AxeqFWy)KeTjOdXw&(XLF)Tw>2SO+c8_bvM$Acb*p^T5`l(8 zQJ)#(Sw3{!0s$#*1A-wGfjq$tll=U(FTrMzzEB0>3fj9ZPkENRWGOa3TYu-e#oisG zr#0cTsnxe|5YBqouEO{bdmSghSL}ovQ=o{gVoCh52H#pIB#Qjk{=`kzG=?&$*b7?a z&yMLDAyTTnq}{XbSG@iO4j6$6GRJlach!c?MoDFV3Bm><^<(K3Xsak-a-`vi_6eh7 z{s>VrWl(;owGZUW;molbn?Vr(4(@@FSd6+ zfEDj}Ilptgf*PX{L1%x+)@8{SxuTwikVC}g@H4*H3r!iMf?&T!?Un^~OwL{V;Y&|C zJywV(Da5+?_jjBT<3)oWDw8mSeYq32Sn}{(NTca^$|=9yPO1c>{jRdP#7)A^y9c>Z z>;` Date: Sun, 3 Aug 2025 19:26:53 +0500 Subject: [PATCH 019/202] Update posts-page-component.js --- components/posts-page-component.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 4c5e37ea5..ff98d9d28 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -96,7 +96,6 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL ? "./assets/images/like-active.svg" : "./assets/images/like-not-active.svg"; - const imageSrc = post.imageUrl && post.imageUrl.trim() !== '' ? post.imageUrl : "./assets/images/default-image.jpg"; @@ -148,9 +147,12 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL document.querySelectorAll(".post-header").forEach(userEl => { userEl.addEventListener("click", () => { - goToPage(USER_POSTS_PAGE, { - userId: userEl.dataset.userId, - }); + const userId = userEl.dataset.userId; + if (userId) { + goToPage(USER_POSTS_PAGE, { + userId: userEl.dataset.userId, + }); + } }); }); @@ -159,7 +161,7 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL button.addEventListener('click', (event) => { event.stopPropagation(); - + button.classList.add('liked'); setTimeout(() => { button.classList.remove('liked'); @@ -176,7 +178,7 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL }); }); - + const container = document.querySelector('.page-container'); if (container) { container.classList.add('page-transition'); From 1126d664af5573b00705911b6b292d6bfbfe2166 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Sun, 3 Aug 2025 19:28:51 +0500 Subject: [PATCH 020/202] Update styles.css --- styles.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/styles.css b/styles.css index 176241db7..f6c2b93b8 100644 --- a/styles.css +++ b/styles.css @@ -1,5 +1,7 @@ .page-container { padding: 0 20px; + opacity: 1; + transition: opacity 0.3s ease; } .page-header { @@ -382,6 +384,10 @@ transform: scale(1.2); } +.like-button.liked { + animation: pulse 0.3s ease; +} + .user-name { font-weight: 500; } From 3e192f33f7168f5a74aa0d3d343f1e49427a77df Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Sun, 3 Aug 2025 19:34:57 +0500 Subject: [PATCH 021/202] Update index.js --- index.js | 50 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index a42d9ac7e..41e2f6a28 100644 --- a/index.js +++ b/index.js @@ -38,6 +38,7 @@ export const logout = () => { goToPage(POSTS_PAGE); }; + const smoothPageTransition = (callback) => { const appEl = document.getElementById("app"); const container = appEl.querySelector('.page-container'); @@ -73,7 +74,7 @@ export const goToPage = (newPage, data) => { ].includes(newPage) ) { if (newPage === ADD_POSTS_PAGE) { - + page = user ? ADD_POSTS_PAGE : AUTH_PAGE; return renderApp(); } @@ -124,7 +125,18 @@ export const goToPage = (newPage, data) => { export const toggleLike = (postId, isLiked) => { const token = getToken(); if (!token) { - smoothPageTransition(() => goToPage(AUTH_PAGE)); + + const appEl = document.getElementById("app"); + const container = appEl.querySelector('.page-container'); + + if (container) { + container.classList.remove('active'); + setTimeout(() => { + goToPage(AUTH_PAGE); + }, 300); + } else { + goToPage(AUTH_PAGE); + } return Promise.resolve(); } @@ -132,14 +144,14 @@ export const toggleLike = (postId, isLiked) => { return likePromise({ token, postId }) .then((responseData) => { - + const postIndex = posts.findIndex(post => post.id === postId); if (postIndex !== -1) { posts[postIndex] = responseData.post; - } + - - renderApp(); + updatePostInDOM(postId, responseData.post); + } }) .catch((error) => { console.error("Ошибка при работе с лайком:", error); @@ -147,6 +159,27 @@ export const toggleLike = (postId, isLiked) => { }); }; + +function updatePostInDOM(postId, updatedPost) { + const postElement = document.querySelector(`.like-button[data-post-id="${postId}"]`); + if (postElement) { + + const likeImage = postElement.querySelector('img'); + const newLikeImage = updatedPost.isLiked + ? "./assets/images/like-active.svg" + : "./assets/images/like-not-active.svg"; + likeImage.src = newLikeImage; + + const likesCountElement = postElement.closest('.post-likes').querySelector('.post-likes-text strong'); + if (likesCountElement) { + likesCountElement.textContent = updatedPost.likes.length; + } + + + postElement.dataset.isLiked = updatedPost.isLiked; + } +} + const renderApp = () => { const appEl = document.getElementById("app"); if (page === LOADING_PAGE) { @@ -180,7 +213,7 @@ const renderApp = () => { return; } - + if (!description.trim()) { alert("Введите описание поста"); return; @@ -191,10 +224,9 @@ const renderApp = () => { return; } - addPost({ token, description, imageUrl }) .then(() => { - + // После успешного добавления переходим к списку постов smoothPageTransition(() => goToPage(POSTS_PAGE)); }) .catch((error) => { From 75a15c5f9c0495dc08b2e76dc04c3af35250be00 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Sun, 3 Aug 2025 19:36:06 +0500 Subject: [PATCH 022/202] Update posts-page-component.js --- components/posts-page-component.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/components/posts-page-component.js b/components/posts-page-component.js index ff98d9d28..e086ebd4f 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -78,7 +78,7 @@ function getDaysWord(days) { } export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleLike, isUserPostsPage = false }) { - + let pageHeader = ''; if (isUserPostsPage && posts.length > 0) { const firstPost = posts[0]; @@ -96,9 +96,10 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL ? "./assets/images/like-active.svg" : "./assets/images/like-not-active.svg"; + const imageSrc = post.imageUrl && post.imageUrl.trim() !== '' ? post.imageUrl - : "./assets/images/default-image.jpg"; + : "https://via.placeholder.com/500x500?text=No+Image"; return `
  • @@ -107,7 +108,7 @@ export function renderPostsPageComponent({ appEl, posts, user, goToPage, toggleL

    ${post.user.name}

    - +
    - +
    `; - + const fileInputElement = element.querySelector(".file-upload-input"); if (fileInputElement) { fileInputElement.addEventListener("change", () => { @@ -43,11 +43,11 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { labelEl.textContent = "Загружаю файл..."; } - + uploadImage({ file }) - .then(({ imageUrl }) => { - console.log("Получен URL изображения:", imageUrl); - imageUrl = imageUrl; + .then((fileUrl) => { + console.log("Получен URL изображения:", fileUrl); + imageUrl = fileUrl; if (typeof onImageUrlChange === 'function') { onImageUrlChange(imageUrl); } @@ -58,7 +58,7 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { if (typeof onImageUrlChange === 'function') { onImageUrlChange(""); } - + const labelEl = element.querySelector(".file-upload-label"); if (labelEl) { labelEl.removeAttribute("disabled"); @@ -73,11 +73,11 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { const removeButton = element.querySelector(".file-upload-remove-button"); if (removeButton) { removeButton.addEventListener("click", () => { - imageUrl = ""; + imageUrl = ""; if (typeof onImageUrlChange === 'function') { onImageUrlChange(imageUrl); } - render(); + render(); }); } }; From fc6873efd4947a9765cc87250e249957423a915b Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Wed, 6 Aug 2025 18:13:33 +0500 Subject: [PATCH 030/202] Update upload-image-component.js --- components/upload-image-component.js | 37 +++------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/components/upload-image-component.js b/components/upload-image-component.js index 7f68b5014..d1cf43a96 100644 --- a/components/upload-image-component.js +++ b/components/upload-image-component.js @@ -31,7 +31,7 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { `; - + const fileInputElement = element.querySelector(".file-upload-input"); if (fileInputElement) { fileInputElement.addEventListener("change", () => { @@ -43,7 +43,7 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { labelEl.textContent = "Загружаю файл..."; } - + uploadImage({ file }) .then((fileUrl) => { console.log("Получен URL изображения:", fileUrl); @@ -53,35 +53,4 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { } render(); }) - .catch((error) => { - console.error("Ошибка при загрузке изображения:", error); - if (typeof onImageUrlChange === 'function') { - onImageUrlChange(""); - } - - const labelEl = element.querySelector(".file-upload-label"); - if (labelEl) { - labelEl.removeAttribute("disabled"); - labelEl.textContent = "Выберите фото"; - } - }); - } - }); - } - - - const removeButton = element.querySelector(".file-upload-remove-button"); - if (removeButton) { - removeButton.addEventListener("click", () => { - imageUrl = ""; - if (typeof onImageUrlChange === 'function') { - onImageUrlChange(imageUrl); - } - render(); - }); - } - }; - - - render(); -} + .catch((error) { From f1cc48ca539b5e1b119d534ce4966fa2b3b1c87c Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Wed, 6 Aug 2025 18:17:42 +0500 Subject: [PATCH 031/202] Update index.js --- index.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 41e2f6a28..966a57d18 100644 --- a/index.js +++ b/index.js @@ -125,7 +125,7 @@ export const goToPage = (newPage, data) => { export const toggleLike = (postId, isLiked) => { const token = getToken(); if (!token) { - + const appEl = document.getElementById("app"); const container = appEl.querySelector('.page-container'); @@ -144,7 +144,7 @@ export const toggleLike = (postId, isLiked) => { return likePromise({ token, postId }) .then((responseData) => { - + const postIndex = posts.findIndex(post => post.id === postId); if (postIndex !== -1) { posts[postIndex] = responseData.post; @@ -163,19 +163,20 @@ export const toggleLike = (postId, isLiked) => { function updatePostInDOM(postId, updatedPost) { const postElement = document.querySelector(`.like-button[data-post-id="${postId}"]`); if (postElement) { - + const likeImage = postElement.querySelector('img'); const newLikeImage = updatedPost.isLiked ? "./assets/images/like-active.svg" : "./assets/images/like-not-active.svg"; likeImage.src = newLikeImage; + const likesCountElement = postElement.closest('.post-likes').querySelector('.post-likes-text strong'); if (likesCountElement) { likesCountElement.textContent = updatedPost.likes.length; } - + postElement.dataset.isLiked = updatedPost.isLiked; } } @@ -224,9 +225,10 @@ const renderApp = () => { return; } + addPost({ token, description, imageUrl }) .then(() => { - // После успешного добавления переходим к списку постов + smoothPageTransition(() => goToPage(POSTS_PAGE)); }) .catch((error) => { From 7ff094df554e3a6ad64574c76d1deb9e6255ed04 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Wed, 6 Aug 2025 18:22:23 +0500 Subject: [PATCH 032/202] Update upload-image-component.js --- components/upload-image-component.js | 41 ++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/components/upload-image-component.js b/components/upload-image-component.js index d1cf43a96..7a046a112 100644 --- a/components/upload-image-component.js +++ b/components/upload-image-component.js @@ -2,10 +2,10 @@ import { uploadImage } from "../api.js"; export function renderUploadImageComponent({ element, onImageUrlChange }) { - + let imageUrl = ""; - + const render = () => { element.innerHTML = `
    @@ -31,7 +31,7 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) {
    `; - + const fileInputElement = element.querySelector(".file-upload-input"); if (fileInputElement) { fileInputElement.addEventListener("change", () => { @@ -45,7 +45,7 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { uploadImage({ file }) - .then((fileUrl) => { + .then((fileUrl) => { console.log("Получен URL изображения:", fileUrl); imageUrl = fileUrl; if (typeof onImageUrlChange === 'function') { @@ -53,4 +53,35 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { } render(); }) - .catch((error) { + .catch((error) => { + console.error("Ошибка при загрузке изображения:", error); + if (typeof onImageUrlChange === 'function') { + onImageUrlChange(""); + } + + const labelEl = element.querySelector(".file-upload-label"); + if (labelEl) { + labelEl.removeAttribute("disabled"); + labelEl.textContent = "Выберите фото"; + } + }); + } + }); + } + + + const removeButton = element.querySelector(".file-upload-remove-button"); + if (removeButton) { + removeButton.addEventListener("click", () => { + imageUrl = ""; + if (typeof onImageUrlChange === 'function') { + onImageUrlChange(imageUrl); + } + render(); + }); + } + }; + + + render(); +} From 945e320255fe4e2f7e0e406a6a982361aa15b5a7 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Wed, 6 Aug 2025 18:33:27 +0500 Subject: [PATCH 033/202] Update upload-image-component.js --- components/upload-image-component.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/components/upload-image-component.js b/components/upload-image-component.js index 7a046a112..481756cec 100644 --- a/components/upload-image-component.js +++ b/components/upload-image-component.js @@ -31,7 +31,7 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { `; - + const fileInputElement = element.querySelector(".file-upload-input"); if (fileInputElement) { fileInputElement.addEventListener("change", () => { @@ -43,22 +43,22 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { labelEl.textContent = "Загружаю файл..."; } - + uploadImage({ file }) .then((fileUrl) => { - console.log("Получен URL изображения:", fileUrl); - imageUrl = fileUrl; - if (typeof onImageUrlChange === 'function') { - onImageUrlChange(imageUrl); + console.log("Получен URL изображения:", fileUrl); + imageUrl = fileUrl; + if (typeof onImageUrlChange === 'function') { + onImageUrlChange(imageUrl); } - render(); + render(); }) .catch((error) => { console.error("Ошибка при загрузке изображения:", error); if (typeof onImageUrlChange === 'function') { - onImageUrlChange(""); + onImageUrlChange(""); } - + const labelEl = element.querySelector(".file-upload-label"); if (labelEl) { labelEl.removeAttribute("disabled"); @@ -69,15 +69,15 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { }); } - + const removeButton = element.querySelector(".file-upload-remove-button"); if (removeButton) { removeButton.addEventListener("click", () => { - imageUrl = ""; - if (typeof onImageUrlChange === 'function') { - onImageUrlChange(imageUrl); + imageUrl = ""; + if (typeof onImageUrlChange === 'function') { + onImageUrlChange(imageUrl); } - render(); + render(); }); } }; From 60371c1cf2a6c99bb0c449e488f8e232390bba3b Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Wed, 6 Aug 2025 18:38:50 +0500 Subject: [PATCH 034/202] Update upload-image-component.js --- components/upload-image-component.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/components/upload-image-component.js b/components/upload-image-component.js index 481756cec..3ea8014c9 100644 --- a/components/upload-image-component.js +++ b/components/upload-image-component.js @@ -5,7 +5,7 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { let imageUrl = ""; - + const render = () => { element.innerHTML = `
    @@ -31,7 +31,7 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) {
    `; - + const fileInputElement = element.querySelector(".file-upload-input"); if (fileInputElement) { fileInputElement.addEventListener("change", () => { @@ -43,22 +43,22 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { labelEl.textContent = "Загружаю файл..."; } - + uploadImage({ file }) - .then((fileUrl) => { - console.log("Получен URL изображения:", fileUrl); - imageUrl = fileUrl; + .then((fileUrl) => { + console.log("Получен URL изображения:", fileUrl); + imageUrl = fileUrl; // if (typeof onImageUrlChange === 'function') { onImageUrlChange(imageUrl); } - render(); + render(); }) .catch((error) => { console.error("Ошибка при загрузке изображения:", error); if (typeof onImageUrlChange === 'function') { - onImageUrlChange(""); + onImageUrlChange(""); } - + const labelEl = element.querySelector(".file-upload-label"); if (labelEl) { labelEl.removeAttribute("disabled"); @@ -69,19 +69,18 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { }); } - + const removeButton = element.querySelector(".file-upload-remove-button"); if (removeButton) { removeButton.addEventListener("click", () => { imageUrl = ""; - if (typeof onImageUrlChange === 'function') { - onImageUrlChange(imageUrl); + if (typeof onImageUrlChange === 'function') { + onImageUrlChange(imageUrl); } - render(); + render(); }); } }; - render(); } From 468680e18c542b8f979e94ee427f0ed8f084f143 Mon Sep 17 00:00:00 2001 From: Alina-skypro2025 Date: Wed, 6 Aug 2025 18:43:02 +0500 Subject: [PATCH 035/202] Update add-post-page-component.js --- components/add-post-page-component.js | 114 +++++++++++--------------- 1 file changed, 47 insertions(+), 67 deletions(-) diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 5fe8b0b82..92269f91a 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -1,81 +1,61 @@ import { uploadImage } from "../api.js"; import { renderUploadImageComponent } from "./upload-image-component.js"; -import { renderHeaderComponent } from "./header-component.js"; export function renderAddPostPageComponent({ appEl, onAddPostClick }) { - let imageUrl = ""; + let currentImageUrl = ""; - const render = () => { - const appHtml = ` -
    -
    -
    -

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

    -
    -
    -
    -
    - -
    -
    -