diff --git a/README.md b/README.md index 216dfc2a..fe3f3edd 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ MVP аналога популярной соц. сети для обмена ф ## Ссылка на приложение: -https:: +https://sciworker.github.io/webdev-cw-instapro-main1/ ## Первоначальная оценка -ХХХХ часов +8 часов ## Фактически затраченное время -YYYY часов +10 часов diff --git a/api.js b/api.js index 8997c44d..c0238138 100644 --- a/api.js +++ b/api.js @@ -1,6 +1,6 @@ // Замени на свой, чтобы получить независимый от других набор данных. // "боевая" версия инстапро лежит в ключе prod -const personalKey = "prod"; +const personalKey = "Petr"; const baseHost = "https://webdev-hw-api.vercel.app"; const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`; @@ -15,7 +15,6 @@ export function getPosts({ token }) { if (response.status === 401) { throw new Error("Нет авторизации"); } - return response.json(); }) .then((data) => { @@ -68,3 +67,71 @@ export function uploadImage({ file }) { return response.json(); }); } + +// Создает новый пост +export function createPost({ token, description, imageUrl }) { + return fetch(postsHost, { + method: "POST", + headers: { + Authorization: token + + }, + body: JSON.stringify({ + description, + imageUrl, + }), + }).then((response) => { + if (response.status === 400) { + throw new Error("Ошибка создания поста"); + } + return response.json(); + }); +} + +export function getUserPosts({ userId, token }) { + 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({ postId, token }) { + return fetch(`${postsHost}/${postId}/like`, { + method: "POST", + headers: { + Authorization: token, + }, + }) + .then((response) => { + if (response.status === 401) { + throw new Error("Нет авторизации"); + } + return response.json(); + }); +} + +export function unlikePost({ postId, token }) { + return fetch(`${postsHost}/${postId}/dislike`, { + method: "POST", + headers: { + Authorization: token, + }, + }) + .then((response) => { + if (response.status === 401) { + throw new Error("Нет авторизации"); + } + return response.json(); + }); +} diff --git a/components/add-post-page-component.js b/components/add-post-page-component.js index 59554d86..d8b88ffb 100644 --- a/components/add-post-page-component.js +++ b/components/add-post-page-component.js @@ -1,23 +1,50 @@ +import { renderHeaderComponent } from "./header-component.js"; +import { renderUploadImageComponent } from "./upload-image-component.js"; + export function renderAddPostPageComponent({ appEl, onAddPostClick }) { + let uploadedImageUrl = ""; // Переменная для хранения URL загруженного изображения + + // Определите функцию для обработки изменения URL изображения + function handleImageUrlChange(newImageUrl) { + uploadedImageUrl = newImageUrl; // Сохраните новый URL изображения + } + const render = () => { - // TODO: Реализовать страницу добавления поста + // Реализовать страницу добавления поста const appHtml = ` -
- Cтраница добавления поста - -
- `; +
+

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

+
+
+ + +
+
+ `; appEl.innerHTML = appHtml; + renderHeaderComponent({ + element: document.querySelector(".header-container"), + }); + + renderUploadImageComponent({ + element: document.querySelector(".upload-image-container"), + onImageUrlChange: handleImageUrlChange, + }); + document.getElementById("add-button").addEventListener("click", () => { + const description = document.getElementById("description").value; // Получить описание из textarea onAddPostClick({ - description: "Описание картинки", - imageUrl: "https://image.png", + description: description, + imageUrl: uploadedImageUrl, }); }); }; render(); -} +} \ No newline at end of file diff --git a/components/header-component.js b/components/header-component.js index 465fbb8f..cd999d06 100644 --- a/components/header-component.js +++ b/components/header-component.js @@ -39,3 +39,23 @@ export function renderHeaderComponent({ element }) { return element; } + +// export const renderHeaderComponent = ({ appEl, user, goToPage }) => { +// const headerContainer = document.createElement('div'); +// headerContainer.className = 'header-container'; + +// headerContainer.innerHTML = ` +//
+//

Welcome ${user ? user.name : 'Guest'}

+// ${user ? '' : ''} +//
+// `; + +// appEl.prepend(headerContainer); + +// if (user) { +// document.getElementById('logout-btn').addEventListener('click', () => { +// goToPage('logout'); +// }); +// } +// }; diff --git a/components/posts-page-component.js b/components/posts-page-component.js index 5b97fdfe..a668b603 100644 --- a/components/posts-page-component.js +++ b/components/posts-page-component.js @@ -1,97 +1,43 @@ 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, unlikePost } from "../api.js"; export function renderPostsPageComponent({ appEl }) { - // TODO: реализовать рендер постов из api console.log("Актуальный список постов:", posts); - /** - * TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" - * можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow - */ const appHtml = ` -
-
- -
`; +
+
+ +
`; appEl.innerHTML = appHtml; @@ -106,4 +52,39 @@ export function renderPostsPageComponent({ appEl }) { }); }); } + + for (let likeButton of document.querySelectorAll(".like-button")) { + likeButton.addEventListener("click", () => { + const postId = likeButton.dataset.postId; + const post = posts.find(post => post.id === postId); + const isLiked = post.isLiked; + + if (isLiked) { + unlikePost({ postId, token: getToken() }).then(() => { + // Обновляем локально список постов после успешного отзыва лайка + post.isLiked = false; + post.likes = post.likes.filter(like => like.id !== user.id); + renderPostsPageComponent({ appEl }); + }).catch(error => { + console.error("Ошибка отзыва лайка:", error); + alert("Ошибка отзыва лайка: " + error.message); + }); + } else { + likePost({ postId, token: getToken() }).then(() => { + // Обновляем локально список постов после успешного лайка + post.isLiked = true; + post.likes.push({ id: user.id, name: user.name }); + renderPostsPageComponent({ appEl }); + }).catch(error => { + console.error("Ошибка лайка:", error); + alert("Ошибка лайка: " + error.message); + }); + } + }); + } } + +const getToken = () => { + const token = user ? `Bearer ${user.token}` : undefined; + return token; +}; diff --git a/components/upload-image-component.js b/components/upload-image-component.js index 2bdcdbf3..8cd12873 100644 --- a/components/upload-image-component.js +++ b/components/upload-image-component.js @@ -5,54 +5,63 @@ export function renderUploadImageComponent({ element, onImageUrlChange }) { const render = () => { element.innerHTML = ` -
- ${ - imageUrl - ? ` -
- - -
- ` - : ` - + ` + } +
+ `; const fileInputElement = element.querySelector(".file-upload-input"); - fileInputElement?.addEventListener("change", () => { - const file = fileInputElement.files[0]; - if (file) { - const lableEl = document.querySelector(".file-upload-label"); - lableEl.setAttribute("disabled", true); - lableEl.textContent = "Загружаю файл..."; - uploadImage({ file }).then(({ fileUrl }) => { - imageUrl = fileUrl; - onImageUrlChange(imageUrl); - render(); - }); - } - }); + if (fileInputElement) { + fileInputElement.addEventListener("change", () => { + const file = fileInputElement.files[0]; + if (file) { + const labelEl = element.querySelector(".file-upload-label"); + labelEl.setAttribute("disabled", true); + labelEl.textContent = "Загружаю файл..."; + uploadImage({ file }) + .then(({ fileUrl }) => { + imageUrl = fileUrl; + onImageUrlChange(imageUrl); + render(); // Перерисовка компонента с новым URL изображения + }) + .catch(error => { + console.error("Ошибка загрузки файла:", error); + labelEl.removeAttribute("disabled"); + labelEl.textContent = "Выберите фото"; + }); + } + }); + } + + const removeButtonElement = element.querySelector(".file-upload-remove-button"); - element - .querySelector(".file-upload-remove-button") - ?.addEventListener("click", () => { + if (removeButtonElement) { + removeButtonElement.addEventListener("click", () => { imageUrl = ""; onImageUrlChange(imageUrl); - render(); + render(); // Перерисовка компонента для удаления изображения }); + } }; render(); -} +} \ No newline at end of file diff --git a/components/user-posts-page-component.js b/components/user-posts-page-component.js new file mode 100644 index 00000000..4e675abb --- /dev/null +++ b/components/user-posts-page-component.js @@ -0,0 +1,83 @@ +import { renderHeaderComponent } from "./header-component.js"; +import { goToPage, user } from "../index.js"; +import { likePost, unlikePost } from "../api.js"; + + +export const renderUserPostsPageComponent = ({ appEl, posts }) => { + if (!Array.isArray(posts)) { + console.error("Posts is not an array or undefined:", posts); + posts = []; + } + + appEl.innerHTML = ` +
+
+ +
`; + + renderHeaderComponent({ + element: document.querySelector(".header-container"), + }); + + for (let likeButton of document.querySelectorAll(".like-button")) { + likeButton.addEventListener("click", () => { + const postId = likeButton.dataset.postId; + const post = posts.find(post => post.id === postId); + const isLiked = post.isLiked; + + if (isLiked) { + unlikePost({ postId, token: getToken() }).then(() => { + // Обновляем локально список постов после успешного отзыва лайка + post.isLiked = false; + post.likes = post.likes.filter(like => like.id !== user.id); + renderUserPostsPageComponent({ appEl, posts }); // Обновляем текущую страницу + }).catch(error => { + console.error("Ошибка отзыва лайка:", error); + alert("Ошибка отзыва лайка: " + error.message); + }); + } else { + likePost({ postId, token: getToken() }).then(() => { + // Обновляем локально список постов после успешного лайка + post.isLiked = true; + post.likes.push({ id: user.id, name: user.name }); + renderUserPostsPageComponent({ appEl, posts }); // Обновляем текущую страницу + }).catch(error => { + console.error("Ошибка лайка:", error); + alert("Ошибка лайка: " + error.message); + }); + } + }); + } +}; + +const getToken = () => { + const token = user ? `Bearer ${user.token}` : undefined; + return token; +}; diff --git a/index.html b/index.html index 96674b4d..2b4471d3 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,8 @@
- + + diff --git a/index.js b/index.js index dd05ef0f..f62f85a2 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import { getPosts } from "./api.js"; +import { getPosts, createPost, likePost, unlikePost } from "./api.js"; import { renderAddPostPageComponent } from "./components/add-post-page-component.js"; import { renderAuthPageComponent } from "./components/auth-page-component.js"; import { @@ -15,6 +15,8 @@ import { removeUserFromLocalStorage, saveUserToLocalStorage, } from "./helpers.js"; +import { renderUserPostsPageComponent } from "./components/user-posts-page-component.js"; +import { getUserPosts } from "./api.js"; export let user = getUserFromLocalStorage(); export let page = null; @@ -67,16 +69,23 @@ 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(); + console.log(data); + return getUserPosts({ userId: data.userId, token: getToken() }) + .then((userPosts) => { + page = USER_POSTS_PAGE; + posts = userPosts; + renderApp(); + }) + .catch((error) => { + console.error(error); + goToPage(POSTS_PAGE); + }); } page = newPage; renderApp(); - return; } @@ -110,9 +119,18 @@ const renderApp = () => { return renderAddPostPageComponent({ appEl, onAddPostClick({ description, imageUrl }) { - // TODO: реализовать добавление поста в API + const token = getToken(); console.log("Добавляю пост...", { description, imageUrl }); - goToPage(POSTS_PAGE); + + createPost({ token, description, imageUrl }) + .then((post) => { + console.log("Пост успешно создан:", post); + goToPage(POSTS_PAGE); + }) + .catch((error) => { + console.error("Ошибка создания поста:", error); + alert("Ошибка создания поста: " + error.message); + }); }, }); } @@ -124,9 +142,7 @@ const renderApp = () => { } if (page === USER_POSTS_PAGE) { - // TODO: реализовать страницу фотографию пользвателя - appEl.innerHTML = "Здесь будет страница фотографий пользователя"; - return; + return renderUserPostsPageComponent({ appEl, posts }); } };