Rounded QR code

This commit is contained in:
Timo Knuth 2025-11-11 21:41:01 +01:00
parent d6ee03f4d8
commit 8ecd58b176
3 changed files with 96 additions and 15 deletions

View File

@ -523,7 +523,27 @@ export default function CreatePage() {
onClick={() => {
const svg = document.querySelector('#create-qr-preview svg');
if (!svg) return;
const svgData = new XMLSerializer().serializeToString(svg);
let svgData = new XMLSerializer().serializeToString(svg);
// If rounded corners, wrap in a clipped SVG
if (cornerStyle === 'rounded') {
const width = svg.getAttribute('width') || '200';
const height = svg.getAttribute('height') || '200';
const borderRadius = 20;
svgData = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<defs>
<clipPath id="rounded-corners">
<rect x="0" y="0" width="${width}" height="${height}" rx="${borderRadius}" ry="${borderRadius}"/>
</clipPath>
</defs>
<g clip-path="url(#rounded-corners)">
${svgData}
</g>
</svg>`;
}
const blob = new Blob([svgData], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
@ -545,6 +565,8 @@ export default function CreatePage() {
if (!svg) return;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) return;
const img = new Image();
const svgData = new XMLSerializer().serializeToString(svg);
const blob = new Blob([svgData], { type: 'image/svg+xml' });
@ -553,7 +575,25 @@ export default function CreatePage() {
img.onload = () => {
canvas.width = 200;
canvas.height = 200;
ctx?.drawImage(img, 0, 0);
// Apply rounded corners if needed
if (cornerStyle === 'rounded') {
const borderRadius = 20;
ctx.beginPath();
ctx.moveTo(borderRadius, 0);
ctx.lineTo(200 - borderRadius, 0);
ctx.quadraticCurveTo(200, 0, 200, borderRadius);
ctx.lineTo(200, 200 - borderRadius);
ctx.quadraticCurveTo(200, 200, 200 - borderRadius, 200);
ctx.lineTo(borderRadius, 200);
ctx.quadraticCurveTo(0, 200, 0, 200 - borderRadius);
ctx.lineTo(0, borderRadius);
ctx.quadraticCurveTo(0, 0, borderRadius, 0);
ctx.closePath();
ctx.clip();
}
ctx.drawImage(img, 0, 0);
canvas.toBlob((blob) => {
if (blob) {
const url = URL.createObjectURL(blob);
@ -564,6 +604,7 @@ export default function CreatePage() {
URL.revokeObjectURL(url);
}
});
URL.revokeObjectURL(url);
};
img.src = url;
}}

View File

@ -354,10 +354,9 @@ export default function SettingsPage() {
<Button
variant="outline"
className="w-full"
onClick={handleManageSubscription}
disabled={loading}
onClick={() => window.location.href = '/pricing'}
>
{loading ? 'Loading...' : 'Manage Subscription'}
Manage Subscription
</Button>
</div>
)}

View File

@ -82,7 +82,26 @@ END:VCARD`;
if (!svg) return;
if (format === 'svg') {
const svgData = new XMLSerializer().serializeToString(svg);
let svgData = new XMLSerializer().serializeToString(svg);
// If rounded corners, wrap in a clipped SVG
if (qr.style?.cornerStyle === 'rounded') {
const width = svg.getAttribute('width') || '96';
const height = svg.getAttribute('height') || '96';
const borderRadius = 10; // Smaller radius for dashboard
svgData = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<defs>
<clipPath id="rounded-corners-${qr.id}">
<rect x="0" y="0" width="${width}" height="${height}" rx="${borderRadius}" ry="${borderRadius}"/>
</clipPath>
</defs>
<g clip-path="url(#rounded-corners-${qr.id})">
${svgData}
</g>
</svg>`;
}
const blob = new Blob([svgData], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
@ -96,6 +115,8 @@ END:VCARD`;
// Convert SVG to PNG
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) return;
const img = new Image();
const svgData = new XMLSerializer().serializeToString(svg);
const blob = new Blob([svgData], { type: 'image/svg+xml' });
@ -104,7 +125,25 @@ END:VCARD`;
img.onload = () => {
canvas.width = 300;
canvas.height = 300;
ctx?.drawImage(img, 0, 0, 300, 300);
// Apply rounded corners if needed
if (qr.style?.cornerStyle === 'rounded') {
const borderRadius = 30; // Scale up for 300px canvas
ctx.beginPath();
ctx.moveTo(borderRadius, 0);
ctx.lineTo(300 - borderRadius, 0);
ctx.quadraticCurveTo(300, 0, 300, borderRadius);
ctx.lineTo(300, 300 - borderRadius);
ctx.quadraticCurveTo(300, 300, 300 - borderRadius, 300);
ctx.lineTo(borderRadius, 300);
ctx.quadraticCurveTo(0, 300, 0, 300 - borderRadius);
ctx.lineTo(0, borderRadius);
ctx.quadraticCurveTo(0, 0, borderRadius, 0);
ctx.closePath();
ctx.clip();
}
ctx.drawImage(img, 0, 0, 300, 300);
canvas.toBlob((blob) => {
if (blob) {
const url = URL.createObjectURL(blob);
@ -166,13 +205,15 @@ END:VCARD`;
</div>
<div id={`qr-${qr.id}`} className="flex items-center justify-center bg-gray-50 rounded-lg p-4 mb-3">
<QRCodeSVG
value={qrUrl}
size={96}
fgColor={qr.style?.foregroundColor || '#000000'}
bgColor={qr.style?.backgroundColor || '#FFFFFF'}
level="M"
/>
<div className={qr.style?.cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''}>
<QRCodeSVG
value={qrUrl}
size={96}
fgColor={qr.style?.foregroundColor || '#000000'}
bgColor={qr.style?.backgroundColor || '#FFFFFF'}
level="M"
/>
</div>
</div>
<div className="space-y-2 text-sm">