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

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

## Реализованная функциональность

### ✅ Основные функции
- **Авторизация и регистрация пользователей**
- Регистрация с валидацией данных (логин, пароль, имя, фото)
- Авторизация с проверкой учетных данных
- Сохранение сессии в localStorage
- Автоматическая проверка авторизации при загрузке
- Кнопка "Выйти" для завершения сессии

- **Общая лента постов**
- Отображение всех постов от всех пользователей
- Доступ для авторизованных и неавторизованных пользователей
- Красивое форматирование дат ("только что", "5 минут назад", etc.)
- Переходы к профилям пользователей по клику на имя

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

- **Добавление новых постов**
- Загрузка изображений в облако
- Валидация обязательных полей (текст и фото)
- Доступ только для авторизованных пользователей
- Мгновенное обновление ленты после добавления

- **Система лайков**
- Постановка и снятие лайков
- Счетчик количества лайков
- Работает на всех страницах (общая лента и профили пользователей)
- Доступ только для авторизованных пользователей

- **Удаление постов**
- Удаление собственных постов
- Подтверждение действия
- Мгновенное обновление интерфейса

### ✅ Техническая реализация
- **API интеграция**
- Получение постов всех пользователей
- Получение постов конкретного пользователя
- Добавление новых постов
- Лайки и дизлайки
- Удаление постов
- Загрузка изображений в облако

- **Безопасность**
- Защита от XSS атак (экранирование HTML)
- Валидация данных на клиенте и сервере
- Безопасная обработка пользовательского ввода
- Правильная обработка ошибок API

- **Пользовательский интерфейс**
- Адаптивный дизайн для всех устройств
- Плавные анимации и переходы
- Hover-эффекты и визуальная обратная связь
- Современный и интуитивный интерфейс

- **Доступность (Accessibility)**
- ARIA-атрибуты для скрин-ридеров
- Поддержка навигации с клавиатуры
- Семантическая HTML-разметка
- Alt-атрибуты для всех изображений

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

ХХХХ часов
**8-10 часов** - базовая функциональность социальной сети

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

YYYY часов
**12-15 часов** - включая дополнительные улучшения и исправления

### Детализация времени:
- **Основная функциональность**: 8 часов
- **Исправление ошибок API**: 2 часа
- **Безопасность и XSS защита**: 2 часа
- **Адаптивность и анимации**: 2 часа
- **Доступность и ARIA**: 1 час

## Дополнительные фичи

### 🔒 Безопасность
- **Полная защита от XSS атак** - все пользовательские данные экранируются
- **Валидация форм** с детальными сообщениями об ошибках
- **Безопасная обработка ошибок** без утечки информации
- **Проверка авторизации** на всех защищенных endpoints

### 🎨 Интерфейс и UX
- **Плавные анимации** - hover-эффекты, переходы, масштабирование
- **Адаптивный дизайн** - оптимизация для мобильных устройств и планшетов
- **Визуальная обратная связь** - анимации загрузки, состояния кнопок
- **Современный UI** - тени, скругления, градиенты

### ♿ Доступность
- **ARIA-атрибуты** для всех интерактивных элементов
- **Навигация с клавиатуры** - поддержка Tab, Enter, Space
- **Семантическая разметка** - правильные HTML-теги и роли
- **Alt-тексты** для всех изображений

### 🚀 Производительность
- **Оптимизированные API запросы** - правильная обработка ответов
- **Мгновенное обновление UI** - без перезагрузки страницы
- **Эффективная фильтрация** - посты пользователей на клиенте
- **Кэширование данных** - сохранение состояния в localStorage

### 🛠️ Разработка
- **Модульная архитектура** - разделение на компоненты
- **Чистый код** - комментарии, документация, читаемость
- **Обработка ошибок** - детальное логирование и пользовательские сообщения
- **Кросс-браузерность** - совместимость с современными браузерами

## Технологии

- **Vanilla JavaScript** - без фреймворков
- **CSS3** - современные стили и анимации
- **HTML5** - семантическая разметка
- **Fetch API** - для работы с сервером
- **LocalStorage** - для сохранения сессии

## Запуск проекта

1. Клонируйте репозиторий
2. Запустите локальный сервер:
```bash
python -m http.server 8000
```
3. Откройте `http://localhost:8000` в браузере

## API

Проект использует API Instapro:
- **Base URL**: `https://wedev-api.sky.pro/api/v1/prod/instapro`
- **Авторизация**: Bearer токен
- **Endpoints**: посты, лайки, загрузка изображений

