79 lines
2.5 KiB
TypeScript
79 lines
2.5 KiB
TypeScript
|
|
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>
|
||
|
|
)
|
||
|
|
}
|