Add notification settings
This commit is contained in:
@@ -12,7 +12,8 @@ import {
|
||||
import {
|
||||
User, Camera, Trophy, Target, CheckCircle, Flame,
|
||||
Loader2, MessageCircle, Link2, Link2Off, ExternalLink,
|
||||
Eye, EyeOff, Save, KeyRound, Shield
|
||||
Eye, EyeOff, Save, KeyRound, Shield, Bell, Sparkles,
|
||||
AlertTriangle, FileCheck
|
||||
} from 'lucide-react'
|
||||
|
||||
// Schemas
|
||||
@@ -51,6 +52,12 @@ export function ProfilePage() {
|
||||
const [isPolling, setIsPolling] = useState(false)
|
||||
const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
|
||||
// Notification settings state
|
||||
const [notifyEvents, setNotifyEvents] = useState(user?.notify_events ?? true)
|
||||
const [notifyDisputes, setNotifyDisputes] = useState(user?.notify_disputes ?? true)
|
||||
const [notifyModeration, setNotifyModeration] = useState(user?.notify_moderation ?? true)
|
||||
const [notificationUpdating, setNotificationUpdating] = useState<string | null>(null)
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
// Forms
|
||||
@@ -265,6 +272,29 @@ export function ProfilePage() {
|
||||
}
|
||||
}
|
||||
|
||||
// Update notification setting
|
||||
const handleNotificationToggle = async (
|
||||
setting: 'notify_events' | 'notify_disputes' | 'notify_moderation',
|
||||
currentValue: boolean,
|
||||
setValue: (value: boolean) => void
|
||||
) => {
|
||||
setNotificationUpdating(setting)
|
||||
const newValue = !currentValue
|
||||
setValue(newValue)
|
||||
|
||||
try {
|
||||
await usersApi.updateNotificationSettings({ [setting]: newValue })
|
||||
updateUser({ [setting]: newValue })
|
||||
toast.success('Настройки сохранены')
|
||||
} catch {
|
||||
// Revert on error
|
||||
setValue(currentValue)
|
||||
toast.error('Не удалось сохранить настройки')
|
||||
} finally {
|
||||
setNotificationUpdating(null)
|
||||
}
|
||||
}
|
||||
|
||||
const isLinked = !!user?.telegram_id
|
||||
const displayAvatar = avatarBlobUrl || user?.telegram_avatar_url
|
||||
|
||||
@@ -544,6 +574,109 @@ export function ProfilePage() {
|
||||
</form>
|
||||
)}
|
||||
</GlassCard>
|
||||
|
||||
{/* Notifications */}
|
||||
{isLinked && (
|
||||
<GlassCard>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 rounded-xl bg-neon-500/20 flex items-center justify-center">
|
||||
<Bell className="w-6 h-6 text-neon-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-white">Уведомления</h2>
|
||||
<p className="text-sm text-gray-400">Настройте типы уведомлений в Telegram</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Events toggle */}
|
||||
<button
|
||||
onClick={() => handleNotificationToggle('notify_events', notifyEvents, setNotifyEvents)}
|
||||
disabled={notificationUpdating !== null}
|
||||
className="w-full flex items-center justify-between p-4 bg-dark-700/50 rounded-xl border border-dark-600 hover:border-dark-500 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-yellow-500/20 flex items-center justify-center">
|
||||
<Sparkles className="w-5 h-5 text-yellow-400" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className="text-white font-medium">События</p>
|
||||
<p className="text-sm text-gray-400">Golden Hour, Jackpot, Double Risk и др.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`
|
||||
w-12 h-7 rounded-full transition-colors relative flex-shrink-0
|
||||
${notifyEvents ? 'bg-neon-500' : 'bg-dark-600'}
|
||||
${notificationUpdating === 'notify_events' ? 'opacity-50' : ''}
|
||||
`}>
|
||||
<div className={`
|
||||
absolute top-1 w-5 h-5 rounded-full bg-white shadow transition-transform
|
||||
${notifyEvents ? 'left-6' : 'left-1'}
|
||||
`} />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Disputes toggle */}
|
||||
<button
|
||||
onClick={() => handleNotificationToggle('notify_disputes', notifyDisputes, setNotifyDisputes)}
|
||||
disabled={notificationUpdating !== null}
|
||||
className="w-full flex items-center justify-between p-4 bg-dark-700/50 rounded-xl border border-dark-600 hover:border-dark-500 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-orange-500/20 flex items-center justify-center">
|
||||
<AlertTriangle className="w-5 h-5 text-orange-400" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className="text-white font-medium">Споры</p>
|
||||
<p className="text-sm text-gray-400">Оспаривания заданий и их решения</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`
|
||||
w-12 h-7 rounded-full transition-colors relative flex-shrink-0
|
||||
${notifyDisputes ? 'bg-neon-500' : 'bg-dark-600'}
|
||||
${notificationUpdating === 'notify_disputes' ? 'opacity-50' : ''}
|
||||
`}>
|
||||
<div className={`
|
||||
absolute top-1 w-5 h-5 rounded-full bg-white shadow transition-transform
|
||||
${notifyDisputes ? 'left-6' : 'left-1'}
|
||||
`} />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Moderation toggle */}
|
||||
<button
|
||||
onClick={() => handleNotificationToggle('notify_moderation', notifyModeration, setNotifyModeration)}
|
||||
disabled={notificationUpdating !== null}
|
||||
className="w-full flex items-center justify-between p-4 bg-dark-700/50 rounded-xl border border-dark-600 hover:border-dark-500 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-green-500/20 flex items-center justify-center">
|
||||
<FileCheck className="w-5 h-5 text-green-400" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className="text-white font-medium">Модерация</p>
|
||||
<p className="text-sm text-gray-400">Одобрение/отклонение игр и челленджей</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`
|
||||
w-12 h-7 rounded-full transition-colors relative flex-shrink-0
|
||||
${notifyModeration ? 'bg-neon-500' : 'bg-dark-600'}
|
||||
${notificationUpdating === 'notify_moderation' ? 'opacity-50' : ''}
|
||||
`}>
|
||||
<div className={`
|
||||
absolute top-1 w-5 h-5 rounded-full bg-white shadow transition-transform
|
||||
${notifyModeration ? 'left-6' : 'left-1'}
|
||||
`} />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Info about mandatory notifications */}
|
||||
<p className="text-xs text-gray-500 mt-4">
|
||||
Уведомления о старте/финише марафонов и коды безопасности нельзя отключить.
|
||||
</p>
|
||||
</div>
|
||||
</GlassCard>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user