qr / src /app /page.tsx
sameernotes's picture
Upload 55 files
084a9db verified
"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">
&copy; {new Date().getFullYear()} QRCode Canvas. Created by Sameer Banchhor, SlimShadow Org. All rights reserved.
</p>
</footer>
</div>
);
}