Add static pages and styles
This commit is contained in:
78
frontend/src/components/AnnouncementBanner.tsx
Normal file
78
frontend/src/components/AnnouncementBanner.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { contentApi } from '@/api/admin'
|
||||
import { Megaphone, X } from 'lucide-react'
|
||||
|
||||
const STORAGE_KEY = 'announcement_dismissed'
|
||||
|
||||
export function AnnouncementBanner() {
|
||||
const [content, setContent] = useState<string | null>(null)
|
||||
const [title, setTitle] = useState<string | null>(null)
|
||||
const [updatedAt, setUpdatedAt] = useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const loadAnnouncement = async () => {
|
||||
try {
|
||||
const data = await contentApi.getPublicContent('announcement')
|
||||
// Check if this announcement was already dismissed (by updated_at)
|
||||
const dismissedAt = localStorage.getItem(STORAGE_KEY)
|
||||
if (dismissedAt === data.updated_at) {
|
||||
setContent(null)
|
||||
} else {
|
||||
setContent(data.content)
|
||||
setTitle(data.title)
|
||||
setUpdatedAt(data.updated_at)
|
||||
}
|
||||
} catch {
|
||||
// No announcement or error - don't show
|
||||
setContent(null)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadAnnouncement()
|
||||
}, [])
|
||||
|
||||
const handleDismiss = () => {
|
||||
if (updatedAt) {
|
||||
// Store the updated_at to know which announcement was dismissed
|
||||
// When admin updates announcement, updated_at changes and banner shows again
|
||||
localStorage.setItem(STORAGE_KEY, updatedAt)
|
||||
setContent(null)
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading || !content) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative rounded-xl overflow-hidden bg-gradient-to-r from-accent-500/20 via-purple-500/20 to-pink-500/20 border border-accent-500/30">
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={handleDismiss}
|
||||
className="absolute top-3 right-3 p-1.5 text-white bg-dark-700/70 hover:bg-dark-600 rounded-lg transition-colors z-10"
|
||||
title="Скрыть"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-4 pr-12 flex items-start gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-accent-500/20 border border-accent-500/30 flex items-center justify-center flex-shrink-0">
|
||||
<Megaphone className="w-5 h-5 text-accent-400" />
|
||||
</div>
|
||||
<div>
|
||||
{title && (
|
||||
<h3 className="font-semibold text-white mb-1">{title}</h3>
|
||||
)}
|
||||
<div
|
||||
className="text-sm text-gray-300"
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user