Версия 1.0.1 Препрод. Добавлена в подвале информация о учредителе.

This commit is contained in:
anibilag 2025-10-09 23:21:08 +03:00
parent 51f112a2e0
commit e5a42670b0
6 changed files with 296 additions and 36 deletions

View File

@ -1,7 +1,7 @@
{
"name": "vite-react-typescript-starter",
"private": true,
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -0,0 +1,101 @@
import { useState } from 'react';
import { ChevronDown, ChevronUp } from 'lucide-react';
interface BlogOwner {
name: string;
photo: string;
shortBio: string;
fullBio: string;
}
const blogOwner: BlogOwner = {
name: 'Илья Дмитриевич Золкин',
photo: '/images/master.jpg?auto=format&fit=crop&q=80&w=400&h=400',
shortBio: 'Я, Илья Золкин, учредитель и главный редактор портала «Культура двух столиц» приветствую и благодарю Вас за проявленные любопытство и внимание. ...',
fullBio: `Я, Илья Золкин, учредитель и главный редактор портала «Культура двух столиц» приветствую и благодарю Вас за проявленные любопытство и внимание.
О проекте
«Культура двух столиц»- это площадка, объединившая журналистов, критиков, актеров, режиссеров, музыкантов и всех тех, кто принимает самое непосредственное участие в культурной жизни Москвы и Санкт-Петербурга. Это пространство для диалога между творцами и зрителями, между критиками и художниками. Это не только агрегатор новостей и анонсов, а живой организм, стремящийся отразить пульс современной культуры двух главных городов России.
Культурная жизнь страны сейчас представляет собой калейдоскоп, которая как стеклышки в трубе мгновенно меняется и переливается разноцветными красками. Наряду с академическими театрами, бережно хранящими традиции, появляются экспериментальные площадки, где молодые режиссеры и актеры ищут новые формы выражения. Современный театр смело экспериментирует с жанрами, стилями и технологиями, стремясь говорить со зрителем на понятном ему языке.
Однако, несмотря на все перемены, театр, киноискусство, эстрада, классическая музыка остается местом, где рождаются эмоции, где зритель может встретиться с самим собой. Культура продолжает играть важную роль в формировании, поддержании духовности и нравственности общества.
О себе
Я родился в одном из самых красивых и театральных городов мира, в Санкт- Петербурге. По первому образованию, которое получил в Санкт-Петербургском Балтийском институте иностранных языков и межкультурного сотрудничества в 2014 году, окончив актерское отделение мастеров Б.Г. Смолкина Б.Г. и О.Г. Кирсановой- Миропольской, я актер театра и кино.
Мои достижения
Beyond my professional work, I'm passionate about cultural education and mentorship. I regularly conduct workshops for aspiring writers and curators, believing that the next generation of cultural critics will bring fresh perspectives that we desperately need.
Through CultureScope, I aim to bridge the gap between high culture and popular culture, between tradition and innovation, and between different communities around the world. Every article, every story we publish is an invitation to see the world through a different lens and to appreciate the rich tapestry of human creativity that surrounds us.`
};
export function BlogOwnerSection() {
const [isExpanded, setIsExpanded] = useState(false);
return (
<section className="bg-gradient-to-br from-slate-50 to-blue-50 py-16 px-4 sm:px-6 lg:px-8">
<div className="max-w-5xl mx-auto">
<div className="bg-white rounded-2xl shadow-xl overflow-hidden">
<div className="md:flex">
<div className="md:flex-shrink-0 md:w-80">
<div className="h-full w-full relative">
<img
src={blogOwner.photo}
alt={blogOwner.name}
className="h-full w-full object-cover md:min-h-[400px]"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent md:hidden" />
</div>
</div>
<div className="p-8 md:p-12 flex-1">
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-3xl font-bold text-gray-900 mb-2">
{blogOwner.name}
</h2>
<p className="text-blue-600 font-medium text-lg">
Основатель & Главный редактор
</p>
</div>
</div>
<div className="relative">
<div className={`text-gray-700 leading-relaxed ${!isExpanded ? 'line-clamp-3' : ''}`}>
{isExpanded ? (
<div className="space-y-4 whitespace-pre-line">
{blogOwner.fullBio}
</div>
) : (
<p>{blogOwner.shortBio}</p>
)}
</div>
{!isExpanded && (
<div className="absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-white to-transparent pointer-events-none" />
)}
</div>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="mt-6 inline-flex items-center gap-2 text-blue-600 hover:text-blue-700 font-medium transition-colors group"
aria-expanded={isExpanded}
aria-label={isExpanded ? 'Закрыть' : 'Читать дальше'}
>
<span>{isExpanded ? 'Закрыть' : 'Читать дальше'}</span>
{isExpanded ? (
<ChevronUp className="w-5 h-5 group-hover:-translate-y-0.5 transition-transform" />
) : (
<ChevronDown className="w-5 h-5 group-hover:translate-y-0.5 transition-transform" />
)}
</button>
</div>
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,123 @@
import { useState } from 'react';
import { ChevronDown, ChevronUp } from 'lucide-react';
interface BlogOwner {
name: string;
photo: string;
shortBio: string;
fullBioContent: JSX.Element;
}
const blogOwner: BlogOwner = {
name: 'Илья Дмитриевич Золкин',
photo: '/images/master.jpg?auto=format&fit=crop&q=80&w=400&h=400',
shortBio: 'Я, Илья Золкин, учредитель и главный редактор портала «Культура двух столиц» приветствую и благодарю Вас за проявленные любопытство и внимание. ...',
fullBioContent: (
<>
<p>
Я, <strong>Илья Золкин, учредитель и главный редактор портала «Культура двух столиц»</strong> приветствую и благодарю Вас за проявленные любопытство и внимание.
</p>
<p>
<strong>О проекте</strong>
</p>
<p>
«Культура двух столиц»- это площадка, объединившая журналистов, критиков, актеров, режиссеров, музыкантов и всех тех, кто принимает самое непосредственное участие в культурной жизни Москвы и Санкт-Петербурга. Это пространство для диалога между творцами и зрителями, между критиками и художниками. Это не только агрегатор новостей и анонсов, а живой организм, стремящийся отразить пульс современной культуры двух главных городов России.
</p>
<p>
Культурная жизнь страны сейчас представляет собой калейдоскоп, которая как стеклышки в трубе мгновенно меняется и переливается разноцветными красками. Наряду с академическими театрами, бережно хранящими традиции, появляются экспериментальные площадки, где молодые режиссеры и актеры ищут новые формы выражения. Современный театр смело экспериментирует с жанрами, стилями и технологиями, стремясь говорить со зрителем на понятном ему языке.
</p>
<p>
Однако, несмотря на все перемены, театр, киноискусство, эстрада, классическая музыка остается местом, где рождаются эмоции, где зритель может встретиться с самим собой. Культура продолжает играть важную роль в формировании, поддержании духовности и нравственности общества.
</p>
<p>
<strong>О себе</strong>
</p>
<p>
Я родился в одном из самых красивых и театральных городов мира, в Санкт- Петербурге. По первому образованию, которое получил в Санкт-Петербургском Балтийском институте иностранных языков и межкультурного сотрудничества в 2014 году, окончив актерское отделение мастеров Б.Г. Смолкина Б.Г. и О.Г. Кирсановой- Миропольской, я актер театра и кино.
</p>
<p>
<strong>Мои достижения</strong>
</p>
<ul className="list-disc list-inside space-y-2 ml-4">
<li>Поработав в нескольких театрах Петербурга и Москвы, я стал театральным обозревателем и в 2015 году разработал и запустил <strong>сайт «Культура двух столиц».</strong></li>
<li>Создал <strong>авторскую программу «Снимаю шляпу»</strong> на 78-канале телевидения Санкт-Петербурга, где я делюсь своим профессиональным взглядом на спектакли, актеров и театральные события. </li>
<li>Как фотохудожник являюсь автором и организатором <strong>фотопроекта "Актеры и образы"</strong>, цель которого запечатлеть выдающихся актеров нашего времени в их самых ярких театральных образах, которая с успехом проходила в театральном музее им. Бахрушина и культурном центре им. В. Я. Вульфа, а также на других популярных площадках Москвы.</li>
<li>В качестве фотокорреспондента ежегодно участвую в <strong>крупнейших кинофестивалях страны</strong>: Московский международный кинофестиваль, «Виват кино России», «Амурская осень», «Литература и кино».</li>
<li>Участник телевизионных проектов: <strong>на канале «Культура»</strong> в телепередаче А. Максимова «Наблюдатель». <strong>На ТВЦ</strong> постоянно участвую в программе «Хватит слухов» в качестве культурного обозревателя.</li>
<li>Организатор, ведущий и участник <strong>культурных мероприятий и концертов</strong>, в том числе в Доме музее Зинаиды Юсуповой и особняке Шредера в Санкт-Петербурге.</li>
</ul>
</>
)
};
export function BlogOwnerSectionFull() {
const [isExpanded, setIsExpanded] = useState(false);
return (
<section className="bg-gradient-to-br from-slate-50 to-blue-50 py-4 px-4 sm:px-6 lg:px-8">
<div className="max-w-5xl mx-auto">
<div className="bg-white rounded-2xl shadow-xl overflow-hidden">
<div className="md:flex">
<div className="md:flex-shrink-0 md:w-80">
<div className="h-full w-full relative">
<img
src={blogOwner.photo}
alt={blogOwner.name}
className="h-full w-full object-cover md:min-h-[150px]"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent md:hidden" />
</div>
</div>
<div className="p-8 md:p-12 flex-1">
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-3xl font-bold text-gray-900 mb-2">
{blogOwner.name}
</h2>
<p className="text-blue-600 font-medium text-lg">
Учредитель и Главный Редактор
</p>
</div>
</div>
<div className="relative">
<div className={`text-gray-700 leading-relaxed ${!isExpanded ? 'line-clamp-3' : ''}`}>
{isExpanded ? (
<div className="space-y-4">
{blogOwner.fullBioContent}
</div>
) : (
<p>{blogOwner.shortBio}</p>
)}
</div>
{!isExpanded && (
<div className="absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-white to-transparent pointer-events-none" />
)}
</div>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="mt-6 inline-flex items-center gap-2 text-blue-600 hover:text-blue-700 font-medium transition-colors group"
aria-expanded={isExpanded}
aria-label={isExpanded ? 'Закрыть' : 'Читать'}
>
<span>{isExpanded ? 'Закрыть' : 'Читать'}</span>
{isExpanded ? (
<ChevronUp className="w-5 h-5 group-hover:-translate-y-0.5 transition-transform" />
) : (
<ChevronDown className="w-5 h-5 group-hover:translate-y-0.5 transition-transform" />
)}
</button>
</div>
</div>
</div>
</div>
</section>
);
}

View File

@ -70,7 +70,7 @@ export function FeaturedSection() {
<div className="flex justify-between items-center mb-8">
<h2 className="text-3xl font-bold text-gray-900">
{city ? `${CityTitles[Number(city)]} ` : ''}
{category ? `${CategoryTitles[Number(category)]} Статьи` : 'Читайте сегодня'}
{category ? `${CategoryTitles[Number(category)]}` : 'Читайте сегодня'}
</h2>
<p className="font-bold text-gray-600">
Статьи {Math.min((currentPage - 1) * ARTICLES_PER_PAGE + 1, totalArticles)}

View File

@ -1,123 +1,157 @@
import { useState, useEffect } from 'react';
import { Author, AuthorFormData, User } from '../types/auth';
import { authorService } from '../services/authorService';
import { userService } from "../services/userService";
import { userService } from '../services/userService';
export function useAuthorManagement() {
const [authors, setAuthors] = useState<Author[]>([]);
const [users, setUsers] = useState<User[]>([]);
const [selectedAuthor, setSelectedAuthor] = useState<Author | null>(null);
const [selectedRole, setSelectedRole] = useState<string>('');
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
// --- Загрузка авторов и пользователей ---
useEffect(() => {
fetchAuthors(selectedRole, currentPage);
fetchUsers();
void fetchAuthors(selectedRole, currentPage);
void fetchUsers();
}, [selectedRole, currentPage]);
// --- Получение списка авторов ---
const fetchAuthors = async (role: string, page: number) => {
try {
setLoading(true);
const response = await authorService.getAuthors(role, page);
setAuthors(response.authors);
setTotalPages(response.totalPages);
setAuthors(response.authors ?? []); // защита от undefined
setTotalPages(response.totalPages ?? 1);
setError(null);
} catch (err) {
setError('Ошибка получения списка авторов');
console.error('Ошибка получения списка авторов:', err);
setError('Ошибка получения списка авторов');
} finally {
setLoading(false);
}
};
// --- Получение списка пользователей ---
const fetchUsers = async () => {
try {
setLoading(true);
const fetchedUsers = await userService.getUsers();
setUsers(fetchedUsers);
setUsers(fetchedUsers ?? []);
setError(null);
} catch (err) {
setError('Ошибка получения списка авторов');
console.error('Ошибка получения списка авторов:', err);
console.error('Ошибка получения списка пользователей:', err);
setError('Ошибка получения списка пользователей');
}
};
// --- Создание автора ---
const createAuthor = async (formData: AuthorFormData) => {
try {
setLoading(true);
const newAuthor = await authorService.createAuthor(formData);
setAuthors(prev => [...prev, newAuthor]);
setError(null);
} catch (err) {
console.error('Ошибка создания автора:', err);
setError('Ошибка создания автора');
throw err;
} finally {
setLoading(false);
}
};
const createAuthor = async (formData: AuthorFormData) => {
try {
const newAuthor = await authorService.createAuthor(formData);
setAuthors([...authors, newAuthor]);
setError(null);
} catch (err) {
setError('Ошибка создания автора');
throw err;
}
};
// --- Обновление автора ---
const updateAuthor = async (authorId: string, formData: AuthorFormData) => {
try {
setLoading(true);
const updatedAuthor = await authorService.updateAuthor(authorId, formData);
setAuthors(authors.map(author => author.id === authorId ? updatedAuthor : author));
setAuthors(prev =>
prev.map(author => (author.id === authorId ? updatedAuthor : author))
);
setError(null);
} catch (err) {
setError('Ошибка редакторования данных автора');
console.error('Ошибка редактирования автора:', err);
setError('Ошибка редактирования автора');
throw err;
} finally {
setLoading(false);
}
};
// --- Связывание пользователя с автором ---
const linkUser = async (authorId: string, userId: string) => {
try {
await authorService.linkUser(authorId, userId);
setError(null);
await fetchAuthors(selectedRole, currentPage);
} catch (err) {
console.error('Ошибка связывания пользователя с автором:', err);
setError('Ошибка связывания пользователя с автором');
throw err;
}
};
// --- Отвязка пользователя от автора ---
const unlinkUser = async (authorId: string) => {
try {
await authorService.unlinkUser(authorId);
setError(null);
await fetchAuthors(selectedRole, currentPage);
} catch (err) {
console.error('Ошибка отвязывания пользователя от автора:', err);
setError('Ошибка отвязывания пользователя от автора');
throw err;
}
};
// --- Перемещение автора вверх / вниз ---
const orderMoveUp = async (authorId: string) => {
await authorService.reorderAuthor(authorId, 'up');
try {
await authorService.reorderAuthor(authorId, 'up');
await fetchAuthors(selectedRole, currentPage);
} catch (err) {
console.error('Ошибка изменения порядка авторов (вверх):', err);
setError('Ошибка изменения порядка авторов');
}
};
const orderMoveDown = async (authorId: string) => {
await authorService.reorderAuthor(authorId, 'down');
try {
await authorService.reorderAuthor(authorId, 'down');
await fetchAuthors(selectedRole, currentPage);
} catch (err) {
console.error('Ошибка изменения порядка авторов (вниз):', err);
setError('Ошибка изменения порядка авторов');
}
};
// --- Активация / деактивация автора ---
const toggleActive = async (authorId: string, isActive: boolean) => {
try {
console.log(isActive);
await authorService.toggleActive(authorId, isActive);
setError(null);
await fetchAuthors(selectedRole, currentPage);
} catch (err) {
setError('Ошибка отвязывания пользователя от автора');
console.error('Ошибка переключения активности автора:', err);
setError('Ошибка изменения активности автора');
throw err;
}
};
// --- Удаление автора ---
const deleteAuthor = async (authorId: string) => {
try {
await authorService.deleteAuthor(authorId);
setAuthors(authors.filter(author => author.id !== authorId));
setAuthors(prev => prev.filter(author => author.id !== authorId));
if (selectedAuthor?.id === authorId) {
setSelectedAuthor(null);
}
setError(null);
} catch (err) {
console.error('Ошибка удаления автора:', err);
setError('Ошибка удаления автора');
throw err;
}
@ -144,6 +178,6 @@ export function useAuthorManagement() {
fetchAuthors,
fetchUsers,
setSelectedRole,
setCurrentPage
setCurrentPage,
};
}
}

View File

@ -3,11 +3,12 @@ import { useSearchParams } from 'react-router-dom';
import { Header } from '../components/Header';
import { FeaturedSection } from '../components/FeaturedSection';
import { AuthorsSection } from '../components/AuthorsSection';
import { BlogOwnerSectionFull } from '../components/BlogOwnerSectionFull';
import { BackgroundImages } from '../hooks/useBackgroundImage';
import { useScrollStore } from '../stores/scrollStore';
import { CategoryDescription, CategoryText, CategoryTitles } from '../types';
import { SEO } from '../components/SEO';
import { MasterBio } from "../components/MasterBio";
//import { MasterBio } from "../components/MasterBio";
export function HomePage() {
@ -94,7 +95,8 @@ export function HomePage() {
<div id="featured">
<FeaturedSection />
</div>
<MasterBio/>
{/* <MasterBio/> */}
<BlogOwnerSectionFull />
<AuthorsSection />
</div>