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
54 changes: 27 additions & 27 deletions src/data/movies.js → public/api/movies.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default [
[
{
"id": "-Cs3GyzRB2k?si=gON_3Ryi-msckxeM",
"title": "Аватар",
Expand All @@ -7,7 +7,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/4adf61aa-3cb7-4381-9245-523971e5b4c8/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/4adf61aa-3cb7-4381-9245-523971e5b4c8/600x900",
"metadata": [
{ name: "Год производства", value: "2009" }
{ "name": "Год производства", "value": "2009" }
]
},
{
Expand All @@ -18,13 +18,13 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1600647/430042eb-ee69-4818-aed0-a312400a26bf/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1600647/430042eb-ee69-4818-aed0-a312400a26bf/600x900",
"metadata": [
{ name: "Год производства", value: "2014" },
{ name: "Жанр", value: "фантастика, драма, приключения" },
{ name: "Слоган", value: "«Следующий шаг человечества станет величайшим»" },
{ name: "Режиссер", value: "Кристофер Нолан" },
{ name: "Продюсер", value: "Кристофер Нолан, Линда Обст, Эмма Томас" },
{ name: "Бюджет", value: "$165 000 000" },
{ name: "Время", value: "2 ч 49 мин" }
{ "name": "Год производства", "value": "2014" },
{ "name": "Жанр", "value": "фантастика, драма, приключения" },
{ "name": "Слоган", "value": "«Следующий шаг человечества станет величайшим»" },
{ "name": "Режиссер", "value": "Кристофер Нолан" },
{ "name": "Продюсер", "value": "Кристофер Нолан, Линда Обст, Эмма Томас" },
{ "name": "Бюджет", "value": "$165 000 000" },
{ "name": "Время", "value": "2 ч 49 мин" }
]
},
{
Expand All @@ -35,7 +35,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1629390/8ab9a119-dd74-44f0-baec-0629797483d7/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1629390/8ab9a119-dd74-44f0-baec-0629797483d7/600x900",
"metadata": [
{ name: "Год производства", value: "2010" }
{ "name": "Год производства", "value": "2010" }
]
},
{
Expand All @@ -46,7 +46,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1773646/af92d310-4ae5-4daa-b42c-5bcc380c2e6e/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1773646/af92d310-4ae5-4daa-b42c-5bcc380c2e6e/600x900",
"metadata": [
{ name: "Год производства", value: "2018" }
{ "name": "Год производства", "value": "2018" }
]
},
{
Expand All @@ -57,7 +57,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4774061/cf1970bc-3f08-4e0e-a095-2fb57c3aa7c6/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/4774061/cf1970bc-3f08-4e0e-a095-2fb57c3aa7c6/600x900",
"metadata": [
{ name: "Год производства", value: "1999" }
{ "name": "Год производства", "value": "1999" }
]
},
{
Expand All @@ -68,7 +68,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1600647/ae22f153-9715-41bb-adb4-f648b3e16092/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1600647/ae22f153-9715-41bb-adb4-f648b3e16092/600x900",
"metadata": [
{ name: "Год производства", value: "2019" }
{ "name": "Год производства", "value": "2019" }
]
},
{
Expand All @@ -79,7 +79,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4303601/9eb762d6-4cdd-464f-9937-aebf30067acc/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/4303601/9eb762d6-4cdd-464f-9937-aebf30067acc/600x900",
"metadata": [
{ name: "Год производства", value: "2021" }
{ "name": "Год производства", "value": "2021" }
]
},
{
Expand All @@ -90,7 +90,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1629390/9e9e2b2c-a3c1-462e-8d84-e6a19fbe5b9c/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1629390/9e9e2b2c-a3c1-462e-8d84-e6a19fbe5b9c/600x900",
"metadata": [
{ name: "Год производства", value: "1997" }
{ "name": "Год производства", "value": "1997" }
]
},
{
Expand All @@ -101,7 +101,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1773646/2e6ab20b-7cf1-49e7-b465-bd5a71c13fa3/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1773646/2e6ab20b-7cf1-49e7-b465-bd5a71c13fa3/600x900",
"metadata": [
{ name: "Год производства", value: "2014" }
{ "name": "Год производства", "value": "2014" }
]
},
{
Expand All @@ -112,7 +112,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/10809116/b722ab4d-497b-4a62-b243-95ca989401ff/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/10809116/b722ab4d-497b-4a62-b243-95ca989401ff/600x900",
"metadata": [
{ name: "Год производства", value: "2025" }
{ "name": "Год производства", "value": "2025" }
]
},
{
Expand All @@ -123,7 +123,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/0fa5bf50-d5ad-446f-a599-b26d070c8b99/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/0fa5bf50-d5ad-446f-a599-b26d070c8b99/600x900",
"metadata": [
{ name: "Год производства", value: "2008" }
{ "name": "Год производства", "value": "2008" }
]
},
{
Expand All @@ -134,7 +134,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4303601/bb966b79-5b10-485d-88d7-fb6aeb79b185/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/4303601/bb966b79-5b10-485d-88d7-fb6aeb79b185/600x900",
"metadata": [
{ name: "Год производства", value: "2016" }
{ "name": "Год производства", "value": "2016" }
]
},
{
Expand All @@ -145,7 +145,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1898899/972b7f43-9677-40ce-a9bc-02a88ad3919d/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1898899/972b7f43-9677-40ce-a9bc-02a88ad3919d/600x900",
"metadata": [
{ name: "Год производства", value: "2012" }
{ "name": "Год производства", "value": "2012" }
]
},
{
Expand All @@ -156,7 +156,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/10893610/2dd14742-f241-42ca-9db4-331e3a483c50/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/10893610/2dd14742-f241-42ca-9db4-331e3a483c50/600x900",
"metadata": [
{ name: "Год производства", value: "1991" }
{ "name": "Год производства", "value": "1991" }
]
},
{
Expand All @@ -167,7 +167,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4774061/c8e2f069-15f1-4803-95c0-aba858fec360/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/4774061/c8e2f069-15f1-4803-95c0-aba858fec360/600x900",
"metadata": [
{ name: "Год производства", value: "2008" }
{ "name": "Год производства", "value": "2008" }
]
},
{
Expand All @@ -178,7 +178,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/73cf2ed0-fd52-47a2-9e26-74104360786a/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/73cf2ed0-fd52-47a2-9e26-74104360786a/600x900",
"metadata": [
{ name: "Год производства", value: "1985" }
{ "name": "Год производства", "value": "1985" }
]
},
{
Expand All @@ -189,7 +189,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1900788/6f631486-e947-487d-94d6-41c2b5a8f5a0/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1900788/6f631486-e947-487d-94d6-41c2b5a8f5a0/600x900",
"metadata": [
{ name: "Год производства", value: "2015" }
{ "name": "Год производства", "value": "2015" }
]
},
{
Expand All @@ -200,7 +200,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/9784475/70c75cf3-f456-4474-a900-9a38c1bb2987/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/9784475/70c75cf3-f456-4474-a900-9a38c1bb2987/600x900",
"metadata": [
{ name: "Год производства", value: "2023" }
{ "name": "Год производства", "value": "2023" }
]
},
{
Expand All @@ -211,7 +211,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/6201401/db4fbef1-466a-4dec-9b7a-d4f13eb45738/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/6201401/db4fbef1-466a-4dec-9b7a-d4f13eb45738/600x900",
"metadata": [
{ name: "Год производства", value: "2021" }
{ "name": "Год производства", "value": "2021" }
]
},
{
Expand All @@ -222,7 +222,7 @@ export default [
"img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1946459/5ae82f4b-fd6a-46b5-b5ba-897106eb1eae/136x204",
"imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1946459/5ae82f4b-fd6a-46b5-b5ba-897106eb1eae/600x900",
"metadata": [
{ name: "Год производства", value: "2018" }
{ "name": "Год производства", "value": "2018" }
]
}
]
48 changes: 40 additions & 8 deletions src/components/Chat/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState, useEffect, useRef } from "react";
import cx from "classnames";
import { useCurrentUser, useLocalStorage, useMessageChannel } from "src/hooks"
import { ChatService } from "src/services"
import styles from "./index.module.css"

/**
Expand Down Expand Up @@ -30,6 +31,28 @@ export function Chat({ movieId }) {
// Ключ в localStorage зависит от movieId, чтобы чат был привязан к конкретному фильму
const { value: messages, setValue: setMessages } = useLocalStorage(`flickmate_chat_${movieId}`, [])

// Загружаем сообщения с публичного тестового API при открытии чата
useEffect(() => {
ChatService.getMessages(movieId).then(apiMessages => {
// Берём 5 псевдослучайных сообщений
// и приводим объект к нужному для отображения формату
const formatted = apiMessages
.sort(() => Math.random() - 0.5)
.slice(0, 5)
.map(item => ({
id: `api_${item.id}`,
sender: item.email,
text: item.body,
}));

// Если в localStorage ещё нет сообщений – инициализируем чат данными с сервера
// prev здесь – это messages, полученные ранее из useLocalStorage
setMessages(prev => prev.length ? prev : formatted);
}).catch(() => {
// TODO: Добавить обработку ошибки
});
}, [movieId]);

// Локальное состояние поля ввода
const [inputValue, setInputValue] = useState("")

Expand All @@ -52,14 +75,23 @@ export function Chat({ movieId }) {
text: inputValue.trim(),
};

// Добавляем сообщение в текущую вкладку
setMessages(prev => [...prev, message]);

// Отправляем сообщение через BroadcastChannel в другие вкладки
sendChannelMessage(message);

// Очищаем поле ввода после отправки сообщения
setInputValue("")
// POST-запрос используется только для демонстрации отправки данных на сервер
// Успешную отправку сообщения и ответ можно увидеть на вкладке Network
// Реальное состояние чата хранится локально (localStorage + BroadcastChannel)
ChatService.sendMessage({
movieId,
sender: currentUser,
text: inputValue,
}).then(() => { // сервер вернул 201 – сообщение успешно добавлено
// Добавляем сообщение в текущую вкладку
setMessages(prev => [...prev, message])
// Отправляем сообщение через BroadcastChannel в другие вкладки
sendChannelMessage(message)
// Очищаем поле ввода после отправки сообщения
setInputValue("")
}).catch(() => {
// TODO: Добавить обработку ошибки
});
}

// Скролл к последнему сообщению и фокус на input при отправке/получении нового сообщения
Expand Down
7 changes: 5 additions & 2 deletions src/components/MovieModal/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ export function MovieModal({ movieId, isOpen, onClose }) {
// Получение данных фильма при смене movieId
useEffect(() => {
if (movieId) {
const movie = MovieService.getById(movieId);
setMovie(movie);
MovieService.getById(movieId).then((data) => {
setMovie(data);
}).catch(() => {
// TODO: Добавить обработку ошибки
});
} else {
setMovie({});
}
Expand Down
7 changes: 5 additions & 2 deletions src/pages/Home/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ export function HomePage() {

useEffect(() => {
// Получаем все фильмы из модели
const movies = MovieService.getAll();
setMovies(movies);
MovieService.getAll().then((data) => {
setMovies(data);
}).catch(() => {
// TODO: Добавить обработку ошибки
});
}, [])

return (
Expand Down
7 changes: 5 additions & 2 deletions src/pages/Watch/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ export function WatchPage() {
const decodedMovieId = decodeURIComponent(movieId);

// Получаем информацию о фильме из модели
const movie = MovieService.getById(decodedMovieId);
setMovie(movie);
MovieService.getById(decodedMovieId).then((data) => {
setMovie(data);
}).catch(() => {
// TODO: Добавить обработку ошибки
});
}, [movieId])

return (
Expand Down
35 changes: 35 additions & 0 deletions src/services/chat/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import stringToNumberInRange from "src/utils/stringToNumberInRange"

const API_URL = "https://jsonplaceholder.typicode.com/comments";

/**
* Сервис для работы с сообщениями чата
*/
export class ChatService {
/**
* Загружает сообщения с сервера
* Используется для демонстрации GET-запроса
*/
static async getMessages(movieId) {
// В jsonplaceholder всего 100 posts,
// поэтому для использования movieId в качестве postId
// генерируем на основе movieId число от 1 до 100
const id = stringToNumberInRange(movieId, 1, 100)
const response = await fetch(`${API_URL}?postId=${id}`);
return response.json();
}

/**
* Отправляет сообщение на сервер
* Используется для демонстрации POST-запроса
*/
static async sendMessage(message) {
return fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(message),
});
}
}
3 changes: 2 additions & 1 deletion src/services/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./movie"
export * from "./user"
export * from "./user"
export * from "./chat"
10 changes: 6 additions & 4 deletions src/services/movie/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import movies from "src/data/movies.js";
const API_URL = import.meta.env.BASE_URL + "api";

/**
* Класс модели фильмов.
Expand All @@ -9,16 +9,18 @@ export class MovieService {
* Возвращает все фильмы
* @returns {Array<Object>} Массив объектов фильмов
*/
static getAll() {
return movies;
static async getAll() {
const response = await fetch(`${API_URL}/movies.json`);
return response.json();
}

/**
* Возвращает фильм по его идентификатору
* @param {number} id - Идентификатор фильма
* @returns {Object|undefined} Объект фильма или undefined, если не найден
*/
static getById(id) {
static async getById(id) {
const movies = await MovieService.getAll()
return movies.find(movie => movie.id === id);
}
}
Loading