lab5
This commit is contained in:
26
.eslintrc.js
26
.eslintrc.js
@@ -1,20 +1,32 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2021: true,
|
es2021: true,
|
||||||
node: true,
|
node: true
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:prettier/recommended',
|
'plugin:react/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:prettier/recommended'
|
||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true
|
||||||
|
},
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
sourceType: 'module',
|
sourceType: 'module'
|
||||||
},
|
},
|
||||||
|
plugins: ['react', 'react-hooks', 'prettier'],
|
||||||
rules: {
|
rules: {
|
||||||
'prettier/prettier': 'error',
|
'react/react-in-jsx-scope': 'off',
|
||||||
'no-unused-vars': 'warn',
|
'react/prop-types': 'off',
|
||||||
'no-console': 'warn',
|
'prettier/prettier': 'warn',
|
||||||
|
'no-unused-vars': 'warn'
|
||||||
},
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect'
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
136
about.html
136
about.html
@@ -1,136 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>О фильме - Online Cinema Theater</title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Навигационная панель -->
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark-custom fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand d-flex align-items-center" href="index.html">
|
|
||||||
<img src="resources/logo.webp" alt="Online Cinema Theater Logo" width="50" height="50" class="me-2">
|
|
||||||
<span>Online Cinema Theater</span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav ms-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="index.html">Главная</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="catalog.html">Каталог</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="reviews.html">Рецензии</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
|
||||||
Что глянуть?
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-dark">
|
|
||||||
<li><a class="dropdown-item" href="films.html">Фильмы</a></li>
|
|
||||||
<li><a class="dropdown-item" href="seriales.html">Сериалы</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Основной контент -->
|
|
||||||
<main class="container py-5 mt-5">
|
|
||||||
<section class="movie-details">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card bg-dark">
|
|
||||||
<img src="resources/movies/gruz.jpeg" class="card-img-top" alt="Movie Poster">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="card bg-dark">
|
|
||||||
<div class="card-body">
|
|
||||||
<h1 class="card-title text-white mb-4">Груз 200</h1>
|
|
||||||
<table class="table table-dark table-hover">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Продолжительность</th>
|
|
||||||
<td class="text-light">1 час 50 минут</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Год выпуска</th>
|
|
||||||
<td class="text-light">2007</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Страна</th>
|
|
||||||
<td class="text-light">Россия</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Режиссер</th>
|
|
||||||
<td class="text-light">Алексей Балабанов</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Жанр</th>
|
|
||||||
<td class="text-light">триллер, драма, криминал</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<h5 class="text-white mt-4">Описание:</h5>
|
|
||||||
<p class="text-light">
|
|
||||||
Груз 200 - это история о двух братьях, которые вместе с другими людьми отправляются в грузовике на
|
|
||||||
поиски золота в Африке. Однако путь к цели оказывается опасным и сложным, и братья сталкиваются
|
|
||||||
с различными препятствиями, в том числе с коррупцией и жестокостью.
|
|
||||||
</p>
|
|
||||||
<div class="ratio ratio-16x9 mt-4">
|
|
||||||
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ?si=iaZ0q33EJFBzeIZ_?autoplay=1"
|
|
||||||
title="YouTube video player"
|
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
||||||
allowfullscreen></iframe>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Футер -->
|
|
||||||
<footer class="bg-dark-custom text-light py-4">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row gy-4">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Контактная информация</h5>
|
|
||||||
<p class="mb-1">Телефон: +7 (123) 456-78-90</p>
|
|
||||||
<p class="mb-1">Email: info@cinema.com</p>
|
|
||||||
<p>Адрес: ул. Примерная, 123, Москва, Россия</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Мы в соцсетях</h5>
|
|
||||||
<div class="d-flex gap-3">
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-telegram fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-whatsapp fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-vk fs-4"></i></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Время работы</h5>
|
|
||||||
<p class="mb-1">Понедельник - Пятница: 10:00 - 22:00</p>
|
|
||||||
<p>Суббота - Воскресенье: 12:00 - 24:00</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col text-center">
|
|
||||||
<p class="mb-0">© 2022 Online Cinema Theater. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
168
add-movie.html
168
add-movie.html
@@ -1,168 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Добавить новый фильм - Online Cinema Theater</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<style>
|
|
||||||
.preview-container img {
|
|
||||||
max-height: 300px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
.movie-card {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.movie-card .card-body {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-dark text-light">
|
|
||||||
<!-- Навигационная панель с использованием Bootstrap -->
|
|
||||||
<header>
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark-custom fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand d-flex align-items-center" href="index.html">
|
|
||||||
<img src="resources/logo.webp" alt="Online Cinema Theater Logo" width="50" height="50" class="me-2">
|
|
||||||
<span>Online Cinema Theater</span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
|
||||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav ms-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="index.html">Главная</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="catalog.html">Каталог</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="reviews.html">Рецензии</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
|
|
||||||
data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
Что глянуть?
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-dark" aria-labelledby="navbarDropdown">
|
|
||||||
<li><a class="dropdown-item" href="films.html">Фильмы</a></li>
|
|
||||||
<li><a class="dropdown-item" href="seriales.html">Сериалы</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="container py-5 mt-5">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="card bg-dark text-light border-secondary">
|
|
||||||
<div class="card-header bg-dark border-secondary">
|
|
||||||
<h2 class="text-orange mb-0">Добавить новый фильм</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form id="addMovieForm">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="movieTitle" class="form-label">
|
|
||||||
<i class="bi bi-film"></i> Название фильма
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="movieTitle" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="movieDirector" class="form-label">
|
|
||||||
<i class="bi bi-person-video3"></i> Режиссер
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="movieDirector" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="movieGenre" class="form-label">
|
|
||||||
<i class="bi bi-tags"></i> Жанр
|
|
||||||
</label>
|
|
||||||
<select class="form-select" id="movieGenre" multiple required>
|
|
||||||
<option>Боевик</option>
|
|
||||||
<option>Драма</option>
|
|
||||||
<option>Комедия</option>
|
|
||||||
<option>Триллер</option>
|
|
||||||
<option>Ужасы</option>
|
|
||||||
<option>Фантастика</option>
|
|
||||||
<option>Приключения</option>
|
|
||||||
<option>Мелодрама</option>
|
|
||||||
<option>Детектив</option>
|
|
||||||
<option>Криминал</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="movieYear" class="form-label">
|
|
||||||
<i class="bi bi-calendar3"></i> Год выпуска
|
|
||||||
</label>
|
|
||||||
<input type="number" class="form-control" id="movieYear" min="1900" max="2099" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="movieDescription" class="form-label">
|
|
||||||
<i class="bi bi-card-text"></i> Описание
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control" id="movieDescription" rows="3"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="moviePoster" class="form-label">
|
|
||||||
<i class="bi bi-image"></i> Постер
|
|
||||||
</label>
|
|
||||||
<input type="file" class="form-control" id="moviePoster" accept="image/*">
|
|
||||||
<div class="preview-container mt-3 d-none">
|
|
||||||
<img id="posterPreview" class="img-fluid rounded" alt="Предпросмотр постера">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-end">
|
|
||||||
<a href="catalog.html" class="btn btn-secondary">Отмена</a>
|
|
||||||
<button type="submit" class="btn btn-primary">Добавить фильм</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="footer-custom py-4 mt-5">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<h5 class="text-white mb-3">Контактная информация</h5>
|
|
||||||
<p>Телефон: +7 (123) 456-78-90</p>
|
|
||||||
<p>Email: info@cinema.com</p>
|
|
||||||
<p>Адрес: ул. Примерная, 123, Москва, Россия</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<h5 class="text-white mb-3">Мы в соцсетях</h5>
|
|
||||||
<div class="d-flex gap-3">
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-telegram fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-whatsapp fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-vk fs-4"></i></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<h5 class="text-white mb-3">Время работы</h5>
|
|
||||||
<p>Понедельник - Пятница: 10:00 - 22:00</p>
|
|
||||||
<p>Суббота - Воскресенье: 12:00 - 24:00</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col text-center">
|
|
||||||
<p class="mb-0">© 2022 Online Cinema Theater. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
240
catalog.html
240
catalog.html
@@ -1,240 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Каталог - Online Cinema Theater</title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<!-- Add CSS for consistent card sizes -->
|
|
||||||
<style>
|
|
||||||
.movie-card {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.movie-card .card-body {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
.movie-card img.card-img-top {
|
|
||||||
height: 400px;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
/* Add styles for notifications and card deletion animation */
|
|
||||||
.notification-toast {
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.movie-card.deleting {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Навигационная панель -->
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark-custom fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand d-flex align-items-center" href="index.html">
|
|
||||||
<img src="resources/logo.webp" alt="Online Cinema Theater Logo" width="50" height="50" class="me-2">
|
|
||||||
<span>Online Cinema Theater</span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav ms-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="index.html">Главная</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link active" href="catalog.html">Каталог</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="reviews.html">Рецензии</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
|
||||||
Что глянуть?
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-dark">
|
|
||||||
<li><a class="dropdown-item" href="films.html">Фильмы</a></li>
|
|
||||||
<li><a class="dropdown-item" href="seriales.html">Сериалы</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Основной контент -->
|
|
||||||
<main class="container py-5 mt-5">
|
|
||||||
<section class="movie-catalog">
|
|
||||||
<h2 class="text-orange mb-4">Каталог фильмов</h2>
|
|
||||||
|
|
||||||
<!-- Фильтры -->
|
|
||||||
<div class="row mb-4">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="genre-select" class="form-label"><i class="bi bi-funnel me-2"></i>Выберите жанр:</label>
|
|
||||||
<select class="form-select" id="genre-select">
|
|
||||||
<option value="all">Все жанры</option>
|
|
||||||
<option value="action">Боевик</option>
|
|
||||||
<option value="comedy">Комедия</option>
|
|
||||||
<option value="drama">Драма</option>
|
|
||||||
<option value="crime">Криминал</option>
|
|
||||||
<option value="thriller">Триллер</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="year-select" class="form-label">Год выпуска:</label>
|
|
||||||
<select class="form-select" id="year-select">
|
|
||||||
<option value="all">Все годы</option>
|
|
||||||
<option value="2024">2024</option>
|
|
||||||
<option value="2023">2023</option>
|
|
||||||
<option value="2022">2022</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="sort-select" class="form-label">Сортировка:</label>
|
|
||||||
<select class="form-select" id="sort-select">
|
|
||||||
<option value="rating">По рейтингу</option>
|
|
||||||
<option value="date">По дате</option>
|
|
||||||
<option value="name">По названию</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Find the section with class "movie-catalog" and add this after the filters -->
|
|
||||||
|
|
||||||
<!-- Movie Container -->
|
|
||||||
<div id="movieContainer" class="row row-cols-1 row-cols-md-3 g-4">
|
|
||||||
<!-- Movies will be rendered here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Пагинация -->
|
|
||||||
<nav class="mt-4">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<a class="page-link" href="#" tabindex="-1"><i class="bi bi-chevron-left"></i> Предыдущая</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
|
||||||
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
|
||||||
<li class="page-item"><a class="page-link" href="#">3</a></li>
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="#">Следующая <i class="bi bi-chevron-right"></i></a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Футер -->
|
|
||||||
<footer class="bg-dark-custom text-light py-4">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row gy-4">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Контактная информация</h5>
|
|
||||||
<p class="mb-1">Телефон: +7 (123) 456-78-90</p>
|
|
||||||
<p class="mb-1">Email: info@cinema.com</p>
|
|
||||||
<p>Адрес: ул. Примерная, 123, Москва, Россия</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Мы в соцсетях</h5>
|
|
||||||
<div class="d-flex gap-3">
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-telegram fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-whatsapp fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-vk fs-4"></i></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Время работы</h5>
|
|
||||||
<p class="mb-1">Понедельник - Пятница: 10:00 - 22:00</p>
|
|
||||||
<p>Суббота - Воскресенье: 12:00 - 24:00</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col text-center">
|
|
||||||
<p class="mb-0">© 2022 Online Cinema Theater. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
|
||||||
|
|
||||||
<!-- Add this at the end of the file, just before the closing </body> tag -->
|
|
||||||
|
|
||||||
<!-- Modal for editing movies -->
|
|
||||||
<div class="modal fade" id="editMovieModal" tabindex="-1" aria-labelledby="editMovieModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-lg">
|
|
||||||
<div class="modal-content bg-dark text-light">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="editMovieModalLabel">Редактировать фильм</h5>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form id="editMovieForm">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="editMovieTitle" class="form-label">
|
|
||||||
<i class="bi bi-film"></i> Название фильма
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="editMovieTitle" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="editMovieDirector" class="form-label">
|
|
||||||
<i class="bi bi-person-video3"></i> Режиссер
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="editMovieDirector" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="editMovieGenre" class="form-label">
|
|
||||||
<i class="bi bi-tags"></i> Жанр
|
|
||||||
</label>
|
|
||||||
<select class="form-select" id="editMovieGenre" multiple required>
|
|
||||||
<option>Боевик</option>
|
|
||||||
<option>Драма</option>
|
|
||||||
<option>Комедия</option>
|
|
||||||
<option>Триллер</option>
|
|
||||||
<option>Ужасы</option>
|
|
||||||
<option>Фантастика</option>
|
|
||||||
<option>Приключения</option>
|
|
||||||
<option>Мелодрама</option>
|
|
||||||
<option>Детектив</option>
|
|
||||||
<option>Криминал</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="editMovieYear" class="form-label">
|
|
||||||
<i class="bi bi-calendar3"></i> Год выпуска
|
|
||||||
</label>
|
|
||||||
<input type="number" class="form-control" id="editMovieYear" min="1900" max="2099" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="editMovieDescription" class="form-label">
|
|
||||||
<i class="bi bi-card-text"></i> Описание
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control" id="editMovieDescription" rows="3"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="editMoviePoster" class="form-label">
|
|
||||||
<i class="bi bi-image"></i> Постер
|
|
||||||
</label>
|
|
||||||
<input type="file" class="form-control" id="editMoviePoster" accept="image/*">
|
|
||||||
<div class="preview-container mt-3 d-none">
|
|
||||||
<img id="editPosterPreview" class="img-fluid rounded" alt="Предпросмотр постера">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-end">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Сохранить изменения</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
242
edit-movie.html
242
edit-movie.html
@@ -1,242 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Редактировать фильм - Online Cinema Theater</title>
|
|
||||||
<!-- Add Bootstrap CSS -->
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<!-- Add custom styles -->
|
|
||||||
<link rel="stylesheet" href="./style.css">
|
|
||||||
<style>
|
|
||||||
.preview-container img {
|
|
||||||
max-height: 300px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
.text-orange {
|
|
||||||
color: #fd7e14;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-dark text-light">
|
|
||||||
<header>
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand" href="index.html">
|
|
||||||
<i class="bi bi-film text-danger"></i> Online Cinema
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="index.html">Главная</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="catalog.html">Каталог</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="reviews.html">Рецензии</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
|
|
||||||
data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
Что глянуть?
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-dark" aria-labelledby="navbarDropdown">
|
|
||||||
<li><a class="dropdown-item" href="films.html">Фильмы</a></li>
|
|
||||||
<li><a class="dropdown-item" href="seriales.html">Сериалы</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="container py-5 mt-5">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="card bg-dark text-light border-secondary">
|
|
||||||
<div class="card-header bg-dark border-secondary">
|
|
||||||
<h2 class="text-orange mb-0">Редактировать фильм</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form id="editMovieForm">
|
|
||||||
<input type="hidden" id="movieId">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="movieTitle" class="form-label">
|
|
||||||
<i class="bi bi-film"></i> Название фильма
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="movieTitle" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="movieDirector" class="form-label">
|
|
||||||
<i class="bi bi-person-video3"></i> Режиссер
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="movieDirector" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="movieGenre" class="form-label">
|
|
||||||
<i class="bi bi-tags"></i> Жанр
|
|
||||||
</label>
|
|
||||||
<select class="form-select" id="movieGenre" multiple required>
|
|
||||||
<option>Боевик</option>
|
|
||||||
<option>Драма</option>
|
|
||||||
<option>Комедия</option>
|
|
||||||
<option>Триллер</option>
|
|
||||||
<option>Ужасы</option>
|
|
||||||
<option>Фантастика</option>
|
|
||||||
<option>Приключения</option>
|
|
||||||
<option>Мелодрама</option>
|
|
||||||
<option>Детектив</option>
|
|
||||||
<option>Криминал</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="movieYear" class="form-label">
|
|
||||||
<i class="bi bi-calendar3"></i> Год выпуска
|
|
||||||
</label>
|
|
||||||
<input type="number" class="form-control" id="movieYear" min="1900" max="2099" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="movieDescription" class="form-label">
|
|
||||||
<i class="bi bi-card-text"></i> Описание
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control" id="movieDescription" rows="3"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="moviePoster" class="form-label">
|
|
||||||
<i class="bi bi-image"></i> Постер
|
|
||||||
</label>
|
|
||||||
<input type="file" class="form-control" id="moviePoster" accept="image/*">
|
|
||||||
<div class="preview-container mt-3">
|
|
||||||
<img id="posterPreview" class="img-fluid rounded" alt="Предпросмотр постера">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-end">
|
|
||||||
<a href="catalog.html" class="btn btn-secondary">Отмена</a>
|
|
||||||
<button type="submit" class="btn btn-primary">Сохранить изменения</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="bg-dark text-light py-4 mt-5">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5>О нас</h5>
|
|
||||||
<p>Online Cinema Theater - ваш проводник в мире кино. Мы предлагаем огромную коллекцию фильмов и сериалов различных жанров.</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5>Контакты</h5>
|
|
||||||
<p><i class="bi bi-envelope"></i> info@cinema.com</p>
|
|
||||||
<p><i class="bi bi-telephone"></i> +7 (123) 456-78-90</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5>Часы работы</h5>
|
|
||||||
<p>Понедельник - Пятница: 9:00 - 22:00</p>
|
|
||||||
<p>Суббота - Воскресенье: 12:00 - 24:00</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col text-center">
|
|
||||||
<p class="mb-0">© 2022 Online Cinema Theater. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import { MovieModel } from './src/components/movie/MovieModel.js';
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
|
||||||
// Get movie ID from URL
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const movieId = urlParams.get('id');
|
|
||||||
|
|
||||||
if (!movieId) {
|
|
||||||
alert('Фильм не найден');
|
|
||||||
window.location.href = 'catalog.html';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize model and fetch movie data
|
|
||||||
const movieModel = new MovieModel();
|
|
||||||
const movie = await movieModel.getMovieById(movieId);
|
|
||||||
|
|
||||||
if (!movie) {
|
|
||||||
alert('Фильм не найден');
|
|
||||||
window.location.href = 'catalog.html';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill form with movie data
|
|
||||||
document.getElementById('movieId').value = movie.id;
|
|
||||||
document.getElementById('movieTitle').value = movie.title;
|
|
||||||
document.getElementById('movieDirector').value = movie.director;
|
|
||||||
document.getElementById('movieYear').value = movie.year;
|
|
||||||
document.getElementById('movieDescription').value = movie.description || '';
|
|
||||||
|
|
||||||
// Handle genres (multi-select)
|
|
||||||
const genreSelect = document.getElementById('movieGenre');
|
|
||||||
if (Array.isArray(movie.genres)) {
|
|
||||||
Array.from(genreSelect.options).forEach(option => {
|
|
||||||
option.selected = movie.genres.includes(option.text);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle poster preview
|
|
||||||
const posterPreview = document.getElementById('posterPreview');
|
|
||||||
if (movie.poster) {
|
|
||||||
posterPreview.src = movie.poster;
|
|
||||||
document.querySelector('.preview-container').classList.remove('d-none');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle poster file input change
|
|
||||||
const moviePoster = document.getElementById('moviePoster');
|
|
||||||
if (moviePoster) {
|
|
||||||
moviePoster.addEventListener('change', (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
posterPreview.src = e.target.result;
|
|
||||||
document.querySelector('.preview-container').classList.remove('d-none');
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle form submission
|
|
||||||
const editMovieForm = document.getElementById('editMovieForm');
|
|
||||||
if (editMovieForm) {
|
|
||||||
editMovieForm.addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const updatedMovie = {
|
|
||||||
id: movieId,
|
|
||||||
title: document.getElementById('movieTitle').value,
|
|
||||||
director: document.getElementById('movieDirector').value,
|
|
||||||
genres: Array.from(document.getElementById('movieGenre').selectedOptions).map(option => option.text),
|
|
||||||
year: document.getElementById('movieYear').value,
|
|
||||||
description: document.getElementById('movieDescription').value,
|
|
||||||
poster: document.getElementById('posterPreview').src
|
|
||||||
};
|
|
||||||
|
|
||||||
await movieModel.updateMovie(movieId, updatedMovie);
|
|
||||||
alert('Фильм успешно обновлен!');
|
|
||||||
window.location.href = 'catalog.html';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
123
films.html
123
films.html
@@ -1,123 +0,0 @@
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Фильмы - Online Cinema Theater</title>
|
|
||||||
<!-- Add Bootstrap CSS -->
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<!-- Add CSS for consistent card sizes -->
|
|
||||||
<style>
|
|
||||||
.movie-card {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.movie-card .card-body {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
.movie-card img.card-img-top {
|
|
||||||
height: 400px;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
/* Add styles for notifications and card deletion animation */
|
|
||||||
.notification-toast {
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.movie-card.deleting {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Навигационная панель -->
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark-custom fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand d-flex align-items-center" href="index.html">
|
|
||||||
<img src="resources/logo.webp" alt="Online Cinema Theater Logo" width="50" height="50" class="me-2">
|
|
||||||
<span>Online Cinema Theater</span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav ms-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="index.html">Главная</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="catalog.html">Каталог</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="reviews.html">Рецензии</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown">
|
|
||||||
Что глянуть?
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-dark">
|
|
||||||
<li><a class="dropdown-item active" href="films.html">Фильмы</a></li>
|
|
||||||
<li><a class="dropdown-item" href="seriales.html">Сериалы</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Основной контент -->
|
|
||||||
<main class="container py-5 mt-5">
|
|
||||||
<!-- Секция основных фильмов -->
|
|
||||||
<!-- Find the section with the movie list and replace it with: -->
|
|
||||||
|
|
||||||
<section class="mb-5">
|
|
||||||
<h2 class="text-orange mb-4">Фильмы</h2>
|
|
||||||
<div id="movieContainer" class="row row-cols-1 row-cols-md-3 g-4">
|
|
||||||
<!-- Movies will be rendered here -->
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Add the edit modal at the end of the file, just before the closing </body> tag -->
|
|
||||||
<!-- Same modal code as in catalog.html -->
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Футер -->
|
|
||||||
<footer class="bg-dark-custom text-light py-4">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row gy-4">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Контактная информация</h5>
|
|
||||||
<p class="mb-1">Телефон: +7 (123) 456-78-90</p>
|
|
||||||
<p class="mb-1">Email: info@cinema.com</p>
|
|
||||||
<p>Адрес: ул. Примерная, 123, Москва, Россия</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Мы в соцсетях</h5>
|
|
||||||
<div class="d-flex gap-3">
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-telegram fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-whatsapp fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-vk fs-4"></i></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Время работы</h5>
|
|
||||||
<p class="mb-1">Понедельник - Пятница: 10:00 - 22:00</p>
|
|
||||||
<p>Суббота - Воскресенье: 12:00 - 24:00</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col text-center">
|
|
||||||
<p class="mb-0">© 2022 Online Cinema Theater. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<!-- Add Bootstrap JS before the closing body tag -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
127
index.html
127
index.html
@@ -1,127 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="ru">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Online Cinema Theater</title>
|
<title>Кинотеатр - React SPA</title>
|
||||||
<!-- Add Bootstrap CSS -->
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<!-- Add CSS for consistent card sizes -->
|
|
||||||
<style>
|
|
||||||
.movie-card {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.movie-card .card-body {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
.movie-card img.card-img-top {
|
|
||||||
height: 400px;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
/* Add styles for notifications and card deletion animation */
|
|
||||||
.notification-toast {
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.movie-card.deleting {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Навигационная панель с использованием Bootstrap -->
|
<div id="root"></div>
|
||||||
<header>
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark-custom fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand d-flex align-items-center" href="index.html">
|
|
||||||
<img src="resources/logo.webp" alt="Online Cinema Theater Logo" width="50" height="50" class="me-2">
|
|
||||||
<span>Online Cinema Theater</span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
|
||||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav ms-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link active" href="index.html">Главная</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="catalog.html">Каталог</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="reviews.html">Рецензии</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
|
|
||||||
data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
Что глянуть?
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-dark" aria-labelledby="navbarDropdown">
|
|
||||||
<li><a class="dropdown-item" href="films.html">Фильмы</a></li>
|
|
||||||
<li><a class="dropdown-item" href="seriales.html">Сериалы</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Основное содержимое -->
|
|
||||||
<main class="container py-5 mt-5">
|
|
||||||
<div id="app">
|
|
||||||
<!-- Find the section with popular movies and update it to use our component -->
|
|
||||||
<section class="mb-5">
|
|
||||||
<h2 class="text-orange mb-4">Популярные фильмы</h2>
|
|
||||||
<div id="movieContainer" class="row row-cols-1 row-cols-md-3 g-4">
|
|
||||||
<!-- Movies will be rendered here -->
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Add this at the end of your body tag, just before the closing </body> -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Футер -->
|
|
||||||
<footer class="footer-custom py-4">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<h5 class="text-white mb-3">Контактная информация</h5>
|
|
||||||
<p>Телефон: +7 (123) 456-78-90</p>
|
|
||||||
<p>Email: info@cinema.com</p>
|
|
||||||
<p>Адрес: ул. Примерная, 123, Москва, Россия</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<h5 class="text-white mb-3">Мы в соцсетях</h5>
|
|
||||||
<!-- ... existing code ... -->
|
|
||||||
<div class="d-flex gap-3">
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-telegram fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-whatsapp fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-vk fs-4"></i></a>
|
|
||||||
</div>
|
|
||||||
<!-- ... existing code ... -->
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<h5 class="text-white mb-3">Время работы</h5>
|
|
||||||
<p>Понедельник - Пятница: 10:00 - 22:00</p>
|
|
||||||
<p>Суббота - Воскресенье: 12:00 - 24:00</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-3">
|
|
||||||
<div class="col-12 text-center">
|
|
||||||
<p class="mb-0">© 2022 Online Cinema Theater. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<!-- Add Bootstrap JS before the closing body tag -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
2840
package-lock.json
generated
2840
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -6,9 +6,9 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint . --ext .js",
|
"lint": "eslint . --ext .js,.jsx",
|
||||||
"lint:fix": "eslint . --ext .js --fix",
|
"lint:fix": "eslint . --ext .js,.jsx --fix",
|
||||||
"format": "prettier --write \"**/*.{js,html,css,json}\"",
|
"format": "prettier --write \"**/*.{js,jsx,html,css,json}\"",
|
||||||
"server": "json-server --watch db.json --port 3000"
|
"server": "json-server --watch db.json --port 3000"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -20,15 +20,21 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^9.22.0",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-prettier": "^10.1.1",
|
"eslint-config-prettier": "^10.1.1",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
|
"eslint-plugin-react": "^7.33.2",
|
||||||
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"json-server": "^0.17.4",
|
"json-server": "^0.17.4",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"vite": "^5.2.6"
|
"vite": "^5.2.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-icons": "^1.11.3"
|
"bootstrap-icons": "^1.11.3",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router-dom": "^6.22.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
126
reviews.html
126
reviews.html
@@ -1,126 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Рецензии - Online Cinema Theater</title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Навигационная панель -->
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark-custom fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand d-flex align-items-center" href="index.html">
|
|
||||||
<img src="resources/logo.webp" alt="Online Cinema Theater Logo" width="50" height="50" class="me-2">
|
|
||||||
<span>Online Cinema Theater</span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav ms-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="index.html">Главная</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="catalog.html">Каталог</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link active" href="reviews.html">Рецензии</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
|
||||||
Что глянуть?
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-dark">
|
|
||||||
<li><a class="dropdown-item" href="films.html">Фильмы</a></li>
|
|
||||||
<li><a class="dropdown-item" href="seriales.html">Сериалы</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Основной контент -->
|
|
||||||
<main class="container py-5 mt-5">
|
|
||||||
<section class="movie-reviews">
|
|
||||||
<h2 class="text-orange mb-4">Рецензии на фильмы</h2>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<article class="card movie-card mb-4 bg-dark">
|
|
||||||
<div class="row g-0">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<img src="resources/movies/gruz.jpeg" class="img-fluid rounded-start" alt="Movie Poster">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title text-white"><i class="bi bi-film text-orange me-2"></i>Груз 200</h5>
|
|
||||||
<p class="card-text text-white"><i class="bi bi-person-video3 me-2"></i>Режиссер: Алексей Балабанов</p>
|
|
||||||
<p class="card-text text-light"><i class="bi bi-tags me-2"></i>Жанр: триллер, драма, криминал</p>
|
|
||||||
<p class="card-text text-light"><i class="bi bi-calendar3 me-2"></i>Год выпуска: 2007</p>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer bg-transparent border-0">
|
|
||||||
<a href="about.html" class="btn btn-orange w-100"><i class="bi bi-play-circle me-2"></i>Смотреть сейчас</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<div class="card bg-dark-custom mb-4">
|
|
||||||
<div class="card-body">
|
|
||||||
<h4 class="card-title text-orange">Популярные рецензии</h4>
|
|
||||||
<ul class="list-unstyled">
|
|
||||||
<li class="mb-3">
|
|
||||||
<a href="#" class="text-decoration-none text-light">Топ-10 фильмов года</a>
|
|
||||||
</li>
|
|
||||||
<li class="mb-3">
|
|
||||||
<a href="#" class="text-decoration-none text-light">Лучшие драмы 2024</a>
|
|
||||||
</li>
|
|
||||||
<li class="mb-3">
|
|
||||||
<a href="#" class="text-decoration-none text-light">Новинки месяца</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Футер -->
|
|
||||||
<footer class="bg-dark-custom text-light py-4">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row gy-4">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Контактная информация</h5>
|
|
||||||
<p class="mb-1">Телефон: +7 (123) 456-78-90</p>
|
|
||||||
<p class="mb-1">Email: info@cinema.com</p>
|
|
||||||
<p>Адрес: ул. Примерная, 123, Москва, Россия</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Мы в соцсетях</h5>
|
|
||||||
<div class="d-flex gap-3">
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-telegram fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-whatsapp fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-vk fs-4"></i></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Время работы</h5>
|
|
||||||
<p class="mb-1">Понедельник - Пятница: 10:00 - 22:00</p>
|
|
||||||
<p>Суббота - Воскресенье: 12:00 - 24:00</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col text-center">
|
|
||||||
<p class="mb-0">© 2022 Online Cinema Theater. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
154
seriales.html
154
seriales.html
@@ -1,154 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Сериалы - Online Cinema Theater</title>
|
|
||||||
<!-- Add Bootstrap CSS -->
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<!-- Add this style section to ensure equal card heights -->
|
|
||||||
<style>
|
|
||||||
.movie-card {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.movie-card .card-body {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
.movie-card img.card-img-top {
|
|
||||||
height: 400px;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Навигационная панель -->
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark-custom fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand d-flex align-items-center" href="index.html">
|
|
||||||
<img src="resources/logo.webp" alt="Online Cinema Theater Logo" width="50" height="50" class="me-2">
|
|
||||||
<span>Online Cinema Theater</span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav ms-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="index.html">Главная</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="catalog.html">Каталог</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="reviews.html">Рецензии</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown">
|
|
||||||
Что глянуть?
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-dark">
|
|
||||||
<li><a class="dropdown-item" href="films.html">Фильмы</a></li>
|
|
||||||
<li><a class="dropdown-item active" href="seriales.html">Сериалы</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Основной контент -->
|
|
||||||
<main class="container py-5 mt-5">
|
|
||||||
<section class="featured-series">
|
|
||||||
<h2 class="text-orange mb-4">Популярные сериалы</h2>
|
|
||||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-4 g-4">
|
|
||||||
<!-- Add more cards with the same structure to test equal heights -->
|
|
||||||
<div class="col">
|
|
||||||
<div class="card movie-card bg-dark">
|
|
||||||
<img src="resources/series/960.webp" class="card-img-top" alt="Series Poster">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title text-white">Преступление и наказание</h5>
|
|
||||||
<p class="card-text text-white">Сезонов: 1</p>
|
|
||||||
<p class="card-text text-white">Серий: 8</p>
|
|
||||||
<p class="card-text text-light">Год выпуска: 2007</p>
|
|
||||||
<div class="progress mb-3" style="height: 5px;">
|
|
||||||
<div class="progress-bar bg-warning" role="progressbar" style="width: 80%"></div>
|
|
||||||
</div>
|
|
||||||
<small class="text-light">Рейтинг: 8.0/10</small>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer bg-transparent border-0">
|
|
||||||
<a href="about.html" class="btn btn-orange w-100">Смотреть сейчас</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="mt-5">
|
|
||||||
<h2 class="text-orange mb-4">Новые серии</h2>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-dark table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Название</th>
|
|
||||||
<th scope="col">Сезон</th>
|
|
||||||
<th scope="col">Серия</th>
|
|
||||||
<th scope="col">Дата выхода</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Преступление и наказание</td>
|
|
||||||
<td>1</td>
|
|
||||||
<td>8</td>
|
|
||||||
<td>01.04.2024</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Футер -->
|
|
||||||
<footer class="bg-dark-custom text-light py-4">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row gy-4">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Контактная информация</h5>
|
|
||||||
<p class="mb-1">Телефон: +7 (123) 456-78-90</p>
|
|
||||||
<p class="mb-1">Email: info@cinema.com</p>
|
|
||||||
<p>Адрес: ул. Примерная, 123, Москва, Россия</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Мы в соцсетях</h5>
|
|
||||||
<div class="d-flex gap-3">
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-telegram fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-whatsapp fs-4"></i></a>
|
|
||||||
<a href="#" class="text-light"><i class="bi bi-vk fs-4"></i></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="mb-3">Время работы</h5>
|
|
||||||
<p class="mb-1">Понедельник - Пятница: 10:00 - 22:00</p>
|
|
||||||
<p>Суббота - Воскресенье: 12:00 - 24:00</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col text-center">
|
|
||||||
<p class="mb-0">© 2022 Online Cinema Theater. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<!-- Add Bootstrap JS before the closing body tag -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
72
src/App.jsx
Normal file
72
src/App.jsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Routes, Route, Link } from 'react-router-dom';
|
||||||
|
import HomePage from './pages/HomePage';
|
||||||
|
import CatalogPage from './pages/CatalogPage';
|
||||||
|
import AddMoviePage from './pages/AddMoviePage';
|
||||||
|
import EditMoviePage from './pages/EditMoviePage';
|
||||||
|
import AboutPage from './pages/AboutPage';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div className="app">
|
||||||
|
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
|
<div className="container">
|
||||||
|
<Link className="navbar-brand" to="/">
|
||||||
|
<i className="bi bi-film text-orange me-2"></i>
|
||||||
|
Кинотеатр
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
className="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<span className="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div className="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul className="navbar-nav">
|
||||||
|
<li className="nav-item">
|
||||||
|
<Link className="nav-link" to="/">Главная</Link>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<Link className="nav-link" to="/catalog">Каталог</Link>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<Link className="nav-link" to="/about">О нас</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className="container py-4">
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<HomePage />} />
|
||||||
|
<Route path="/catalog" element={<CatalogPage />} />
|
||||||
|
<Route path="/add-movie" element={<AddMoviePage />} />
|
||||||
|
<Route path="/edit-movie/:id" element={<EditMoviePage />} />
|
||||||
|
<Route path="/about" element={<AboutPage />} />
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer className="bg-dark text-white py-4">
|
||||||
|
<div className="container">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-6">
|
||||||
|
<h5>Кинотеатр</h5>
|
||||||
|
<p>Лучшие фильмы и сериалы в одном месте</p>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6 text-md-end">
|
||||||
|
<p>© 2023 Кинотеатр. Все права защищены.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
56
src/components/movie/MovieCard.jsx
Normal file
56
src/components/movie/MovieCard.jsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
function MovieCard({ movie, onDelete }) {
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (window.confirm('Вы уверены, что хотите удалить этот фильм?')) {
|
||||||
|
onDelete(movie.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card movie-card h-100 bg-dark p-0">
|
||||||
|
<img src={movie.poster} className="card-img-top" alt={`${movie.title} Poster`} />
|
||||||
|
<div className="card-body">
|
||||||
|
<h5 className="card-title text-white">
|
||||||
|
<i className="bi bi-film text-orange me-2"></i>{movie.title}
|
||||||
|
</h5>
|
||||||
|
<p className="card-text text-white">
|
||||||
|
<i className="bi bi-person-video3 text-secondary me-2"></i>{movie.director}
|
||||||
|
</p>
|
||||||
|
<p className="card-text text-light">
|
||||||
|
<i className="bi bi-tags text-secondary me-2"></i>
|
||||||
|
{Array.isArray(movie.genres) ? movie.genres.join(', ') : movie.genres}
|
||||||
|
</p>
|
||||||
|
<p className="card-text text-light">
|
||||||
|
<i className="bi bi-calendar3 text-secondary me-2"></i>{movie.year}
|
||||||
|
</p>
|
||||||
|
{movie.description && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<p className="card-text text-light small">
|
||||||
|
<i className="bi bi-text-paragraph text-secondary me-2"></i>
|
||||||
|
{movie.description.length > 100
|
||||||
|
? movie.description.substring(0, 100) + '...'
|
||||||
|
: movie.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="card-footer d-flex justify-content-between">
|
||||||
|
<Link to="/about" className="btn btn-orange">
|
||||||
|
<i className="bi bi-play-circle me-1"></i>Смотреть
|
||||||
|
</Link>
|
||||||
|
<div>
|
||||||
|
<Link to={`/edit-movie/${movie.id}`} className="btn btn-outline-warning edit-movie me-1">
|
||||||
|
<i className="bi bi-pencil"></i>
|
||||||
|
</Link>
|
||||||
|
<button className="btn btn-outline-danger delete-movie" onClick={handleDelete}>
|
||||||
|
<i className="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MovieCard;
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
export class MovieController {
|
|
||||||
constructor(model, view) {
|
|
||||||
this.model = model;
|
|
||||||
this.view = view;
|
|
||||||
|
|
||||||
// Initialize the controller based on the current page
|
|
||||||
this.initializeController();
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeController() {
|
|
||||||
// Check which page we're on and initialize accordingly
|
|
||||||
const path = window.location.pathname;
|
|
||||||
|
|
||||||
if (path.includes('catalog.html')) {
|
|
||||||
// Catalog page initialization
|
|
||||||
this.initializeCatalogPage();
|
|
||||||
} else if (path.includes('add-movie.html')) {
|
|
||||||
// Add movie page initialization
|
|
||||||
this.initializeAddMoviePage();
|
|
||||||
} else if (path.includes('edit-movie.html')) {
|
|
||||||
// Edit movie page initialization
|
|
||||||
this.initializeEditMoviePage();
|
|
||||||
} else if (path.endsWith('index.html') || path.endsWith('/')) {
|
|
||||||
// Homepage initialization
|
|
||||||
this.initializeHomepage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeHomepage() {
|
|
||||||
// Load and display featured movies on the homepage
|
|
||||||
this.model.getMovies().then(movies => {
|
|
||||||
// Sort movies by some criteria to get "featured" ones
|
|
||||||
// For example, sort by year (newest first)
|
|
||||||
const sortedMovies = [...movies].sort((a, b) => b.year - a.year);
|
|
||||||
this.view.renderMovies(sortedMovies);
|
|
||||||
|
|
||||||
// Bind delete functionality on homepage too
|
|
||||||
this.view.bindDeleteMovie(this.handleDeleteMovie.bind(this));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeCatalogPage() {
|
|
||||||
// Load and display all movies
|
|
||||||
this.model.getMovies().then(movies => {
|
|
||||||
this.view.renderMovies(movies);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bind event handlers for the catalog page
|
|
||||||
this.view.bindDeleteMovie(this.handleDeleteMovie.bind(this));
|
|
||||||
this.view.bindFilterMovies(this.handleFilterMovies.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeAddMoviePage() {
|
|
||||||
// Bind the save new movie handler
|
|
||||||
const addMovieForm = document.getElementById('addMovieForm');
|
|
||||||
if (addMovieForm) {
|
|
||||||
addMovieForm.addEventListener('submit', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const newMovie = {
|
|
||||||
title: document.getElementById('movieTitle').value,
|
|
||||||
director: document.getElementById('movieDirector').value,
|
|
||||||
genres: Array.from(document.getElementById('movieGenre').selectedOptions).map(option => option.text),
|
|
||||||
year: document.getElementById('movieYear').value,
|
|
||||||
description: document.getElementById('movieDescription').value,
|
|
||||||
poster: document.getElementById('posterPreview')?.src || 'resources/movies/placeholder.jpg'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleSaveNewMovie(newMovie);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle poster preview
|
|
||||||
const moviePoster = document.getElementById('moviePoster');
|
|
||||||
const posterPreview = document.getElementById('posterPreview');
|
|
||||||
const previewContainer = document.querySelector('.preview-container');
|
|
||||||
|
|
||||||
if (moviePoster && posterPreview && previewContainer) {
|
|
||||||
moviePoster.addEventListener('change', (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
posterPreview.src = e.target.result;
|
|
||||||
previewContainer.classList.remove('d-none');
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeEditMoviePage() {
|
|
||||||
// Get movie ID from URL
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const movieId = urlParams.get('id');
|
|
||||||
|
|
||||||
if (!movieId) {
|
|
||||||
alert('Фильм не найден');
|
|
||||||
window.location.href = 'catalog.html';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load movie data and set up form
|
|
||||||
this.model.getMovieById(movieId)
|
|
||||||
.then(movie => {
|
|
||||||
if (!movie) {
|
|
||||||
alert('Фильм не найден');
|
|
||||||
window.location.href = 'catalog.html';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill form with movie data
|
|
||||||
document.getElementById('movieId').value = movie.id;
|
|
||||||
document.getElementById('movieTitle').value = movie.title;
|
|
||||||
document.getElementById('movieDirector').value = movie.director;
|
|
||||||
document.getElementById('movieYear').value = movie.year;
|
|
||||||
document.getElementById('movieDescription').value = movie.description || '';
|
|
||||||
|
|
||||||
// Handle genres (multi-select)
|
|
||||||
const genreSelect = document.getElementById('movieGenre');
|
|
||||||
if (Array.isArray(movie.genres)) {
|
|
||||||
Array.from(genreSelect.options).forEach(option => {
|
|
||||||
option.selected = movie.genres.includes(option.text);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle poster preview
|
|
||||||
const posterPreview = document.getElementById('posterPreview');
|
|
||||||
if (movie.poster) {
|
|
||||||
posterPreview.src = movie.poster;
|
|
||||||
document.querySelector('.preview-container').classList.remove('d-none');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error loading movie:', error);
|
|
||||||
alert('Произошла ошибка при загрузке фильма');
|
|
||||||
window.location.href = 'catalog.html';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle poster file input change
|
|
||||||
const moviePoster = document.getElementById('moviePoster');
|
|
||||||
const posterPreview = document.getElementById('posterPreview');
|
|
||||||
const previewContainer = document.querySelector('.preview-container');
|
|
||||||
|
|
||||||
if (moviePoster && posterPreview && previewContainer) {
|
|
||||||
moviePoster.addEventListener('change', (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
posterPreview.src = e.target.result;
|
|
||||||
previewContainer.classList.remove('d-none');
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle form submission
|
|
||||||
const editMovieForm = document.getElementById('editMovieForm');
|
|
||||||
if (editMovieForm) {
|
|
||||||
editMovieForm.addEventListener('submit', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const updatedMovie = {
|
|
||||||
id: movieId,
|
|
||||||
title: document.getElementById('movieTitle').value,
|
|
||||||
director: document.getElementById('movieDirector').value,
|
|
||||||
genres: Array.from(document.getElementById('movieGenre').selectedOptions).map(option => option.text),
|
|
||||||
year: document.getElementById('movieYear').value,
|
|
||||||
description: document.getElementById('movieDescription').value,
|
|
||||||
poster: document.getElementById('posterPreview').src
|
|
||||||
};
|
|
||||||
|
|
||||||
this.model.updateMovie(movieId, updatedMovie)
|
|
||||||
.then(() => {
|
|
||||||
alert('Фильм успешно обновлен!');
|
|
||||||
window.location.href = 'catalog.html';
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error updating movie:', error);
|
|
||||||
alert('Произошла ошибка при обновлении фильма');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSaveNewMovie(movieData) {
|
|
||||||
this.model.addMovie(movieData)
|
|
||||||
.then(() => {
|
|
||||||
alert('Фильм успешно добавлен!');
|
|
||||||
window.location.href = 'catalog.html';
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error adding movie:', error);
|
|
||||||
alert('Произошла ошибка при добавлении фильма.');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteMovie(movieId) {
|
|
||||||
// Find the movie card element
|
|
||||||
const movieCard = document.querySelector(`.movie-card[data-movie-id="${movieId}"]`);
|
|
||||||
|
|
||||||
if (movieCard) {
|
|
||||||
// Add visual indication that deletion is in progress
|
|
||||||
movieCard.classList.add('deleting');
|
|
||||||
movieCard.style.opacity = '0.5';
|
|
||||||
movieCard.style.transition = 'all 0.3s ease';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.model.deleteMovie(movieId)
|
|
||||||
.then(() => {
|
|
||||||
// Remove the movie card from DOM without page refresh
|
|
||||||
if (movieCard) {
|
|
||||||
movieCard.style.opacity = '0';
|
|
||||||
movieCard.style.transform = 'scale(0.8)';
|
|
||||||
|
|
||||||
// Remove the element after animation completes
|
|
||||||
setTimeout(() => {
|
|
||||||
movieCard.remove();
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show success notification
|
|
||||||
this.showNotification('Фильм успешно удален!', 'success');
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error deleting movie:', error);
|
|
||||||
|
|
||||||
// Reset the card if deletion failed
|
|
||||||
if (movieCard) {
|
|
||||||
movieCard.classList.remove('deleting');
|
|
||||||
movieCard.style.opacity = '1';
|
|
||||||
movieCard.style.transform = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showNotification('Произошла ошибка при удалении фильма.', 'danger');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a notification method to show feedback without alerts
|
|
||||||
showNotification(message, type = 'info') {
|
|
||||||
// Create notification element
|
|
||||||
const notification = document.createElement('div');
|
|
||||||
notification.className = `alert alert-${type} notification-toast`;
|
|
||||||
notification.style.position = 'fixed';
|
|
||||||
notification.style.top = '20px';
|
|
||||||
notification.style.right = '20px';
|
|
||||||
notification.style.zIndex = '9999';
|
|
||||||
notification.style.minWidth = '250px';
|
|
||||||
notification.style.opacity = '0';
|
|
||||||
notification.style.transform = 'translateY(-20px)';
|
|
||||||
notification.style.transition = 'all 0.3s ease';
|
|
||||||
notification.innerHTML = message;
|
|
||||||
|
|
||||||
// Add close button
|
|
||||||
const closeBtn = document.createElement('button');
|
|
||||||
closeBtn.type = 'button';
|
|
||||||
closeBtn.className = 'btn-close';
|
|
||||||
closeBtn.setAttribute('aria-label', 'Close');
|
|
||||||
closeBtn.style.float = 'right';
|
|
||||||
closeBtn.onclick = () => notification.remove();
|
|
||||||
notification.prepend(closeBtn);
|
|
||||||
|
|
||||||
// Add to document
|
|
||||||
document.body.appendChild(notification);
|
|
||||||
|
|
||||||
// Animate in
|
|
||||||
setTimeout(() => {
|
|
||||||
notification.style.opacity = '1';
|
|
||||||
notification.style.transform = 'translateY(0)';
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
// Auto remove after 5 seconds
|
|
||||||
setTimeout(() => {
|
|
||||||
notification.style.opacity = '0';
|
|
||||||
notification.style.transform = 'translateY(-20px)';
|
|
||||||
setTimeout(() => notification.remove(), 300);
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFilterMovies(filters) {
|
|
||||||
this.model.getFilteredMovies(filters)
|
|
||||||
.then(movies => {
|
|
||||||
this.view.renderMovies(movies);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error filtering movies:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
167
src/components/movie/MovieForm.jsx
Normal file
167
src/components/movie/MovieForm.jsx
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
function MovieForm({ movie, onSubmit, isEditing = false }) {
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const [director, setDirector] = useState('');
|
||||||
|
const [genres, setGenres] = useState([]);
|
||||||
|
const [year, setYear] = useState('');
|
||||||
|
const [description, setDescription] = useState('');
|
||||||
|
const [poster, setPoster] = useState('');
|
||||||
|
const [previewVisible, setPreviewVisible] = useState(false);
|
||||||
|
|
||||||
|
// Initialize form with movie data if editing
|
||||||
|
useEffect(() => {
|
||||||
|
if (movie) {
|
||||||
|
setTitle(movie.title || '');
|
||||||
|
setDirector(movie.director || '');
|
||||||
|
setGenres(Array.isArray(movie.genres) ? movie.genres : []);
|
||||||
|
setYear(movie.year || '');
|
||||||
|
setDescription(movie.description || '');
|
||||||
|
setPoster(movie.poster || '');
|
||||||
|
if (movie.poster) {
|
||||||
|
setPreviewVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [movie]);
|
||||||
|
|
||||||
|
const handleGenreChange = (e) => {
|
||||||
|
const selectedGenres = Array.from(e.target.selectedOptions).map(option => option.value);
|
||||||
|
setGenres(selectedGenres);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePosterChange = (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
setPoster(e.target.result);
|
||||||
|
setPreviewVisible(true);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const movieData = {
|
||||||
|
title,
|
||||||
|
director,
|
||||||
|
genres,
|
||||||
|
year,
|
||||||
|
description,
|
||||||
|
poster
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit(movieData);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="movieTitle" className="form-label">Название фильма</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
id="movieTitle"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="movieDirector" className="form-label">Режиссер</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
id="movieDirector"
|
||||||
|
value={director}
|
||||||
|
onChange={(e) => setDirector(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="movieGenre" className="form-label">Жанры</label>
|
||||||
|
<select
|
||||||
|
className="form-select"
|
||||||
|
id="movieGenre"
|
||||||
|
multiple
|
||||||
|
value={genres}
|
||||||
|
onChange={handleGenreChange}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="Боевик">Боевик</option>
|
||||||
|
<option value="Комедия">Комедия</option>
|
||||||
|
<option value="Драма">Драма</option>
|
||||||
|
<option value="Фантастика">Фантастика</option>
|
||||||
|
<option value="Ужасы">Ужасы</option>
|
||||||
|
<option value="Триллер">Триллер</option>
|
||||||
|
<option value="Детектив">Детектив</option>
|
||||||
|
<option value="Приключения">Приключения</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="movieYear" className="form-label">Год выпуска</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="form-control"
|
||||||
|
id="movieYear"
|
||||||
|
min="1900"
|
||||||
|
max={new Date().getFullYear()}
|
||||||
|
value={year}
|
||||||
|
onChange={(e) => setYear(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="movieDescription" className="form-label">Описание</label>
|
||||||
|
<textarea
|
||||||
|
className="form-control"
|
||||||
|
id="movieDescription"
|
||||||
|
rows="3"
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="moviePoster" className="form-label">Постер</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
className="form-control"
|
||||||
|
id="moviePoster"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handlePosterChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{previewVisible && (
|
||||||
|
<div className="preview-container mb-3">
|
||||||
|
<label className="form-label">Предпросмотр постера</label>
|
||||||
|
<img
|
||||||
|
src={poster}
|
||||||
|
alt="Предпросмотр постера"
|
||||||
|
id="posterPreview"
|
||||||
|
className="img-thumbnail"
|
||||||
|
style={{ maxHeight: '300px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-between">
|
||||||
|
<button type="submit" className="btn btn-primary">
|
||||||
|
{isEditing ? 'Сохранить изменения' : 'Добавить фильм'}
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn btn-secondary" onClick={() => window.history.back()}>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MovieForm;
|
||||||
45
src/components/movie/MovieList.jsx
Normal file
45
src/components/movie/MovieList.jsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import MovieCard from './MovieCard';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
function MovieList({ movies, onDeleteMovie, isHomepage = false }) {
|
||||||
|
// If we're on the homepage, only show up to 6 featured movies
|
||||||
|
const moviesToShow = isHomepage ? movies.slice(0, 6) : movies;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mb-4 d-flex justify-content-between align-items-center">
|
||||||
|
<h2 className={isHomepage ? "text-orange" : ""}>
|
||||||
|
{isHomepage ? 'Популярные фильмы' : 'Каталог фильмов'}
|
||||||
|
</h2>
|
||||||
|
<Link to="/add-movie" className="btn btn-success">
|
||||||
|
<i className="bi bi-plus-circle me-2"></i>Добавить фильм
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4" id="movieContainer">
|
||||||
|
{moviesToShow.length > 0 ? (
|
||||||
|
moviesToShow.map(movie => (
|
||||||
|
<div className="col" key={movie.id}>
|
||||||
|
<MovieCard movie={movie} onDelete={onDeleteMovie} />
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="col-12 text-center py-5">
|
||||||
|
<p className="text-muted">Фильмы не найдены</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isHomepage && movies.length > 6 && (
|
||||||
|
<div className="col-12 text-center mt-4">
|
||||||
|
<Link to="/catalog" className="btn btn-primary">
|
||||||
|
Смотреть все фильмы
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MovieList;
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
export class MovieModel {
|
|
||||||
constructor() {
|
|
||||||
this.apiUrl = 'http://localhost:3000';
|
|
||||||
this.movies = [];
|
|
||||||
this.genres = [];
|
|
||||||
this.directors = [];
|
|
||||||
|
|
||||||
// Initialize data
|
|
||||||
this.initializeData();
|
|
||||||
}
|
|
||||||
|
|
||||||
async initializeData() {
|
|
||||||
try {
|
|
||||||
await this.fetchAllData();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to initialize from API, using localStorage:', error);
|
|
||||||
|
|
||||||
// Fallback to localStorage
|
|
||||||
let movies = JSON.parse(localStorage.getItem('movies')) || [];
|
|
||||||
|
|
||||||
if (movies.length === 0) {
|
|
||||||
movies = this.getSampleMovies();
|
|
||||||
localStorage.setItem('movies', JSON.stringify(movies));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.movies = movies;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchAllData() {
|
|
||||||
try {
|
|
||||||
// Fetch all necessary data in parallel using Fetch API
|
|
||||||
const [moviesResponse, genresResponse, directorsResponse] = await Promise.all([
|
|
||||||
fetch(`${this.apiUrl}/movies`),
|
|
||||||
fetch(`${this.apiUrl}/genres`),
|
|
||||||
fetch(`${this.apiUrl}/directors`)
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!moviesResponse.ok || !genresResponse.ok || !directorsResponse.ok) {
|
|
||||||
throw new Error('Failed to fetch data from server');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.movies = await moviesResponse.json();
|
|
||||||
this.genres = await genresResponse.json();
|
|
||||||
this.directors = await directorsResponse.json();
|
|
||||||
|
|
||||||
// Process movies to include full genre and director objects
|
|
||||||
this.movies = this.movies.map(movie => this.processMovie(movie));
|
|
||||||
|
|
||||||
return this.movies;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching data:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processMovie(movie) {
|
|
||||||
// Convert genreIds to full genre names
|
|
||||||
const genres = movie.genreIds?.map(id =>
|
|
||||||
this.genres.find(genre => genre.id === id)?.name
|
|
||||||
).filter(Boolean) || [];
|
|
||||||
|
|
||||||
// Get director name
|
|
||||||
const director = this.directors.find(dir => dir.id === movie.directorId)?.name || 'Unknown Director';
|
|
||||||
|
|
||||||
return {
|
|
||||||
...movie,
|
|
||||||
genres,
|
|
||||||
director
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this method to match what's being called in the controller
|
|
||||||
async getMovies() {
|
|
||||||
return this.getAllMovies();
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllMovies() {
|
|
||||||
if (this.movies.length === 0) {
|
|
||||||
try {
|
|
||||||
await this.fetchAllData();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching movies:', error);
|
|
||||||
// Fallback to localStorage
|
|
||||||
this.movies = JSON.parse(localStorage.getItem('movies')) || this.getSampleMovies();
|
|
||||||
this.saveToStorage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.movies;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMovieById(id) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${this.apiUrl}/movies/${id}`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch movie');
|
|
||||||
}
|
|
||||||
const movie = await response.json();
|
|
||||||
return this.processMovie(movie);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching movie:', error);
|
|
||||||
// Fallback to local cache
|
|
||||||
return this.movies.find(movie => movie.id === id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async addMovie(movie) {
|
|
||||||
try {
|
|
||||||
// Convert genre names to ids
|
|
||||||
const genreIds = movie.genres.map(genreName => {
|
|
||||||
const genre = this.genres.find(g => g.name === genreName);
|
|
||||||
return genre ? genre.id : null;
|
|
||||||
}).filter(Boolean);
|
|
||||||
|
|
||||||
// Find director id by name or create a new one
|
|
||||||
let directorId;
|
|
||||||
const directorObj = this.directors.find(d => d.name === movie.director);
|
|
||||||
|
|
||||||
if (directorObj) {
|
|
||||||
directorId = directorObj.id;
|
|
||||||
} else {
|
|
||||||
// If director doesn't exist, create a new one
|
|
||||||
const newDirector = {
|
|
||||||
name: movie.director,
|
|
||||||
birthYear: "Unknown",
|
|
||||||
country: "Unknown"
|
|
||||||
};
|
|
||||||
|
|
||||||
const dirResponse = await fetch(`${this.apiUrl}/directors`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(newDirector)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!dirResponse.ok) {
|
|
||||||
throw new Error('Failed to add new director');
|
|
||||||
}
|
|
||||||
|
|
||||||
const createdDirector = await dirResponse.json();
|
|
||||||
this.directors.push(createdDirector);
|
|
||||||
directorId = createdDirector.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const movieData = {
|
|
||||||
title: movie.title,
|
|
||||||
directorId,
|
|
||||||
year: movie.year,
|
|
||||||
description: movie.description || '',
|
|
||||||
poster: movie.poster,
|
|
||||||
genreIds
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch(`${this.apiUrl}/movies`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(movieData)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to add movie');
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMovie = await response.json();
|
|
||||||
const processedMovie = this.processMovie(newMovie);
|
|
||||||
this.movies.push(processedMovie);
|
|
||||||
|
|
||||||
return processedMovie;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error adding movie:', error);
|
|
||||||
// Fallback to local storage
|
|
||||||
return this.addMovieLocally(movie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteMovie(id) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${this.apiUrl}/movies/${id}`, {
|
|
||||||
method: 'DELETE'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to delete movie');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.movies = this.movies.filter(movie => movie.id !== id);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting movie:', error);
|
|
||||||
// Fallback to local storage
|
|
||||||
return this.deleteMovieLocally(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateMovie(id, updatedMovie) {
|
|
||||||
try {
|
|
||||||
// Convert genre names to ids
|
|
||||||
const genreIds = updatedMovie.genres.map(genreName => {
|
|
||||||
const genre = this.genres.find(g => g.name === genreName);
|
|
||||||
return genre ? genre.id : null;
|
|
||||||
}).filter(Boolean);
|
|
||||||
|
|
||||||
// Find director id by name
|
|
||||||
let directorId;
|
|
||||||
const directorObj = this.directors.find(d => d.name === updatedMovie.director);
|
|
||||||
|
|
||||||
if (directorObj) {
|
|
||||||
directorId = directorObj.id;
|
|
||||||
} else {
|
|
||||||
// If director doesn't exist, create a new one
|
|
||||||
const newDirector = {
|
|
||||||
name: updatedMovie.director,
|
|
||||||
birthYear: "Unknown",
|
|
||||||
country: "Unknown"
|
|
||||||
};
|
|
||||||
|
|
||||||
const dirResponse = await fetch(`${this.apiUrl}/directors`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(newDirector)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!dirResponse.ok) {
|
|
||||||
throw new Error('Failed to add new director');
|
|
||||||
}
|
|
||||||
|
|
||||||
const createdDirector = await dirResponse.json();
|
|
||||||
this.directors.push(createdDirector);
|
|
||||||
directorId = createdDirector.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const movieData = {
|
|
||||||
title: updatedMovie.title,
|
|
||||||
directorId,
|
|
||||||
year: updatedMovie.year,
|
|
||||||
description: updatedMovie.description || '',
|
|
||||||
poster: updatedMovie.poster,
|
|
||||||
genreIds
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch(`${this.apiUrl}/movies/${id}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(movieData)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to update movie');
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedMovieData = await response.json();
|
|
||||||
const processedMovie = this.processMovie(updatedMovieData);
|
|
||||||
|
|
||||||
const index = this.movies.findIndex(movie => movie.id === id);
|
|
||||||
if (index !== -1) {
|
|
||||||
this.movies[index] = processedMovie;
|
|
||||||
return processedMovie;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating movie:', error);
|
|
||||||
// Fallback to local storage
|
|
||||||
const index = this.movies.findIndex(movie => movie.id === id);
|
|
||||||
if (index !== -1) {
|
|
||||||
this.movies[index] = { ...this.movies[index], ...updatedMovie };
|
|
||||||
this.saveToStorage();
|
|
||||||
return this.movies[index];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper methods for local storage fallback
|
|
||||||
addMovieLocally(movie) {
|
|
||||||
const newMovie = {
|
|
||||||
id: Date.now().toString(),
|
|
||||||
...movie
|
|
||||||
};
|
|
||||||
this.movies.push(newMovie);
|
|
||||||
this.saveToStorage();
|
|
||||||
return newMovie;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteMovieLocally(id) {
|
|
||||||
this.movies = this.movies.filter(movie => movie.id !== id);
|
|
||||||
this.saveToStorage();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToStorage() {
|
|
||||||
localStorage.setItem('movies', JSON.stringify(this.movies));
|
|
||||||
}
|
|
||||||
|
|
||||||
filterMovies(filters = {}) {
|
|
||||||
return this.movies.filter(movie => {
|
|
||||||
let match = true;
|
|
||||||
|
|
||||||
if (filters.genre && filters.genre !== 'all') {
|
|
||||||
match = match && movie.genres.includes(filters.genre);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.year && filters.year !== 'all') {
|
|
||||||
match = match && movie.year === filters.year;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.search) {
|
|
||||||
const searchLower = filters.search.toLowerCase();
|
|
||||||
match = match && (
|
|
||||||
movie.title.toLowerCase().includes(searchLower) ||
|
|
||||||
movie.director.toLowerCase().includes(searchLower)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return match;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getSampleMovies() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
title: 'Груз 200',
|
|
||||||
director: 'Алексей Балабанов',
|
|
||||||
genres: ['Триллер', 'Драма', 'Криминал'],
|
|
||||||
year: '2007',
|
|
||||||
description: 'Действие фильма происходит в 1984 году в провинциальном городе. Молодая девушка оказывается в руках маньяка, который представляется сотрудником милиции.',
|
|
||||||
poster: 'resources/movies/gruz.jpeg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
title: 'Брат',
|
|
||||||
director: 'Алексей Балабанов',
|
|
||||||
genres: ['Драма', 'Криминал', 'Боевик'],
|
|
||||||
year: '1997',
|
|
||||||
description: 'Демобилизовавшись, Данила Багров возвращается в родной городок. Но скучная жизнь провинциального городка не устраивает его, и он решает поехать в Петербург, где, по слухам, уже давно процветает его старший брат.',
|
|
||||||
poster: 'resources/movies/brat.webp'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
title: 'Зеленый слоник',
|
|
||||||
director: 'Светлана Баскова',
|
|
||||||
genres: ['Драма', 'Арт-хаус'],
|
|
||||||
year: '1999',
|
|
||||||
description: 'Два офицера, "Младший лейтенант" и "Капитан", сидят в одной камере на гауптвахте. Капитан — дослуживающий до пенсии армейский алкоголик, а Младший лейтенант — молодой офицер, мечтающий о карьере.',
|
|
||||||
poster: 'resources/movies/slonik.jpg'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
// Import Bootstrap modal functionality
|
|
||||||
import { Modal } from 'bootstrap';
|
|
||||||
|
|
||||||
export class MovieView {
|
|
||||||
constructor() {
|
|
||||||
this.movieContainer = document.getElementById('movieContainer');
|
|
||||||
|
|
||||||
// Create add movie button if we're on the catalog page
|
|
||||||
if (window.location.pathname.includes('catalog.html')) {
|
|
||||||
this.createAddMovieButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize featured movies section if we're on the homepage
|
|
||||||
if (window.location.pathname.endsWith('index.html') || window.location.pathname.endsWith('/')) {
|
|
||||||
this.initializeHomepage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize homepage elements
|
|
||||||
initializeHomepage() {
|
|
||||||
// We'll use the same movieContainer for homepage
|
|
||||||
if (!this.movieContainer) {
|
|
||||||
console.error('Movie container element not found on homepage');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a heading for the featured movies section if not already present
|
|
||||||
const parentSection = this.movieContainer.closest('section');
|
|
||||||
if (parentSection) {
|
|
||||||
const heading = parentSection.querySelector('h2');
|
|
||||||
if (heading) {
|
|
||||||
heading.textContent = 'Популярные фильмы';
|
|
||||||
heading.className = 'text-orange mb-4';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a button to add movies on the homepage too
|
|
||||||
this.createAddMovieButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create "Add Movie" button and append it to the page
|
|
||||||
createAddMovieButton() {
|
|
||||||
// Find the appropriate container - either .mb-5 or the section containing movieContainer
|
|
||||||
let container = document.querySelector('.mb-5');
|
|
||||||
if (!container && this.movieContainer) {
|
|
||||||
container = this.movieContainer.closest('section');
|
|
||||||
}
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
// Check if button already exists
|
|
||||||
if (container.querySelector('.add-movie-btn')) return;
|
|
||||||
|
|
||||||
const buttonContainer = document.createElement('div');
|
|
||||||
buttonContainer.className = 'mb-4 d-flex justify-content-end';
|
|
||||||
|
|
||||||
const addButton = document.createElement('a');
|
|
||||||
addButton.className = 'btn btn-success add-movie-btn';
|
|
||||||
addButton.href = 'add-movie.html';
|
|
||||||
addButton.innerHTML = '<i class="bi bi-plus-circle me-2"></i>Добавить фильм';
|
|
||||||
|
|
||||||
buttonContainer.appendChild(addButton);
|
|
||||||
container.insertBefore(buttonContainer, this.movieContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind the save new movie form submit event
|
|
||||||
bindSaveNewMovie(handler) {
|
|
||||||
const addMovieForm = document.getElementById('addMovieForm');
|
|
||||||
if (!addMovieForm) return;
|
|
||||||
|
|
||||||
addMovieForm.addEventListener('submit', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const newMovie = {
|
|
||||||
title: document.getElementById('movieTitle').value,
|
|
||||||
director: document.getElementById('movieDirector').value,
|
|
||||||
genres: Array.from(document.getElementById('movieGenre').selectedOptions).map(option => option.text),
|
|
||||||
year: document.getElementById('movieYear').value,
|
|
||||||
description: document.getElementById('movieDescription').value,
|
|
||||||
poster: document.getElementById('posterPreview')?.src || 'resources/movies/placeholder.jpg'
|
|
||||||
};
|
|
||||||
|
|
||||||
handler(newMovie);
|
|
||||||
|
|
||||||
// Redirect back to catalog after adding
|
|
||||||
window.location.href = 'catalog.html';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
createMovieElement(movie) {
|
|
||||||
const movieCard = document.createElement('div');
|
|
||||||
movieCard.className = 'card movie-card h-100 bg-dark';
|
|
||||||
movieCard.dataset.movieId = movie.id;
|
|
||||||
|
|
||||||
movieCard.innerHTML = `
|
|
||||||
<img src="${movie.poster}" class="card-img-top" alt="${movie.title} Poster">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title text-white"><i class="bi bi-film text-orange me-2"></i>${movie.title}</h5>
|
|
||||||
<p class="card-text text-white"><i class="bi bi-person-video3 me-2"></i>Режиссер: ${movie.director}</p>
|
|
||||||
<p class="card-text text-light"><i class="bi bi-tags me-2"></i>Жанр: ${Array.isArray(movie.genres) ? movie.genres.join(', ') : movie.genres}</p>
|
|
||||||
<p class="card-text text-light"><i class="bi bi-calendar3 me-2"></i>Год выпуска: ${movie.year}</p>
|
|
||||||
${movie.description ? `<p class="card-text text-light"><i class="bi bi-text-paragraph me-2"></i>Описание: ${movie.description}</p>` : ''}
|
|
||||||
</div>
|
|
||||||
<div class="card-footer bg-transparent border-0 d-flex justify-content-between">
|
|
||||||
<a href="about.html" class="btn btn-orange"><i class="bi bi-play-circle me-2"></i>Смотреть</a>
|
|
||||||
<button class="btn btn-danger delete-movie"><i class="bi bi-trash"></i> Удалить</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return movieCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
bindDeleteMovie(handler) {
|
|
||||||
if (!this.movieContainer) return;
|
|
||||||
|
|
||||||
// Use event delegation to handle delete button clicks
|
|
||||||
this.movieContainer.addEventListener('click', (e) => {
|
|
||||||
if (e.target.closest('.delete-movie')) {
|
|
||||||
const movieCard = e.target.closest('.movie-card');
|
|
||||||
const movieId = movieCard.dataset.movieId;
|
|
||||||
if (confirm('Вы уверены, что хотите удалить этот фильм?')) {
|
|
||||||
handler(movieId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMovies(movies) {
|
|
||||||
// Make sure movies is an array before using forEach
|
|
||||||
if (!Array.isArray(movies)) {
|
|
||||||
console.error('Expected movies to be an array but got:', movies);
|
|
||||||
movies = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use movieContainer property instead of looking for 'movies-container'
|
|
||||||
if (!this.movieContainer) {
|
|
||||||
console.error('Movie container element not found. Make sure an element with id "movieContainer" exists in your HTML.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save scroll position before clearing the container
|
|
||||||
const scrollPosition = window.scrollY;
|
|
||||||
|
|
||||||
this.movieContainer.innerHTML = '';
|
|
||||||
|
|
||||||
// If we're on the homepage, only show up to 6 featured movies
|
|
||||||
const isHomepage = window.location.pathname.endsWith('index.html') || window.location.pathname.endsWith('/');
|
|
||||||
const moviesToShow = isHomepage ? movies.slice(0, 6) : movies;
|
|
||||||
|
|
||||||
moviesToShow.forEach(movie => {
|
|
||||||
const movieCard = this.createMovieElement(movie);
|
|
||||||
this.movieContainer.appendChild(movieCard);
|
|
||||||
});
|
|
||||||
|
|
||||||
// If we're on the homepage and there are more movies, add a "See All" button
|
|
||||||
if (isHomepage && movies.length > 6) {
|
|
||||||
const seeAllContainer = document.createElement('div');
|
|
||||||
seeAllContainer.className = 'col-12 text-center mt-4';
|
|
||||||
|
|
||||||
const seeAllButton = document.createElement('a');
|
|
||||||
seeAllButton.href = 'catalog.html';
|
|
||||||
seeAllButton.className = 'btn btn-primary';
|
|
||||||
seeAllButton.textContent = 'Смотреть все фильмы';
|
|
||||||
|
|
||||||
seeAllContainer.appendChild(seeAllButton);
|
|
||||||
this.movieContainer.parentNode.appendChild(seeAllContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore scroll position after rendering
|
|
||||||
window.scrollTo(0, scrollPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
fillEditModal(movie) {
|
|
||||||
if (!this.editModal) return;
|
|
||||||
|
|
||||||
const titleInput = this.editModal.querySelector('#editMovieTitle');
|
|
||||||
const directorInput = this.editModal.querySelector('#editMovieDirector');
|
|
||||||
const genreSelect = this.editModal.querySelector('#editMovieGenre');
|
|
||||||
const yearInput = this.editModal.querySelector('#editMovieYear');
|
|
||||||
const descriptionInput = this.editModal.querySelector('#editMovieDescription');
|
|
||||||
const posterPreview = this.editModal.querySelector('#editPosterPreview');
|
|
||||||
|
|
||||||
titleInput.value = movie.title;
|
|
||||||
directorInput.value = movie.director;
|
|
||||||
|
|
||||||
// Handle genres (multi-select)
|
|
||||||
if (Array.isArray(movie.genres)) {
|
|
||||||
Array.from(genreSelect.options).forEach(option => {
|
|
||||||
option.selected = movie.genres.includes(option.text);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
yearInput.value = movie.year;
|
|
||||||
descriptionInput.value = movie.description || '';
|
|
||||||
|
|
||||||
if (movie.poster) {
|
|
||||||
posterPreview.src = movie.poster;
|
|
||||||
this.editModal.querySelector('.preview-container').classList.remove('d-none');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.editModal.dataset.movieId = movie.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
bindEditMovie(handler) {
|
|
||||||
if (!this.movieContainer) return;
|
|
||||||
|
|
||||||
this.movieContainer.addEventListener('click', (e) => {
|
|
||||||
if (e.target.closest('.edit-movie')) {
|
|
||||||
const movieCard = e.target.closest('.movie-card');
|
|
||||||
const movieId = movieCard.dataset.movieId;
|
|
||||||
handler(movieId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bindSaveEditedMovie(handler) {
|
|
||||||
if (!this.editModal) return;
|
|
||||||
|
|
||||||
const form = this.editModal.querySelector('form');
|
|
||||||
form.addEventListener('submit', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const movieId = this.editModal.dataset.movieId;
|
|
||||||
const updatedMovie = {
|
|
||||||
title: form.querySelector('#editMovieTitle').value,
|
|
||||||
director: form.querySelector('#editMovieDirector').value,
|
|
||||||
genres: Array.from(form.querySelector('#editMovieGenre').selectedOptions).map(option => option.text),
|
|
||||||
year: form.querySelector('#editMovieYear').value,
|
|
||||||
description: form.querySelector('#editMovieDescription').value,
|
|
||||||
poster: form.querySelector('#editPosterPreview').src
|
|
||||||
};
|
|
||||||
|
|
||||||
handler(movieId, updatedMovie);
|
|
||||||
|
|
||||||
// Close modal using Bootstrap
|
|
||||||
bootstrap.Modal.getInstance(this.editModal).hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bindFilterMovies(handler) {
|
|
||||||
const genreSelect = document.getElementById('genre-select');
|
|
||||||
const yearSelect = document.getElementById('year-select');
|
|
||||||
const searchInput = document.getElementById('search-input');
|
|
||||||
|
|
||||||
if (genreSelect) {
|
|
||||||
genreSelect.addEventListener('change', () => {
|
|
||||||
const filters = {
|
|
||||||
genre: genreSelect.value,
|
|
||||||
year: yearSelect ? yearSelect.value : 'all',
|
|
||||||
search: searchInput ? searchInput.value : ''
|
|
||||||
};
|
|
||||||
handler(filters);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (yearSelect) {
|
|
||||||
yearSelect.addEventListener('change', () => {
|
|
||||||
const filters = {
|
|
||||||
genre: genreSelect ? genreSelect.value : 'all',
|
|
||||||
year: yearSelect.value,
|
|
||||||
search: searchInput ? searchInput.value : ''
|
|
||||||
};
|
|
||||||
handler(filters);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchInput) {
|
|
||||||
searchInput.addEventListener('input', () => {
|
|
||||||
const filters = {
|
|
||||||
genre: genreSelect ? genreSelect.value : 'all',
|
|
||||||
year: yearSelect ? yearSelect.value : 'all',
|
|
||||||
search: searchInput.value
|
|
||||||
};
|
|
||||||
handler(filters);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
243
src/main.js
243
src/main.js
@@ -1,243 +0,0 @@
|
|||||||
// Импортируем Bootstrap
|
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
||||||
import * as bootstrap from 'bootstrap/dist/js/bootstrap.bundle.min.js';
|
|
||||||
|
|
||||||
// Make bootstrap available globally
|
|
||||||
window.bootstrap = bootstrap;
|
|
||||||
|
|
||||||
// Импортируем собственные стили
|
|
||||||
import '../style.css';
|
|
||||||
|
|
||||||
// Импортируем MVC компоненты
|
|
||||||
import { MovieModel } from './components/movie/MovieModel.js';
|
|
||||||
import { MovieView } from './components/movie/MovieView.js';
|
|
||||||
import { MovieController } from './components/movie/MovieController.js';
|
|
||||||
|
|
||||||
// Инициализация общего функционала сайта
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
if (!document.querySelector('style#custom-styles')) {
|
|
||||||
const styleElement = document.createElement('style');
|
|
||||||
styleElement.id = 'custom-styles';
|
|
||||||
styleElement.textContent = `
|
|
||||||
.btn-orange {
|
|
||||||
background-color: #fd7e14;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.btn-orange:hover {
|
|
||||||
background-color: #e76b00;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.text-orange {
|
|
||||||
color: #fd7e14 !important;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.appendChild(styleElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Сайт успешно загружен!');
|
|
||||||
|
|
||||||
// Инициализация MVC компонентов
|
|
||||||
const movieModel = new MovieModel();
|
|
||||||
const movieView = new MovieView();
|
|
||||||
const movieController = new MovieController(movieModel, movieView);
|
|
||||||
|
|
||||||
// Инициализация выпадающего меню, если оно есть на странице
|
|
||||||
const dropdownElements = document.querySelectorAll('.dropdown');
|
|
||||||
if (dropdownElements.length > 0) {
|
|
||||||
dropdownElements.forEach(dropdown => {
|
|
||||||
dropdown.addEventListener('click', () => {
|
|
||||||
dropdown.querySelector('.features-menu')?.classList.toggle('active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize all Bootstrap modals properly
|
|
||||||
const modalElements = document.querySelectorAll('.modal');
|
|
||||||
if (modalElements.length > 0) {
|
|
||||||
modalElements.forEach(modalEl => {
|
|
||||||
new bootstrap.Modal(modalEl, {
|
|
||||||
keyboard: true,
|
|
||||||
focus: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add event listeners for close and cancel buttons in modals
|
|
||||||
const closeButtons = document.querySelectorAll('[data-bs-dismiss="modal"]');
|
|
||||||
if (closeButtons.length > 0) {
|
|
||||||
closeButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
const modalElement = button.closest('.modal');
|
|
||||||
if (modalElement) {
|
|
||||||
const modalInstance = bootstrap.Modal.getInstance(modalElement);
|
|
||||||
if (modalInstance) {
|
|
||||||
modalInstance.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработка формы добавления фильма
|
|
||||||
const addMovieForm = document.getElementById('addMovieForm');
|
|
||||||
if (addMovieForm) {
|
|
||||||
// Предпросмотр постера
|
|
||||||
const addMoviePoster = document.getElementById('addMoviePoster');
|
|
||||||
const addPosterPreview = document.getElementById('addPosterPreview');
|
|
||||||
const addPreviewContainer = document.querySelector('#addMovieModal .preview-container');
|
|
||||||
|
|
||||||
if (addMoviePoster && addPosterPreview && addPreviewContainer) {
|
|
||||||
addMoviePoster.addEventListener('change', (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
addPosterPreview.src = e.target.result;
|
|
||||||
addPreviewContainer.classList.remove('d-none');
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправка формы
|
|
||||||
addMovieForm.addEventListener('submit', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const movieData = {
|
|
||||||
title: document.getElementById('addMovieTitle').value,
|
|
||||||
director: document.getElementById('addMovieDirector').value,
|
|
||||||
genres: Array.from(document.getElementById('addMovieGenre').selectedOptions)
|
|
||||||
.map(option => option.text),
|
|
||||||
year: document.getElementById('addMovieYear').value,
|
|
||||||
description: document.getElementById('addMovieDescription').value,
|
|
||||||
poster: document.getElementById('addPosterPreview')?.src || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
movieController.handleSaveNewMovie(movieData);
|
|
||||||
addMovieForm.reset();
|
|
||||||
|
|
||||||
if (addPreviewContainer) {
|
|
||||||
addPreviewContainer.classList.add('d-none');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the modal window
|
|
||||||
const addMovieModal = document.getElementById('addMovieModal');
|
|
||||||
if (addMovieModal) {
|
|
||||||
const modalInstance = bootstrap.Modal.getInstance(addMovieModal);
|
|
||||||
if (modalInstance) {
|
|
||||||
modalInstance.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alert('Фильм успешно добавлен!');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add cancel button handler
|
|
||||||
const cancelButton = addMovieForm.querySelector('button[type="button"]');
|
|
||||||
if (cancelButton) {
|
|
||||||
cancelButton.addEventListener('click', () => {
|
|
||||||
const modalElement = cancelButton.closest('.modal');
|
|
||||||
if (modalElement) {
|
|
||||||
const modalInstance = bootstrap.Modal.getInstance(modalElement);
|
|
||||||
if (modalInstance) {
|
|
||||||
modalInstance.hide();
|
|
||||||
} else {
|
|
||||||
// Fallback if modal instance isn't available
|
|
||||||
modalElement.classList.remove('show');
|
|
||||||
modalElement.style.display = 'none';
|
|
||||||
document.body.classList.remove('modal-open');
|
|
||||||
const backdrop = document.querySelector('.modal-backdrop');
|
|
||||||
if (backdrop) {
|
|
||||||
backdrop.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработка формы редактирования фильма
|
|
||||||
const editMovieForm = document.getElementById('editMovieForm');
|
|
||||||
if (editMovieForm) {
|
|
||||||
const editMoviePoster = document.getElementById('editMoviePoster');
|
|
||||||
const editPosterPreview = document.getElementById('editPosterPreview');
|
|
||||||
const editPreviewContainer = editMovieForm.querySelector('.preview-container');
|
|
||||||
|
|
||||||
if (editMoviePoster && editPosterPreview && editPreviewContainer) {
|
|
||||||
editMoviePoster.addEventListener('change', (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
editPosterPreview.src = e.target.result;
|
|
||||||
editPreviewContainer.classList.remove('d-none');
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Инициализация фильтров для каталога
|
|
||||||
const genreSelect = document.getElementById('genre-select');
|
|
||||||
const yearSelect = document.getElementById('year-select');
|
|
||||||
const searchInput = document.getElementById('search-input');
|
|
||||||
|
|
||||||
if (genreSelect || yearSelect || searchInput) {
|
|
||||||
// Заполнение выпадающего списка годов
|
|
||||||
if (yearSelect) {
|
|
||||||
const currentYear = new Date().getFullYear();
|
|
||||||
yearSelect.innerHTML = '<option value="all">Все годы</option>';
|
|
||||||
for (let year = currentYear; year >= 1900; year--) {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = year.toString();
|
|
||||||
option.textContent = year.toString();
|
|
||||||
yearSelect.appendChild(option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add this to your main.js or in a script tag at the end of your HTML
|
|
||||||
|
|
||||||
// Handle poster image preview for Add Movie modal
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const addPosterInput = document.getElementById('addMoviePoster');
|
|
||||||
const addPosterPreview = document.getElementById('addPosterPreview');
|
|
||||||
const addPreviewContainer = document.querySelector('#addMovieModal .preview-container');
|
|
||||||
|
|
||||||
if (addPosterInput && addPosterPreview && addPreviewContainer) {
|
|
||||||
addPosterInput.addEventListener('change', function() {
|
|
||||||
const file = this.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
addPosterPreview.src = e.target.result;
|
|
||||||
addPreviewContainer.classList.remove('d-none');
|
|
||||||
}
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Similar code for edit modal if needed
|
|
||||||
const editPosterInput = document.getElementById('editMoviePoster');
|
|
||||||
const editPosterPreview = document.getElementById('editPosterPreview');
|
|
||||||
const editPreviewContainer = document.querySelector('#editMovieModal .preview-container');
|
|
||||||
|
|
||||||
if (editPosterInput && editPosterPreview && editPreviewContainer) {
|
|
||||||
editPosterInput.addEventListener('change', function() {
|
|
||||||
const file = this.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
editPosterPreview.src = e.target.result;
|
|
||||||
editPreviewContainer.classList.remove('d-none');
|
|
||||||
}
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
17
src/main.jsx
Normal file
17
src/main.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
// Import Bootstrap
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
import 'bootstrap-icons/font/bootstrap-icons.css';
|
||||||
|
import './style.css';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
23
src/pages/AboutPage.jsx
Normal file
23
src/pages/AboutPage.jsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function AboutPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="mb-4">О нас</h2>
|
||||||
|
<div className="card bg-dark text-white">
|
||||||
|
<div className="card-body">
|
||||||
|
<h5 className="card-title">Наш кинотеатр</h5>
|
||||||
|
<p className="card-text">
|
||||||
|
Мы предлагаем широкий выбор фильмов различных жанров и направлений.
|
||||||
|
Наша цель - сделать просмотр кино максимально комфортным и приятным для вас.
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
Наслаждайтесь просмотром любимых фильмов в отличном качестве!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AboutPage;
|
||||||
28
src/pages/AddMoviePage.jsx
Normal file
28
src/pages/AddMoviePage.jsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import MovieForm from '../components/Movie/MovieForm';
|
||||||
|
import MovieService from '../services/MovieService';
|
||||||
|
|
||||||
|
function AddMoviePage() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleAddMovie = async (movieData) => {
|
||||||
|
try {
|
||||||
|
await MovieService.addMovie(movieData);
|
||||||
|
alert('Фильм успешно добавлен!');
|
||||||
|
navigate('/catalog');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding movie:', error);
|
||||||
|
alert('Произошла ошибка при добавлении фильма.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="mb-4">Добавить новый фильм</h2>
|
||||||
|
<MovieForm onSubmit={handleAddMovie} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddMoviePage;
|
||||||
98
src/pages/CatalogPage.jsx
Normal file
98
src/pages/CatalogPage.jsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import MovieList from '../components/Movie/MovieList';
|
||||||
|
import MovieService from '../services/MovieService';
|
||||||
|
|
||||||
|
function CatalogPage() {
|
||||||
|
const [movies, setMovies] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
// For filtering
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [selectedGenre, setSelectedGenre] = useState('');
|
||||||
|
const [genres, setGenres] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchMovies = async () => {
|
||||||
|
try {
|
||||||
|
const data = await MovieService.getMovies();
|
||||||
|
setMovies(data);
|
||||||
|
|
||||||
|
// Extract unique genres for filter
|
||||||
|
const allGenres = data.flatMap(movie => movie.genres || []);
|
||||||
|
const uniqueGenres = [...new Set(allGenres)];
|
||||||
|
setGenres(uniqueGenres);
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching movies:', error);
|
||||||
|
setError('Не удалось загрузить фильмы. Пожалуйста, попробуйте позже.');
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchMovies();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDeleteMovie = async (id) => {
|
||||||
|
try {
|
||||||
|
await MovieService.deleteMovie(id);
|
||||||
|
setMovies(movies.filter(movie => movie.id !== id));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting movie:', error);
|
||||||
|
alert('Произошла ошибка при удалении фильма.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter movies based on search term and selected genre
|
||||||
|
const filteredMovies = movies.filter(movie => {
|
||||||
|
const matchesSearch = searchTerm ?
|
||||||
|
(movie.title?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
movie.director?.toLowerCase().includes(searchTerm.toLowerCase())) : true;
|
||||||
|
|
||||||
|
const matchesGenre = selectedGenre === '' ||
|
||||||
|
(Array.isArray(movie.genres) && movie.genres.includes(selectedGenre));
|
||||||
|
|
||||||
|
return matchesSearch && matchesGenre;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div className="text-center py-5"><div className="spinner-border" role="status"></div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div className="alert alert-danger">{error}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="row mb-4">
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
placeholder="Поиск по названию или режиссеру"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
|
<select
|
||||||
|
className="form-select"
|
||||||
|
value={selectedGenre}
|
||||||
|
onChange={(e) => setSelectedGenre(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">Все жанры</option>
|
||||||
|
{genres.map(genre => (
|
||||||
|
<option key={genre} value={genre}>{genre}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MovieList movies={filteredMovies} onDeleteMovie={handleDeleteMovie} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CatalogPage;
|
||||||
60
src/pages/EditMoviePage.jsx
Normal file
60
src/pages/EditMoviePage.jsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import MovieForm from '../components/Movie/MovieForm';
|
||||||
|
import MovieService from '../services/MovieService';
|
||||||
|
|
||||||
|
function EditMoviePage() {
|
||||||
|
const { id } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [movie, setMovie] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchMovie = async () => {
|
||||||
|
try {
|
||||||
|
const data = await MovieService.getMovieById(id);
|
||||||
|
if (!data) {
|
||||||
|
setError('Фильм не найден');
|
||||||
|
} else {
|
||||||
|
setMovie(data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching movie:', error);
|
||||||
|
setError('Не удалось загрузить данные фильма');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchMovie();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const handleUpdateMovie = async (movieData) => {
|
||||||
|
try {
|
||||||
|
await MovieService.updateMovie(id, movieData);
|
||||||
|
alert('Фильм успешно обновлен!');
|
||||||
|
navigate('/catalog');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating movie:', error);
|
||||||
|
alert('Произошла ошибка при обновлении фильма.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div className="text-center py-5"><div className="spinner-border" role="status"></div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div className="alert alert-danger">{error}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="mb-4">Редактировать фильм</h2>
|
||||||
|
<MovieForm movie={movie} onSubmit={handleUpdateMovie} isEditing={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditMoviePage;
|
||||||
60
src/pages/HomePage.jsx
Normal file
60
src/pages/HomePage.jsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import MovieList from '../components/Movie/MovieList';
|
||||||
|
import MovieService from '../services/MovieService';
|
||||||
|
|
||||||
|
function HomePage() {
|
||||||
|
const [movies, setMovies] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchMovies = async () => {
|
||||||
|
try {
|
||||||
|
const data = await MovieService.getMovies();
|
||||||
|
// Sort by year (newest first) for featured movies
|
||||||
|
const sortedMovies = [...data].sort((a, b) => b.year - a.year);
|
||||||
|
setMovies(sortedMovies);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching movies:', error);
|
||||||
|
setError('Не удалось загрузить фильмы. Пожалуйста, попробуйте позже.');
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchMovies();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDeleteMovie = async (id) => {
|
||||||
|
try {
|
||||||
|
await MovieService.deleteMovie(id);
|
||||||
|
setMovies(movies.filter(movie => movie.id !== id));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting movie:', error);
|
||||||
|
alert('Произошла ошибка при удалении фильма.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div className="text-center py-5"><div className="spinner-border" role="status"></div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div className="alert alert-danger">{error}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="jumbotron bg-dark text-white p-5 mb-4 rounded">
|
||||||
|
<h1 className="display-4">Добро пожаловать в наш кинотеатр!</h1>
|
||||||
|
<p className="lead">Лучшие фильмы и сериалы в одном месте</p>
|
||||||
|
<hr className="my-4" />
|
||||||
|
<p>Смотрите новинки кино и классику в отличном качестве</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MovieList movies={movies} onDeleteMovie={handleDeleteMovie} isHomepage={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomePage;
|
||||||
82
src/services/MovieService.js
Normal file
82
src/services/MovieService.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
const API_URL = 'http://localhost:3000';
|
||||||
|
|
||||||
|
export default class MovieService {
|
||||||
|
static async getMovies() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_URL}/movies`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch movies');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching movies:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getMovieById(id) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_URL}/movies/${id}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch movie');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching movie:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addMovie(movie) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_URL}/movies`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(movie)
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to add movie');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding movie:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateMovie(id, movie) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_URL}/movies/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(movie)
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to update movie');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating movie:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async deleteMovie(id) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_URL}/movies/${id}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete movie');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting movie:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
src/style.css
Normal file
150
src/style.css
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
/* Custom styles for the movie application */
|
||||||
|
.btn-orange {
|
||||||
|
background-color: #fd7e14;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.btn-orange:hover {
|
||||||
|
background-color: #e76b00;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.text-orange {
|
||||||
|
color: #fd7e14 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Redesigned movie card styling */
|
||||||
|
.movie-card {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #212529 !important;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
.movie-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 12px 16px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.movie-card .card-body {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
.card-img-top {
|
||||||
|
height: 380px;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-top-right-radius: 8px;
|
||||||
|
}
|
||||||
|
.movie-card .card-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.movie-card .card-text {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.movie-card .card-footer {
|
||||||
|
background-color: rgba(0,0,0,0.1) !important;
|
||||||
|
border-top: none !important;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
}
|
||||||
|
.movie-card .btn {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation for card deletion */
|
||||||
|
.movie-card.deleting {
|
||||||
|
animation: fadeOut 0.5s ease forwards;
|
||||||
|
}
|
||||||
|
@keyframes fadeOut {
|
||||||
|
from { opacity: 1; transform: scale(1); }
|
||||||
|
to { opacity: 0; transform: scale(0.8); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form styling */
|
||||||
|
.preview-container {
|
||||||
|
max-width: 300px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Jumbotron styling for homepage */
|
||||||
|
.jumbotron {
|
||||||
|
background-color: #212529;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #131516;
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global text color adjustments */
|
||||||
|
h1, h2, h3, h4, h5, h6, p, span, div, label, a {
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control, .form-select {
|
||||||
|
background-color: #212529;
|
||||||
|
color: #f8f9fa;
|
||||||
|
border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus, .form-select:focus {
|
||||||
|
background-color: #2c3034;
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add these styles for placeholder text */
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: #adb5bd;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For Firefox */
|
||||||
|
.form-control::-moz-placeholder {
|
||||||
|
color: #adb5bd;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For Internet Explorer */
|
||||||
|
.form-control:-ms-input-placeholder {
|
||||||
|
color: #adb5bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For Microsoft Edge */
|
||||||
|
.form-control::-ms-input-placeholder {
|
||||||
|
color: #adb5bd;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background-color: #212529;
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: #adb5bd !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: #fd7e14;
|
||||||
|
}
|
||||||
53
style.css
53
style.css
@@ -1,53 +0,0 @@
|
|||||||
/* Минимальные настройки внешнего вида */
|
|
||||||
body {
|
|
||||||
background-color: #121212;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Кастомные цвета для элементов */
|
|
||||||
.bg-dark-custom {
|
|
||||||
background-color: #222222;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-orange {
|
|
||||||
color: #ff6600 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-orange {
|
|
||||||
background-color: #ff6600;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-orange:hover {
|
|
||||||
background-color: #e55c00;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для карточек фильмов */
|
|
||||||
.movie-card {
|
|
||||||
background-color: #222222;
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.movie-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для футера */
|
|
||||||
.footer-custom {
|
|
||||||
background-color: #222222;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для социальных иконок */
|
|
||||||
.social-icon {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-icon:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Page Title</title>
|
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<div class="header-container">
|
|
||||||
<div class="logo">
|
|
||||||
<img src="resources/logo.webp" alt="Online Cinema Theater Logo">
|
|
||||||
<span>Online Cinema Theater</span>
|
|
||||||
</div>
|
|
||||||
<nav>
|
|
||||||
<ul class="navbar">
|
|
||||||
<li><a href="index.html">Главная</a></li>
|
|
||||||
<li><a href="catalog.html">Каталог</a></li>
|
|
||||||
<li><a href="reviews.html">Рецензии</a></li>
|
|
||||||
<li class="dropdown">
|
|
||||||
<span>Что глянуть? ▾</span>
|
|
||||||
<ul class="features-menu">
|
|
||||||
<li><a href="films.html">Фильмы</a></li>
|
|
||||||
<li><a href="seriales.html">Сериалы</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<section class="content">
|
|
||||||
<!-- Main content goes here -->
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
<div class="footer-container">
|
|
||||||
<div class="contact-info">
|
|
||||||
<h3>Контактная информация</h3>
|
|
||||||
<p>Телефон: +7 (123) 456-78-90</p>
|
|
||||||
<p>Email: info@cinema.com</p>
|
|
||||||
<p>Адрес: ул. Примерная, 123, Москва, Россия</p>
|
|
||||||
</div>
|
|
||||||
<div class="social-media">
|
|
||||||
<h3>Мы в соцсетях</h3>
|
|
||||||
<a href="#"><img src="resources/icons/icq.svg" alt="ICQ"></a>
|
|
||||||
<a href="#"><img src="resources/icons/tg.webp" alt="Telegram"></a>
|
|
||||||
<a href="#"><img src="resources/icons/vk.webp" alt="VK"></a>
|
|
||||||
</div>
|
|
||||||
<div class="working-hours">
|
|
||||||
<h3>Время работы</h3>
|
|
||||||
<p>Понедельник - Пятница: 10:00 - 22:00</p>
|
|
||||||
<p>Суббота - Воскресенье: 12:00 - 24:00</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p>© 2022 Online Cinema Theater. All rights reserved.</p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,29 +1,11 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
// Получаем список HTML-файлов для мультистраничного приложения
|
|
||||||
const pages = [
|
|
||||||
'index.html',
|
|
||||||
'catalog.html',
|
|
||||||
'films.html',
|
|
||||||
'seriales.html',
|
|
||||||
'reviews.html',
|
|
||||||
'about.html',
|
|
||||||
'add-movie.html',
|
|
||||||
'edit-movie.html'
|
|
||||||
];
|
|
||||||
|
|
||||||
// Создаем объект с входными точками для каждой страницы
|
|
||||||
const input = {};
|
|
||||||
pages.forEach(page => {
|
|
||||||
input[page.replace('.html', '')] = resolve(__dirname, page);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
outDir: 'dist',
|
||||||
input
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
@@ -31,6 +13,6 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
open: '/index.html'
|
open: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
BIN
Отчет.docx
BIN
Отчет.docx
Binary file not shown.
Reference in New Issue
Block a user