Barcode fix
This commit is contained in:
@@ -62,6 +62,43 @@ const getFrameOptionsForContentType = (contentType: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Injects a caption <text> element below a barcode SVG and expands its height/viewBox.
|
||||
// Used so the "scanner app" hint is baked into the downloaded SVG.
|
||||
function addBarcodeCaptionToSvg(svgElement: SVGElement, caption: string): string {
|
||||
const cloned = svgElement.cloneNode(true) as SVGElement;
|
||||
const NS = 'http://www.w3.org/2000/svg';
|
||||
|
||||
const widthAttr = cloned.getAttribute('width');
|
||||
const heightAttr = cloned.getAttribute('height');
|
||||
const width = widthAttr ? parseFloat(widthAttr) : 200;
|
||||
const height = heightAttr ? parseFloat(heightAttr) : 100;
|
||||
const extraHeight = 18;
|
||||
|
||||
cloned.setAttribute('height', String(height + extraHeight));
|
||||
const viewBox = cloned.getAttribute('viewBox');
|
||||
if (viewBox) {
|
||||
const parts = viewBox.split(/\s+/);
|
||||
if (parts.length === 4) {
|
||||
cloned.setAttribute(
|
||||
'viewBox',
|
||||
`${parts[0]} ${parts[1]} ${parts[2]} ${parseFloat(parts[3]) + extraHeight}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const text = document.createElementNS(NS, 'text');
|
||||
text.setAttribute('x', String(width / 2));
|
||||
text.setAttribute('y', String(height + 12));
|
||||
text.setAttribute('text-anchor', 'middle');
|
||||
text.setAttribute('font-size', '9');
|
||||
text.setAttribute('font-family', 'Arial, Helvetica, sans-serif');
|
||||
text.setAttribute('fill', '#666666');
|
||||
text.textContent = caption;
|
||||
cloned.appendChild(text);
|
||||
|
||||
return new XMLSerializer().serializeToString(cloned);
|
||||
}
|
||||
|
||||
export default function CreatePage() {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
@@ -212,7 +249,9 @@ export default function CreatePage() {
|
||||
if (frameType === 'none') {
|
||||
const svgElement = qrRef.current.querySelector('svg');
|
||||
if (svgElement) {
|
||||
const svgData = new XMLSerializer().serializeToString(svgElement);
|
||||
const svgData = contentType === 'BARCODE'
|
||||
? addBarcodeCaptionToSvg(svgElement, 'Scan: iPhone -> Barcode Scanner App | Android -> Google Lens')
|
||||
: new XMLSerializer().serializeToString(svgElement);
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
@@ -651,11 +690,16 @@ export default function CreatePage() {
|
||||
<>
|
||||
{isDynamic ? (
|
||||
<>
|
||||
<div className="rounded-lg bg-blue-50 border border-blue-200 p-3 text-sm text-blue-800">
|
||||
<strong>How dynamic barcodes work:</strong> The barcode encodes a short redirect URL
|
||||
(e.g. <span className="font-mono text-xs">qrmaster.net/r/…</span>). When scanned with a
|
||||
smartphone camera, it opens the browser and redirects to your destination — which you
|
||||
can update anytime. Works with smartphone cameras, not POS laser scanners.
|
||||
<div className="rounded-lg bg-blue-50 border border-blue-200 p-3 text-sm text-blue-800 space-y-2">
|
||||
<p>
|
||||
<strong>How dynamic barcodes work:</strong> The barcode encodes a short redirect URL
|
||||
(e.g. <span className="font-mono text-xs">qrmaster.net/r/…</span>) that you can update anytime.
|
||||
</p>
|
||||
<p className="rounded border border-amber-300 bg-amber-50 p-2 text-xs text-amber-900">
|
||||
<strong>📱 Scanner tip:</strong> Use a <strong>barcode scanner app</strong> on iPhone
|
||||
(iOS Camera doesn't auto-open links from barcodes). Android Google Lens / Camera works
|
||||
out of the box. Print min. 5 cm wide for reliable scanning.
|
||||
</p>
|
||||
</div>
|
||||
<Input
|
||||
label="Destination URL"
|
||||
@@ -1038,13 +1082,12 @@ export default function CreatePage() {
|
||||
<CardTitle>{t('create.preview')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-center">
|
||||
<div id="create-qr-preview" className="flex justify-center mb-4">
|
||||
<div id="create-qr-preview" className="flex justify-center mb-4 w-full min-w-0 overflow-hidden">
|
||||
{/* WRAPPER FOR REF AND FRAME */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="relative bg-white rounded-xl p-4 flex flex-col items-center justify-center transition-all duration-300"
|
||||
className="relative bg-white rounded-xl p-4 flex flex-col items-center justify-center transition-all duration-300 w-full max-w-full min-w-0"
|
||||
style={{
|
||||
minWidth: '280px',
|
||||
minHeight: '280px',
|
||||
}}
|
||||
>
|
||||
@@ -1060,7 +1103,7 @@ export default function CreatePage() {
|
||||
|
||||
{contentType === 'BARCODE' ? (
|
||||
qrContent ? (
|
||||
<div className="p-2 bg-white">
|
||||
<div className="p-2 bg-white w-full max-w-full [&_svg]:!w-full [&_svg]:!h-auto [&_svg]:!max-w-full">
|
||||
<Barcode
|
||||
key={`${qrContent}-${content.format}-${foregroundColor}`}
|
||||
value={qrContent}
|
||||
@@ -1068,9 +1111,14 @@ export default function CreatePage() {
|
||||
lineColor={foregroundColor}
|
||||
background={backgroundColor}
|
||||
width={2}
|
||||
height={100}
|
||||
height={80}
|
||||
margin={10}
|
||||
displayValue={true}
|
||||
fontSize={14}
|
||||
/>
|
||||
<p className="mt-2 text-center text-[10px] leading-tight text-gray-600 px-2">
|
||||
Scan: iPhone → Barcode Scanner App · Android → Google Lens / Camera
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-[200px] h-[200px] bg-gray-100 rounded flex items-center justify-center text-gray-500">
|
||||
|
||||
@@ -8,6 +8,41 @@ import { Badge } from '@/components/ui/Badge';
|
||||
import { Dropdown, DropdownItem } from '@/components/ui/Dropdown';
|
||||
import { formatDate } from '@/lib/utils';
|
||||
|
||||
function addBarcodeCaptionToSvg(svgElement: SVGElement, caption: string): string {
|
||||
const cloned = svgElement.cloneNode(true) as SVGElement;
|
||||
const NS = 'http://www.w3.org/2000/svg';
|
||||
|
||||
const widthAttr = cloned.getAttribute('width');
|
||||
const heightAttr = cloned.getAttribute('height');
|
||||
const width = widthAttr ? parseFloat(widthAttr) : 200;
|
||||
const height = heightAttr ? parseFloat(heightAttr) : 100;
|
||||
const extraHeight = 18;
|
||||
|
||||
cloned.setAttribute('height', String(height + extraHeight));
|
||||
const viewBox = cloned.getAttribute('viewBox');
|
||||
if (viewBox) {
|
||||
const parts = viewBox.split(/\s+/);
|
||||
if (parts.length === 4) {
|
||||
cloned.setAttribute(
|
||||
'viewBox',
|
||||
`${parts[0]} ${parts[1]} ${parts[2]} ${parseFloat(parts[3]) + extraHeight}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const text = document.createElementNS(NS, 'text');
|
||||
text.setAttribute('x', String(width / 2));
|
||||
text.setAttribute('y', String(height + 12));
|
||||
text.setAttribute('text-anchor', 'middle');
|
||||
text.setAttribute('font-size', '9');
|
||||
text.setAttribute('font-family', 'Arial, Helvetica, sans-serif');
|
||||
text.setAttribute('fill', '#666666');
|
||||
text.textContent = caption;
|
||||
cloned.appendChild(text);
|
||||
|
||||
return new XMLSerializer().serializeToString(cloned);
|
||||
}
|
||||
|
||||
interface QRCodeCardProps {
|
||||
qr: {
|
||||
id: string;
|
||||
@@ -113,7 +148,9 @@ END:VCARD`;
|
||||
const svgContainer = document.querySelector(`#qr-svg-${qr.id}`);
|
||||
const svg = svgContainer?.querySelector('svg');
|
||||
if (svg) {
|
||||
let svgData = new XMLSerializer().serializeToString(svg);
|
||||
let svgData = qr.contentType === 'BARCODE'
|
||||
? addBarcodeCaptionToSvg(svg as SVGElement, 'Scan: iPhone -> Barcode Scanner App | Android -> Google Lens')
|
||||
: new XMLSerializer().serializeToString(svg);
|
||||
|
||||
if (qr.style?.cornerStyle === 'rounded') {
|
||||
const width = svg.getAttribute('width') || '96';
|
||||
@@ -210,17 +247,23 @@ END:VCARD`;
|
||||
)}
|
||||
<div id={`qr-svg-${qr.id}`} className={qr.style?.cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''}>
|
||||
{qr.contentType === 'BARCODE' ? (
|
||||
<Barcode
|
||||
key={`${qr.id}-${qr.type === 'STATIC' ? qr.content?.value : qrUrl}-${qr.content?.format}`}
|
||||
value={qr.type === 'STATIC' ? (qr.content?.value || '123456789') : qrUrl}
|
||||
format={(qr.content?.format as any) || 'CODE128'}
|
||||
lineColor={qr.style?.foregroundColor || '#000000'}
|
||||
background={qr.style?.backgroundColor || '#FFFFFF'}
|
||||
width={1.5}
|
||||
height={60}
|
||||
displayValue={true}
|
||||
fontSize={10}
|
||||
/>
|
||||
<div className="w-full max-w-[240px] [&_svg]:!w-full [&_svg]:!h-auto [&_svg]:!max-w-full">
|
||||
<Barcode
|
||||
key={`${qr.id}-${qr.type === 'STATIC' ? qr.content?.value : qrUrl}-${qr.content?.format}`}
|
||||
value={qr.type === 'STATIC' ? (qr.content?.value || '123456789') : qrUrl}
|
||||
format={(qr.content?.format as any) || 'CODE128'}
|
||||
lineColor={qr.style?.foregroundColor || '#000000'}
|
||||
background={qr.style?.backgroundColor || '#FFFFFF'}
|
||||
width={2}
|
||||
height={70}
|
||||
margin={8}
|
||||
displayValue={true}
|
||||
fontSize={11}
|
||||
/>
|
||||
<p className="mt-1 text-center text-[9px] leading-tight text-gray-600 px-1">
|
||||
Scan: iPhone → Barcode Scanner App · Android → Google Lens
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<QRCodeSVG
|
||||
value={qrUrl}
|
||||
|
||||
Reference in New Issue
Block a user