Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 108 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,115 @@ 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: реализовать страницу с фотографиями отдельного пользвателя

Добявляем в api.js функцию getUserPosts для получения постов от конкретного пользователя и отправляем ее в index.js

Меняем логику получения постов для USER_POSTS_PAGE

3) Интерфейс создания поста:
add-post-page-component.js
@TODO: Реализовать страницу добавления поста

- Добавляем разметку страницы добавления поста
- Добовляем шапку
- И форму выбора фото с валидацией
- И О Чудо активировались страницы входа и регистрации

4) Теперь публикация. Возвращаемся в index.js - @TODO: реализовать добавление поста в API

В api добавляем функцию создания поста addPost
В index добавляем импорт из нее и вызываем функцию в ADD_POSTS_PAGE



5) И в конце когда все будет работать мы можем внести правку в posts-page-component.js @TODO: чтобы отформатировать дату создания поста в виде "19 минут назад" можно использовать https://date-fns.org/v2.29.3/docs/formatDistanceToNow

Подключил библиотеки для локолизации и форматирования даты публикации

ФИНАЛ Доп задание - Лайки
Лайки можно ставить и удалять
api добавим функции POST и DELETE

Импортируем функции в posts-page-component
Добовляем обработчик кликов
если не авторизован то ошибка в алерт

ГОТОВО
57 changes: 55 additions & 2 deletions api.js
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -15,7 +34,24 @@ export function getPosts({ token }) {
if (response.status === 401) {
throw new Error("Нет авторизации");
}
return response.json();
})
.then((data) => {
return data.posts;
});
}

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) => {
Expand Down Expand Up @@ -55,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();
Expand All @@ -66,4 +119,4 @@ export function uploadImage({ file }) {
}).then((response) => {
return response.json();
});
}
}
66 changes: 58 additions & 8 deletions components/add-post-page-component.js
Original file line number Diff line number Diff line change
@@ -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 = `
<div class="page-container">
<div class="header-container"></div>
Cтраница добавления поста
<button class="button" id="add-button">Добавить</button>
</div>
`;
<div class="page-container">
<div class="header-container"></div>
<div class="form">
<h3 class="form-title">Добавить пост</h3>
<div class="form-inputs">
<div class="upload-image-container"></div>

<label>
Опишите фотографию:
<textarea class="input textarea" id="description-input" rows="4"></textarea>
</label>

<button class="button" id="add-button">Добавить</button>
</div>
</div>
</div>
`;

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,
});
});
};
Expand Down
Loading