icons/bilder

This commit is contained in:
Timo 2026-01-07 19:41:40 +01:00
parent 749cabf0bf
commit e7478a4af7
2 changed files with 154 additions and 5 deletions

View File

@ -33,6 +33,11 @@ export default function CreatePage() {
const [cornerStyle, setCornerStyle] = useState('square');
const [size, setSize] = useState(200);
// Logo state
const [logoUrl, setLogoUrl] = useState('');
const [logoSize, setLogoSize] = useState(24);
const [excavate, setExcavate] = useState(true);
// QR preview
const [qrDataUrl, setQrDataUrl] = useState('');
@ -150,6 +155,48 @@ export default function CreatePage() {
}
};
const handleLogoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
if (file.size > 10 * 1024 * 1024) { // 10MB limit (soft limit for upload, will be resized)
showToast('Logo file size too large (max 10MB)', 'error');
return;
}
const reader = new FileReader();
reader.onload = (evt) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const maxDimension = 500; // Resize to max 500px
let width = img.width;
let height = img.height;
if (width > maxDimension || height > maxDimension) {
if (width > height) {
height = Math.round((height * maxDimension) / width);
width = maxDimension;
} else {
width = Math.round((width * maxDimension) / height);
height = maxDimension;
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx?.drawImage(img, 0, 0, width, height);
// Compress to JPEG/PNG with reduced quality to save space
const dataUrl = canvas.toDataURL(file.type === 'image/png' ? 'image/png' : 'image/jpeg', 0.8);
setLogoUrl(dataUrl);
};
img.src = evt.target?.result as string;
};
reader.readAsDataURL(file);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
@ -167,6 +214,12 @@ export default function CreatePage() {
backgroundColor: canCustomizeColors ? backgroundColor : '#FFFFFF',
cornerStyle,
size,
imageSettings: (canCustomizeColors && logoUrl) ? {
src: logoUrl,
height: logoSize,
width: logoSize,
excavate,
} : undefined,
},
};
@ -488,6 +541,90 @@ export default function CreatePage() {
</div>
</CardContent>
</Card>
{/* Logo Section */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>Logo</CardTitle>
{!canCustomizeColors && (
<Badge variant="warning">PRO Feature</Badge>
)}
</div>
</CardHeader>
<CardContent className="space-y-4">
{!canCustomizeColors && (
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg mb-4">
<p className="text-sm text-blue-900">
<strong>Upgrade to PRO</strong> to add logos to your QR codes.
</p>
<Link href="/pricing">
<Button variant="primary" size="sm" className="mt-2">
Upgrade Now
</Button>
</Link>
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Upload Logo
</label>
<div className="flex items-center space-x-4">
<input
type="file"
accept="image/*"
onChange={handleLogoUpload}
disabled={!canCustomizeColors}
className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 disabled:opacity-50 disabled:cursor-not-allowed"
/>
{logoUrl && (
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
setLogoUrl('');
setLogoSize(40);
}}
>
Remove
</Button>
)}
</div>
</div>
{logoUrl && (
<>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Logo Size: {logoSize}px
</label>
<input
type="range"
min="20"
max="70"
value={logoSize}
onChange={(e) => setLogoSize(Number(e.target.value))}
className="w-full"
/>
</div>
<div className="flex items-center">
<input
type="checkbox"
checked={excavate}
onChange={(e) => setExcavate(e.target.checked)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
id="excavate-checkbox"
/>
<label htmlFor="excavate-checkbox" className="ml-2 block text-sm text-gray-900">
Excavate background (remove dots behind logo)
</label>
</div>
</>
)}
</CardContent>
</Card>
</div>
{/* Right: Preview */}
@ -505,7 +642,13 @@ export default function CreatePage() {
size={200}
fgColor={foregroundColor}
bgColor={backgroundColor}
level="M"
level="H"
imageSettings={logoUrl ? {
src: logoUrl,
height: logoSize,
width: logoSize,
excavate: excavate,
} : undefined}
/>
</div>
) : (

View File

@ -31,10 +31,10 @@ export const QRCodeCard: React.FC<QRCodeCardProps> = ({
// For dynamic QR codes, use the redirect URL for tracking
// For static QR codes, use the direct URL from content
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || (typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3050');
// Get the QR URL based on type
let qrUrl = '';
// SIMPLE FIX: For STATIC QR codes, ALWAYS use the direct content
if (qr.type === 'STATIC') {
// Extract the actual URL/content based on contentType
@ -171,7 +171,7 @@ END:VCARD`;
</Badge>
</div>
</div>
<Dropdown
align="right"
trigger={
@ -200,7 +200,13 @@ END:VCARD`;
size={96}
fgColor={qr.style?.foregroundColor || '#000000'}
bgColor={qr.style?.backgroundColor || '#FFFFFF'}
level="M"
level="H"
imageSettings={qr.style?.imageSettings ? {
src: qr.style.imageSettings.src,
height: qr.style.imageSettings.height * (96 / 200), // Scale logo for smaller QR
width: qr.style.imageSettings.width * (96 / 200),
excavate: qr.style.imageSettings.excavate,
} : undefined}
/>
</div>
</div>