## Статус проекта

✅ **Полностью готов к использованию** - все основные функции реализованы и протестированы
172 changes: 160 additions & 12 deletions api.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Замени на свой, чтобы получить независимый от других набор данных.
// "боевая" версия инстапро лежит в ключе prod
const personalKey = "prod";
const baseHost = "https://webdev-hw-api.vercel.app";
const baseHost = "https://wedev-api.sky.pro";
const postsHost = `${baseHost}/api/v1/${personalKey}/instapro`;

export function getPosts({ token }) {
Expand All @@ -23,33 +23,78 @@ export function getPosts({ token }) {
});
}

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 registerUser({ login, password, name, imageUrl }) {
return fetch(baseHost + "/api/user", {
const requestBody = {
login,
password,
name,
};

// Добавляем imageUrl только если он есть
if (imageUrl) {
requestBody.imageUrl = imageUrl;
}

return fetch("https://wedev-api.sky.pro/api/user", {
method: "POST",
body: JSON.stringify({
login,
password,
name,
imageUrl,
}),
body: JSON.stringify(requestBody),
}).then((response) => {
if (response.status === 400) {
throw new Error("Такой пользователь уже существует");
return response.json().then((errorData) => {
throw new Error("Такой пользователь уже существует");
});
}
if (response.status === 201) {
return response.json();
}
if (!response.ok) {
throw new Error(
`Ошибка при регистрации: ${response.status} ${response.statusText}`
);
}
return response.json();
});
}

export function loginUser({ login, password }) {
return fetch(baseHost + "/api/user/login", {
return fetch("https://wedev-api.sky.pro/api/user/login", {
method: "POST",
body: JSON.stringify({
login,
password,
}),
}).then((response) => {
if (response.status === 400) {
throw new Error("Неверный логин или пароль");
return response.json().then((errorData) => {
throw new Error("Неверный логин или пароль");
});
}
if (response.status === 201) {
return response.json();
}
if (!response.ok) {
throw new Error(
`Ошибка при входе: ${response.status} ${response.statusText}`
);
}
return response.json();
});
Expand All @@ -60,10 +105,113 @@ export function uploadImage({ file }) {
const data = new FormData();
data.append("file", file);

return fetch(baseHost + "/api/upload/image", {
return fetch("https://wedev-api.sky.pro/api/upload/image", {
method: "POST",
body: data,
}).then((response) => {
if (!response.ok) {
throw new Error(
`Ошибка при загрузке изображения: ${response.status} ${response.statusText}`
);
}
return response.json();
});
}

// Добавить новый пост
export function addPost({ description, imageUrl, token }) {
const requestBody = {
description,
imageUrl,
};

console.log("API запрос:", {
url: postsHost,
method: "POST",
headers: {
Authorization: token,
},
body: requestBody,
});

return fetch(postsHost, {
method: "POST",
headers: {
Authorization: token,
},
body: JSON.stringify(requestBody),
}).then((response) => {
console.log("Ответ API:", response.status, response.statusText);

if (response.status === 401) {
throw new Error("Нет авторизации");
}
if (response.status === 400) {
return response.json().then((errorData) => {
console.log("Детали ошибки 400:", errorData);
throw new Error("Неверные данные поста");
});
}
if (response.status === 201) {
return response.json();
}
if (!response.ok) {
throw new Error("Ошибка при добавлении поста");
}
return response.json();
});
}

// Поставить лайк
export function likePost({ postId, token }) {
return fetch(`${postsHost}/${postId}/like`, {
method: "POST",
headers: {
Authorization: token,
},
}).then((response) => {
if (response.status === 401) {
throw new Error("Нет авторизации");
}
if (!response.ok) {
throw new Error("Ошибка при постановке лайка");
}
return response.json();
});
}

// Убрать лайк
export function dislikePost({ postId, token }) {
return fetch(`${postsHost}/${postId}/dislike`, {
method: "POST",
headers: {
Authorization: token,
},
}).then((response) => {
if (response.status === 401) {
throw new Error("Нет авторизации");
}
if (!response.ok) {
throw new Error("Ошибка при удалении лайка");
}
return response.json();
});
}

// Удалить пост
export function deletePost({ postId, token }) {
return fetch(`${postsHost}/${postId}`, {
method: "DELETE",
headers: {
Authorization: token,
},
}).then((response) => {
if (response.status === 401) {
throw new Error("Нет авторизации");
}
if (!response.ok) {
throw new Error("Ошибка при удалении поста");
}
return response.json();
});
}
Loading