QR
+{method.label} QR
+ {method.qrDataUrl ? ( +Could not generate QR code for this value.
+ )} +{method.qrPayload}
+diff --git a/app/globals.css b/app/globals.css index 2ad0345..c21895f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -244,6 +244,57 @@ select.ui-control { background: rgb(var(--color-panel) / 1); } +.method-qr-details { + position: relative; +} + +.method-qr-details > summary { + list-style: none; +} + +.method-qr-details > summary::-webkit-details-marker { + display: none; +} + +.method-qr-panel { + position: absolute; + right: 0; + top: calc(100% + 8px); + width: min(22rem, 86vw); + border: 1px solid rgb(var(--color-border) / 0.9); + border-radius: 8px; + background: rgb(var(--color-panel) / 0.98); + padding: 12px; + display: grid; + gap: 10px; + z-index: 70; +} + +.method-qr-title { + font-size: 13px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.method-qr-image { + width: 220px; + height: 220px; + margin: 0 auto; + background: #fff; + border-radius: 6px; + padding: 6px; +} + +.method-qr-payload { + font-size: 12px; + line-height: 1.5; + color: rgb(var(--color-muted)); + word-break: break-all; + max-height: 110px; + overflow-y: auto; +} + .dashboard-action { min-height: 38px; padding: 8px 12px; diff --git a/app/u/[username]/page.tsx b/app/u/[username]/page.tsx index 077b3e9..2db5e01 100644 --- a/app/u/[username]/page.tsx +++ b/app/u/[username]/page.tsx @@ -1,7 +1,9 @@ import { notFound } from "next/navigation"; import type { CSSProperties } from "react"; +import QRCode from "qrcode"; import { db } from "@/lib/db"; -import { THEME_TOKENS } from "@/lib/constants"; +import { SUPPORTS_QR, THEME_TOKENS } from "@/lib/constants"; +import { buildPaymentUri } from "@/lib/payment-uri"; import { ProfileHeader } from "@/components/public/profile-header"; import { PaymentMethodCard } from "@/components/public/payment-method-card"; import { SocialLinksList } from "@/components/public/social-links-list"; @@ -54,6 +56,23 @@ export default async function PublicProfilePage({ params }: PublicProfilePagePro notFound(); } + const methodsWithQr = await Promise.all( + profile.paymentMethods.map(async (method) => { + if (!SUPPORTS_QR.has(method.type)) { + return { ...method, qrPayload: null, qrDataUrl: null }; + } + + const qrPayload = buildPaymentUri(method.type, method.value, method.network); + + try { + const qrDataUrl = await QRCode.toDataURL(qrPayload, { margin: 1, width: 280 }); + return { ...method, qrPayload, qrDataUrl }; + } catch { + return { ...method, qrPayload, qrDataUrl: null }; + } + }) + ); + const tokens = THEME_TOKENS[profile.themeId] ?? THEME_TOKENS["terminal-dark"]; return ( @@ -86,7 +105,7 @@ export default async function PublicProfilePage({ params }: PublicProfilePagePro
) : ({method.label} QR
+ {method.qrDataUrl ? ( +Could not generate QR code for this value.
+ )} +{method.qrPayload}
+