Spaces:
Sleeping
Sleeping
| "use client"; | |
| import React, { useState, useEffect, useRef } from 'react'; | |
| import { QRCodeCanvas } from 'qrcode.react'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Input } from '@/components/ui/input'; | |
| import { Label } from '@/components/ui/label'; | |
| import { Textarea } from '@/components/ui/textarea'; | |
| import { | |
| Select, | |
| SelectContent, | |
| SelectItem, | |
| SelectTrigger, | |
| SelectValue, | |
| } from '@/components/ui/select'; | |
| import { Slider } from '@/components/ui/slider'; | |
| import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; | |
| import { Download, ImagePlus, Palette, Settings, Trash2, XCircle } from 'lucide-react'; | |
| import { AppLogoIcon } from '@/components/icons/app-logo-icon'; | |
| import { useToast } from "@/hooks/use-toast"; | |
| type ErrorCorrectionLevel = 'L' | 'M' | 'Q' | 'H'; | |
| export default function QRCodeCanvasPage() { | |
| const [text, setText] = useState<string>('hello qr'); | |
| const [fgColor, setFgColor] = useState<string>('#805CFF'); // Default to new dark primary | |
| const [bgColor, setBgColor] = useState<string>('#FFFFFF'); | |
| const [level, setLevel] = useState<ErrorCorrectionLevel>('M'); | |
| const [qrSize, setQrSize] = useState<number>(256); | |
| const [logoSrc, setLogoSrc] = useState<string | null>(null); | |
| const [logoFile, setLogoFile] = useState<File | null>(null); | |
| const [mounted, setMounted] = useState(false); | |
| const { toast } = useToast(); | |
| const fileInputRef = useRef<HTMLInputElement>(null); | |
| useEffect(() => { | |
| setMounted(true); | |
| }, []); | |
| const handleLogoChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const file = e.target.files?.[0]; | |
| if (file) { | |
| setLogoFile(file); | |
| const reader = new FileReader(); | |
| reader.onloadend = () => { | |
| setLogoSrc(reader.result as string); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }; | |
| const removeLogo = () => { | |
| setLogoSrc(null); | |
| setLogoFile(null); | |
| if (fileInputRef.current) { | |
| fileInputRef.current.value = ''; | |
| } | |
| }; | |
| const downloadQRCode = () => { | |
| if (!text) { | |
| toast({ | |
| title: "Error", | |
| description: "Please enter text or URL to generate QR code.", | |
| variant: "destructive", | |
| }); | |
| return; | |
| } | |
| const canvas = document.getElementById('qr-canvas') as HTMLCanvasElement; | |
| if (canvas) { | |
| try { | |
| const pngUrl = canvas.toDataURL('image/png'); | |
| const downloadLink = document.createElement('a'); | |
| downloadLink.href = pngUrl; | |
| downloadLink.download = 'qrcode.png'; | |
| document.body.appendChild(downloadLink); | |
| downloadLink.click(); | |
| document.body.removeChild(downloadLink); | |
| toast({ | |
| title: "Success!", | |
| description: "QR Code downloaded successfully.", | |
| }); | |
| } catch (error) { | |
| console.error("Failed to download QR Code:", error); | |
| toast({ | |
| title: "Error", | |
| description: "Failed to download QR Code. The logo might be from a restricted source if hosted externally. Try uploading it directly.", | |
| variant: "destructive", | |
| }); | |
| } | |
| } | |
| }; | |
| const imageSettings = logoSrc | |
| ? { | |
| src: logoSrc, | |
| height: qrSize * 0.22, | |
| width: qrSize * 0.22, | |
| excavate: true, | |
| x: undefined, | |
| y: undefined, | |
| } | |
| : undefined; | |
| const qrKey = JSON.stringify({ text, fgColor, bgColor, level, qrSize, logoSrc }); | |
| return ( | |
| <div className="min-h-screen bg-background text-foreground flex flex-col items-center p-4 sm:p-6 md:p-8"> | |
| <header className="w-full max-w-6xl mb-8"> | |
| <div className="flex items-center gap-3"> | |
| <AppLogoIcon className="h-10 w-10 text-primary" /> | |
| <h1 className="text-4xl font-headline font-semibold text-primary tracking-tight">QRCode Canvas</h1> | |
| </div> | |
| <p className="text-lg text-muted-foreground font-body mt-1"> | |
| Create and customize your QR codes with ease. | |
| </p> | |
| </header> | |
| <main className="w-full max-w-6xl flex-grow"> | |
| <div className="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <Card className="lg:col-span-1 rounded-xl shadow-xl"> | |
| <CardHeader> | |
| <CardTitle className="font-headline text-2xl flex items-center gap-2"> | |
| <Settings className="h-6 w-6 text-accent" /> | |
| Customize | |
| </CardTitle> | |
| <CardDescription className="font-body"> | |
| Tailor your QR code to match your style. | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="space-y-6"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="text-input" className="font-body font-medium">Content (URL or Text)</Label> | |
| <Textarea | |
| id="text-input" | |
| value={text} | |
| onChange={(e) => setText(e.target.value)} | |
| placeholder="Enter URL or text" | |
| rows={3} | |
| className="font-body" | |
| /> | |
| </div> | |
| <div className="grid grid-cols-2 gap-4"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="fg-color" className="font-body font-medium flex items-center gap-1.5"> | |
| <Palette size={16} /> Foreground | |
| </Label> | |
| <Input | |
| id="fg-color" | |
| type="color" | |
| value={fgColor} | |
| onChange={(e) => setFgColor(e.target.value)} | |
| className="h-10 w-full p-1" | |
| /> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="bg-color" className="font-body font-medium flex items-center gap-1.5"> | |
| <Palette size={16} /> Background | |
| </Label> | |
| <Input | |
| id="bg-color" | |
| type="color" | |
| value={bgColor} | |
| onChange={(e) => setBgColor(e.target.value)} | |
| className="h-10 w-full p-1" | |
| /> | |
| </div> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="error-correction" className="font-body font-medium">Error Correction</Label> | |
| <Select value={level} onValueChange={(value: string) => setLevel(value as ErrorCorrectionLevel)}> | |
| <SelectTrigger id="error-correction" className="font-body"> | |
| <SelectValue placeholder="Select level" /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="L">Low (L)</SelectItem> | |
| <SelectItem value="M">Medium (M)</SelectItem> | |
| <SelectItem value="Q">Quartile (Q)</SelectItem> | |
| <SelectItem value="H">High (H)</SelectItem> | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="qr-size" className="font-body font-medium">Size ({qrSize}px)</Label> | |
| <Slider | |
| id="qr-size" | |
| min={64} | |
| max={1024} | |
| step={8} | |
| value={[qrSize]} | |
| onValueChange={(value) => setQrSize(value[0])} | |
| /> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="logo-upload" className="font-body font-medium">Logo (Optional)</Label> | |
| <div className="flex items-center gap-2"> | |
| <Input | |
| id="logo-upload" | |
| type="file" | |
| accept="image/png, image/jpeg, image/svg+xml" | |
| onChange={handleLogoChange} | |
| ref={fileInputRef} | |
| className="font-body flex-grow" | |
| /> | |
| {logoSrc && ( | |
| <Button variant="ghost" size="icon" onClick={removeLogo} aria-label="Remove logo" className="text-destructive hover:text-destructive"> | |
| <XCircle size={20} /> | |
| </Button> | |
| )} | |
| </div> | |
| {logoFile && ( | |
| <p className="text-xs text-muted-foreground font-body truncate"> | |
| Selected: {logoFile.name} | |
| </p> | |
| )} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| <Card className="lg:col-span-2 rounded-xl shadow-xl flex flex-col"> | |
| <CardHeader> | |
| <CardTitle className="font-headline text-2xl">Preview</CardTitle> | |
| <CardDescription className="font-body"> | |
| Your generated QR code will appear here. | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="flex-grow flex items-center justify-center p-6 bg-muted/30 rounded-md aspect-square overflow-hidden"> | |
| {mounted && text ? ( | |
| <div style={{ width: qrSize, height: qrSize }} className="transition-all duration-300 ease-in-out"> | |
| <QRCodeCanvas | |
| id="qr-canvas" | |
| value={text} | |
| size={qrSize} | |
| fgColor={fgColor} | |
| bgColor={bgColor} | |
| level={level} | |
| imageSettings={imageSettings} | |
| includeMargin={true} | |
| key={qrKey} | |
| /> | |
| </div> | |
| ) : ( | |
| <div className="flex flex-col items-center justify-center text-muted-foreground p-8 aspect-square w-full max-w-[256px]"> | |
| <ImagePlus size={64} className="mb-4" /> | |
| <p className="text-center font-body"> | |
| {text ? "Generating QR Code..." : "Enter content to generate a QR code."} | |
| </p> | |
| </div> | |
| )} | |
| </CardContent> | |
| <CardFooter className="mt-auto pt-6"> | |
| <Button onClick={downloadQRCode} className="w-full font-body text-base py-3" size="lg" disabled={!text}> | |
| <Download size={20} className="mr-2" /> | |
| Download QR Code | |
| </Button> | |
| </CardFooter> | |
| </Card> | |
| </div> | |
| </main> | |
| <footer className="w-full max-w-6xl mt-12 text-center"> | |
| <p className="text-sm text-muted-foreground font-body"> | |
| © {new Date().getFullYear()} QRCode Canvas. Created by Sameer Banchhor, SlimShadow Org. All rights reserved. | |
| </p> | |
| </footer> | |
| </div> | |
| ); | |
| } | |