receipt notifications

This commit is contained in:
2026-04-15 23:53:34 +03:00
parent 8efa6f28ad
commit 08a5c2e560
@@ -0,0 +1,76 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { motion, AnimatePresence } from 'motion/react'
import { QRCodeSVG } from 'qrcode.react'
import { IconCopyFilled, IconCheckFilled } from '@tabler/icons-react'
import { useStore } from '../store'
export default function ReceiptNotification() {
const { t } = useTranslation()
const pending = useStore(s => s.pendingReceipt)
const dismiss = useStore(s => s.dismissReceipt)
const claim = useStore(s => s.claimPendingReceipt)
const [claiming, setClaiming] = useState(false)
const [copied, setCopied] = useState(false)
const handleClaim = async () => {
if (!pending) return
setClaiming(true)
await claim(pending.receipt.receipt_id)
setClaiming(false)
}
const copyUrl = () => {
if (!pending) return
navigator.clipboard.writeText(pending.receipt_url).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 1500)
})
}
return (
<AnimatePresence>
{pending && (
<motion.div className="receipt-notification"
role="alertdialog" aria-label={t('receipt.claimTitle')}
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 20, scale: 0.95 }}
transition={{ duration: 0.2, ease: [0.25, 0.1, 0.25, 1] }}>
<h4 className="receipt-title">{t('receipt.claimTitle')}</h4>
<div className="receipt-info">
<p>{t('receipt.solvedOn', { instance: pending.receipt.instance_url })}</p>
<p className="receipt-detail">{t('receipt.time', { time: pending.receipt.solve_time_secs })} - {t('receipt.hints', { hints: pending.receipt.hints_used })}</p>
<p className="receipt-detail">{t('receipt.date', { date: pending.receipt.solve_date })}</p>
{pending.receipt.room_code && (
<p className="receipt-detail">{t('receipt.room', { code: pending.receipt.room_code, mode: pending.receipt.mode })}</p>
)}
{pending.receipt.players && pending.receipt.players.length > 0 && (
<p className="receipt-detail">{t('receipt.with', { players: pending.receipt.players.join(', ') })}</p>
)}
</div>
<div className="receipt-qr">
<QRCodeSVG value={pending.receipt_url} size={96} bgColor="transparent" fgColor="currentColor" aria-label={t('receipt.qrLabel')} />
</div>
<div className="receipt-url-row">
<span className="receipt-url" title={pending.receipt_url}>{pending.receipt_url}</span>
<button className="btn btn-sm btn-ghost receipt-copy-btn" onClick={copyUrl} aria-label={t('common.copyLink')}>
{copied ? <span role="status"><IconCheckFilled size={12} aria-hidden="true" /><span className="visually-hidden">{t('common.copied')}</span></span> : <IconCopyFilled size={12} aria-hidden="true" />}
</button>
</div>
<div className="receipt-actions">
<button className="btn btn-sm btn-accent" onClick={handleClaim} disabled={claiming}>
{claiming ? t('receipt.claiming') : t('receipt.claim')}
</button>
<button className="btn btn-sm" onClick={() => dismiss(pending.receipt.receipt_id)}>
{t('common.dismiss')}
</button>
</div>
</motion.div>
)}
</AnimatePresence>
)
}