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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
Empty file added .prettierrc.yaml
Empty file.
20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# Instapro

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

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

ХХХХ часов

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

YYYY часов
Интеграция списка постов с API.
Реализация страницы добавления поста конкретного пользователя.
Реализация страницы с постами конкретного пользователя.
Функционал лайков и дизлайков для авторизованных пользователей.
Анимация при наведении на лайк.
Анимация при наведении на кнопку 'Удалить'.
Страница авторизации/регистрации с переключением режимов.
Форматирование дат на русском языке с использованием date-fns.
Всплывающие уведомления.
126 changes: 90 additions & 36 deletions api.js
Original file line number Diff line number Diff line change
@@ -1,69 +1,123 @@
// Замени на свой, чтобы получить независимый от других набор данных.
// "боевая" версия инстапро лежит в ключе prod
const personalKey = "prod";
const baseHost = "https://webdev-hw-api.vercel.app";
const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`;
const personalKey = ":veronika-milya";
const baseUrlImg = "https://webdev-hw-api.vercel.app/api";
const baseHost = "https://wedev-api.sky.pro/api";
const baseUrl = `${baseHost}/v1/${personalKey}/instapro`;

export function getPosts({ token }) {
return fetch(postsHost, {
export async function getPostsUser(id) {
const response = await fetch(baseUrl + "/user-posts/" + id);
const data = await response.json();
return data.posts;
}

export async function getPosts({ token }) {
const response = await fetch(baseUrl, {
method: "GET",
headers: {
Authorization: token,
},
})
.then((response) => {
if (response.status === 401) {
throw new Error("Нет авторизации");
}
});
if (response.status === 401) {
throw new Error("Нет авторизации");
}
const data = await response.json();
return data.posts;
}

export async function createPost({ token, imageUrl, description }) {
const response = await fetch(baseUrl, {
method: "POST",
headers: {
Authorization: token,
},
body: JSON.stringify({
description,
imageUrl,
}),
});
if (response.status != 201) {
throw new Error("Не удалось добавить пост");
}
return await response.json();
}

export async function deletePost({ token, id }) {
const response = await fetch(baseUrl + "/" + id, {
method: "DELETE",
headers: {
Authorization: token,
},
});
if (response.status != 200) {
throw new Error("Не удалось удалить пост");
}
return await response.json();
}

export async function addLike({ token, id }) {
const response = await fetch(baseUrl + "/" + id + "/like", {
method: "POST",
headers: {
Authorization: token,
},
});
if (response.status != 200) {
alert("Авторизуйтесь, чтобы поставить лайк");
throw new Error("Не удалось поставить лайк");
}
return await response.json();
}

return response.json();
})
.then((data) => {
return data.posts;
});
export async function disLike({ token, id }) {
const response = await fetch(baseUrl + "/" + id + "/dislike", {
method: "POST",
headers: {
Authorization: token,
},
});
if (response.status != 200) {
alert("Авторизуйтесь, чтобы убрать лайк");
throw new Error("Не удалось убрать лайк");
}
return await response.json();
}

export function registerUser({ login, password, name, imageUrl }) {
return fetch(baseHost + "/api/user", {
export async function registerUser({ login, password, name, imageUrl }) {
const response = await fetch(baseHost + "/user", {
method: "POST",
body: JSON.stringify({
login,
password,
name,
imageUrl,
}),
}).then((response) => {
if (response.status === 400) {
throw new Error("Такой пользователь уже существует");
}
return response.json();
});
if (response.status === 400) {
throw new Error("Такой пользователь уже существует");
}
return await response.json();
}

export function loginUser({ login, password }) {
return fetch(baseHost + "/api/user/login", {
export async function loginUser({ login, password }) {
const response = await fetch(baseHost + "/user/login", {
method: "POST",
body: JSON.stringify({
login,
password,
}),
}).then((response) => {
if (response.status === 400) {
throw new Error("Неверный логин или пароль");
}
return response.json();
});
if (response.status === 400) {
throw new Error("Неверный логин или пароль");
}
return await response.json();
}

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

return fetch(baseHost + "/api/upload/image", {
const response = await fetch(baseUrlImg + "/upload/image", {
method: "POST",
body: data,
}).then((response) => {
return response.json();
});
return await response.json();
}
40 changes: 35 additions & 5 deletions components/add-post-page-component.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,52 @@
import { renderHeaderComponent } from "./header-component.js";
import { renderUploadImageComponent } from "./upload-image-component.js";
import { goToPage, user } from "../index.js";

let imageUrl = "";

export function renderAddPostPageComponent({ appEl, onAddPostClick }) {
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>
<label>
Опишите фотографию:
<textarea class="input textarea" rows="4"></textarea>
</label>
<button class="button" id="add-button">Добавить</button>
</div>
</div>
</div>
`;

appEl.innerHTML = appHtml;

document.getElementById("add-button").addEventListener("click", () => {
const textarea = document.querySelector("textarea");
onAddPostClick({
description: "Описание картинки",
imageUrl: "https://image.png",
description: textarea.value,
imageUrl,
});
});

renderHeaderComponent({
element: document.querySelector(".header-container"),
user,
goToPage,
});
const uploadImageContainer = appEl.querySelector(".upload-image-container");
if (uploadImageContainer) {
renderUploadImageComponent({
element: uploadImageContainer,
onImageUrlChange(newImageUrl) {
imageUrl = newImageUrl;
},
});
}
};

