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
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,36 @@

MVP социальной сети для обмена фотографиями

## Реализованный функционал

### Основные функции
- Авторизация и регистрация пользователей с загрузкой аватарок
- Просмотр ленты постов всех пользователей
- Добавление новых постов с изображениями и описанием
- Просмотр постов конкретного пользователя
- Система лайков с мгновенным обновлением
- Форматирование дат постов в относительном формате

### Технические особенности
- Модульная архитектура приложения
- Компонентный подход к разработке
- Работа с REST API
- Загрузка изображений в облачное хранилище
- Динамическое обновление контента без перезагрузки страницы
- Локальное хранение данных пользователя
- Обработка ошибок и валидация форм

### Интерфейсные улучшения
- Адаптивный и интуитивный интерфейс
- Информативные сообщения об ошибках
- Состояния загрузки для всех операций
- Интерактивные элементы с hover-эффектами
- Красивое отображение времени публикации

## Первоначальная оценка

ХХХХ часов
8 часов

## Фактически затраченное время

YYYY часов
10 часов
137 changes: 117 additions & 20 deletions api.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Замени на свой, чтобы получить независимый от других набор данных.
// "боевая" версия инстапро лежит в ключе prod
const personalKey = "prod";

const personalKey = "dmitriy-skachkov";
const baseHost = "https://webdev-hw-api.vercel.app";
const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`;

Expand All @@ -15,7 +14,94 @@ export function getPosts({ token }) {
if (response.status === 401) {
throw new Error("Нет авторизации");
}
if (!response.ok) {
throw new Error(`Ошибка HTTP! статус: ${response.status}`);
}
return response.json();
})
.then((data) => {
return data.posts;
});
}

export function addPost({ token, description, imageUrl }) {
return fetch(postsHost, {
method: "POST",
body: JSON.stringify({
description,
imageUrl,
}),
headers: {
Authorization: token,
},
})
.then((response) => {
if (response.status === 401) {
throw new Error("Нет авторизации");
}
if (!response.ok) {
throw new Error(`Ошибка HTTP! статус: ${response.status}`);
}
return response.json();
});
}

export function likePost({ token, postId }) {
return fetch(`${postsHost}/${postId}/like`, {
method: "POST",
headers: {
Authorization: token,
},
})
.then((response) => {
if (response.status === 401) {
throw new Error("Нет авторизации");
}
if (response.status === 404) {
throw new Error("Пост не найден");
}
if (!response.ok) {
throw new Error(`Ошибка лайка! статус: ${response.status}`);
}
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("Нет авторизации");
}
if (response.status === 404) {
throw new Error("Пост не найден");
}
if (!response.ok) {
throw new Error(`Ошибка дизлайка! статус: ${response.status}`);
}
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("Нет авторизации");
}
if (!response.ok) {
throw new Error(`Ошибка HTTP! статус: ${response.status}`);
}
return response.json();
})
.then((data) => {
Expand All @@ -32,12 +118,16 @@ export function registerUser({ login, password, name, imageUrl }) {
name,
imageUrl,
}),
}).then((response) => {
if (response.status === 400) {
throw new Error("Такой пользователь уже существует");
}
return response.json();
});
})
.then((response) => {
if (response.status === 400) {
throw new Error("Такой пользователь уже существует");
}
if (!response.ok) {
throw new Error(`Ошибка HTTP! статус: ${response.status}`);
}
return response.json();
});
}

export function loginUser({ login, password }) {
Expand All @@ -47,23 +137,30 @@ export function loginUser({ login, password }) {
login,
password,
}),
}).then((response) => {
if (response.status === 400) {
throw new Error("Неверный логин или пароль");
}
return response.json();
});
})
.then((response) => {
if (response.status === 400) {
throw new Error("Неверный логин или пароль");
}
if (!response.ok) {
throw new Error(`Ошибка HTTP! статус: ${response.status}`);
}
return response.json();
});
}

// Загружает картинку в облако, возвращает url загруженной картинки
export function uploadImage({ file }) {
const data = new FormData();
data.append("file", file);

return fetch(baseHost + "/api/upload/image", {
method: "POST",
body: data,
}).then((response) => {
return response.json();
});
}
})
.then((response) => {
if (!response.ok) {
throw new Error(`Ошибка HTTP! статус: ${response.status}`);
}
return response.json();
});
}
54 changes: 48 additions & 6 deletions components/add-post-page-component.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,65 @@
import { renderHeaderComponent } from "./header-component.js";
import { renderUploadImageComponent } from "./upload-image-component.js";

export function renderAddPostPageComponent({ appEl, onAddPostClick }) {
let imageUrl = "";
let description = "";

const render = () => {
// @TODO: Реализовать страницу добавления поста
const appHtml = `
<div class="page-container">
<div class="header-container"></div>
Cтраница добавления поста
<button class="button" id="add-button">Добавить</button>
<div class="form">
<h3 class="form-title">Добавить пост</h3>
<div class="form-inputs">
<div class="upload-image-container">Загрузка изображения...</div>
<textarea
id="description-input"
class="input textarea"
placeholder="Описание фотографии"
rows="4"
>${description}</textarea>
<button class="button" id="add-button">Добавить пост</button>
</div>
</div>
</div>
`;

appEl.innerHTML = appHtml;

renderHeaderComponent({
element: document.querySelector(".header-container"),
});

renderUploadImageComponent({
element: document.querySelector(".upload-image-container"),
onImageUrlChange: (newImageUrl) => {
imageUrl = newImageUrl;
},
});

const descriptionInput = document.getElementById("description-input");
descriptionInput.addEventListener("input", (e) => {
description = e.target.value;
});

document.getElementById("add-button").addEventListener("click", () => {
if (!imageUrl) {
alert("Не выбрана фотография");
return;
}

if (!description.trim()) {
alert("Добавьте описание фотографии");
return;
}

onAddPostClick({
description: "Описание картинки",
imageUrl: "https://image.png",
description: description.trim(),
imageUrl: imageUrl,
});
});
};

render();
}
}
Loading