Админ панель создания медиа

Пользователи с ролью "editor" и "admin" могут создавать и в дальнейшем редактировать медиа
This commit is contained in:
degradin 2025-05-07 13:38:46 +03:00
parent 89bbc975ce
commit 690c18e601

View File

@ -1,13 +1,19 @@
import { useState } from "react"; import React, { useEffect, useState } from 'react';
import { useMedia } from '../contexts/MediaContext';
import { listMedia } from '../services/supabase';
import { mediaTypes } from '../services/mediaService';
import { useAuth } from "../contexts/AuthContext";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { createMedia } from "../services/supabase"; import { createMedia } from "../services/supabase";
import { useAuth } from "../contexts/AuthContext";
function AdminMediaPage() { const AdminMediaPage = () => {
const [media, setMedia] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const navigate = useNavigate(); const navigate = useNavigate();
const { currentUser, userProfile } = useAuth(); const { currentUser, userProfile } = useAuth();
const [loading, setLoading] = useState(false); const [page, setPage] = useState(1);
const [error, setError] = useState(""); const [hasMore, setHasMore] = useState(true);
const [mediaData, setMediaData] = useState({ const [mediaData, setMediaData] = useState({
title: "", title: "",
@ -19,21 +25,36 @@ function AdminMediaPage() {
is_published: false, is_published: false,
}); });
// Check if user has admin/moderator privileges // Проверка прав доступа
if ( if (!userProfile?.role || !["admin", "editor"].includes(userProfile.role)) {
!userProfile?.role ||
!["admin", "moderator"].includes(userProfile.role)
) {
return ( return (
<div className="pt-20 container-custom py-12"> <div className="pt-20 container-custom py-12">
<div className="bg-status-error bg-opacity-20 text-status-error p-6 rounded-lg"> <div className="bg-status-error bg-opacity-20 text-status-error p-6 rounded-lg">
<h2 className="text-xl font-bold mb-2">Access Denied</h2> <h2 className="text-xl font-bold mb-2">Доступ запрещен</h2>
<p>You don't have permission to access this page.</p> <p>У вас нет прав для доступа к этой странице.</p>
</div> </div>
</div> </div>
); );
} }
useEffect(() => {
const fetchMedia = async () => {
try {
setLoading(true);
setError(null);
const data = await listMedia(null, 1, 100); // Получаем все медиа
setMedia(data || []);
} catch (err) {
console.error('Error fetching media:', err);
setError('Не удалось загрузить медиа');
} finally {
setLoading(false);
}
};
fetchMedia();
}, []); // Запускаем только при монтировании компонента
const handleInputChange = (e) => { const handleInputChange = (e) => {
const { name, value, type, checked } = e.target; const { name, value, type, checked } = e.target;
setMediaData((prev) => ({ setMediaData((prev) => ({
@ -53,123 +74,119 @@ function AdminMediaPage() {
created_by: currentUser.id, created_by: currentUser.id,
}); });
navigate(`/media/${newMedia.id}`); setMedia(prev => [newMedia, ...prev]);
setMediaData({
title: "",
type: "movie",
poster_url: "",
backdrop_url: "",
overview: "",
release_date: "",
is_published: false,
});
} catch (err) { } catch (err) {
setError("Failed to create media. Please try again."); setError("Ошибка при создании медиа. Пожалуйста, попробуйте снова.");
console.error("Error creating media:", err); console.error("Error creating media:", err);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
return ( if (loading) {
<div className="pt-20 container-custom py-12"> return <div className="text-center">Загрузка...</div>;
<h1 className="text-3xl font-bold mb-6">Create New Media</h1> }
if (error) {
return <div className="text-red-500">{error}</div>;
}
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">Управление медиа</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{media.map((item) => (
<div key={`${item.id}-${item.type}`} className="bg-white rounded-lg shadow-md p-4">
<h3 className="text-lg font-semibold mb-2">{item.title}</h3>
<p className="text-gray-600 mb-2">Тип: {item.type}</p>
{item.rating && (
<p className="text-gray-600">Рейтинг: {item.rating}</p>
)}
</div>
))}
</div>
{/* Форма создания медиа */}
<div className="bg-campfire-charcoal p-6 rounded-lg mb-8">
<h2 className="text-2xl font-bold mb-4">Создать новое медиа</h2>
{error && ( {error && (
<div className="bg-status-error bg-opacity-20 text-status-error p-4 rounded-md mb-6"> <div className="bg-status-error bg-opacity-20 text-status-error p-4 rounded-lg mb-4">
{error} {error}
</div> </div>
)} )}
<form onSubmit={handleSubmit} className="space-y-4">
<form onSubmit={handleSubmit} className="max-w-2xl">
<div className="space-y-6">
<div> <div>
<label className="block text-campfire-light mb-2" htmlFor="title"> <label className="block text-sm font-medium mb-1">Название</label>
Title *
</label>
<input <input
type="text" type="text"
id="title"
name="title" name="title"
value={mediaData.title} value={mediaData.title}
onChange={handleInputChange} onChange={handleInputChange}
className="input w-full"
required required
className="input w-full"
/> />
</div> </div>
<div> <div>
<label className="block text-campfire-light mb-2" htmlFor="type"> <label className="block text-sm font-medium mb-1">Тип</label>
Type *
</label>
<select <select
id="type"
name="type" name="type"
value={mediaData.type} value={mediaData.type}
onChange={handleInputChange} onChange={handleInputChange}
className="input w-full" className="input w-full"
required
> >
<option value="movie">Movie</option> <option value="movie">Фильм</option>
<option value="tv">TV Show</option> <option value="series">Сериал</option>
<option value="game">Game</option> <option value="game">Игра</option>
</select> </select>
</div> </div>
<div> <div>
<label <label className="block text-sm font-medium mb-1">URL постера</label>
className="block text-campfire-light mb-2"
htmlFor="poster_url"
>
Poster URL
</label>
<input <input
type="url" type="url"
id="poster_url"
name="poster_url" name="poster_url"
value={mediaData.poster_url} value={mediaData.poster_url}
onChange={handleInputChange} onChange={handleInputChange}
className="input w-full" className="input w-full"
placeholder="https://example.com/poster.jpg"
/> />
</div> </div>
<div> <div>
<label <label className="block text-sm font-medium mb-1">URL фона</label>
className="block text-campfire-light mb-2"
htmlFor="backdrop_url"
>
Backdrop URL
</label>
<input <input
type="url" type="url"
id="backdrop_url"
name="backdrop_url" name="backdrop_url"
value={mediaData.backdrop_url} value={mediaData.backdrop_url}
onChange={handleInputChange} onChange={handleInputChange}
className="input w-full" className="input w-full"
placeholder="https://example.com/backdrop.jpg"
/> />
</div> </div>
<div> <div>
<label <label className="block text-sm font-medium mb-1">Описание</label>
className="block text-campfire-light mb-2"
htmlFor="overview"
>
Overview *
</label>
<textarea <textarea
id="overview"
name="overview" name="overview"
value={mediaData.overview} value={mediaData.overview}
onChange={handleInputChange} onChange={handleInputChange}
className="input w-full h-32" className="input w-full h-32"
required
/> />
</div> </div>
<div> <div>
<label <label className="block text-sm font-medium mb-1">Дата выхода</label>
className="block text-campfire-light mb-2"
htmlFor="release_date"
>
Release Date
</label>
<input <input
type="date" type="date"
id="release_date"
name="release_date" name="release_date"
value={mediaData.release_date} value={mediaData.release_date}
onChange={handleInputChange} onChange={handleInputChange}
@ -180,34 +197,25 @@ function AdminMediaPage() {
<div className="flex items-center"> <div className="flex items-center">
<input <input
type="checkbox" type="checkbox"
id="is_published"
name="is_published" name="is_published"
checked={mediaData.is_published} checked={mediaData.is_published}
onChange={handleInputChange} onChange={handleInputChange}
className="w-4 h-4 text-campfire-amber bg-campfire-dark border-campfire-ash rounded focus:ring-campfire-amber" className="mr-2"
/> />
<label className="ml-2 text-campfire-light" htmlFor="is_published"> <label className="text-sm font-medium">Опубликовать сразу</label>
Publish immediately
</label>
</div> </div>
<div className="flex justify-end gap-4">
<button <button
type="button" type="submit"
onClick={() => navigate(-1)}
className="btn-secondary"
disabled={loading} disabled={loading}
className="btn-primary w-full"
> >
Cancel {loading ? "Создание..." : "Создать медиа"}
</button> </button>
<button type="submit" className="btn-primary" disabled={loading}>
{loading ? "Creating..." : "Create Media"}
</button>
</div>
</div>
</form> </form>
</div> </div>
</div>
); );
} };
export default AdminMediaPage; export default AdminMediaPage;