AEO / SEO
This commit is contained in:
163
src/components/marketing/MiniGenerator.tsx
Normal file
163
src/components/marketing/MiniGenerator.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Download, RefreshCw, Smartphone, Image as ImageIcon, ScanLine } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export function MiniGenerator() {
|
||||
const [url, setUrl] = useState('');
|
||||
const [color, setColor] = useState('#000000');
|
||||
const [withLogo, setWithLogo] = useState(false);
|
||||
const [frame, setFrame] = useState<'none' | 'scan_me' | 'phone'>('none');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
// Prevent hydration mismatch
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className="bg-gray-100 rounded-lg p-8 mb-6 flex items-center justify-center min-h-[300px] animate-pulse">
|
||||
<Smartphone className="w-16 h-16 text-gray-300" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderQR = () => (
|
||||
<QRCodeSVG
|
||||
value={url || 'https://www.qrmaster.net'}
|
||||
size={180}
|
||||
fgColor={color}
|
||||
bgColor="#ffffff"
|
||||
level="H" // High error correction for logo
|
||||
includeMargin={false}
|
||||
imageSettings={withLogo ? {
|
||||
src: "/logo.svg", // Assuming this exists, or use a placeholder
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
height: 40,
|
||||
width: 40,
|
||||
excavate: true,
|
||||
} : undefined}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<h3 className="font-semibold text-xl mb-6 text-center">Live Preview</h3>
|
||||
|
||||
<div className="flex-1 flex flex-col items-center justify-center space-y-6">
|
||||
<div className="relative group">
|
||||
{/* Frame Rendering */}
|
||||
<div className={cn(
|
||||
"bg-white p-4 rounded-xl shadow-lg transition-all duration-300",
|
||||
frame === 'scan_me' && "pt-12 pb-4 px-4 bg-black rounded-lg",
|
||||
frame === 'phone' && "p-2 border-8 border-gray-800 rounded-[2rem]"
|
||||
)}>
|
||||
{frame === 'scan_me' && (
|
||||
<div className="absolute top-3 left-0 right-0 text-center text-white font-bold tracking-wider text-sm">
|
||||
SCAN ME
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* White background for QR inside frames */}
|
||||
<div className={cn(
|
||||
"bg-white",
|
||||
frame === 'scan_me' && "p-2 rounded",
|
||||
frame === 'phone' && "rounded-2xl overflow-hidden"
|
||||
)}>
|
||||
{renderQR()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-4">
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Enter your website URL..."
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
className="text-center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex flex-wrap gap-2 justify-center">
|
||||
{/* Color Picker */}
|
||||
<div className="flex items-center space-x-2 bg-gray-50 px-3 py-1.5 rounded-full border border-gray-200" title="Choose Color">
|
||||
<div className="relative overflow-hidden w-6 h-6 rounded-full border border-gray-300 shadow-sm cursor-pointer hover:scale-110 transition-transform">
|
||||
<input
|
||||
type="color"
|
||||
value={color}
|
||||
onChange={(e) => setColor(e.target.value)}
|
||||
className="absolute inset-0 w-[150%] h-[150%] -top-1/4 -left-1/4 cursor-pointer p-0 border-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Logo Toggle */}
|
||||
<Button
|
||||
variant={withLogo ? 'secondary' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setWithLogo(!withLogo)}
|
||||
className="text-xs h-9"
|
||||
title="Toggle Logo"
|
||||
>
|
||||
<ImageIcon className="w-4 h-4 mr-1" />
|
||||
{withLogo ? 'Logo On' : 'Logo'}
|
||||
</Button>
|
||||
|
||||
{/* Frame Toggle */}
|
||||
<Button
|
||||
variant={frame !== 'none' ? 'secondary' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setFrame(prev => prev === 'none' ? 'scan_me' : prev === 'scan_me' ? 'phone' : 'none')}
|
||||
className="text-xs h-9"
|
||||
title="Change Frame"
|
||||
>
|
||||
<ScanLine className="w-4 h-4 mr-1" />
|
||||
Frame
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setUrl('');
|
||||
setColor('#000000');
|
||||
setWithLogo(false);
|
||||
setFrame('none');
|
||||
}}
|
||||
className="text-xs text-gray-500 hover:text-gray-900 h-9 px-2"
|
||||
title="Reset"
|
||||
>
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 space-y-3">
|
||||
<Link href="/create" className="block">
|
||||
<Button className="w-full" size="lg">
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download High-Res
|
||||
</Button>
|
||||
</Link>
|
||||
<div className="text-center">
|
||||
<p className="text-xs text-gray-500">
|
||||
Unlock gradients, custom shapes & analytics →{' '}
|
||||
<Link href="/signup" className="text-primary-600 hover:underline font-medium">
|
||||
Try Pro Free
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user