From 7801f88429699e8d3d3f4910f38e97430df5629d Mon Sep 17 00:00:00 2001
From: zvspany
Date: Sun, 29 Mar 2026 14:42:27 +0200
Subject: [PATCH] fix(profile): make QR action open inline panel on public
profile
---
app/globals.css | 51 +++++++++++++++++++++++
app/u/[username]/page.tsx | 23 +++++++++-
components/public/payment-method-card.tsx | 28 +++++++++----
components/ui/modal.tsx | 2 +-
4 files changed, 94 insertions(+), 10 deletions(-)
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
) : (
- {profile.paymentMethods.map((method) => (
+ {methodsWithQr.map((method) => (
))}
diff --git a/components/public/payment-method-card.tsx b/components/public/payment-method-card.tsx
index f7b12b6..cd86150 100644
--- a/components/public/payment-method-card.tsx
+++ b/components/public/payment-method-card.tsx
@@ -1,15 +1,17 @@
"use client";
+/* eslint-disable @next/next/no-img-element */
import { PaymentMethod, PaymentMethodType } from "@prisma/client";
-import { PAYMENT_METHOD_LABELS, SUPPORTS_QR } from "@/lib/constants";
-import { buildPaymentUri } from "@/lib/payment-uri";
+import { PAYMENT_METHOD_LABELS } from "@/lib/constants";
import { CopyButton } from "@/components/public/copy-button";
-import { QrModal } from "@/components/public/qr-modal";
type PublicPaymentMethod = Pick<
PaymentMethod,
"id" | "type" | "label" | "value" | "network" | "description" | "isVisible"
->;
+> & {
+ qrPayload: string | null;
+ qrDataUrl: string | null;
+};
type PaymentMethodCardProps = {
method: PublicPaymentMethod;
@@ -26,8 +28,7 @@ function isHttpUrl(value: string) {
export function PaymentMethodCard({ method }: PaymentMethodCardProps) {
const typeLabel = PAYMENT_METHOD_LABELS[method.type as PaymentMethodType];
- const qrPayload = buildPaymentUri(method.type, method.value, method.network);
- const linkTarget = isHttpUrl(qrPayload) ? qrPayload : null;
+ const linkTarget = method.qrPayload && isHttpUrl(method.qrPayload) ? method.qrPayload : null;
return (
@@ -44,7 +45,20 @@ export function PaymentMethodCard({ method }: PaymentMethodCardProps) {