forked from MergeFi/frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRequestQRCode.tsx
More file actions
85 lines (74 loc) · 2.57 KB
/
Copy pathRequestQRCode.tsx
File metadata and controls
85 lines (74 loc) · 2.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import React, { useEffect, useRef, useState } from 'react'
import QRCode from 'qrcode'
import { Check, Copy } from 'lucide-react'
import { copyToClipboard } from '@/lib/utils'
interface RequestQRCodeProps {
value: string
size?: number
}
/**
* Renders a QR code for a shareable payment-request link using the `qrcode`
* package's canvas renderer (see package.json — chosen because hand-rolling a
* correct QR encoder involves Reed-Solomon error correction and mask-pattern
* scoring that's easy to get subtly wrong; `qrcode` is a small, dependency-light,
* widely used library whose browser bundle excludes its Node-only PNG/CLI code).
*/
export function RequestQRCode({ value, size = 200 }: RequestQRCodeProps) {
const canvasRef = useRef<HTMLCanvasElement>(null)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
let cancelled = false
if (!canvasRef.current) return
QRCode.toCanvas(canvasRef.current, value, {
width: size,
margin: 1,
color: { dark: '#0f172a', light: '#ffffff' },
}).catch((err: unknown) => {
if (!cancelled) {
setError(err instanceof Error ? err.message : 'Failed to render QR code')
}
})
return () => {
cancelled = true
}
}, [value, size])
if (error) {
return (
<div
className="flex items-center justify-center rounded-xl bg-navy-900/60 border border-danger-500/20 text-xs text-danger-400"
style={{ width: size, height: size }}
>
{error}
</div>
)
}
return (
<canvas
ref={canvasRef}
role="img"
aria-label="QR code for payment request link"
className="rounded-xl bg-white p-2"
/>
)
}
// ─── Shareable link row with copy-to-clipboard ────────────────────────────────
export function ShareableLink({ link }: { link: string }) {
const [copied, setCopied] = useState(false)
const handleCopy = async () => {
await copyToClipboard(link)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<div className="flex items-center gap-2 p-2.5 rounded-lg bg-navy-900/60 border border-navy-700/40">
<span className="text-xs font-mono text-slate-300 flex-1 truncate">{link}</span>
<button
onClick={handleCopy}
aria-label="Copy link"
className="p-1.5 rounded-lg hover:bg-white/5 text-slate-400 hover:text-white transition-colors shrink-0"
>
{copied ? <Check size={13} className="text-success-400" /> : <Copy size={13} />}
</button>
</div>
)
}