Добавлены bookmarks. Улучшения интерфейса, логотип. Есть дублирующмй код Header в отдельной папке, разбитый на отдельные компоненты. Сейчас используется монолит.

This commit is contained in:
anibilag 2025-03-19 22:06:42 +03:00
parent 7fb5daf210
commit e4d5029e72
21 changed files with 197 additions and 122 deletions

30
.nginx/nginx.conf Normal file
View File

@ -0,0 +1,30 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/dist;
index index.html;
server_name russcult.anibilag.ru;
location / {
try_files $uri /index.html;
}
location /api/ {
proxy_pass http://192.168.1.67:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
error_page 404 /index.html;
}

BIN
public/images/Logo-1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
public/images/Logo-2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

View File

@ -13,6 +13,7 @@ import { Footer } from './components/Footer';
import { AuthGuard } from './components/AuthGuard'; import { AuthGuard } from './components/AuthGuard';
import { ImportArticlesPage } from "./pages/ImportArticlesPage"; import { ImportArticlesPage } from "./pages/ImportArticlesPage";
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000'; const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000';
function App() { function App() {

View File

@ -1,4 +1,4 @@
import { Clock, ThumbsUp, MapPin } from 'lucide-react'; import { Clock, ThumbsUp, MapPin, ThumbsDown } from 'lucide-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Article, CategoryTitles, CityTitles } from '../types'; import { Article, CategoryTitles, CityTitles } from '../types';
import MinutesWord from './MinutesWord'; import MinutesWord from './MinutesWord';
@ -9,30 +9,28 @@ interface ArticleCardProps {
} }
export function ArticleCard({ article, featured = false }: ArticleCardProps) { export function ArticleCard({ article, featured = false }: ArticleCardProps) {
const categoryName = CategoryTitles[article.categoryId];
return ( return (
<article className={`overflow-hidden rounded-lg shadow-lg transition-transform hover:scale-[1.02] ${ <article className={`overflow-hidden rounded-lg shadow-lg transition-transform hover:scale-[1.02] ${
featured ? 'col-span-2 row-span-2' : '' featured ? 'col-span-2 row-span-2' : ''
}`}> } flex flex-col`}>
<div className="relative pt-7"> <div className="relative pt-7">
<img <img
src={article.coverImage} src={article.coverImage}
alt={article.title} alt={article.title}
className={`w-full object-cover ${featured ? 'h-96' : 'h-64'}`} className={`w-full object-cover ${featured ? 'h-96' : 'h-64'}`}
/> />
<div className="absolute top-1 left-0 flex gap-2 z-10"> <div className="absolute bottom-0 left-0 w-full flex justify-between items-center z-10 px-4 py-2 bg-gradient-to-t from-black/70 to-transparent">
<span className="bg-gray-100 shadow px-3 py-1 rounded-full text-sm font-medium"> <span className="text-white text-sm md:text-base font-medium">
{categoryName} {CategoryTitles[article.categoryId]}
</span> </span>
<span className="bg-gray-100 shadow px-3 py-1 rounded-full text-sm font-medium flex items-center"> <span className="text-white text-sm md:text-base font-medium flex items-center">
<MapPin size={14} className="mr-1" /> <MapPin size={14} className="mr-1 text-white" />
{CityTitles[article.cityId]} {CityTitles[article.cityId]}
</span> </span>
</div> </div>
</div> </div>
<div className="p-6 flex flex-col flex-grow">
<div className="p-6">
<div className="flex items-center gap-4 mb-4"> <div className="flex items-center gap-4 mb-4">
<img <img
src={article.author.avatarUrl} src={article.author.avatarUrl}
@ -58,7 +56,7 @@ export function ArticleCard({ article, featured = false }: ArticleCardProps) {
</h2> </h2>
<p className="text-gray-600 line-clamp-2">{article.excerpt}</p> <p className="text-gray-600 line-clamp-2">{article.excerpt}</p>
<div className="mt-4 flex items-center justify-between"> <div className="p-4 mt-auto flex items-center justify-between">
<Link <Link
to={`/article/${article.id}`} to={`/article/${article.id}`}
className="text-blue-600 font-medium hover:text-blue-800" className="text-blue-600 font-medium hover:text-blue-800"
@ -68,6 +66,10 @@ export function ArticleCard({ article, featured = false }: ArticleCardProps) {
<div className="flex items-center text-gray-500"> <div className="flex items-center text-gray-500">
<ThumbsUp size={16} className="mr-1" /> <ThumbsUp size={16} className="mr-1" />
<span>{article.likes}</span> <span>{article.likes}</span>
<span className="ml-2">
<ThumbsDown size={16} className="mr-1" />
</span>
<span>{article.dislikes}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -36,7 +36,7 @@ export function AuthGuard({ children, requiredPermissions }: AuthGuardProps) {
<div className="text-center"> <div className="text-center">
<h2 className="text-2xl font-bold text-gray-900 mb-4">Access Denied</h2> <h2 className="text-2xl font-bold text-gray-900 mb-4">Access Denied</h2>
<p className="text-gray-600"> <p className="text-gray-600">
У вас нет прав на {action} статьи в {categoryId} категории. У вас нет прав на {action} статьи в разделе {categoryId}.
</p> </p>
</div> </div>
</div> </div>

View File

@ -27,14 +27,14 @@ export function AuthorsSection() {
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-3xl font-bold text-gray-900">Наши авторы</h2> <h2 className="text-3xl font-bold text-gray-900">Наши авторы</h2>
<p className="mt-4 text-lg text-gray-600 max-w-2xl mx-auto"> <p className="mt-4 text-lg text-gray-600 max-w-2xl mx-auto">
Познакомьтесь с талантливыми писателями и экспертами, работающими для вас Познакомьтесь с талантливыми писателями и экспертами, работающими для Вас
</p> </p>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{authors.map((author) => ( {authors.map((author) => (
<div key={author.id} className="bg-white rounded-lg shadow-md overflow-hidden transition-transform hover:scale-[1.02]"> <div key={author.id} className="bg-white rounded-lg shadow-md overflow-hidden transition-transform hover:scale-[1.02] h-full">
<div className="p-6"> <div className="p-6 flex flex-col flex-grow min-h-[200px] h-full">
<div className="flex items-center mb-4"> <div className="flex items-center mb-4">
<img <img
src={author.avatarUrl} src={author.avatarUrl}
@ -58,11 +58,11 @@ export function AuthorsSection() {
</div> </div>
<p className="text-gray-600">{author.bio}</p> <p className="text-gray-600">{author.bio}</p>
<div className="mt-6 pt-6 border-t border-gray-100"> <div className="mt-auto pt-6 border-t border-gray-100">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="text-sm text-gray-500"> <span className="text-sm text-gray-500">
{author.articlesCount} статей {author.articlesCount} статей
</span> </span>
<Link <Link
to={`/search?author=${author.id}`} to={`/search?author=${author.id}`}
className="text-blue-600 hover:text-blue-800 text-sm font-medium" className="text-blue-600 hover:text-blue-800 text-sm font-medium"

View File

@ -1,4 +1,3 @@
import React from 'react';
import { Bookmark } from 'lucide-react'; import { Bookmark } from 'lucide-react';
import { useBookmarkStore } from '../stores/bookmarkStore'; import { useBookmarkStore } from '../stores/bookmarkStore';
import { Article } from '../types'; import { Article } from '../types';

View File

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

View File

@ -1,7 +1,9 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Menu, Search, X, ChevronDown } from 'lucide-react'; import { Logo } from "./Header/Logo";
import { Menu, Search, X, ChevronDown, Bookmark } from 'lucide-react';
import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Link, useLocation, useNavigate } from 'react-router-dom';
import { CategoryIds, CategoryTitles, CityIds, CityTitles } from '../types'; import { CategoryIds, CategoryTitles, CityIds, CityTitles } from '../types';
import { useBookmarkStore } from "../stores/bookmarkStore";
export function Header() { export function Header() {
@ -12,6 +14,7 @@ export function Header() {
const searchParams = new URLSearchParams(location.search); const searchParams = new URLSearchParams(location.search);
const currentCategory = searchParams.get('category'); const currentCategory = searchParams.get('category');
const currentCity = searchParams.get('city'); const currentCity = searchParams.get('city');
const { bookmarks } = useBookmarkStore();
const handleCityChange = (event: React.ChangeEvent<HTMLSelectElement>) => { const handleCityChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const city = event.target.value; const city = event.target.value;
@ -50,9 +53,9 @@ export function Header() {
> >
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />} {isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button> </button>
<Link to="/" className="ml-2 flex items-center space-x-2"> <div className="ml-2">
<h1 className="text-2xl font-bold text-gray-900">Культура</h1> <Logo />
</Link> </div>
</div> </div>
<nav className="hidden lg:flex items-center space-x-8"> <nav className="hidden lg:flex items-center space-x-8">
@ -80,26 +83,16 @@ export function Header() {
<div className="h-6 w-px bg-gray-200" /> <div className="h-6 w-px bg-gray-200" />
{/* Menu */}
<div className="flex items-center space-x-4 overflow-x-auto"> <div className="flex items-center space-x-4 overflow-x-auto">
<span className="text-sm font-medium text-gray-500"></span>
<Link
to={currentCity ? `/?city=${currentCity}` : '/'}
className={`px-3 py-2 text-sm font-medium transition-colors whitespace-nowrap ${
!currentCategory
? 'text-blue-600 hover:text-blue-800'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Все категории
</Link>
{CategoryIds.map((category) => ( {CategoryIds.map((category) => (
<Link <Link
key={category} key={category}
to={`/?category=${category}${currentCity ? `&city=${currentCity}` : ''}`} to={`/?category=${category}${currentCity ? `&city=${currentCity}` : ''}`}
className={`px-3 py-2 text-sm font-medium transition-colors whitespace-nowrap ${ className={`px-3 py-2 font-medium transition-colors whitespace-nowrap ${
Number(currentCategory) === category Number(currentCategory) === category
? 'text-blue-600 hover:text-blue-800' ? 'text-base md:text-lg lg:text-xl font-semibold text-blue-600 hover:text-blue-800 underline'
: 'text-gray-600 hover:text-gray-900' : 'text-sm md:text-base lg:text-lg font-medium text-gray-600 hover:text-gray-900'
}`} }`}
> >
{CategoryTitles[category]} {CategoryTitles[category]}
@ -112,7 +105,7 @@ export function Header() {
<form onSubmit={handleSearch} className="relative"> <form onSubmit={handleSearch} className="relative">
<input <input
type="text" type="text"
placeholder="Search..." placeholder="Поиск..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={handleSearchKeyPress} onKeyDown={handleSearchKeyPress}
@ -125,6 +118,17 @@ export function Header() {
<Search size={18} /> <Search size={18} />
</button> </button>
</form> </form>
<Link
to="/bookmarks"
className="relative p-2 rounded-full hover:bg-gray-100 transition-colors"
>
<Bookmark size={20} className="text-gray-500" />
{bookmarks.length > 0 && (
<span className="absolute -top-1 -right-1 bg-blue-600 text-white text-xs w-4 h-4 flex items-center justify-center rounded-full">
{bookmarks.length}
</span>
)}
</Link>
</div> </div>
</div> </div>
</div> </div>
@ -137,7 +141,7 @@ export function Header() {
> >
<div className="px-2 pt-2 pb-3 space-y-1"> <div className="px-2 pt-2 pb-3 space-y-1">
<div className="px-3 py-2"> <div className="px-3 py-2">
<h3 className="text-sm font-medium text-gray-500 mb-2">City</h3> <h3 className="text-sm font-medium text-gray-500 mb-2">Столицы</h3>
<select <select
value={currentCity || ''} value={currentCity || ''}
onChange={handleCityChange} onChange={handleCityChange}
@ -153,18 +157,8 @@ export function Header() {
</div> </div>
<div className="px-3 py-2"> <div className="px-3 py-2">
<h3 className="text-sm font-medium text-gray-500 mb-2">Categories</h3> <h3 className="text-sm font-medium text-gray-500 mb-2">Разделы</h3>
<div className="space-y-1"> <div className="space-y-0">
<Link
to={currentCity ? `/?city=${currentCity}` : '/'}
className={`block px-3 py-2 rounded-md text-base font-medium ${
!currentCategory
? 'bg-blue-50 text-blue-600'
: 'text-gray-600 hover:bg-gray-50'
}`}
>
Все категории
</Link>
{CategoryIds.map((category) => ( {CategoryIds.map((category) => (
<Link <Link
key={category} key={category}

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ChevronDown } from 'lucide-react'; import { ChevronDown } from 'lucide-react';
import { City } from '../../types'; import { CityTitles } from '../../types';
interface CitySelectorProps { interface CitySelectorProps {
cities: City[]; cities: number[];
currentCity: string | null; currentCity: string | null;
} }
@ -34,7 +34,7 @@ export function CitySelector({ cities, currentCity }: CitySelectorProps) {
<option value="">All Cities</option> <option value="">All Cities</option>
{cities.map((city) => ( {cities.map((city) => (
<option key={city} value={city}> <option key={city} value={city}>
{city} {CityTitles[city]}
</option> </option>
))} ))}
</select> </select>

View File

@ -1,11 +1,14 @@
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Palette } from 'lucide-react';
export function Logo() { export function Logo() {
return ( return (
<Link to="/" className="flex items-center space-x-2 text-gray-900"> <Link to="/" className="flex items-center space-x-4 text-gray-900 min-w-0">
<Palette size={32} className="text-blue-600" /> <img
<span className="text-2xl font-bold">CultureScope</span> src="/images/Logo-2.webp"
</Link> alt="Культура двух столиц"
className="h-10 w-10 md:h-12 md:w-12 flex-shrink-0 transition-transform duration-300 hover:scale-150"
/>
<span className="invisible">_____</span>
</Link>
); );
} }

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Category, City } from '../../types'; import { CityTitles, CategoryTitles } from '../../types';
interface MobileMenuProps { interface MobileMenuProps {
isOpen: boolean; isOpen: boolean;
categories: Category[]; categories: number[];
cities: City[]; cities: number[];
currentCategory: string | null; currentCategory: string | null;
currentCity: string | null; currentCity: string | null;
onCityChange: (event: React.ChangeEvent<HTMLSelectElement>) => void; onCityChange: (event: React.ChangeEvent<HTMLSelectElement>) => void;
@ -34,7 +34,7 @@ export function MobileMenu({
<option value="">All Cities</option> <option value="">All Cities</option>
{cities.map((city) => ( {cities.map((city) => (
<option key={city} value={city}> <option key={city} value={city}>
{city} {CityTitles[city]}
</option> </option>
))} ))}
</select> </select>
@ -58,12 +58,12 @@ export function MobileMenu({
key={category} key={category}
to={`/?category=${category}${currentCity ? `&city=${currentCity}` : ''}`} to={`/?category=${category}${currentCity ? `&city=${currentCity}` : ''}`}
className={`block px-3 py-2 rounded-md text-base font-medium ${ className={`block px-3 py-2 rounded-md text-base font-medium ${
currentCategory === category Number(currentCategory) === category
? 'bg-blue-50 text-blue-600' ? 'bg-blue-50 text-blue-600'
: 'text-gray-600 hover:bg-gray-50' : 'text-gray-600 hover:bg-gray-50'
}`} }`}
> >
{category} {CategoryTitles[category]}
</Link> </Link>
))} ))}
</div> </div>

View File

@ -1,8 +1,8 @@
import { Link, useLocation } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Category } from '../../types'; import { CategoryTitles } from '../../types';
interface NavigationProps { interface NavigationProps {
categories: Category[]; categories: number[];
currentCategory: string | null; currentCategory: string | null;
currentCity: string | null; currentCity: string | null;
} }
@ -26,12 +26,12 @@ export function Navigation({ categories, currentCategory, currentCity }: Navigat
key={category} key={category}
to={`/?category=${category}${currentCity ? `&city=${currentCity}` : ''}`} to={`/?category=${category}${currentCity ? `&city=${currentCity}` : ''}`}
className={`px-3 py-2 text-sm font-medium transition-colors whitespace-nowrap ${ className={`px-3 py-2 text-sm font-medium transition-colors whitespace-nowrap ${
currentCategory === category Number(currentCategory) === category
? 'text-blue-600 hover:text-blue-800' ? 'text-blue-600 hover:text-blue-800'
: 'text-gray-600 hover:text-gray-900' : 'text-gray-600 hover:text-gray-900'
}`} }`}
> >
{category} {CategoryTitles[category]}
</Link> </Link>
))} ))}
</div> </div>

View File

@ -1,16 +1,14 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Menu, X, Bookmark } from 'lucide-react'; import { Menu, X, Bookmark } from 'lucide-react';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { Category, City } from '../../types';
import { Logo } from './Logo'; import { Logo } from './Logo';
import { SearchBar } from './SearchBar'; import { SearchBar } from './SearchBar';
import { Navigation } from './Navigation'; import { Navigation } from './Navigation';
import { CitySelector } from './CitySelector'; import { CitySelector } from './CitySelector';
import { MobileMenu } from './MobileMenu'; import { MobileMenu } from './MobileMenu';
import { CategoryIds, CityIds } from '../../types';
import { useBookmarkStore } from '../../stores/bookmarkStore'; import { useBookmarkStore } from '../../stores/bookmarkStore';
const categories: Category[] = ['Film', 'Theater', 'Music', 'Sports', 'Art', 'Legends', 'Anniversaries', 'Memory'];
const cities: City[] = ['New York', 'London'];
export function Header() { export function Header() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
@ -52,13 +50,13 @@ export function Header() {
<nav className="hidden lg:flex items-center space-x-8"> <nav className="hidden lg:flex items-center space-x-8">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<span className="text-sm font-medium text-gray-500"></span> <span className="text-sm font-medium text-gray-500"></span>
<CitySelector cities={cities} currentCity={currentCity} /> <CitySelector cities={CityIds} currentCity={currentCity} />
</div> </div>
<div className="h-6 w-px bg-gray-200" /> <div className="h-6 w-px bg-gray-200" />
<Navigation <Navigation
categories={categories} categories={CategoryIds}
currentCategory={currentCategory} currentCategory={currentCategory}
currentCity={currentCity} currentCity={currentCity}
/> />
@ -83,8 +81,8 @@ export function Header() {
<MobileMenu <MobileMenu
isOpen={isMobileMenuOpen} isOpen={isMobileMenuOpen}
categories={categories} categories={CategoryIds}
cities={cities} cities={CityIds}
currentCategory={currentCategory} currentCategory={currentCategory}
currentCity={currentCity} currentCity={currentCity}
onCityChange={handleCityChange} onCityChange={handleCityChange}

View File

@ -0,0 +1,46 @@
import { useState } from 'react';
import { Share2 } from 'lucide-react';
interface ShareButtonProps {
title: string;
url: string;
className?: string;
}
export function ShareButton({ title, url, className = '' }: ShareButtonProps) {
const [showTooltip, setShowTooltip] = useState(false);
const handleShare = async () => {
try {
if (navigator.share) {
await navigator.share({
title,
url
});
} else {
await navigator.clipboard.writeText(url);
setShowTooltip(true);
setTimeout(() => setShowTooltip(false), 2000);
}
} catch (error) {
console.error('Error sharing:', error);
}
};
return (
<div className="relative">
<button
onClick={handleShare}
className={`p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100 ${className}`}
aria-label="Share article"
>
<Share2 size={20} />
</button>
{showTooltip && (
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded whitespace-nowrap">
Ссылка скопированна!
</div>
)}
</div>
);
}

View File

@ -1,11 +1,13 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom'; import { useParams, Link } from 'react-router-dom';
import { ArrowLeft, Clock, Share2, Bookmark } from 'lucide-react'; import { ArrowLeft, Clock } from 'lucide-react';
import { Header } from '../components/Header'; import { Header } from '../components/Header';
import { ReactionButtons } from '../components/ReactionButtons'; import { ReactionButtons } from '../components/ReactionButtons';
import { PhotoGallery } from '../components/PhotoGallery'; import { PhotoGallery } from '../components/PhotoGallery';
import { Article, CategoryTitles } from '../types'; import { Article, CategoryTitles } from '../types';
import { ArticleContent } from '../components/ArticleContent'; import { ArticleContent } from '../components/ArticleContent';
import { ShareButton } from '../components/ShareButton';
import { BookmarkButton } from '../components/BookmarkButton';
import MinutesWord from '../components/MinutesWord'; import MinutesWord from '../components/MinutesWord';
import axios from "axios"; import axios from "axios";
import api from "../utils/api"; import api from "../utils/api";
@ -122,12 +124,8 @@ export function ArticlePage() {
{CategoryTitles[articleData.categoryId]} {CategoryTitles[articleData.categoryId]}
</span> </span>
<div className="flex-1" /> <div className="flex-1" />
<button className="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100"> <ShareButton title={articleData.title} url={window.location.href} />
<Share2 size={20} /> <BookmarkButton article={articleData} />
</button>
<button className="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100">
<Bookmark size={20} />
</button>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@ export function BookmarksPage() {
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12"> <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="flex items-center space-x-4 mb-8"> <div className="flex items-center space-x-4 mb-8">
<Bookmark size={24} className="text-blue-600" /> <Bookmark size={24} className="text-blue-600" />
<h1 className="text-3xl font-bold text-gray-900">Your Bookmarks</h1> <h1 className="text-3xl font-bold text-gray-900">Мои закладки</h1>
</div> </div>
{bookmarks.length > 0 ? ( {bookmarks.length > 0 ? (

View File

@ -30,49 +30,50 @@ export function HomePage() {
const { main, sub, description } = getHeroTitle(); const { main, sub, description } = getHeroTitle();
return ( return (
<>
<div className="min-h-screen bg-cover bg-center bg-fixed bg-white/95" style={{ backgroundImage: `url('/images/main-bg.jpg')` }}> <div className="min-h-screen bg-cover bg-center bg-fixed bg-white/95" style={{ backgroundImage: `url('/images/main-bg.jpg')` }}>
<Header /> <Header />
<main> <main>
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0"> <div className="px-4 py-6 sm:px-0">
<div className="relative">
<div className="absolute inset-0">
<div
className="w-full h-full bg-cover bg-center transition-all duration-500 ease-in-out"
style={{
backgroundImage: `url("${backgroundImage}")`,
}}
>
<div className="absolute inset-0 bg-gradient-to-r from-gray-900/90 to-gray-900/60 backdrop-blur-sm" />
</div>
</div>
<div className="relative"> <div className="relative">
<div className="text-center max-w-4xl mx-auto py-24 px-4 sm:py-32 sm:px-6 lg:px-8">
<h1 className="text-4xl font-extrabold tracking-tight text-white sm:text-5xl md:text-6xl"> <div className="absolute inset-0">
<span className="block mb-2">{main}</span> <div
<span className="block text-blue-400">{sub}</span> className="w-full h-full bg-cover bg-center transition-all duration-500 ease-in-out"
</h1> style={{
<p className="mt-6 text-xl text-gray-200 max-w-2xl mx-auto"> backgroundImage: `url("${backgroundImage}")`,
{description} }}
</p> >
<div className="mt-8 flex justify-center space-x-4"> <div className="absolute inset-0 bg-gradient-to-r from-gray-900/90 to-gray-900/60 backdrop-blur-sm" />
</div> </div>
</div> </div>
</div>
</div>
<div className="relative bg-white/95"> <div className="relative">
<div id="featured"> <div className="text-center max-w-4xl mx-auto py-24 px-4 sm:py-32 sm:px-6 lg:px-8">
<FeaturedSection /> <h1 className="text-4xl font-extrabold tracking-tight text-white sm:text-5xl md:text-6xl">
</div> <span className="block mb-2">{main}</span>
<AuthorsSection /> <span className="block text-blue-400">{sub}</span>
</div> </h1>
<p className="mt-6 text-xl text-gray-200 max-w-2xl mx-auto">
{description}
</p>
<div className="mt-8 flex justify-center space-x-4">
</div>
</div>
</div>
</div>
<div className="relative bg-white/95">
<div id="featured">
<FeaturedSection />
</div>
<AuthorsSection />
</div>
</div>
</div> </div>
</div> </main>
</main>
</div> </div>
</>
); );
} }

View File

@ -6,6 +6,9 @@ export default defineConfig(({ mode }) => {
return { return {
plugins: [react()], plugins: [react()],
esbuild: {
minifyIdentifiers: false, // Отключаем переименование переменных
},
optimizeDeps: { optimizeDeps: {
exclude: ['@prisma/client', 'lucide-react'], exclude: ['@prisma/client', 'lucide-react'],
}, },