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:
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user