feat(vault): real QR code generation for WiFi entries

Replace placeholder WiFi string display with actual QR code rendered
on canvas via 'qrcode' library. Dialog now shows scannable QR image
with copy-image and download-PNG buttons.
This commit is contained in:
AI Assistant
2026-02-28 16:47:21 +02:00
parent 6295508f99
commit f7a2372e56
3 changed files with 309 additions and 41 deletions
@@ -1,6 +1,6 @@
"use client";
import { useState, useMemo } from "react";
import { useState, useMemo, useEffect, useRef } from "react";
import {
Plus,
Pencil,
@@ -558,48 +558,92 @@ function WifiQrDisplay({
password: string;
onCopy: (text: string) => void;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [qrDataUrl, setQrDataUrl] = useState<string | null>(null);
// Build WIFI connection string
const wifiString = useMemo(() => {
const escaped = (s: string) => s.replace(/[\\;,"`:]/g, (c) => `\\${c}`);
return `WIFI:T:WPA;S:${escaped(ssid)};P:${escaped(password)};;`;
}, [ssid, password]);
// Generate QR code on canvas
useEffect(() => {
let cancelled = false;
async function render() {
const QRCode = (await import("qrcode")).default;
const canvas = canvasRef.current;
if (!canvas || cancelled) return;
await QRCode.toCanvas(canvas, wifiString, {
width: 280,
margin: 2,
color: { dark: "#000000", light: "#ffffff" },
});
// Also store as data URL for copy/download
const url = canvas.toDataURL("image/png");
if (!cancelled) setQrDataUrl(url);
}
render();
return () => { cancelled = true; };
}, [wifiString]);
const handleDownload = () => {
if (!qrDataUrl) return;
const a = document.createElement("a");
a.href = qrDataUrl;
a.download = `WiFi-${ssid}.png`;
a.click();
};
const handleCopyImage = async () => {
const canvas = canvasRef.current;
if (!canvas) return;
try {
const blob = await new Promise<Blob | null>((res) =>
canvas.toBlob(res, "image/png"),
);
if (blob) {
await navigator.clipboard.write([
new ClipboardItem({ "image/png": blob }),
]);
onCopy("__image__");
}
} catch {
// Fallback: copy the wifi string instead
onCopy(wifiString);
}
};
return (
<div className="space-y-4">
<div className="flex flex-col items-center gap-4">
{/* Visual WiFi indicator */}
<div className="rounded-lg border bg-white p-6 flex flex-col items-center gap-3">
<Wifi className="h-16 w-16 text-blue-500" />
{/* QR Code canvas */}
<div className="rounded-lg border bg-white p-4 flex flex-col items-center gap-3">
<canvas ref={canvasRef} className="rounded" />
<div className="text-center">
<p className="font-medium text-lg text-gray-900">{ssid}</p>
<p className="text-sm text-gray-500 mt-1">
Copiază string-ul de mai jos într-un generator QR
<p className="font-medium text-base text-gray-900">{ssid}</p>
<p className="text-xs text-gray-500 mt-0.5">
Scanează cu telefonul pentru conectare
</p>
</div>
</div>
{/* Connection string for copy */}
<div className="w-full space-y-2">
<Label className="text-xs text-muted-foreground">
String conexiune WiFi
</Label>
<div className="flex gap-2">
<code className="flex-1 rounded border bg-muted/50 px-3 py-2 text-xs font-mono break-all">
{wifiString}
</code>
<Button
variant="outline"
size="icon"
className="shrink-0"
onClick={() => onCopy(wifiString)}
>
<Copy className="h-4 w-4" />
</Button>
</div>
<p className="text-[10px] text-muted-foreground">
Lipește acest text într-un generator QR online (ex:
qr-code-generator.com) pentru a crea un QR code scanabil.
</p>
{/* Action buttons */}
<div className="flex gap-2 w-full">
<Button
variant="outline"
className="flex-1"
onClick={handleCopyImage}
>
<Copy className="mr-1.5 h-4 w-4" /> Copiază imaginea
</Button>
<Button
variant="outline"
className="flex-1"
onClick={handleDownload}
>
<ExternalLink className="mr-1.5 h-4 w-4" /> Descarcă PNG
</Button>
</div>
{/* Password for quick copy */}