payment QR: center Goblin mark (opt-in, level H)

This commit is contained in:
2ro
2026-07-04 20:43:04 -04:00
parent f1bd64d28e
commit 88e8fde9c8
3 changed files with 2211 additions and 5 deletions
+2171
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -99,7 +99,7 @@ function PayQR({ value }: { value: string }) {
return (
<div className="flex justify-center">
<div className="rounded-2xl bg-white p-3 shadow-sm">
<QRCodeCanvas value={value} size={224} level="M" className="h-auto w-full max-w-56" />
<QRCodeCanvas value={value} size={224} level="H" logo className="h-auto w-full max-w-56" />
</div>
</div>
);
+39 -4
View File
@@ -6,11 +6,17 @@ interface QRCodeCanvasProps {
size?: number;
level?: 'L' | 'M' | 'Q' | 'H';
className?: string;
/** Overlay the Goblin mark in the center (payment QRs only). Forces level H. */
logo?: boolean;
}
export function QRCodeCanvas({ value, size = 256, level = 'M', className }: QRCodeCanvasProps) {
export function QRCodeCanvas({ value, size = 256, level = 'M', className, logo = false }: QRCodeCanvasProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
// When the logo is shown, force the highest error-correction level so the
// center overlay never breaks scanning.
const effectiveLevel = logo ? 'H' : level;
useEffect(() => {
if (!canvasRef.current) return;
@@ -22,7 +28,7 @@ export function QRCodeCanvas({ value, size = 256, level = 'M', className }: QRCo
{
width: size,
margin: 1,
errorCorrectionLevel: level,
errorCorrectionLevel: effectiveLevel,
},
(error) => {
if (error) console.error('QR Code generation error:', error);
@@ -35,7 +41,36 @@ export function QRCodeCanvas({ value, size = 256, level = 'M', className }: QRCo
// className (e.g. `h-auto w-full`) controls the rendered size responsively.
canvas.style.removeProperty('width');
canvas.style.removeProperty('height');
}, [value, size, level]);
}, [value, size, effectiveLevel]);
return <canvas ref={canvasRef} className={className} />;
if (!logo) {
return <canvas ref={canvasRef} className={className} />;
}
return (
<div className={`relative inline-block ${className ?? ''}`}>
<canvas ref={canvasRef} className="block h-auto w-full" />
<div
aria-hidden="true"
style={{
position: 'absolute',
inset: 0,
margin: 'auto',
width: '24%',
height: '24%',
padding: '4%',
borderRadius: '18%',
background: '#fff',
boxSizing: 'border-box',
}}
>
<svg viewBox="0 0 64 64" aria-hidden="true" style={{ width: '100%', height: '100%', display: 'block' }}>
<path fill="#201d09" d="M20 22c0-3 3-5 6-4l6 3 6-3c3-1 6 1 6 4v10c0 8-6 14-12 14S20 40 20 32z" />
<circle cx="26" cy="30" r="3" fill="#fff" />
<circle cx="38" cy="30" r="3" fill="#fff" />
<path fill="#fff" d="M28 40h8l-4 5z" />
</svg>
</div>
</div>
);
}