render();
Expand Down
36 changes: 0 additions & 36 deletions components/auth-page-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,10 @@ import { loginUser, registerUser } from "../api.js";
import { renderHeaderComponent } from "./header-component.js";
import { renderUploadImageComponent } from "./upload-image-component.js";

/**
* Компонент страницы авторизации.
* Этот компонент предоставляет пользователю интерфейс для входа в систему или регистрации.
* Форма переключается между режимами "Вход" и "Регистрация".
*
* @param {HTMLElement} params.appEl - Корневой элемент приложения, в который будет рендериться страница.
* @param {Function} params.setUser - Функция, вызываемая при успешной авторизации или регистрации.
* Принимает объект пользователя в качестве аргумента.
*/
export function renderAuthPageComponent({ appEl, setUser }) {
/**
* Флаг, указывающий текущий режим формы.
* Если `true`, форма находится в режиме входа. Если `false`, в режиме регистрации.
* @type {boolean}
*/
let isLoginMode = true;

/**
* URL изображения, загруженного пользователем при регистрации.
* Используется только в режиме регистрации.
* @type {string}
*/
let imageUrl = "";

/**
* Рендерит форму авторизации или регистрации.
* В зависимости от значения `isLoginMode` отображает соответствующий интерфейс.
*/
const renderForm = () => {
const appHtml = `
<div class="page-container">
Expand Down Expand Up @@ -72,20 +48,14 @@ export function renderAuthPageComponent({ appEl, setUser }) {

appEl.innerHTML = appHtml;

/**
* Устанавливает сообщение об ошибке в форме.
* @param {string} message - Текст сообщения об ошибке.
*/
const setError = (message) => {
appEl.querySelector(".form-error").textContent = message;
};

// Рендерим заголовок страницы
renderHeaderComponent({
element: document.querySelector(".header-container"),
});

// Если режим регистрации, рендерим компонент загрузки изображения
const uploadImageContainer = appEl.querySelector(".upload-image-container");
if (uploadImageContainer) {
renderUploadImageComponent({
Expand All @@ -96,12 +66,10 @@ export function renderAuthPageComponent({ appEl, setUser }) {
});
}

// Обработка клика на кнопку входа/регистрации
document.getElementById("login-button").addEventListener("click", () => {
setError("");

if (isLoginMode) {
// Обработка входа
const login = document.getElementById("login-input").value;
const password = document.getElementById("password-input").value;

Expand All @@ -124,7 +92,6 @@ export function renderAuthPageComponent({ appEl, setUser }) {
setError(error.message);
});
} else {
// Обработка регистрации
const login = document.getElementById("login-input").value;
const name = document.getElementById("name-input").value;
const password = document.getElementById("password-input").value;
Expand Down Expand Up @@ -160,13 +127,10 @@ export function renderAuthPageComponent({ appEl, setUser }) {
}
});

// Обработка переключения режима (вход ↔ регистрация)
document.getElementById("toggle-button").addEventListener("click", () => {
isLoginMode = !isLoginMode;
renderForm(); // Перерисовываем форму с новым режимом
});
};

// Инициализация формы
renderForm();
}
29 changes: 5 additions & 24 deletions components/header-component.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
import { goToPage, logout, user } from "../index.js";
import { ADD_POSTS_PAGE, AUTH_PAGE, POSTS_PAGE } from "../routes.js";

/**
* Компонент заголовка страницы.
* Этот компонент отображает шапку страницы с логотипом, кнопкой добавления постов/входа и кнопкой выхода (если пользователь авторизован).
*
* @param {HTMLElement} params.element - HTML-элемент, в который будет рендериться заголовок.
* @returns {HTMLElement} Возвращает элемент заголовка после рендеринга.
*/
export function renderHeaderComponent({ element }) {
/**
* Рендерит содержимое заголовка.
*/
element.innerHTML = `
<div class="page-header">
<h1 class="logo">instapro</h1>
<button class="header-button add-or-login-button">
<button class="header-button add-or-login-button">
${
user
? `<div title="Добавить пост" class="add-post-sign"></div>`
Expand All @@ -29,12 +19,11 @@ export function renderHeaderComponent({ element }) {
}
</div>
`;
const logo = element.querySelector(".logo");
logo.addEventListener("click", () => {
goToPage(POSTS_PAGE);
});

/**
* Обработчик клика по кнопке "Добавить пост"/"Войти".
* Если пользователь авторизован, перенаправляет на страницу добавления постов.
* Если пользователь не авторизован, перенаправляет на страницу авторизации.
*/
element
.querySelector(".add-or-login-button")
.addEventListener("click", () => {
Expand All @@ -45,18 +34,10 @@ export function renderHeaderComponent({ element }) {
}
});

/**
* Обработчик клика по логотипу.
* Перенаправляет на страницу с постами.
*/
element.querySelector(".logo").addEventListener("click", () => {
goToPage(POSTS_PAGE);
});

/**
* Обработчик клика по кнопке "Выйти".
* Если кнопка существует (т.е. пользователь авторизован), вызывает функцию `logout`.
*/
element.querySelector(".logout-button")?.addEventListener("click", logout);

return element;
Expand Down
Loading