From 95e6473d21968a7544689768a114aecf9668c0d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 09:59:07 +0000 Subject: [PATCH] feat: add QR code generation and decode page Agent-Logs-Url: https://github.com/slhmy/dev-tools-web/sessions/496b22fa-979a-477a-b7b5-5a9fe7469d3b Co-authored-by: slhmy <31381093+slhmy@users.noreply.github.com> --- package-lock.json | 17 ++++ package.json | 2 + src/App.tsx | 2 + src/components/app-sidebar.tsx | 4 + src/pages/qrcode.tsx | 146 +++++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+) create mode 100644 src/pages/qrcode.tsx diff --git a/package-lock.json b/package-lock.json index fd92f26..996a1c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,9 @@ "@tailwindcss/vite": "^4.2.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "jsqr": "^1.4.0", "lucide-react": "^1.8.0", + "qrcode.react": "^4.2.0", "radix-ui": "^1.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", @@ -6621,6 +6623,12 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsqr": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz", + "integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==", + "license": "Apache-2.0" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7877,6 +7885,15 @@ "node": ">=6" } }, + "node_modules/qrcode.react": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/qs": { "version": "6.15.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", diff --git a/package.json b/package.json index 161e784..e50b409 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "@tailwindcss/vite": "^4.2.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "jsqr": "^1.4.0", "lucide-react": "^1.8.0", + "qrcode.react": "^4.2.0", "radix-ui": "^1.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", diff --git a/src/App.tsx b/src/App.tsx index 9e25606..75bd97b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import { import { Base64Page } from "@/pages/base64" import { GistPage } from "@/pages/gist" import { HomePage } from "@/pages/home" +import { QRCodePage } from "@/pages/qrcode" import { TimestampPage } from "@/pages/timestamp" export function App() { @@ -28,6 +29,7 @@ export function App() { } /> } /> } /> + } /> diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index e879e97..62bfa2c 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -28,6 +28,10 @@ const navItems = [ title: "Storage", items: [{ title: "Gist", url: "/gist" }], }, + { + title: "Image", + items: [{ title: "QR Code", url: "/qrcode" }], + }, ] export function AppSidebar({ ...props }: React.ComponentProps) { diff --git a/src/pages/qrcode.tsx b/src/pages/qrcode.tsx new file mode 100644 index 0000000..8ef24bc --- /dev/null +++ b/src/pages/qrcode.tsx @@ -0,0 +1,146 @@ +import { useRef, useState } from "react" +import { QRCodeSVG } from "qrcode.react" +import jsQR from "jsqr" +import { toast } from "sonner" + +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" + +export function QRCodePage() { + // Generate section + const [genInput, setGenInput] = useState("") + + // Decode section + const [decodeResult, setDecodeResult] = useState("") + const fileInputRef = useRef(null) + + function downloadQR() { + const svg = document.getElementById("qrcode-svg") + if (!svg) return + const serializer = new XMLSerializer() + const svgStr = serializer.serializeToString(svg) + const blob = new Blob([svgStr], { type: "image/svg+xml" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = "qrcode.svg" + a.click() + URL.revokeObjectURL(url) + } + + function handleImageUpload(e: React.ChangeEvent) { + const file = e.target.files?.[0] + if (!file) return + + const reader = new FileReader() + reader.onload = (ev) => { + const img = new Image() + img.onload = () => { + const canvas = document.createElement("canvas") + canvas.width = img.width + canvas.height = img.height + const ctx = canvas.getContext("2d") + if (!ctx) return + ctx.drawImage(img, 0, 0) + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) + const code = jsQR(imageData.data, imageData.width, imageData.height) + if (code) { + setDecodeResult(code.data) + } else { + toast.error("No QR code found in the image.") + setDecodeResult("") + } + } + img.src = ev.target?.result as string + } + reader.readAsDataURL(file) + // Reset so re-uploading same file fires change event + e.target.value = "" + } + + return ( +
+
+

QR Code

+

+ Generate a QR code from text, or decode a QR code image to text. +

+
+ + {/* Generate */} +
+

Text → QR Code

+