Initial commit - QR Master application
This commit is contained in:
63
src/components/marketing/FAQ.tsx
Normal file
63
src/components/marketing/FAQ.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
interface FAQProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const FAQ: React.FC<FAQProps> = ({ t }) => {
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||
|
||||
const questions = [
|
||||
'account',
|
||||
'static_vs_dynamic',
|
||||
'forever',
|
||||
'file_type',
|
||||
'password',
|
||||
'analytics',
|
||||
'privacy',
|
||||
'bulk',
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-gray-50">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t('faq.title')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
{questions.map((key, index) => (
|
||||
<Card key={key} className="cursor-pointer" onClick={() => setOpenIndex(openIndex === index ? null : index)}>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
{t(`faq.questions.${key}.question`)}
|
||||
</h3>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-500 transition-transform ${openIndex === index ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{openIndex === index && (
|
||||
<div className="mt-4 text-gray-600">
|
||||
{t(`faq.questions.${key}.answer`)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
97
src/components/marketing/Features.tsx
Normal file
97
src/components/marketing/Features.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
|
||||
interface FeaturesProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Features: React.FC<FeaturesProps> = ({ t }) => {
|
||||
const features = [
|
||||
{
|
||||
key: 'analytics',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-blue-600 bg-blue-100',
|
||||
},
|
||||
{
|
||||
key: 'customization',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-purple-600 bg-purple-100',
|
||||
},
|
||||
{
|
||||
key: 'bulk',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-green-600 bg-green-100',
|
||||
},
|
||||
{
|
||||
key: 'integrations',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 4a2 2 0 114 0v1a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-1a2 2 0 100 4h1a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-1a2 2 0 10-4 0v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-3a1 1 0 00-1-1H4a2 2 0 110-4h1a1 1 0 001-1V7a1 1 0 011-1h3a1 1 0 001-1V4z" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-orange-600 bg-orange-100',
|
||||
},
|
||||
{
|
||||
key: 'api',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-indigo-600 bg-indigo-100',
|
||||
},
|
||||
{
|
||||
key: 'support',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-red-600 bg-red-100',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-gray-50">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t('features.title')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
||||
{features.map((feature) => (
|
||||
<Card key={feature.key} hover>
|
||||
<CardHeader>
|
||||
<div className={`w-12 h-12 rounded-lg ${feature.color} flex items-center justify-center mb-4`}>
|
||||
{feature.icon}
|
||||
</div>
|
||||
<CardTitle>{t(`features.${feature.key}.title`)}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600">
|
||||
{t(`features.${feature.key}.description`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
83
src/components/marketing/Hero.tsx
Normal file
83
src/components/marketing/Hero.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
interface HeroProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Hero: React.FC<HeroProps> = ({ t }) => {
|
||||
const templateCards = [
|
||||
{ title: 'Restaurant Menu', color: 'bg-pink-100', icon: '🍽️' },
|
||||
{ title: 'Business Card', color: 'bg-blue-100', icon: '💼' },
|
||||
{ title: 'Event Tickets', color: 'bg-green-100', icon: '🎫' },
|
||||
{ title: 'WiFi Access', color: 'bg-purple-100', icon: '📶' },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 py-20">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
{/* Left Content */}
|
||||
<div className="space-y-8">
|
||||
<Badge variant="info" className="inline-flex items-center space-x-2">
|
||||
<span>{t('hero.badge')}</span>
|
||||
</Badge>
|
||||
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
{t('hero.title')}
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-600 leading-relaxed">
|
||||
{t('hero.subtitle')}
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{t('hero.features', { returnObjects: true }).map((feature: string, index: number) => (
|
||||
<div key={index} className="flex items-center space-x-3">
|
||||
<div className="flex-shrink-0 w-5 h-5 bg-success-500 rounded-full flex items-center justify-center">
|
||||
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Button size="lg" className="text-lg px-8 py-4">
|
||||
{t('hero.cta_primary')}
|
||||
</Button>
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-4">
|
||||
{t('hero.cta_secondary')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Preview Widget */}
|
||||
<div className="relative">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{templateCards.map((card, index) => (
|
||||
<Card key={index} className={`${card.color} border-0 p-6 text-center hover:scale-105 transition-transform`}>
|
||||
<div className="text-3xl mb-2">{card.icon}</div>
|
||||
<h3 className="font-semibold text-gray-800">{card.title}</h3>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -top-4 -right-4 bg-success-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg">
|
||||
{t('hero.engagement_badge')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
220
src/components/marketing/InstantGenerator.tsx
Normal file
220
src/components/marketing/InstantGenerator.tsx
Normal file
@@ -0,0 +1,220 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { calculateContrast } from '@/lib/utils';
|
||||
|
||||
interface InstantGeneratorProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const InstantGenerator: React.FC<InstantGeneratorProps> = ({ t }) => {
|
||||
const [url, setUrl] = useState('https://example.com');
|
||||
const [foregroundColor, setForegroundColor] = useState('#000000');
|
||||
const [backgroundColor, setBackgroundColor] = useState('#FFFFFF');
|
||||
const [cornerStyle, setCornerStyle] = useState('square');
|
||||
const [size, setSize] = useState(200);
|
||||
|
||||
const contrast = calculateContrast(foregroundColor, backgroundColor);
|
||||
const hasGoodContrast = contrast >= 4.5;
|
||||
|
||||
const downloadQR = (format: 'svg' | 'png') => {
|
||||
const svg = document.querySelector('#instant-qr-preview svg');
|
||||
if (!svg || !url) return;
|
||||
|
||||
if (format === 'svg') {
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml' });
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = 'qrcode.svg';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
} else {
|
||||
// Convert SVG to PNG using Canvas
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = new Image();
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
img.onload = () => {
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
if (ctx) {
|
||||
ctx.fillStyle = backgroundColor;
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
ctx.drawImage(img, 0, 0, size, size);
|
||||
}
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = 'qrcode.png';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
});
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-gray-50">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t('generator.title')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-12 max-w-6xl mx-auto">
|
||||
{/* Left Form */}
|
||||
<Card className="space-y-6">
|
||||
<Input
|
||||
label="URL"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder={t('generator.url_placeholder')}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t('generator.foreground')}
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="color"
|
||||
value={foregroundColor}
|
||||
onChange={(e) => setForegroundColor(e.target.value)}
|
||||
className="w-12 h-10 rounded border border-gray-300"
|
||||
/>
|
||||
<Input
|
||||
value={foregroundColor}
|
||||
onChange={(e) => setForegroundColor(e.target.value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t('generator.background')}
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="color"
|
||||
value={backgroundColor}
|
||||
onChange={(e) => setBackgroundColor(e.target.value)}
|
||||
className="w-12 h-10 rounded border border-gray-300"
|
||||
/>
|
||||
<Input
|
||||
value={backgroundColor}
|
||||
onChange={(e) => setBackgroundColor(e.target.value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t('generator.corners')}
|
||||
</label>
|
||||
<select
|
||||
value={cornerStyle}
|
||||
onChange={(e) => setCornerStyle(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
>
|
||||
<option value="square">Square</option>
|
||||
<option value="rounded">Rounded</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t('generator.size')}
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="100"
|
||||
max="400"
|
||||
value={size}
|
||||
onChange={(e) => setSize(Number(e.target.value))}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="text-sm text-gray-500 text-center mt-1">{size}px</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Badge variant={hasGoodContrast ? 'success' : 'warning'}>
|
||||
{hasGoodContrast ? t('generator.contrast_good') : 'Low contrast'}
|
||||
</Badge>
|
||||
<div className="text-sm text-gray-500">
|
||||
Contrast: {contrast.toFixed(1)}:1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-3">
|
||||
<Button variant="outline" className="flex-1" onClick={() => downloadQR('svg')}>
|
||||
{t('generator.download_svg')}
|
||||
</Button>
|
||||
<Button variant="outline" className="flex-1" onClick={() => downloadQR('png')}>
|
||||
{t('generator.download_png')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button className="w-full">
|
||||
{t('generator.save_track')}
|
||||
</Button>
|
||||
</Card>
|
||||
|
||||
{/* Right Preview */}
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<Card className="text-center p-8">
|
||||
<h3 className="text-lg font-semibold mb-4">{t('generator.live_preview')}</h3>
|
||||
<div id="instant-qr-preview" className="flex justify-center mb-4">
|
||||
{url ? (
|
||||
<div className={`${cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''}`}>
|
||||
<QRCodeSVG
|
||||
value={url}
|
||||
size={Math.min(size, 200)}
|
||||
fgColor={foregroundColor}
|
||||
bgColor={backgroundColor}
|
||||
level="M"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="bg-gray-200 flex items-center justify-center text-gray-500"
|
||||
style={{ width: 200, height: 200 }}
|
||||
>
|
||||
Enter URL
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 mb-2">URL</div>
|
||||
<div className="text-xs text-gray-500">{t('generator.demo_note')}</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
94
src/components/marketing/Pricing.tsx
Normal file
94
src/components/marketing/Pricing.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
|
||||
interface PricingProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Pricing: React.FC<PricingProps> = ({ t }) => {
|
||||
const plans = [
|
||||
{
|
||||
key: 'free',
|
||||
popular: false,
|
||||
},
|
||||
{
|
||||
key: 'pro',
|
||||
popular: true,
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
popular: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t('pricing.title')}
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600">
|
||||
{t('pricing.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||
{plans.map((plan) => (
|
||||
<Card
|
||||
key={plan.key}
|
||||
className={plan.popular ? 'border-primary-500 shadow-xl relative' : ''}
|
||||
>
|
||||
{plan.popular && (
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2">
|
||||
<Badge variant="info" className="px-3 py-1">
|
||||
{t(`pricing.${plan.key}.badge`)}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CardHeader className="text-center pb-8">
|
||||
<CardTitle className="text-2xl mb-4">
|
||||
{t(`pricing.${plan.key}.title`)}
|
||||
</CardTitle>
|
||||
<div className="flex items-baseline justify-center">
|
||||
<span className="text-4xl font-bold">
|
||||
{t(`pricing.${plan.key}.price`)}
|
||||
</span>
|
||||
<span className="text-gray-600 ml-2">
|
||||
{t(`pricing.${plan.key}.period`)}
|
||||
</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-4">
|
||||
<ul className="space-y-3">
|
||||
{t(`pricing.${plan.key}.features`, { returnObjects: true }).map((feature: string, index: number) => (
|
||||
<li key={index} className="flex items-start space-x-3">
|
||||
<svg className="w-5 h-5 text-success-500 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<Button
|
||||
variant={plan.popular ? 'primary' : 'outline'}
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
69
src/components/marketing/StaticVsDynamic.tsx
Normal file
69
src/components/marketing/StaticVsDynamic.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
|
||||
interface StaticVsDynamicProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const StaticVsDynamic: React.FC<StaticVsDynamicProps> = ({ t }) => {
|
||||
return (
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="grid lg:grid-cols-2 gap-8 max-w-6xl mx-auto">
|
||||
{/* Static QR Codes */}
|
||||
<Card className="relative">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-2xl">{t('static_vs_dynamic.static.title')}</CardTitle>
|
||||
<Badge variant="success">{t('static_vs_dynamic.static.subtitle')}</Badge>
|
||||
</div>
|
||||
<p className="text-gray-600">{t('static_vs_dynamic.static.description')}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-3">
|
||||
{t('static_vs_dynamic.static.features', { returnObjects: true }).map((feature: string, index: number) => (
|
||||
<li key={index} className="flex items-center space-x-3">
|
||||
<div className="flex-shrink-0 w-5 h-5 bg-gray-400 rounded-full flex items-center justify-center">
|
||||
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Dynamic QR Codes */}
|
||||
<Card className="relative border-primary-200 bg-primary-50">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-2xl">{t('static_vs_dynamic.dynamic.title')}</CardTitle>
|
||||
<Badge variant="info">{t('static_vs_dynamic.dynamic.subtitle')}</Badge>
|
||||
</div>
|
||||
<p className="text-gray-600">{t('static_vs_dynamic.dynamic.description')}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-3">
|
||||
{t('static_vs_dynamic.dynamic.features', { returnObjects: true }).map((feature: string, index: number) => (
|
||||
<li key={index} className="flex items-center space-x-3">
|
||||
<div className="flex-shrink-0 w-5 h-5 bg-primary-500 rounded-full flex items-center justify-center">
|
||||
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
35
src/components/marketing/StatsStrip.tsx
Normal file
35
src/components/marketing/StatsStrip.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface StatsStripProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const StatsStrip: React.FC<StatsStripProps> = ({ t }) => {
|
||||
const stats = [
|
||||
{ key: 'users', value: '10,000+', label: t('trust.users') },
|
||||
{ key: 'codes', value: '500,000+', label: t('trust.codes') },
|
||||
{ key: 'scans', value: '50M+', label: t('trust.scans') },
|
||||
{ key: 'countries', value: '120+', label: t('trust.countries') },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-gray-50">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{stats.map((stat, index) => (
|
||||
<div key={stat.key} className="text-center">
|
||||
<div className="text-3xl lg:text-4xl font-bold text-primary-600 mb-2">
|
||||
{stat.value}
|
||||
</div>
|
||||
<div className="text-gray-600 font-medium">
|
||||
{stat.label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
70
src/components/marketing/TemplateCards.tsx
Normal file
70
src/components/marketing/TemplateCards.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
interface TemplateCardsProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const TemplateCards: React.FC<TemplateCardsProps> = ({ t }) => {
|
||||
const templates = [
|
||||
{
|
||||
key: 'restaurant',
|
||||
title: t('templates.restaurant'),
|
||||
icon: '🍽️',
|
||||
color: 'bg-red-50 border-red-200',
|
||||
iconBg: 'bg-red-100',
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
title: t('templates.business'),
|
||||
icon: '💼',
|
||||
color: 'bg-blue-50 border-blue-200',
|
||||
iconBg: 'bg-blue-100',
|
||||
},
|
||||
{
|
||||
key: 'wifi',
|
||||
title: t('templates.wifi'),
|
||||
icon: '📶',
|
||||
color: 'bg-purple-50 border-purple-200',
|
||||
iconBg: 'bg-purple-100',
|
||||
},
|
||||
{
|
||||
key: 'event',
|
||||
title: t('templates.event'),
|
||||
icon: '🎫',
|
||||
color: 'bg-green-50 border-green-200',
|
||||
iconBg: 'bg-green-100',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t('templates.title')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{templates.map((template) => (
|
||||
<Card key={template.key} className={`${template.color} text-center hover:scale-105 transition-transform cursor-pointer`}>
|
||||
<div className={`w-16 h-16 ${template.iconBg} rounded-full flex items-center justify-center mx-auto mb-4`}>
|
||||
<span className="text-2xl">{template.icon}</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
{template.title}
|
||||
</h3>
|
||||
<Button variant="outline" size="sm" className="w-full">
|
||||
{t('templates.use_template')}
|
||||
</Button>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user