Donate stranka: PAY by square QR s vyberom sumy
Linka Na kavu v hlavicke lobby vedie na /donate. QR sa generuje dynamicky (balicek bysquare) pre presety 1/2/5 EUR alebo vlastnu sumu; IBAN sa da skopirovat do schranky. Navstevy loguje existujuci pageview beacon. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Generated
+35
@@ -8,6 +8,7 @@
|
|||||||
"name": "bridzik-frontend",
|
"name": "bridzik-frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bysquare": "^4.0.0",
|
||||||
"qrcode.react": "^4.1.0",
|
"qrcode.react": "^4.1.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@@ -3152,6 +3153,22 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/bysquare": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bysquare/-/bysquare-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-V1gGJFn39TY9XnvQDbzMTgGau3wCatsH5+XNSC2wXGgYijIVUD0gkZGalNIub28R+XhM/zUvge+8ccMPJ2+bZw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lzma1": "0.2.0",
|
||||||
|
"validator": "^13.15.26"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"bysquare": "lib/cli.js"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/xseman"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/call-bind": {
|
"node_modules/call-bind": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
|
||||||
@@ -5096,6 +5113,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lzma1": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lzma1/-/lzma1-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-JkPtHHKANh2je4Gp2kTjtobUXf7+zzfGHYm9PPRnx+Mr4KhAqoyb8dwtCExGaxkJl5upwWStdkIJWpn28XWiHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/xseman"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
@@ -6952,6 +6978,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/validator": {
|
||||||
|
"version": "13.15.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz",
|
||||||
|
"integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/victory-vendor": {
|
"node_modules/victory-vendor": {
|
||||||
"version": "36.9.2",
|
"version": "36.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bysquare": "^4.0.0",
|
||||||
"qrcode.react": "^4.1.0",
|
"qrcode.react": "^4.1.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Lobby from './pages/Lobby';
|
|||||||
import GameTable from './pages/GameTable';
|
import GameTable from './pages/GameTable';
|
||||||
import Auth from './pages/Auth';
|
import Auth from './pages/Auth';
|
||||||
import History from './pages/History';
|
import History from './pages/History';
|
||||||
|
import Donate from './pages/Donate';
|
||||||
|
|
||||||
// Admin pulls in recharts — lazy-load it so players never download that chunk.
|
// Admin pulls in recharts — lazy-load it so players never download that chunk.
|
||||||
const AdminLayout = lazy(() => import('./pages/admin/AdminLayout'));
|
const AdminLayout = lazy(() => import('./pages/admin/AdminLayout'));
|
||||||
@@ -107,6 +108,7 @@ function AppInner() {
|
|||||||
<Route path="/auth" element={<Auth />} />
|
<Route path="/auth" element={<Auth />} />
|
||||||
<Route path="/" element={<GameList />} />
|
<Route path="/" element={<GameList />} />
|
||||||
<Route path="/history" element={<History />} />
|
<Route path="/history" element={<History />} />
|
||||||
|
<Route path="/donate" element={<Donate />} />
|
||||||
<Route path="/lobby/:gid" element={<Lobby />} />
|
<Route path="/lobby/:gid" element={<Lobby />} />
|
||||||
<Route path="/game/:gid" element={<GameTable />} />
|
<Route path="/game/:gid" element={<GameTable />} />
|
||||||
<Route path="/admin" element={<AdminLayout />}>
|
<Route path="/admin" element={<AdminLayout />}>
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
|
import { encode, PaymentOptions, CurrencyCode } from 'bysquare/pay';
|
||||||
|
|
||||||
|
const IBAN = 'SK2583300000002803542822';
|
||||||
|
const AMOUNTS = [1, 2, 5];
|
||||||
|
const PAYMENT_NOTE = 'Bridzik';
|
||||||
|
const BENEFICIARY = 'Liptak Timotej';
|
||||||
|
|
||||||
|
export default function Donate() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [amount, setAmount] = useState(2);
|
||||||
|
const [custom, setCustom] = useState('');
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
// Vlastna suma ma prednost pred presetom; akceptuje ciarku aj bodku.
|
||||||
|
const customParsed = parseFloat(custom.replace(',', '.'));
|
||||||
|
const customValid = custom !== '' && !isNaN(customParsed) && customParsed > 0;
|
||||||
|
const effectiveAmount = customValid ? Math.round(customParsed * 100) / 100 : amount;
|
||||||
|
|
||||||
|
const qrValue = useMemo(
|
||||||
|
() =>
|
||||||
|
encode({
|
||||||
|
payments: [
|
||||||
|
{
|
||||||
|
type: PaymentOptions.PaymentOrder,
|
||||||
|
amount: effectiveAmount,
|
||||||
|
currencyCode: CurrencyCode.EUR,
|
||||||
|
paymentNote: PAYMENT_NOTE,
|
||||||
|
beneficiary: { name: BENEFICIARY },
|
||||||
|
bankAccounts: [{ iban: IBAN }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
[effectiveAmount],
|
||||||
|
);
|
||||||
|
|
||||||
|
const copyIban = async () => {
|
||||||
|
await navigator.clipboard.writeText(IBAN);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-md mx-auto p-4 pt-8 min-h-screen">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<h1 className="font-serif text-2xl text-gold">Podpor Bridžik ☕</h1>
|
||||||
|
<button onClick={() => navigate('/')} className="text-sm text-green-dim hover:text-gold">
|
||||||
|
Späť
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-header border border-[#142018] rounded-2xl p-6 text-sm text-green-score">
|
||||||
|
<p className="text-green-dim mb-4">
|
||||||
|
Bridžik je zadarmo. Ak sa ti hra páči, dobrovoľný príspevok pomôže s prevádzkou servera a rozvojom nových funkcionalít.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex gap-2 mb-2">
|
||||||
|
{AMOUNTS.map((a) => (
|
||||||
|
<button
|
||||||
|
key={a}
|
||||||
|
onClick={() => {
|
||||||
|
setAmount(a);
|
||||||
|
setCustom('');
|
||||||
|
}}
|
||||||
|
className={`flex-1 py-1.5 rounded-lg font-serif font-semibold transition-colors ${
|
||||||
|
!customValid && amount === a
|
||||||
|
? 'bg-gold text-table'
|
||||||
|
: 'border border-gold/40 text-gold hover:bg-gold/10'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{a} €
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`flex items-center gap-2 rounded-lg border px-3 py-1.5 mb-4 transition-colors ${
|
||||||
|
customValid ? 'border-gold bg-gold/10' : 'border-gold/40'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
inputMode="decimal"
|
||||||
|
placeholder="Vlastná suma"
|
||||||
|
value={custom}
|
||||||
|
onChange={(e) => setCustom(e.target.value)}
|
||||||
|
className="flex-1 bg-transparent outline-none text-gold placeholder-green-dim font-serif font-semibold"
|
||||||
|
/>
|
||||||
|
<span className="text-gold font-serif font-semibold">€</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-xl p-4 flex justify-center mb-2">
|
||||||
|
<QRCodeSVG value={qrValue} size={176} />
|
||||||
|
</div>
|
||||||
|
<p className="text-center text-xs text-green-dim mb-4">
|
||||||
|
Naskenuj QR kód bankovou aplikáciou (PAY by square)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between gap-2 border border-gold/20 rounded-lg px-3 py-2">
|
||||||
|
<span className="font-mono text-xs break-all">{IBAN}</span>
|
||||||
|
<button
|
||||||
|
onClick={copyIban}
|
||||||
|
className="shrink-0 text-gold hover:text-gold-bright text-xs font-semibold"
|
||||||
|
>
|
||||||
|
{copied ? 'Skopírované ✓' : 'Kopírovať'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -38,6 +38,9 @@ export default function GameList() {
|
|||||||
<button onClick={() => navigate('/history')} className="text-gold hover:text-gold-bright">
|
<button onClick={() => navigate('/history')} className="text-gold hover:text-gold-bright">
|
||||||
História
|
História
|
||||||
</button>
|
</button>
|
||||||
|
<button onClick={() => navigate('/donate')} className="text-gold hover:text-gold-bright">
|
||||||
|
Na kávu
|
||||||
|
</button>
|
||||||
<button onClick={handleLogout} className="text-green-dim hover:text-gold">
|
<button onClick={handleLogout} className="text-green-dim hover:text-gold">
|
||||||
Odhlásiť
|
Odhlásiť
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user