leader board + setting
This commit is contained in:
101
src/pages/settings/NotificationsCard.tsx
Normal file
101
src/pages/settings/NotificationsCard.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { useState } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface NotifPrefs {
|
||||
daily: boolean
|
||||
streak: boolean
|
||||
weekly: boolean
|
||||
leaderboard: boolean
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'settings_notifications'
|
||||
const TIME_KEY = 'settings_notif_time'
|
||||
|
||||
const DEFAULT_PREFS: NotifPrefs = { daily: true, streak: true, weekly: false, leaderboard: true }
|
||||
|
||||
function loadPrefs(): NotifPrefs {
|
||||
try {
|
||||
return { ...DEFAULT_PREFS, ...JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}') }
|
||||
} catch {
|
||||
return DEFAULT_PREFS
|
||||
}
|
||||
}
|
||||
|
||||
interface ToggleProps {
|
||||
checked: boolean
|
||||
onChange: (v: boolean) => void
|
||||
}
|
||||
|
||||
function Toggle({ checked, onChange }: ToggleProps) {
|
||||
return (
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
onClick={() => onChange(!checked)}
|
||||
className={cn(
|
||||
'relative w-11 h-6 rounded-full transition-colors flex-shrink-0',
|
||||
checked ? 'bg-blue-600' : 'bg-slate-200',
|
||||
)}
|
||||
>
|
||||
<span className={cn(
|
||||
'absolute top-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform',
|
||||
checked ? 'translate-x-5' : 'translate-x-0.5',
|
||||
)} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export function NotificationsCard() {
|
||||
const [prefs, setPrefs] = useState<NotifPrefs>(loadPrefs)
|
||||
const [time, setTime] = useState(() => localStorage.getItem(TIME_KEY) ?? '20:00')
|
||||
|
||||
function toggle(key: keyof NotifPrefs) {
|
||||
const next = { ...prefs, [key]: !prefs[key] }
|
||||
setPrefs(next)
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(next))
|
||||
}
|
||||
|
||||
function saveTime(v: string) {
|
||||
setTime(v)
|
||||
localStorage.setItem(TIME_KEY, v)
|
||||
}
|
||||
|
||||
const items: { key: keyof NotifPrefs; label: string; desc: string }[] = [
|
||||
{ key: 'daily', label: 'Nhắc nhở hàng ngày', desc: 'Tùy chỉnh thời gian học mỗi ngày' },
|
||||
{ key: 'streak', label: 'Cảnh báo chuỗi học tập', desc: 'Không bao giờ bỏ lỡ Streak của bạn' },
|
||||
{ key: 'weekly', label: 'Nhắc nhở mục tiêu tuần', desc: 'Theo dõi tiến độ học tập hàng tuần' },
|
||||
{ key: 'leaderboard', label: 'Cập nhật bảng xếp hạng', desc: 'Biết ngay khi ai đó vượt qua bạn' },
|
||||
]
|
||||
|
||||
return (
|
||||
<section className="col-span-12 bg-white rounded-xl p-6 shadow-sm">
|
||||
<h2 className="text-lg font-bold mb-6 flex items-center gap-2 text-slate-800">
|
||||
<span className="material-symbols-outlined text-blue-600" style={{ fontSize: 22 }}>notifications_active</span>
|
||||
Cài đặt thông báo
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-10 gap-y-6">
|
||||
{items.map((item) => (
|
||||
<div key={item.key} className="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800 text-sm">{item.label}</p>
|
||||
<p className="text-xs text-slate-400 mt-0.5">{item.desc}</p>
|
||||
{item.key === 'daily' && prefs.daily && (
|
||||
<div className="mt-2 inline-flex items-center gap-1.5 px-2.5 py-1 bg-slate-100 rounded-lg text-xs font-semibold text-slate-600">
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 13 }}>schedule</span>
|
||||
<input
|
||||
type="time"
|
||||
value={time}
|
||||
onChange={(e) => saveTime(e.target.value)}
|
||||
className="bg-transparent outline-none text-xs font-semibold w-16"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Toggle checked={prefs[item.key]} onChange={() => toggle(item.key)} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user