SEO: Fix structured data validation errors, delete static sitemap, and update indexing scripts
This commit is contained in:
289
src/app/(main)/(marketing)/MarketingLayout.tsx
Normal file
289
src/app/(main)/(marketing)/MarketingLayout.tsx
Normal file
@@ -0,0 +1,289 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Footer } from '@/components/ui/Footer';
|
||||
import en from '@/i18n/en.json';
|
||||
import { ChevronDown, Wifi, Contact, MessageCircle, QrCode, Link2, Type, Mail, MessageSquare, Phone, Calendar, MapPin, Facebook, Instagram, Twitter, Youtube, Music, Bitcoin, CreditCard, Video, Users, Barcode as BarcodeIcon } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
export default function MarketingLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [toolsOpen, setToolsOpen] = useState(false);
|
||||
const [mobileToolsOpen, setMobileToolsOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setScrolled(window.scrollY > 20);
|
||||
};
|
||||
|
||||
// Check immediately on mount
|
||||
handleScroll();
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
// Close simple menus when path changes
|
||||
useEffect(() => {
|
||||
setMobileMenuOpen(false);
|
||||
setToolsOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
// Default to English for general marketing pages
|
||||
const t = en;
|
||||
|
||||
const tools = [
|
||||
{ name: 'URL / Link', description: 'Link to any website', href: '/tools/url-qr-code', icon: Link2, color: 'text-blue-500', bgColor: 'bg-blue-50' },
|
||||
{ name: 'Text', description: 'Plain text message', href: '/tools/text-qr-code', icon: Type, color: 'text-slate-500', bgColor: 'bg-slate-50' },
|
||||
{ name: 'WiFi', description: 'Share WiFi credentials', href: '/tools/wifi-qr-code', icon: Wifi, color: 'text-indigo-500', bgColor: 'bg-indigo-50' },
|
||||
{ name: 'VCard', description: 'Digital business card', href: '/tools/vcard-qr-code', icon: Contact, color: 'text-pink-500', bgColor: 'bg-pink-50' },
|
||||
{ name: 'WhatsApp', description: 'Start a chat', href: '/tools/whatsapp-qr-code', icon: MessageCircle, color: 'text-green-500', bgColor: 'bg-green-50' },
|
||||
{ name: 'Email', description: 'Compose an email', href: '/tools/email-qr-code', icon: Mail, color: 'text-amber-500', bgColor: 'bg-amber-50' },
|
||||
{ name: 'SMS', description: 'Send a text message', href: '/tools/sms-qr-code', icon: MessageSquare, color: 'text-cyan-500', bgColor: 'bg-cyan-50' },
|
||||
{ name: 'Call', description: 'Start a call', href: '/tools/call-qr-code-generator', icon: Phone, color: 'text-violet-500', bgColor: 'bg-violet-50' },
|
||||
{ name: 'Event', description: 'Add calendar event', href: '/tools/event-qr-code', icon: Calendar, color: 'text-red-500', bgColor: 'bg-red-50' },
|
||||
{ name: 'Location', description: 'Share a place', href: '/tools/geolocation-qr-code', icon: MapPin, color: 'text-emerald-500', bgColor: 'bg-emerald-50' },
|
||||
{ name: 'Facebook', description: 'Facebook profile/page', href: '/tools/facebook-qr-code', icon: Facebook, color: 'text-blue-600', bgColor: 'bg-blue-50' },
|
||||
{ name: 'Instagram', description: 'Instagram profile', href: '/tools/instagram-qr-code', icon: Instagram, color: 'text-pink-600', bgColor: 'bg-pink-50' },
|
||||
{ name: 'Twitter / X', description: 'Twitter profile', href: '/tools/twitter-qr-code', icon: Twitter, color: 'text-sky-500', bgColor: 'bg-sky-50' },
|
||||
{ name: 'YouTube', description: 'YouTube video/channel', href: '/tools/youtube-qr-code', icon: Youtube, color: 'text-red-600', bgColor: 'bg-red-50' },
|
||||
{ name: 'TikTok', description: 'TikTok profile', href: '/tools/tiktok-qr-code', icon: Music, color: 'text-slate-800', bgColor: 'bg-slate-100' },
|
||||
{ name: 'Crypto', description: 'Share wallet address', href: '/tools/crypto-qr-code', icon: Bitcoin, color: 'text-orange-500', bgColor: 'bg-orange-50' },
|
||||
{ name: 'PayPal', description: 'Receive payments', href: '/tools/paypal-qr-code', icon: CreditCard, color: 'text-blue-700', bgColor: 'bg-blue-50' },
|
||||
{ name: 'Zoom', description: 'Join Zoom meeting', href: '/tools/zoom-qr-code', icon: Video, color: 'text-sky-500', bgColor: 'bg-sky-50' },
|
||||
{ name: 'Teams', description: 'Join Teams meeting', href: '/tools/teams-qr-code', icon: Users, color: 'text-violet-500', bgColor: 'bg-violet-50' },
|
||||
{ name: 'Barcode', description: 'Generate barcodes', href: '/tools/barcode-generator', icon: BarcodeIcon, color: 'text-slate-800', bgColor: 'bg-slate-100' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Server-rendered navigation links for SEO (crawlers) - Placed first for priority */}
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
<nav aria-label="Site Map">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/pricing">Pricing</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
<li><a href="/login">Login</a></li>
|
||||
<li><a href="/signup">Sign Up</a></li>
|
||||
{/* Tools */}
|
||||
<li><a href="/tools/url-qr-code">URL QR Code</a></li>
|
||||
<li><a href="/tools/text-qr-code">Text QR Code</a></li>
|
||||
<li><a href="/tools/wifi-qr-code">WiFi QR Code</a></li>
|
||||
<li><a href="/tools/vcard-qr-code">vCard QR Code</a></li>
|
||||
<li><a href="/tools/whatsapp-qr-code">WhatsApp QR Code</a></li>
|
||||
<li><a href="/tools/email-qr-code">Email QR Code</a></li>
|
||||
<li><a href="/tools/sms-qr-code">SMS QR Code</a></li>
|
||||
<li><a href="/tools/call-qr-code-generator">Call QR Code</a></li>
|
||||
<li><a href="/tools/event-qr-code">Event QR Code</a></li>
|
||||
<li><a href="/tools/geolocation-qr-code">Location QR Code</a></li>
|
||||
<li><a href="/tools/facebook-qr-code">Facebook QR Code</a></li>
|
||||
<li><a href="/tools/instagram-qr-code">Instagram QR Code</a></li>
|
||||
<li><a href="/tools/twitter-qr-code">Twitter QR Code</a></li>
|
||||
<li><a href="/tools/youtube-qr-code">YouTube QR Code</a></li>
|
||||
<li><a href="/tools/tiktok-qr-code">TikTok QR Code</a></li>
|
||||
<li><a href="/tools/crypto-qr-code">Crypto QR Code</a></li>
|
||||
<li><a href="/tools/paypal-qr-code">PayPal QR Code</a></li>
|
||||
<li><a href="/tools/zoom-qr-code">Zoom QR Code</a></li>
|
||||
<li><a href="/tools/teams-qr-code">Teams QR Code</a></li>
|
||||
<li><a href="/tools/barcode-generator">Barcode Generator</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<header
|
||||
className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-slate-200 shadow-sm"
|
||||
|
||||
>
|
||||
<nav className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl h-20 flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="flex items-center space-x-2.5 group">
|
||||
<div className="relative w-9 h-9 flex items-center justify-center bg-indigo-600 rounded-lg shadow-indigo-200 shadow-lg group-hover:scale-105 transition-transform duration-200">
|
||||
<QrCode className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<span className="text-xl font-bold text-slate-900 tracking-tight group-hover:text-indigo-600 transition-colors">QR Master</span>
|
||||
</Link>
|
||||
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden md:flex items-center space-x-1">
|
||||
|
||||
{/* Tools Dropdown */}
|
||||
<div
|
||||
className="relative group px-3 py-2"
|
||||
onMouseEnter={() => setToolsOpen(true)}
|
||||
onMouseLeave={() => setToolsOpen(false)}
|
||||
>
|
||||
<button className="flex items-center space-x-1 text-sm font-medium text-slate-600 group-hover:text-slate-900 transition-colors">
|
||||
<span>{t.nav.tools}</span>
|
||||
<ChevronDown className={cn("w-4 h-4 transition-transform duration-200", toolsOpen && "rotate-180")} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{toolsOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 10 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
className="absolute left-1/2 -translate-x-1/2 top-full mt-2 w-[750px] bg-white rounded-2xl shadow-lg border border-slate-100 p-4 overflow-hidden"
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-1">
|
||||
{tools.map((tool) => (
|
||||
<Link
|
||||
key={tool.name}
|
||||
href={tool.href}
|
||||
className="flex items-center space-x-3 p-2.5 rounded-xl transition-colors hover:bg-slate-50"
|
||||
>
|
||||
<div className={cn("p-2 rounded-lg shrink-0", tool.bgColor, tool.color)}>
|
||||
<tool.icon className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-slate-900">{tool.name}</div>
|
||||
<p className="text-xs text-slate-500 leading-snug">{tool.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-3 pt-3 border-t border-slate-100 -mx-4 -mb-4 px-4 py-3 text-center bg-slate-50/50">
|
||||
<p className="text-xs text-slate-500 font-medium">{t.nav.all_free}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<Link href="/#features" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.features}
|
||||
</Link>
|
||||
<Link href="/#pricing" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.pricing}
|
||||
</Link>
|
||||
<Link href="/blog" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.blog}
|
||||
</Link>
|
||||
<Link href="/#faq" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.faq}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="hidden md:flex items-center space-x-4">
|
||||
<Link href="/login" className="text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.login}
|
||||
</Link>
|
||||
|
||||
<Link href="/signup">
|
||||
<Button className={cn(
|
||||
"font-semibold shadow-lg shadow-indigo-500/20 transition-all hover:scale-105",
|
||||
scrolled ? "bg-blue-600 text-white hover:bg-blue-700" : "bg-blue-600 text-white hover:bg-blue-700"
|
||||
)}>
|
||||
{t.nav.cta || "Get Started Free"}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button - Always dark */}
|
||||
<button
|
||||
className="md:hidden p-2 text-slate-900"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{mobileMenuOpen ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<AnimatePresence>
|
||||
{mobileMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="md:hidden bg-white border-b border-slate-100 overflow-hidden"
|
||||
>
|
||||
<div className="container mx-auto px-4 py-6 space-y-2">
|
||||
{/* Free Tools Accordion */}
|
||||
<button
|
||||
onClick={() => setMobileToolsOpen(!mobileToolsOpen)}
|
||||
className="flex items-center justify-between w-full px-4 py-3 rounded-xl hover:bg-slate-50 text-slate-700 font-semibold"
|
||||
>
|
||||
<span>{t.nav.tools}</span>
|
||||
<ChevronDown className={cn("w-5 h-5 transition-transform", mobileToolsOpen && "rotate-180")} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{mobileToolsOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="max-h-[50vh] overflow-y-auto pl-4 space-y-1 border-l-2 border-slate-100 ml-4">
|
||||
{tools.map((tool) => (
|
||||
<Link
|
||||
key={tool.name}
|
||||
href={tool.href}
|
||||
className="flex items-center gap-3 px-4 py-2.5 rounded-lg hover:bg-slate-50 text-slate-600 text-sm"
|
||||
onClick={() => { setMobileMenuOpen(false); setMobileToolsOpen(false); }}
|
||||
>
|
||||
<tool.icon className={cn("w-4 h-4", tool.color)} />
|
||||
{tool.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className="h-px bg-slate-100 my-2"></div>
|
||||
|
||||
<Link href="/#features" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.features}</Link>
|
||||
<Link href="/#pricing" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.pricing}</Link>
|
||||
<Link href="/blog" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.blog}</Link>
|
||||
<Link href="/#faq" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.faq}</Link>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
||||
<Link href="/login" onClick={() => setMobileMenuOpen(false)}>
|
||||
<Button variant="outline" className="w-full justify-center">{t.nav.login}</Button>
|
||||
</Link>
|
||||
<Link href="/signup" onClick={() => setMobileMenuOpen(false)}>
|
||||
<Button className="w-full justify-center bg-indigo-600 hover:bg-indigo-700">{t.nav.cta}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="pt-20">
|
||||
{/* Server-rendered navigation links for SEO (crawlers) */}
|
||||
|
||||
{children}
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<Footer t={t} />
|
||||
</div >
|
||||
);
|
||||
}
|
||||
157
src/app/(main)/(marketing)/blog/[slug]/page.tsx
Normal file
157
src/app/(main)/(marketing)/blog/[slug]/page.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { notFound } from 'next/navigation';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs from '@/components/Breadcrumbs';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { blogPosts } from '@/lib/blog-data';
|
||||
|
||||
interface PageProps {
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return Object.keys(blogPosts).map((slug) => ({
|
||||
slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: PageProps): Metadata {
|
||||
const post = blogPosts[params.slug];
|
||||
|
||||
if (!post) {
|
||||
notFound();
|
||||
return {} as Metadata; // Typescript satisfaction (unreachable)
|
||||
}
|
||||
|
||||
return {
|
||||
title: {
|
||||
absolute: `${post.title} | QR Master Blog`,
|
||||
},
|
||||
description: post.excerpt,
|
||||
openGraph: {
|
||||
title: post.title,
|
||||
description: post.excerpt,
|
||||
type: 'article',
|
||||
publishedTime: post.datePublished,
|
||||
modifiedTime: post.dateModified,
|
||||
authors: [post.author],
|
||||
images: [
|
||||
{
|
||||
url: post.image,
|
||||
alt: post.imageAlt,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: post.title,
|
||||
description: post.excerpt,
|
||||
images: [post.image],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function BlogPostPage({ params }: PageProps) {
|
||||
const post = blogPosts[params.slug];
|
||||
|
||||
if (!post) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BlogPosting',
|
||||
headline: post.title,
|
||||
image: post.image,
|
||||
datePublished: post.datePublished,
|
||||
dateModified: post.dateModified,
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: post.author,
|
||||
url: post.authorUrl,
|
||||
},
|
||||
};
|
||||
|
||||
const breadcrumbItems = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Blog', url: '/blog' },
|
||||
{ name: post.title, url: `/blog/${post.slug}` },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[jsonLd]} />
|
||||
|
||||
<div className="min-h-screen bg-white pb-20">
|
||||
{/* Hero Header */}
|
||||
<div className="bg-gray-50 border-b border-gray-100">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-12 max-w-4xl">
|
||||
<Breadcrumbs items={breadcrumbItems} className="mb-8" />
|
||||
|
||||
<Badge variant="info" className="mb-6">
|
||||
{post.category}
|
||||
</Badge>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 leading-tight mb-6">
|
||||
{post.title}
|
||||
</h1>
|
||||
|
||||
<div className="flex items-center text-gray-600 mb-8 space-x-6 text-sm">
|
||||
<div className="flex items-center">
|
||||
<span className="font-medium text-gray-900 mr-2">{post.author}</span>
|
||||
</div>
|
||||
<div>•</div>
|
||||
<div>{post.date}</div>
|
||||
<div>•</div>
|
||||
<div>{post.readTime} read</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Featured Image */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl -mt-8 mb-12">
|
||||
<div className="relative aspect-video w-full overflow-hidden rounded-2xl shadow-xl">
|
||||
<Image
|
||||
src={post.image}
|
||||
alt={post.imageAlt}
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<article className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-3xl">
|
||||
<div
|
||||
className="prose prose-lg prose-blue max-w-none hover:prose-a:text-blue-600 transition-colors"
|
||||
dangerouslySetInnerHTML={{ __html: post.content }}
|
||||
/>
|
||||
|
||||
{/* Share / CTA */}
|
||||
<div className="mt-16 pt-8 border-t border-gray-200">
|
||||
<div className="bg-blue-50 rounded-2xl p-8 text-center">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">
|
||||
Enjoyed this article?
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-6 text-lg">
|
||||
Create your first dynamic QR code for free and start tracking your campaigns today.
|
||||
</p>
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="px-8">
|
||||
Create Free QR Code
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
182
src/app/(main)/(marketing)/blog/page.tsx
Normal file
182
src/app/(main)/(marketing)/blog/page.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { websiteSchema, breadcrumbSchema } from '@/lib/schema';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
|
||||
function truncateAtWord(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) return text;
|
||||
const truncated = text.slice(0, maxLength);
|
||||
const lastSpace = truncated.lastIndexOf(' ');
|
||||
return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated;
|
||||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const title = truncateAtWord('QR Insights: Latest QR Strategies', 60);
|
||||
const description = truncateAtWord(
|
||||
'Expert guides on QR code strategies, marketing campaigns, tracking analytics, and best practices for small businesses and enterprises.',
|
||||
160
|
||||
);
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/blog',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/blog',
|
||||
en: 'https://www.qrmaster.net/blog',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
url: 'https://www.qrmaster.net/blog',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const blogPosts = [
|
||||
// NEW POSTS (January 2026)
|
||||
{
|
||||
slug: 'qr-code-restaurant-menu',
|
||||
title: 'How to Create a QR Code for Restaurant Menu',
|
||||
excerpt: 'Step-by-step guide to creating digital menu QR codes for your restaurant. Learn best practices for touchless menus, placement tips, and tracking.',
|
||||
date: 'January 5, 2026',
|
||||
readTime: '12 Min',
|
||||
category: 'Restaurant',
|
||||
image: '/blog/restaurant-qr-menu.png',
|
||||
},
|
||||
{
|
||||
slug: 'vcard-qr-code-generator',
|
||||
title: 'Free vCard QR Code Generator: Digital Business Cards',
|
||||
excerpt: 'Create professional vCard QR codes for digital business cards. Share contact info instantly with a scan—includes templates and best practices.',
|
||||
date: 'January 5, 2026',
|
||||
readTime: '10 Min',
|
||||
category: 'Business Cards',
|
||||
image: '/blog/vcard-qr-code.png',
|
||||
},
|
||||
{
|
||||
slug: 'qr-code-small-business',
|
||||
title: 'Best QR Code Generator for Small Business: 2025 Guide',
|
||||
excerpt: 'Find the best QR code solution for your small business. Compare features, pricing, and use cases for marketing, payments, and operations.',
|
||||
date: 'January 5, 2026',
|
||||
readTime: '14 Min',
|
||||
category: 'Business',
|
||||
image: '/blog/small-business-qr.png',
|
||||
},
|
||||
{
|
||||
slug: 'qr-code-print-size-guide',
|
||||
title: 'QR Code Print Size Guide: Minimum Sizes for Every Use Case',
|
||||
excerpt: 'Complete guide to QR code print sizes. Learn minimum dimensions for business cards, posters, banners, and more to ensure reliable scanning.',
|
||||
date: 'January 5, 2026',
|
||||
readTime: '8 Min',
|
||||
category: 'Printing',
|
||||
image: '/blog/qr-print-sizes.png',
|
||||
},
|
||||
// EXISTING POSTS
|
||||
{
|
||||
slug: 'qr-code-tracking-guide-2025',
|
||||
title: 'QR Code Tracking: Complete Guide 2025',
|
||||
excerpt: 'Learn how to track QR code scans with real-time analytics. Compare free vs paid tracking tools, setup Google Analytics, and measure ROI.',
|
||||
date: 'October 18, 2025',
|
||||
readTime: '12 Min',
|
||||
category: 'Tracking & Analytics',
|
||||
image: '/blog/1-hero.webp',
|
||||
},
|
||||
{
|
||||
slug: 'dynamic-vs-static-qr-codes',
|
||||
title: 'Dynamic vs Static QR Codes: Which Should You Use?',
|
||||
excerpt: 'Understand the difference between static and dynamic QR codes. Learn when to use each type, pros/cons, and how dynamic QR codes save money.',
|
||||
date: 'October 17, 2025',
|
||||
readTime: '10 Min',
|
||||
category: 'QR Code Basics',
|
||||
image: '/blog/2-hero.webp',
|
||||
},
|
||||
{
|
||||
slug: 'bulk-qr-code-generator-excel',
|
||||
title: 'How to Generate Bulk QR Codes from Excel',
|
||||
excerpt: 'Generate hundreds of QR codes from Excel or CSV files in minutes. Step-by-step guide with templates, best practices, and free tools.',
|
||||
date: 'October 16, 2025',
|
||||
readTime: '13 Min',
|
||||
category: 'Bulk Generation',
|
||||
image: '/blog/bulk-qr-events-hero.png',
|
||||
},
|
||||
{
|
||||
slug: 'qr-code-analytics',
|
||||
title: 'QR Code Analytics: Track, Measure & Optimize Campaigns',
|
||||
excerpt: 'Learn how to leverage scan analytics, campaign tracking, and dashboard insights to maximize QR code ROI.',
|
||||
date: 'October 16, 2025',
|
||||
readTime: '15 Min',
|
||||
category: 'Analytics',
|
||||
image: '/blog/qr-code-analytics-hero.webp',
|
||||
},
|
||||
];
|
||||
|
||||
export default function BlogPage() {
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Blog', url: '/blog' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[websiteSchema(), breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="py-20 bg-gradient-to-b from-gray-50 to-white">
|
||||
<div className="container mx-auto px-4">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
<div className="text-center mb-16">
|
||||
<h1 className="text-4xl lg:text-5xl font-bold text-gray-900 mb-6">
|
||||
QR Code Insights
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||
Expert guides on dynamic QR codes, campaign tracking, UTM analytics, and smart marketing use cases.
|
||||
Discover how-to tutorials and best practices for QR code analytics.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto">
|
||||
{blogPosts.map((post) => (
|
||||
<Link key={post.slug} href={`/blog/${post.slug}`}>
|
||||
<Card hover className="h-full overflow-hidden shadow-md hover:shadow-xl transition-all duration-300">
|
||||
<div className="relative h-56 overflow-hidden">
|
||||
<Image
|
||||
src={post.image}
|
||||
alt={`${post.title} - QR code guide showing ${post.category.toLowerCase()} strategies`}
|
||||
width={800}
|
||||
height={600}
|
||||
className="w-full h-full object-cover transition-transform duration-500 hover:scale-110"
|
||||
/>
|
||||
</div>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<Badge variant="info">{post.category}</Badge>
|
||||
<span className="text-sm text-gray-500 font-medium">{post.readTime} read</span>
|
||||
</div>
|
||||
<CardTitle className="text-xl leading-tight mb-3">{post.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-gray-600 mb-4 leading-relaxed">{post.excerpt}</p>
|
||||
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
|
||||
<p className="text-sm text-gray-500">{post.date}</p>
|
||||
<span className="text-primary-600 text-sm font-medium">Read more →</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
666
src/app/(main)/(marketing)/bulk-qr-code-generator/page.tsx
Normal file
666
src/app/(main)/(marketing)/bulk-qr-code-generator/page.tsx
Normal file
@@ -0,0 +1,666 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Bulk QR Code Generator - Create 1000s from Excel',
|
||||
},
|
||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding. Perfect for products and events.',
|
||||
keywords: 'bulk qr code generator, batch qr code, qr code from excel, csv qr code generator, mass qr code generation, bulk vcard qr code, bulk qr codes free',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/bulk-qr-code-generator',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/bulk-qr-code-generator',
|
||||
en: 'https://www.qrmaster.net/bulk-qr-code-generator',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Bulk QR Code Generator - Create 1000s from Excel',
|
||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding.',
|
||||
url: 'https://www.qrmaster.net/bulk-qr-code-generator',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
title: 'Bulk QR Code Generator - Create 1000s from Excel',
|
||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function BulkQRCodeGeneratorPage() {
|
||||
const qrCodeTypes = [
|
||||
{
|
||||
type: 'URL',
|
||||
icon: '🌐',
|
||||
title: 'Website Links',
|
||||
description: 'Generate QR codes for websites, landing pages, and online content',
|
||||
format: 'https://example.com/product',
|
||||
example: 'Product Page,URL,https://example.com/product',
|
||||
},
|
||||
{
|
||||
type: 'VCARD',
|
||||
icon: '👤',
|
||||
title: 'Contact Cards',
|
||||
description: 'Create vCard QR codes with contact information',
|
||||
format: 'FirstName,LastName,Email,Phone,Organization,Title',
|
||||
example: 'John Doe,VCARD,John,Doe,john[at]example.com,+1234567890,Company Inc,CEO',
|
||||
},
|
||||
{
|
||||
type: 'GEO',
|
||||
icon: '📍',
|
||||
title: 'Locations',
|
||||
description: 'Generate location QR codes with GPS coordinates',
|
||||
format: 'latitude,longitude,label',
|
||||
example: 'Office Location,GEO,37.7749,-122.4194,Main Office',
|
||||
},
|
||||
{
|
||||
type: 'PHONE',
|
||||
icon: '📞',
|
||||
title: 'Phone Numbers',
|
||||
description: 'Create QR codes that dial phone numbers',
|
||||
format: '+1234567890',
|
||||
example: 'Support Hotline,PHONE,+1234567890',
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
icon: '📝',
|
||||
title: 'Plain Text',
|
||||
description: 'Generate QR codes with any text content',
|
||||
format: 'Your text content here',
|
||||
example: 'Serial Number,TEXT,SN-12345-ABCDE',
|
||||
},
|
||||
];
|
||||
|
||||
const bulkFeatures = [
|
||||
{
|
||||
icon: '📊',
|
||||
title: 'Excel & CSV Import',
|
||||
description: 'Upload Excel or CSV files to generate hundreds of QR codes in seconds. Simple column mapping.',
|
||||
},
|
||||
{
|
||||
icon: '⚡',
|
||||
title: 'Fast Processing',
|
||||
description: 'Generate up to 1000 QR codes in under a minute. Optimized for speed and reliability.',
|
||||
},
|
||||
{
|
||||
icon: '🎨',
|
||||
title: 'Unified Branding',
|
||||
description: 'Apply your logo, colors, and design to all QR codes at once. Consistent brand identity.',
|
||||
},
|
||||
{
|
||||
icon: '📦',
|
||||
title: 'Batch Download',
|
||||
description: 'Download all QR codes as a ZIP file with custom filenames. Organized and ready to use.',
|
||||
},
|
||||
{
|
||||
icon: '📈',
|
||||
title: 'Individual Tracking',
|
||||
description: 'Track each QR code separately. See which products or locations perform best.',
|
||||
},
|
||||
{
|
||||
icon: '🔄',
|
||||
title: 'Update in Bulk',
|
||||
description: 'Edit multiple QR codes at once. Save time when updating campaigns or product info.',
|
||||
},
|
||||
];
|
||||
|
||||
const useCases = [
|
||||
{
|
||||
title: 'Product Labels',
|
||||
icon: '🏷️',
|
||||
description: 'Generate unique QR codes for each product SKU. Link to manuals, warranty info, or product pages.',
|
||||
stats: ['1000+ products', 'Individual tracking', 'Custom naming'],
|
||||
},
|
||||
{
|
||||
title: 'Event Tickets',
|
||||
icon: '🎟️',
|
||||
description: 'Create unique QR codes for every attendee. Enable fast check-ins and track attendance.',
|
||||
stats: ['Unique per ticket', 'Real-time validation', 'Analytics dashboard'],
|
||||
},
|
||||
{
|
||||
title: 'Asset Management',
|
||||
icon: '💼',
|
||||
description: 'Tag equipment, furniture, and assets with unique QR codes. Track location and maintenance.',
|
||||
stats: ['Equipment tracking', 'Maintenance logs', 'Location history'],
|
||||
},
|
||||
{
|
||||
title: 'Marketing Campaigns',
|
||||
icon: '📢',
|
||||
description: 'Generate codes for different locations or channels. Track which campaigns perform best.',
|
||||
stats: ['Location-specific', 'Campaign tracking', 'ROI measurement'],
|
||||
},
|
||||
];
|
||||
|
||||
const howItWorks = [
|
||||
{
|
||||
step: 1,
|
||||
title: 'Prepare Your File',
|
||||
description: 'Create an Excel or CSV file with your URLs, names, and any custom data.',
|
||||
example: 'Product Name | URL | SKU',
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: 'Upload & Map',
|
||||
description: 'Upload your file and map columns to QR code fields. Preview before generating.',
|
||||
example: 'Map columns: Name → Title, URL → Destination',
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: 'Customize Design',
|
||||
description: 'Apply logo, colors, and branding to all QR codes at once. Consistent look.',
|
||||
example: 'Add logo, set colors, choose frame',
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: 'Generate & Download',
|
||||
description: 'Click generate and download all QR codes as PNG files in a ZIP archive.',
|
||||
example: 'product-001.png, product-002.png, ...',
|
||||
},
|
||||
];
|
||||
|
||||
const fileFormat = [
|
||||
{ column: 'name', description: 'QR code title/label', required: true },
|
||||
{ column: 'url', description: 'Destination URL', required: true },
|
||||
{ column: 'description', description: 'Optional description', required: false },
|
||||
{ column: 'tags', description: 'Comma-separated tags', required: false },
|
||||
];
|
||||
|
||||
const softwareSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
'@id': 'https://www.qrmaster.net/bulk-qr-code-generator#software',
|
||||
name: 'QR Master - Bulk QR Code Generator',
|
||||
applicationCategory: 'BusinessApplication',
|
||||
operatingSystem: 'Web Browser',
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'USD',
|
||||
availability: 'https://schema.org/InStock',
|
||||
},
|
||||
aggregateRating: {
|
||||
'@type': 'AggregateRating',
|
||||
ratingValue: '4.8',
|
||||
ratingCount: '980',
|
||||
},
|
||||
description: 'Generate hundreds of QR codes at once from CSV or Excel files. Perfect for products, events, inventory management with custom branding.',
|
||||
featureList: [
|
||||
'Excel and CSV file import',
|
||||
'Generate up to 1000 QR codes at once',
|
||||
'Unified branding and design',
|
||||
'Batch download as ZIP',
|
||||
'Individual tracking per code',
|
||||
'Bulk update capabilities',
|
||||
'Custom filenames',
|
||||
'High-resolution exports',
|
||||
],
|
||||
};
|
||||
|
||||
const howToSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'HowTo',
|
||||
'@id': 'https://www.qrmaster.net/bulk-qr-code-generator#howto',
|
||||
name: 'How to Generate Bulk QR Codes from Excel',
|
||||
description: 'Learn how to create hundreds of QR codes from an Excel or CSV file',
|
||||
totalTime: 'PT10M',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Prepare Excel File',
|
||||
text: 'Create an Excel or CSV file with columns for name, URL, and any custom data you need',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Upload File',
|
||||
text: 'Log into QR Master and upload your file to the bulk generator',
|
||||
url: 'https://www.qrmaster.net/bulk-creation',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Map Columns',
|
||||
text: 'Map your file columns to QR code fields (name, URL, description, etc.)',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Customize Design',
|
||||
text: 'Apply your logo, brand colors, and design settings to all QR codes at once',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Generate and Download',
|
||||
text: 'Click generate and download all QR codes as a ZIP file with custom filenames',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const faqSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
'@id': 'https://www.qrmaster.net/bulk-qr-code-generator#faq',
|
||||
mainEntity: [
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'How many QR codes can I generate at once?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'With QR Master, you can generate up to 1000 QR codes at once from a CSV or Excel file. For larger volumes, contact our enterprise team.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'What file formats are supported?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'QR Master supports CSV (.csv), Excel (.xlsx, .xls), and other spreadsheet formats. Simply ensure your file has columns for name and destination URL.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'Can I apply my branding to all QR codes?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Yes, you can apply your logo, brand colors, and custom design to all QR codes in your bulk generation. All codes will have consistent branding.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'Are bulk generated QR codes trackable?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Yes, each QR code generated in bulk is individually trackable. You can see scans, locations, and analytics for every single code in your dashboard.',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Bulk QR Code Generator', url: '/bulk-qr-code-generator' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-green-50 via-white to-blue-50 py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
<div className="space-y-8">
|
||||
<div className="inline-flex items-center space-x-2 bg-green-100 text-green-800 px-4 py-2 rounded-full text-sm font-semibold">
|
||||
<span>⚡</span>
|
||||
<span>Generate 1000s in Minutes</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
Bulk QR Code Generator
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-600 leading-relaxed">
|
||||
Create hundreds or thousands of QR codes from Excel or CSV files. Generate URLs, vCards, locations, phone numbers, and text QR codes in bulk. Perfect for products, events, inventory, and marketing campaigns.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
'Upload Excel or CSV files',
|
||||
'Generate URLs, vCards, locations & more',
|
||||
'Custom branding on all codes',
|
||||
'Download as organized ZIP',
|
||||
].map((feature, index) => (
|
||||
<div key={index} className="flex items-center space-x-3">
|
||||
<div className="flex-shrink-0 w-5 h-5 bg-green-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">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Start Bulk Generation
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/signup">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Try Single QR First
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Example */}
|
||||
<div className="relative">
|
||||
<Card className="p-6 shadow-2xl">
|
||||
<h3 className="font-semibold text-lg mb-4">Upload Your File</h3>
|
||||
<div className="bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg p-8 text-center mb-4">
|
||||
<div className="text-4xl mb-2">📊</div>
|
||||
<p className="text-gray-600 font-medium mb-1">products.xlsx</p>
|
||||
<p className="text-sm text-gray-500">1,247 rows ready</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
|
||||
<div key={i} className="aspect-square bg-gray-200 rounded flex items-center justify-center text-xs text-gray-500">
|
||||
QR {i}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-center text-sm text-gray-600 mt-4">
|
||||
+ 1,239 more codes
|
||||
</p>
|
||||
</Card>
|
||||
<div className="absolute -top-4 -right-4 bg-green-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg">
|
||||
1000s at Once!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Supported QR Code Types */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Supported QR Code Types
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Generate multiple types of QR codes from your CSV or Excel file. Each type has its own format requirements.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{qrCodeTypes.map((qrType, index) => (
|
||||
<Card key={index} className="p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<div className="text-3xl">{qrType.icon}</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
{qrType.title}
|
||||
</h3>
|
||||
<span className="text-xs font-mono text-gray-500">{qrType.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-600 mb-4 text-sm">
|
||||
{qrType.description}
|
||||
</p>
|
||||
<div className="bg-gray-50 rounded-lg p-3 mb-3">
|
||||
<p className="text-xs font-semibold text-gray-700 mb-1">Format:</p>
|
||||
<code className="text-xs text-gray-900 break-all">{qrType.format}</code>
|
||||
</div>
|
||||
<div className="bg-blue-50 rounded-lg p-3">
|
||||
<p className="text-xs font-semibold text-blue-700 mb-1">CSV Example:</p>
|
||||
<code className="text-xs text-blue-900 break-all">{qrType.example}</code>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-12 bg-gradient-to-r from-blue-50 to-purple-50 rounded-lg p-8">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4 text-center">
|
||||
📥 CSV/Excel File Format
|
||||
</h3>
|
||||
<p className="text-gray-600 text-center mb-6">
|
||||
Your file must have at least these three columns: <code className="bg-white px-2 py-1 rounded">title</code>, <code className="bg-white px-2 py-1 rounded">contentType</code>, and <code className="bg-white px-2 py-1 rounded">content</code>
|
||||
</p>
|
||||
<div className="bg-white rounded-lg p-6 shadow-sm overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b-2 border-gray-300">
|
||||
<th className="text-left py-2 px-3 font-semibold text-gray-700">title</th>
|
||||
<th className="text-left py-2 px-3 font-semibold text-gray-700">contentType</th>
|
||||
<th className="text-left py-2 px-3 font-semibold text-gray-700">content</th>
|
||||
<th className="text-left py-2 px-3 font-semibold text-gray-700">tags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="font-mono text-xs">
|
||||
<tr className="border-b border-gray-200">
|
||||
<td className="py-2 px-3">Product Page</td>
|
||||
<td className="py-2 px-3">URL</td>
|
||||
<td className="py-2 px-3">https://example.com/product</td>
|
||||
<td className="py-2 px-3">product,shop</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-200">
|
||||
<td className="py-2 px-3">John Doe</td>
|
||||
<td className="py-2 px-3">VCARD</td>
|
||||
<td className="py-2 px-3">John,Doe,john<span>@</span>example.com,+1234567890,Company,CEO</td>
|
||||
<td className="py-2 px-3">contact</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-200">
|
||||
<td className="py-2 px-3">Office Location</td>
|
||||
<td className="py-2 px-3">GEO</td>
|
||||
<td className="py-2 px-3">37.7749,-122.4194,Main Office</td>
|
||||
<td className="py-2 px-3">location</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-200">
|
||||
<td className="py-2 px-3">Support Hotline</td>
|
||||
<td className="py-2 px-3">PHONE</td>
|
||||
<td className="py-2 px-3">+1234567890</td>
|
||||
<td className="py-2 px-3">support</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="py-2 px-3">Serial Number</td>
|
||||
<td className="py-2 px-3">TEXT</td>
|
||||
<td className="py-2 px-3">SN-12345-ABCDE</td>
|
||||
<td className="py-2 px-3">product,serial</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Powerful Bulk Generation Features
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Everything you need to create and manage QR codes at scale
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{bulkFeatures.map((feature, index) => (
|
||||
<Card key={index} className="p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="text-4xl mb-4">{feature.icon}</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
{feature.description}
|
||||
</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
{/* How It Works */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
How Bulk QR Generation Works
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600">
|
||||
Simple 4-step process to create hundreds of QR codes
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{howItWorks.map((item, index) => (
|
||||
<Card key={index} className="p-8">
|
||||
<div className="flex items-start space-x-6">
|
||||
<div className="flex-shrink-0 w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-2xl font-bold text-primary-600">{item.step}</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-3">
|
||||
{item.description}
|
||||
</p>
|
||||
<div className="bg-blue-50 border-l-4 border-blue-500 p-3">
|
||||
<p className="text-sm text-gray-700 font-mono">
|
||||
{item.example}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* File Format Guide */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
CSV/Excel File Format
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600">
|
||||
Simple file structure for bulk QR code generation
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="overflow-hidden shadow-xl mb-8">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-100">
|
||||
<tr>
|
||||
<th className="px-6 py-4 text-left text-gray-900 font-semibold">Column</th>
|
||||
<th className="px-6 py-4 text-left text-gray-900 font-semibold">Description</th>
|
||||
<th className="px-6 py-4 text-center text-gray-900 font-semibold">Required</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{fileFormat.map((field, index) => (
|
||||
<tr key={index}>
|
||||
<td className="px-6 py-4">
|
||||
<code className="bg-gray-100 px-2 py-1 rounded text-sm font-mono text-gray-900">
|
||||
{field.column}
|
||||
</code>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-gray-600">{field.description}</td>
|
||||
<td className="px-6 py-4 text-center">
|
||||
{field.required ? (
|
||||
<span className="text-red-500 font-semibold">Yes</span>
|
||||
) : (
|
||||
<span className="text-gray-400">No</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-blue-50 border border-blue-200">
|
||||
<h4 className="font-semibold text-gray-900 mb-2">Example CSV:</h4>
|
||||
<pre className="bg-white p-4 rounded border border-blue-200 overflow-x-auto text-sm font-mono">
|
||||
{`name,url,description,tags
|
||||
Product A,https://example.com/product-a,Premium Widget,electronics,featured
|
||||
Product B,https://example.com/product-b,Standard Widget,electronics
|
||||
Product C,https://example.com/product-c,Budget Widget,electronics,sale`}
|
||||
</pre>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use Cases */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Bulk QR Code Use Cases
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Industries and scenarios where bulk generation shines
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{useCases.map((useCase, index) => (
|
||||
<Card key={index} className="p-8">
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="text-4xl">{useCase.icon}</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-3">
|
||||
{useCase.title}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
{useCase.description}
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{useCase.stats.map((stat, idx) => (
|
||||
<li key={idx} className="flex items-center space-x-2">
|
||||
<svg className="w-5 h-5 text-green-500 flex-shrink-0" 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">{stat}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 bg-gradient-to-r from-green-600 to-blue-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Generate 1000s of QR Codes in Minutes
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-green-100">
|
||||
Save hours of manual work. Upload your file and get all QR codes ready instantly.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-green-600 hover:bg-gray-100">
|
||||
Start Bulk Generation
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
||||
View Pricing
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
661
src/app/(main)/(marketing)/custom-qr-code-generator/page.tsx
Normal file
661
src/app/(main)/(marketing)/custom-qr-code-generator/page.tsx
Normal file
@@ -0,0 +1,661 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import {
|
||||
Palette,
|
||||
Upload,
|
||||
Frame,
|
||||
Download,
|
||||
CheckCircle2,
|
||||
Smartphone,
|
||||
FileType,
|
||||
Zap,
|
||||
Shield,
|
||||
Eye
|
||||
} from 'lucide-react';
|
||||
import { MiniGenerator } from '@/components/marketing/MiniGenerator';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Custom QR Code Generator with Logo & Colors | QR Master',
|
||||
description: 'Create custom QR codes with your logo, brand colors, and unique frames. Free designer with instant preview. Download PNG/SVG. No signup needed to try.',
|
||||
keywords: [
|
||||
'custom qr code generator',
|
||||
'qr code with logo',
|
||||
'qr code generator with logo',
|
||||
'branded qr code generator',
|
||||
'custom qr code design',
|
||||
'qr code maker with logo',
|
||||
'personalized qr code',
|
||||
'design qr code online'
|
||||
],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/custom-qr-code-generator',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/custom-qr-code-generator',
|
||||
en: 'https://www.qrmaster.net/custom-qr-code-generator',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Custom QR Code Generator with Logo & Brand Colors',
|
||||
description: 'Design unique QR codes with your logo, colors, and frames. Free, high-quality exports.',
|
||||
url: 'https://www.qrmaster.net/custom-qr-code-generator',
|
||||
type: 'website',
|
||||
images: [{
|
||||
url: '/images/og/og-custom-qr-generator.png',
|
||||
width: 1200,
|
||||
height: 630
|
||||
}]
|
||||
},
|
||||
twitter: {
|
||||
title: 'Custom QR Code Generator with Logo & Brand Colors',
|
||||
description: 'Design unique QR codes with your logo, colors, and frames. Free, high-quality exports.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function CustomQRCodeGeneratorPage() {
|
||||
const features = [
|
||||
{
|
||||
icon: Upload,
|
||||
title: 'Logo Integration',
|
||||
description: 'Upload your logo or icon. Auto-resize and safe positioning. Transparent backgrounds supported.',
|
||||
},
|
||||
{
|
||||
icon: Palette,
|
||||
title: 'Custom Colors & Gradients',
|
||||
description: 'Match your brand palette. Solid colors or smooth gradients. Separate eye, pattern, frame colors.',
|
||||
},
|
||||
{
|
||||
icon: Frame,
|
||||
title: 'Custom Frames',
|
||||
description: '"Scan Me", "Learn More", "Follow Us". 20+ pre-designed frames. Custom text frames.',
|
||||
},
|
||||
{
|
||||
icon: FileType,
|
||||
title: 'Multiple Export Formats',
|
||||
description: 'PNG (high-res, up to 4000px). SVG (vector, infinitely scalable). PDF (print-ready).',
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
title: 'Scan-Safe Design Engine',
|
||||
description: 'Automatic error correction (Level H - 30%). Logo placement tested for scannability. Works on all devices.',
|
||||
},
|
||||
{
|
||||
icon: Eye,
|
||||
title: 'Instant Preview',
|
||||
description: 'See changes in real-time. Test scan with your phone. What you see is what you get.',
|
||||
},
|
||||
];
|
||||
|
||||
const designExamples = [
|
||||
{
|
||||
name: 'E-Commerce',
|
||||
description: 'Logo center, gradient purple-blue',
|
||||
image: '/images/examples/qr_example_ecommerce_1769163606650.png',
|
||||
category: 'Retail',
|
||||
},
|
||||
{
|
||||
name: 'Restaurant Menu',
|
||||
description: 'Food icon, warm colors, "Menu" frame',
|
||||
image: '/images/examples/qr_example_restaurant_1769163621640.png',
|
||||
category: 'Hospitality',
|
||||
},
|
||||
{
|
||||
name: 'Event Ticket',
|
||||
description: 'Gradient, geometric pattern, "Scan to Enter"',
|
||||
image: '/images/examples/qr_example_event_1769163635077.png',
|
||||
category: 'Events',
|
||||
},
|
||||
{
|
||||
name: 'Business Card',
|
||||
description: 'Minimalist, corporate blue/gray',
|
||||
image: '/images/examples/qr_example_business_1769163647196.png',
|
||||
category: 'Professional',
|
||||
},
|
||||
{
|
||||
name: 'Social Media',
|
||||
description: 'Instagram-style gradient, "Follow Us"',
|
||||
image: '/images/examples/qr_example_social_1769163660794.png',
|
||||
category: 'Social',
|
||||
},
|
||||
{
|
||||
name: 'Product Packaging',
|
||||
description: 'Premium black & gold, brand logo',
|
||||
image: '/images/examples/qr_example_packaging_1769163674382.png',
|
||||
category: 'Luxury',
|
||||
},
|
||||
{
|
||||
name: 'Real Estate',
|
||||
description: 'House icon, professional blue, "View Listing"',
|
||||
image: '/images/examples/qr_example_realestate_1769163689433.png',
|
||||
category: 'Real Estate',
|
||||
},
|
||||
{
|
||||
name: 'Non-Profit',
|
||||
description: 'Heart icon, warm gradient, "Donate Now"',
|
||||
image: '/images/examples/qr_example_nonprofit_1769163705055.png',
|
||||
category: 'Charity',
|
||||
},
|
||||
];
|
||||
|
||||
const useCases = [
|
||||
{
|
||||
icon: '📢',
|
||||
title: 'Marketing Campaigns',
|
||||
description: 'Print ads, flyers, billboards. Match campaign branding. Track by design/location.',
|
||||
},
|
||||
{
|
||||
icon: '💼',
|
||||
title: 'Business Cards',
|
||||
description: 'vCard QR with your logo. Professional first impression. Easy contact sharing.',
|
||||
},
|
||||
{
|
||||
icon: '📦',
|
||||
title: 'Product Packaging',
|
||||
description: 'QR codes on labels and boxes. Link to manuals, videos, support. Premium branded look.',
|
||||
},
|
||||
{
|
||||
icon: '🎉',
|
||||
title: 'Events & Conferences',
|
||||
description: 'Branded event QR codes. Registration, schedules, networking. Consistent event identity.',
|
||||
},
|
||||
];
|
||||
|
||||
const comparison = [
|
||||
{ feature: 'Colors', generic: 'Black & white only', branded: 'Your brand colors' },
|
||||
{ feature: 'Logo', generic: 'No logo', branded: 'Your logo integrated' },
|
||||
{ feature: 'Design', generic: 'Generic look', branded: 'Professional design' },
|
||||
{ feature: 'Brand Recognition', generic: 'Low', branded: 'Instant brand trust' },
|
||||
{ feature: 'Campaign Tracking', generic: 'Harder to track', branded: 'Campaign-specific branding' },
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'Can I add my company logo to a QR code?',
|
||||
answer: 'Yes! Upload your logo (PNG, SVG, JPG) and we\'ll automatically position it safely in the center. The logo is sized to maintain scannability using error correction.',
|
||||
},
|
||||
{
|
||||
question: 'Will my QR code still work with a logo and custom colors?',
|
||||
answer: 'Absolutely. Our generator uses advanced error correction (Level H - 30% redundancy) to ensure your QR code remains scannable even with logos and colors.',
|
||||
},
|
||||
{
|
||||
question: 'What file formats can I download?',
|
||||
answer: 'High-resolution PNG (up to 4000x4000px), scalable SVG (vector), or print-ready PDF.',
|
||||
},
|
||||
{
|
||||
question: 'Can I use gradients instead of solid colors?',
|
||||
answer: 'Yes! Choose from preset gradients or create custom linear/radial gradients.',
|
||||
},
|
||||
{
|
||||
question: 'How do I know if my custom QR code works?',
|
||||
answer: 'Use the built-in preview and test scan with your phone camera before downloading.',
|
||||
},
|
||||
{
|
||||
question: 'Is there a limit on logo size?',
|
||||
answer: 'Logos up to 5MB. We automatically resize for optimal scanning. Transparent backgrounds (PNG) work best.',
|
||||
},
|
||||
{
|
||||
question: 'Can I customize the QR code frame?',
|
||||
answer: 'Yes! Choose from 20+ frames ("Scan Me", "Learn More") or create custom text frames.',
|
||||
},
|
||||
{
|
||||
question: 'Are custom QR codes free?',
|
||||
answer: 'Static custom QR codes (URL, text, vCard, WiFi) are completely free forever. Dynamic QR codes (editable, trackable) require a paid plan.',
|
||||
},
|
||||
{
|
||||
question: 'Can I change the corner style?',
|
||||
answer: 'Yes! Customize corner dots, eyes, and patterns. Choose square, rounded, or circular styles.',
|
||||
},
|
||||
{
|
||||
question: 'What\'s the difference between static and dynamic custom QR codes?',
|
||||
answer: 'Both can be customized with logos/colors. Static = data encoded directly (free, permanent). Dynamic = redirect URL (editable, trackable, requires account).',
|
||||
},
|
||||
];
|
||||
|
||||
const softwareSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
'@id': 'https://www.qrmaster.net/custom-qr-code-generator#software',
|
||||
name: 'QR Master - Custom QR Code Generator with Logo',
|
||||
applicationCategory: 'DesignApplication',
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'EUR',
|
||||
},
|
||||
featureList: [
|
||||
'Upload custom logo',
|
||||
'Custom brand colors and gradients',
|
||||
'20+ frame designs',
|
||||
'High-resolution PNG downloads',
|
||||
'Scalable SVG exports',
|
||||
'Print-ready PDF output',
|
||||
'Real-time preview',
|
||||
'Scan-safe design validation',
|
||||
],
|
||||
};
|
||||
|
||||
const howToSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'HowTo',
|
||||
'@id': 'https://www.qrmaster.net/custom-qr-code-generator#howto',
|
||||
name: 'How to Create a Custom QR Code with Logo',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Choose QR Code Type',
|
||||
text: 'Select URL, vCard, WiFi, or other QR code type.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Upload Your Logo',
|
||||
text: 'Upload your company logo (PNG, SVG, JPG). Auto-positioned safely.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Customize Colors & Frame',
|
||||
text: 'Choose brand colors, gradients, and frame style.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download',
|
||||
text: 'Download as high-res PNG, SVG, or PDF. Ready to print or share.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const faqSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
'@id': 'https://www.qrmaster.net/custom-qr-code-generator#faq',
|
||||
mainEntity: faqs.map((faq) => ({
|
||||
'@type': 'Question',
|
||||
name: faq.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: faq.answer,
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Custom QR Code Generator', url: '/custom-qr-code-generator' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-purple-50 via-white to-blue-50 py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center mt-8">
|
||||
<div className="space-y-8">
|
||||
<div className="inline-flex items-center space-x-2 bg-purple-100 text-purple-800 px-4 py-2 rounded-full text-sm font-semibold">
|
||||
<span>✨</span>
|
||||
<span>Free Static QR Codes Forever</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
Custom QR Code Generator with Logo
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-600 leading-relaxed">
|
||||
Add your logo, choose custom colors, and design unique frames. Professional QR codes in minutes – try it free, no signup required.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
'No signup required to test',
|
||||
'High-resolution downloads',
|
||||
'Tested for scannability',
|
||||
].map((feature, index) => (
|
||||
<div key={index} className="flex items-center space-x-3">
|
||||
<div className="flex-shrink-0 w-5 h-5 bg-green-500 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-3 h-3 text-white" />
|
||||
</div>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Try Designer Now
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
href="#examples"
|
||||
className="inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 border border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-gray-500 px-6 py-3 text-lg w-full sm:w-auto"
|
||||
>
|
||||
See Examples
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preview Card */}
|
||||
<div className="relative">
|
||||
<Card className="p-8 shadow-2xl h-full min-h-[500px]">
|
||||
<MiniGenerator />
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Scan-Safe Technology Section */}
|
||||
<section className="py-16 bg-blue-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Your Logo Won't Break the QR Code – Here's Why
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
QR codes have built-in error correction. Our generator uses the highest level (H = 30% redundancy), which means up to 30% of the code can be covered or damaged and still scan perfectly.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="p-8">
|
||||
<div className="prose max-w-none">
|
||||
<p className="text-gray-700 mb-6">
|
||||
When you add a logo, we automatically:
|
||||
</p>
|
||||
<ul className="space-y-3 mb-6">
|
||||
<li className="flex items-start space-x-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<span>Calculate safe placement zones</span>
|
||||
</li>
|
||||
<li className="flex items-start space-x-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<span>Size your logo to stay within error correction limits</span>
|
||||
</li>
|
||||
<li className="flex items-start space-x-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<span>Test scannability before download</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className="bg-gray-50 border border-gray-200 rounded-lg p-6 mt-6">
|
||||
<p className="text-sm text-gray-600 mb-2 font-semibold">Technical Details:</p>
|
||||
<p className="text-sm text-gray-700">
|
||||
Based on DENSO WAVE QR Code specification (ISO/IEC 18004). Error correction levels: L (7%), M (15%), Q (25%), H (30%). We use Level H for maximum reliability with custom designs.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Grid */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Everything You Need for Branded QR Codes
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{features.map((feature, index) => (
|
||||
<Card key={index} className="p-6 hover:shadow-lg transition-shadow">
|
||||
<feature.icon className="w-10 h-10 text-primary-600 mb-4" />
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-gray-600">{feature.description}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Design Gallery */}
|
||||
<section id="examples" className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Get Inspired: Custom QR Code Examples
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Real examples of custom QR codes for different industries and use cases
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{designExamples.map((example, index) => (
|
||||
<Card key={index} className="p-4 hover:shadow-xl transition-shadow group">
|
||||
<div className="relative aspect-square mb-4 bg-white rounded-lg overflow-hidden">
|
||||
<Image
|
||||
src={example.image}
|
||||
alt={`${example.name} - ${example.description}`}
|
||||
fill
|
||||
className="object-contain p-4"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-semibold text-primary-600 uppercase">
|
||||
{example.category}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-gray-900">{example.name}</h3>
|
||||
<p className="text-sm text-gray-600">{example.description}</p>
|
||||
<Link href="/signup">
|
||||
<Button variant="outline" size="sm" className="w-full mt-3">
|
||||
Use This Style
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How It Works */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Create Your Custom QR Code in 4 Steps
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{[
|
||||
{
|
||||
step: 1,
|
||||
title: 'Choose QR Type & Enter Data',
|
||||
description: 'URL, vCard, WiFi, Text, Email, SMS. Enter your destination.',
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: 'Upload Your Logo',
|
||||
description: 'Drag & drop PNG/SVG/JPG. Auto-positioned and sized. Or choose from icon library.',
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: 'Customize Design',
|
||||
description: 'Pick brand colors or gradients. Choose frame style ("Scan Me", custom text). Adjust corner shapes.',
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: 'Download & Use',
|
||||
description: 'High-res PNG or vector SVG. Print-ready quality. Scan-tested and verified.',
|
||||
},
|
||||
].map((step, index) => (
|
||||
<Card key={index} className="p-6">
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="flex-shrink-0 w-12 h-12 bg-primary-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xl font-bold text-primary-600">{step.step}</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">{step.title}</h3>
|
||||
<p className="text-gray-600">{step.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Comparison Section */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Why Custom QR Codes Perform Better
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-100">
|
||||
<tr>
|
||||
<th className="px-6 py-4 text-left text-gray-900 font-semibold">Feature</th>
|
||||
<th className="px-6 py-4 text-center text-gray-900 font-semibold">Generic QR Code</th>
|
||||
<th className="px-6 py-4 text-center text-primary-600 font-semibold">Custom Branded QR Code</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{comparison.map((row, index) => (
|
||||
<tr key={index}>
|
||||
<td className="px-6 py-4 text-gray-900 font-medium">{row.feature}</td>
|
||||
<td className="px-6 py-4 text-center text-gray-600">❌ {row.generic}</td>
|
||||
<td className="px-6 py-4 text-center text-primary-600 font-semibold">✅ {row.branded}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
|
||||
<div className="mt-8 bg-blue-50 border border-blue-200 rounded-lg p-6">
|
||||
<p className="text-sm text-gray-700">
|
||||
<strong>Note:</strong> Branded QR codes can improve brand recognition and recall, user trust and engagement, and campaign tracking and attribution.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use Cases */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Perfect For Every Industry
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{useCases.map((useCase, index) => (
|
||||
<Card key={index} className="p-6 text-center hover:shadow-lg transition-shadow">
|
||||
<div className="text-5xl mb-4">{useCase.icon}</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">{useCase.title}</h3>
|
||||
<p className="text-gray-600">{useCase.description}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{faqs.map((faq, index) => (
|
||||
<Card key={index} className="p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<p className="text-gray-600">{faq.answer}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Trust Section */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-8">
|
||||
Secure and Reliable
|
||||
</h2>
|
||||
<div className="flex flex-wrap justify-center gap-8">
|
||||
<div className="flex items-center space-x-2 text-gray-700">
|
||||
<div className="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<span>Your data is never stored</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-gray-700">
|
||||
<div className="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<span>Works on all modern devices and QR scanners</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-gray-700">
|
||||
<div className="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<span>Used by businesses and creators worldwide</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Final CTA */}
|
||||
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Start Creating Branded QR Codes Today
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-purple-100">
|
||||
Free static QR codes with logo, colors, and frames. No signup required to try. Upgrade for tracking and bulk generation.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/signup">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-purple-600 hover:bg-gray-100"
|
||||
>
|
||||
Try Designer Free
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10"
|
||||
>
|
||||
See Pricing for Dynamic Codes
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
520
src/app/(main)/(marketing)/dynamic-qr-code-generator/page.tsx
Normal file
520
src/app/(main)/(marketing)/dynamic-qr-code-generator/page.tsx
Normal file
@@ -0,0 +1,520 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Dynamic QR Code Generator - Edit Anytime',
|
||||
},
|
||||
description: 'Create dynamic QR codes that can be edited after printing. Change destination URLs and track scans without reprinting. Free generator with advanced features.',
|
||||
keywords: 'dynamic qr code generator, editable qr code, dynamic qr code, free dynamic qr code, qr code generator dynamic, best dynamic qr code generator',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/dynamic-qr-code-generator',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/dynamic-qr-code-generator',
|
||||
en: 'https://www.qrmaster.net/dynamic-qr-code-generator',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Dynamic QR Code Generator - Edit Anytime',
|
||||
description: 'Create dynamic QR codes that can be edited after printing. Change URLs, track scans, and update content anytime.',
|
||||
url: 'https://www.qrmaster.net/dynamic-qr-code-generator',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
title: 'Dynamic QR Code Generator - Edit Anytime',
|
||||
description: 'Create dynamic QR codes that can be edited after printing. Change URLs, track scans, and update content anytime.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function DynamicQRCodeGeneratorPage() {
|
||||
const dynamicFeatures = [
|
||||
{
|
||||
icon: '✏️',
|
||||
title: 'Edit Anytime',
|
||||
description: 'Change the destination URL or content after your QR code is printed. No need to reprint!',
|
||||
},
|
||||
{
|
||||
icon: '📊',
|
||||
title: 'Advanced Analytics',
|
||||
description: 'Track scans, locations, devices, and time patterns. Get insights to optimize your campaigns.',
|
||||
},
|
||||
{
|
||||
icon: '🎨',
|
||||
title: 'Full Customization',
|
||||
description: 'Add your logo, brand colors, custom shapes, and frames. Make your QR code stand out.',
|
||||
},
|
||||
{
|
||||
icon: '🔄',
|
||||
title: 'A/B Testing',
|
||||
description: 'Test different landing pages without changing the QR code. Optimize conversions easily.',
|
||||
},
|
||||
{
|
||||
icon: '⏰',
|
||||
title: 'Schedule Content',
|
||||
description: 'Set time-based redirects. Show different content based on day, time, or season.',
|
||||
},
|
||||
{
|
||||
icon: '🌍',
|
||||
title: 'Geo-Targeting',
|
||||
description: 'Redirect users to different pages based on their location. Perfect for multi-region campaigns.',
|
||||
},
|
||||
];
|
||||
|
||||
const staticVsDynamic = [
|
||||
{
|
||||
feature: 'Edit After Printing',
|
||||
static: false,
|
||||
dynamic: true,
|
||||
},
|
||||
{
|
||||
feature: 'Track Scans',
|
||||
static: false,
|
||||
dynamic: true,
|
||||
},
|
||||
{
|
||||
feature: 'A/B Testing',
|
||||
static: false,
|
||||
dynamic: true,
|
||||
},
|
||||
{
|
||||
feature: 'Analytics Dashboard',
|
||||
static: false,
|
||||
dynamic: true,
|
||||
},
|
||||
{
|
||||
feature: 'Custom Domain',
|
||||
static: false,
|
||||
dynamic: true,
|
||||
},
|
||||
{
|
||||
feature: 'Password Protection',
|
||||
static: false,
|
||||
dynamic: true,
|
||||
},
|
||||
{
|
||||
feature: 'Expiration Date',
|
||||
static: false,
|
||||
dynamic: true,
|
||||
},
|
||||
];
|
||||
|
||||
const useCases = [
|
||||
{
|
||||
title: 'Marketing Campaigns',
|
||||
icon: '📢',
|
||||
description: 'Update campaign landing pages without reprinting materials. Test different offers and track performance.',
|
||||
example: 'Print QR codes on billboards, then test different promotions weekly.',
|
||||
},
|
||||
{
|
||||
title: 'Product Packaging',
|
||||
icon: '📦',
|
||||
description: 'Link to product manuals, videos, or registration forms. Update information as products evolve.',
|
||||
example: 'Update software download links without changing packaging.',
|
||||
},
|
||||
{
|
||||
title: 'Business Cards',
|
||||
icon: '💼',
|
||||
description: 'Keep your contact information current. Update your vCard details without printing new cards.',
|
||||
example: 'Change job title, phone, or email anytime.',
|
||||
},
|
||||
{
|
||||
title: 'Restaurant Menus',
|
||||
icon: '🍽️',
|
||||
description: 'Update menu items, prices, and specials daily. Track which items get the most views.',
|
||||
example: 'Show daily specials without printing new menus.',
|
||||
},
|
||||
];
|
||||
|
||||
const softwareSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
'@id': 'https://www.qrmaster.net/dynamic-qr-code-generator#software',
|
||||
name: 'QR Master - Dynamic QR Code Generator',
|
||||
applicationCategory: 'BusinessApplication',
|
||||
operatingSystem: 'Web Browser',
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'USD',
|
||||
availability: 'https://schema.org/InStock',
|
||||
},
|
||||
aggregateRating: {
|
||||
'@type': 'AggregateRating',
|
||||
ratingValue: '4.9',
|
||||
ratingCount: '2150',
|
||||
},
|
||||
description: 'Create dynamic QR codes that can be edited after printing. Change destination URLs, track scans, and update content without reprinting.',
|
||||
featureList: [
|
||||
'Edit QR codes after printing',
|
||||
'Real-time scan tracking',
|
||||
'A/B testing capabilities',
|
||||
'Custom branding and design',
|
||||
'Geo-targeting options',
|
||||
'Scheduled content updates',
|
||||
'Password protection',
|
||||
'Expiration dates',
|
||||
],
|
||||
};
|
||||
|
||||
const howToSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'HowTo',
|
||||
'@id': 'https://www.qrmaster.net/dynamic-qr-code-generator#howto',
|
||||
name: 'How to Create a Dynamic QR Code',
|
||||
description: 'Learn how to create editable QR codes that can be updated after printing',
|
||||
totalTime: 'PT3M',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Sign Up Free',
|
||||
text: 'Create a free QR Master account to start generating dynamic QR codes',
|
||||
url: 'https://www.qrmaster.net/signup',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Generate QR Code',
|
||||
text: 'Enter your destination URL and customize the design with your branding',
|
||||
url: 'https://www.qrmaster.net/signup',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download and Print',
|
||||
text: 'Download your QR code in high resolution and add it to your marketing materials',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Update Anytime',
|
||||
text: 'Log into your dashboard to change the destination URL whenever needed - no reprinting required',
|
||||
url: 'https://www.qrmaster.net/dashboard',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const faqSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
'@id': 'https://www.qrmaster.net/dynamic-qr-code-generator#faq',
|
||||
mainEntity: [
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'What is a dynamic QR code?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'A dynamic QR code is an editable QR code that redirects through a short URL, allowing you to change the destination without reprinting the code. Unlike static QR codes, dynamic codes can be tracked and updated anytime.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'Can I edit a QR code after printing?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Yes, with dynamic QR codes you can edit the destination URL anytime after printing. The QR code image stays the same, but the content it points to can be changed from your dashboard.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'Is dynamic QR code generator free?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Yes, QR Master offers a free plan for creating dynamic QR codes with basic tracking features. Premium plans include advanced analytics and customization options.',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Dynamic QR Code Generator', url: '/dynamic-qr-code-generator' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-purple-50 via-white to-blue-50 py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
<div className="space-y-8">
|
||||
<div className="inline-flex items-center space-x-2 bg-purple-100 text-purple-800 px-4 py-2 rounded-full text-sm font-semibold">
|
||||
<span>✨</span>
|
||||
<span>Edit After Printing</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
Dynamic QR Code Generator
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-600 leading-relaxed">
|
||||
Create QR codes you can edit anytime - even after printing. Change URLs, track scans, and update content without reprinting. The smart choice for businesses.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
'Edit content after printing',
|
||||
'Track scans and analytics',
|
||||
'A/B test without reprinting',
|
||||
'Custom branding and design',
|
||||
].map((feature, index) => (
|
||||
<div key={index} className="flex items-center space-x-3">
|
||||
<div className="flex-shrink-0 w-5 h-5 bg-green-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">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Create Dynamic QR Code
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
View Pricing
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Demo */}
|
||||
<div className="relative">
|
||||
<Card className="p-8 shadow-2xl">
|
||||
<div className="text-center mb-6">
|
||||
<div className="inline-block bg-gray-200 rounded-lg p-8">
|
||||
<div className="w-48 h-48 bg-black rounded-lg flex items-center justify-center">
|
||||
<span className="text-white text-sm font-mono">QR Code</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg">
|
||||
<span className="text-gray-700">Current URL:</span>
|
||||
<span className="text-blue-600 font-mono">summer-sale.com</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg">
|
||||
<span className="text-gray-700">Updated URL:</span>
|
||||
<span className="text-green-600 font-mono">fall-sale.com</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-center text-sm text-gray-600 mt-4">
|
||||
Same QR code, different destination!
|
||||
</p>
|
||||
</Card>
|
||||
<div className="absolute -top-4 -right-4 bg-purple-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg">
|
||||
No Reprint Needed!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Static vs Dynamic */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Dynamic vs Static QR Codes
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Understand why dynamic QR codes are the smart choice for businesses
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="overflow-hidden shadow-xl">
|
||||
<div className="grid md:grid-cols-3">
|
||||
<div className="p-6 bg-white">
|
||||
<h3 className="font-semibold text-lg mb-4">Feature</h3>
|
||||
{staticVsDynamic.map((item, index) => (
|
||||
<div key={index} className="py-4 border-b last:border-b-0">
|
||||
<p className="text-gray-900 font-medium">{item.feature}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="p-6 bg-gray-50">
|
||||
<h3 className="font-semibold text-lg mb-4 text-gray-600">Static QR</h3>
|
||||
{staticVsDynamic.map((item, index) => (
|
||||
<div key={index} className="py-4 border-b last:border-b-0 flex items-center justify-center">
|
||||
{item.static ? (
|
||||
<span className="text-green-500 text-2xl">✓</span>
|
||||
) : (
|
||||
<span className="text-red-500 text-2xl">✗</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="p-6 bg-primary-50">
|
||||
<h3 className="font-semibold text-lg mb-4 text-primary-600">Dynamic QR</h3>
|
||||
{staticVsDynamic.map((item, index) => (
|
||||
<div key={index} className="py-4 border-b last:border-b-0 flex items-center justify-center">
|
||||
{item.dynamic ? (
|
||||
<span className="text-green-500 text-2xl">✓</span>
|
||||
) : (
|
||||
<span className="text-red-500 text-2xl">✗</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Powerful Dynamic QR Features
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Everything you need to create, manage, and optimize your QR code campaigns
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{dynamicFeatures.map((feature, index) => (
|
||||
<Card key={index} className="p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="text-4xl mb-4">{feature.icon}</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
{feature.description}
|
||||
</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use Cases */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
How Businesses Use Dynamic QR Codes
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Real-world examples of dynamic QR code applications
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{useCases.map((useCase, index) => (
|
||||
<Card key={index} className="p-8">
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="text-4xl">{useCase.icon}</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-3">
|
||||
{useCase.title}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
{useCase.description}
|
||||
</p>
|
||||
<div className="bg-blue-50 border-l-4 border-blue-500 p-4">
|
||||
<p className="text-sm text-gray-700">
|
||||
<strong>Example:</strong> {useCase.example}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How It Works */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
How Dynamic QR Codes Work
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600">
|
||||
Simple technology, powerful results
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<Card className="p-6 text-center">
|
||||
<div className="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-2xl font-bold text-primary-600">1</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">Create QR Code</h3>
|
||||
<p className="text-gray-600">
|
||||
Generate a dynamic QR code with a short redirect URL
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 text-center">
|
||||
<div className="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-2xl font-bold text-primary-600">2</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">Print Anywhere</h3>
|
||||
<p className="text-gray-600">
|
||||
Add to packaging, posters, cards, or anywhere you need
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 text-center">
|
||||
<div className="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-2xl font-bold text-primary-600">3</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">Update Anytime</h3>
|
||||
<p className="text-gray-600">
|
||||
Change the destination URL from your dashboard whenever needed
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Start Creating Dynamic QR Codes Today
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-purple-100">
|
||||
Join thousands of businesses who never worry about reprinting QR codes again
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-purple-600 hover:bg-gray-100">
|
||||
Get Started Free
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/signup">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
||||
Create QR Code Now
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
119
src/app/(main)/(marketing)/error.tsx
Normal file
119
src/app/(main)/(marketing)/error.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
// Log the error to an error reporting service
|
||||
console.error('Error:', error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white flex items-center justify-center px-4">
|
||||
<div className="max-w-2xl w-full text-center">
|
||||
{/* Error Icon */}
|
||||
<div className="mb-8">
|
||||
<div className="inline-flex items-center justify-center w-24 h-24 bg-red-100 rounded-full mb-6">
|
||||
<svg
|
||||
className="w-12 h-12 text-red-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Error Text */}
|
||||
<h1 className="text-6xl md:text-8xl font-bold text-gray-900 mb-4">500</h1>
|
||||
<h2 className="text-2xl md:text-3xl font-semibold text-gray-700 mb-4">
|
||||
Something Went Wrong
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 mb-8 max-w-md mx-auto">
|
||||
We're sorry, but something unexpected happened. Our team has been notified and is working on a fix.
|
||||
</p>
|
||||
|
||||
{/* Error Details (only in development) */}
|
||||
{process.env.NODE_ENV === 'development' && error.message && (
|
||||
<div className="mb-8 p-4 bg-red-50 border border-red-200 rounded-lg text-left">
|
||||
<p className="text-sm font-mono text-red-800 break-all">
|
||||
<strong>Error:</strong> {error.message}
|
||||
</p>
|
||||
{error.digest && (
|
||||
<p className="text-sm font-mono text-red-600 mt-2">
|
||||
<strong>Digest:</strong> {error.digest}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||
<Button size="lg" onClick={reset}>
|
||||
<svg
|
||||
className="w-5 h-5 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
Try Again
|
||||
</Button>
|
||||
|
||||
<Link href="/">
|
||||
<Button variant="outline" size="lg">
|
||||
<svg
|
||||
className="w-5 h-5 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Go Home
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Help Text */}
|
||||
<div className="mt-12 pt-8 border-t border-gray-200">
|
||||
<p className="text-sm text-gray-500">
|
||||
If this problem persists, please{' '}
|
||||
<Link href="/#faq" className="text-primary-600 hover:text-primary-700 font-medium">
|
||||
check our FAQ
|
||||
</Link>
|
||||
{' '}or contact support.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
src/app/(main)/(marketing)/faq/ContactSupport.tsx
Normal file
22
src/app/(main)/(marketing)/faq/ContactSupport.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
|
||||
|
||||
export function ContactSupport() {
|
||||
return (
|
||||
<div className="mt-16 bg-blue-50 border-l-4 border-blue-500 p-8 rounded-r-lg">
|
||||
<h2 className="text-2xl font-bold mb-4 text-gray-900">
|
||||
Still have questions?
|
||||
</h2>
|
||||
<p className="text-lg text-gray-700 mb-6 leading-relaxed">
|
||||
Our support team is here to help. Contact us at{' '}
|
||||
<ObfuscatedMailto
|
||||
email="support@qrmaster.net"
|
||||
className="text-blue-600 hover:text-blue-700 font-semibold"
|
||||
/>{' '}
|
||||
or reach out through our live chat.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
142
src/app/(main)/(marketing)/faq/page.tsx
Normal file
142
src/app/(main)/(marketing)/faq/page.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { faqPageSchema } from '@/lib/schema';
|
||||
import { Card, CardContent } from '@/components/ui/Card';
|
||||
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
|
||||
|
||||
function truncateAtWord(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) return text;
|
||||
const truncated = text.slice(0, maxLength);
|
||||
const lastSpace = truncated.lastIndexOf(' ');
|
||||
return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated;
|
||||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const title = truncateAtWord('QR Master FAQ: Dynamic & Bulk QR', 60);
|
||||
const description = truncateAtWord(
|
||||
'All answers: dynamic QR, security, analytics, bulk, events & print.',
|
||||
160
|
||||
);
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/faq',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/faq',
|
||||
en: 'https://www.qrmaster.net/faq',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
url: 'https://www.qrmaster.net/faq',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'What is a dynamic QR code?',
|
||||
answer: 'A dynamic QR code allows you to change the destination URL after the code has been created and printed. Unlike static QR codes, dynamic codes redirect through a short URL that you control, enabling real-time updates, scan analytics, and campaign tracking without reprinting the code.',
|
||||
},
|
||||
{
|
||||
question: 'How do I track QR scans?',
|
||||
answer: 'QR Master provides a comprehensive analytics dashboard that tracks every scan in real-time. You can monitor scan rates, geographic locations, device types, timestamps, and user behavior. Enable UTM parameters to integrate with Google Analytics for advanced campaign tracking and conversion attribution.',
|
||||
},
|
||||
{
|
||||
question: 'What security features does QR Master offer?',
|
||||
answer: 'QR Master employs enterprise-grade security including SSL encryption, link validation to prevent malicious redirects, fraud detection, and GDPR-compliant data handling. All scan analytics are stored securely and access is protected with multi-factor authentication for business accounts.',
|
||||
},
|
||||
{
|
||||
question: 'Can I generate bulk QR codes for print?',
|
||||
answer: 'Yes. Our bulk QR generation tool allows you to create thousands of QR codes at once by uploading a CSV file. Each code can be customized with unique URLs, UTM parameters, and branding. Download print-ready files in SVG, PNG, or PDF formats optimized for high-resolution printing.',
|
||||
},
|
||||
{
|
||||
question: 'How do I brand my QR codes?',
|
||||
answer: 'QR Master offers customization options including custom colors, corner styles, and pattern designs. Branded QR codes maintain scannability while matching your brand identity. Choose your color palette and preview designs before downloading.',
|
||||
},
|
||||
{
|
||||
question: 'Is scan analytics GDPR compliant?',
|
||||
answer: 'Yes. All QR Master analytics are fully GDPR compliant. We collect only necessary data, provide transparent privacy policies, allow users to opt out, and store data securely in EU-compliant data centers. You maintain full control over data retention and deletion.',
|
||||
},
|
||||
{
|
||||
question: 'Can QR Master track campaigns with UTM?',
|
||||
answer: 'Absolutely. QR Master supports UTM parameter integration for all dynamic QR codes. Automatically append source, medium, campaign, term, and content parameters to track QR performance in Google Analytics, Adobe Analytics, and other marketing platforms. UTM tracking enables multi-channel attribution and ROI measurement.',
|
||||
},
|
||||
{
|
||||
question: 'Difference between static and dynamic QR codes?',
|
||||
answer: 'Static QR codes encode the destination URL directly in the code pattern and cannot be changed after creation. Dynamic QR codes use a short redirect URL, allowing you to update destinations, track scans, enable/disable codes, and gather analytics—all without reprinting. Dynamic codes are essential for professional marketing campaigns.',
|
||||
},
|
||||
{
|
||||
question: 'How are QR codes used for events?',
|
||||
answer: 'QR codes streamline event check-ins, ticket validation, attendee tracking, and engagement measurement. Generate unique codes for each ticket, track scan times and locations, enable contactless entry, and analyze attendee behavior. Event organizers use QR analytics to measure session popularity and optimize future events.',
|
||||
},
|
||||
{
|
||||
question: 'Can I make QR codes for business cards?',
|
||||
answer: 'Yes. QR codes on business cards provide instant contact sharing via vCard format, link to your portfolio or LinkedIn profile, and track networking effectiveness. Use branded QR codes that match your card design, and leverage scan analytics to see how many contacts engage and when they follow up.',
|
||||
},
|
||||
{
|
||||
question: 'How do I use QR codes for bulk marketing?',
|
||||
answer: 'Bulk QR codes enable scalable campaigns across print ads, packaging, direct mail, and retail displays. Generate thousands of codes with unique tracking URLs, distribute them across channels, and use analytics to measure which placements drive the highest engagement. Bulk generation supports CSV upload, API integration, and automated workflows.',
|
||||
},
|
||||
{
|
||||
question: 'Is API access available for bulk QR generation?',
|
||||
answer: 'Yes. QR Master offers a developer-friendly REST API for programmatic QR code generation, URL management, and analytics retrieval. Integrate QR creation into your CRM, marketing automation platform, or e-commerce system. API access is included in Business plans and supports bulk operations, webhooks, and real-time updates.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function FAQPage() {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={faqPageSchema(faqs)} />
|
||||
<div className="py-20 bg-gradient-to-b from-gray-50 to-white">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="text-center mb-16">
|
||||
<h1 className="text-4xl lg:text-5xl font-bold text-gray-900 mb-6">
|
||||
Frequently Asked Questions
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600">
|
||||
Everything you need to know about dynamic QR codes, security, analytics, bulk generation, events, and print quality.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{faqs.map((faq, index) => (
|
||||
<Card key={index} className="border-l-4 border-blue-500">
|
||||
<CardContent className="p-8">
|
||||
<h2 className="text-2xl font-semibold mb-4 text-gray-900">
|
||||
{faq.question}
|
||||
</h2>
|
||||
<p className="text-lg text-gray-700 leading-relaxed">
|
||||
{faq.answer}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-16 bg-blue-50 border-l-4 border-blue-500 p-8 rounded-r-lg">
|
||||
<h2 className="text-2xl font-bold mb-4 text-gray-900">
|
||||
Still have questions?
|
||||
</h2>
|
||||
<p className="text-lg text-gray-700 mb-6 leading-relaxed">
|
||||
Our support team is here to help. Contact us at{' '}
|
||||
<ObfuscatedMailto email="support@qrmaster.net" className="text-blue-600 hover:text-blue-700 font-semibold" />{' '}
|
||||
or reach out through our live chat.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
70
src/app/(main)/(marketing)/layout.tsx
Normal file
70
src/app/(main)/(marketing)/layout.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { Metadata } from 'next';
|
||||
import '@/styles/globals.css';
|
||||
import { Providers } from '@/components/Providers';
|
||||
import MarketingLayout from './MarketingLayout';
|
||||
// Import schema functions from library
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
|
||||
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://www.qrmaster.net'),
|
||||
title: {
|
||||
default: 'QR Master – Smart QR Generator & Analytics',
|
||||
template: '%s | QR Master',
|
||||
},
|
||||
description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics.',
|
||||
keywords: 'QR code, QR generator, dynamic QR, QR tracking, QR analytics, branded QR, bulk QR generator',
|
||||
robots: isIndexable
|
||||
? { index: true, follow: true }
|
||||
: { index: false, follow: false },
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: '/favicon.svg', type: 'image/svg+xml' },
|
||||
{ url: '/logo.svg', type: 'image/svg+xml' },
|
||||
],
|
||||
apple: '/logo.svg',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
site: '@qrmaster',
|
||||
images: ['https://www.qrmaster.net/og-image.png'],
|
||||
},
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
siteName: 'QR Master',
|
||||
title: 'QR Master – Smart QR Generator & Analytics',
|
||||
description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics.',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'QR Master - Dynamic QR Code Generator and Analytics Platform',
|
||||
},
|
||||
],
|
||||
locale: 'en_US',
|
||||
},
|
||||
};
|
||||
|
||||
export default function MarketingGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }}
|
||||
/>
|
||||
<MarketingLayout>
|
||||
{children}
|
||||
</MarketingLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
742
src/app/(main)/(marketing)/manage-qr-codes/page.tsx
Normal file
742
src/app/(main)/(marketing)/manage-qr-codes/page.tsx
Normal file
@@ -0,0 +1,742 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import {
|
||||
LayoutDashboard,
|
||||
FolderTree,
|
||||
Edit3,
|
||||
Users,
|
||||
BarChart3,
|
||||
Bell,
|
||||
CheckCircle2,
|
||||
XCircle,
|
||||
Store,
|
||||
UtensilsCrossed,
|
||||
CalendarDays,
|
||||
Megaphone,
|
||||
} from 'lucide-react';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'QR Code Management Software – Organize, Edit & Scale',
|
||||
},
|
||||
description: 'Manage QR codes at scale with folders, bulk editing, team collaboration, and campaign organization. Centralized dashboard for businesses. Free trial available.',
|
||||
keywords: [
|
||||
'manage qr codes',
|
||||
'qr code management software',
|
||||
'qr code management system',
|
||||
'bulk qr code management',
|
||||
'qr code campaign management',
|
||||
'qr code dashboard',
|
||||
'organize qr codes',
|
||||
],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/manage-qr-codes',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/manage-qr-codes',
|
||||
en: 'https://www.qrmaster.net/manage-qr-codes',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'QR Code Management Software – Organize, Edit & Scale',
|
||||
description: 'Manage QR codes at scale with folders, bulk editing, team collaboration, and campaign organization.',
|
||||
url: 'https://www.qrmaster.net/manage-qr-codes',
|
||||
type: 'website',
|
||||
images: [{
|
||||
url: '/images/og/og-manage-qr-codes.png',
|
||||
width: 1200,
|
||||
height: 630
|
||||
}]
|
||||
},
|
||||
twitter: {
|
||||
title: 'QR Code Management Software – Organize, Edit & Scale',
|
||||
description: 'Manage QR codes at scale with folders, bulk editing, team collaboration, and campaign organization.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function ManageQRCodesPage() {
|
||||
const problems = [
|
||||
{
|
||||
icon: BarChart3,
|
||||
title: 'No Visibility',
|
||||
issues: [
|
||||
'Can\'t find QR codes across campaigns',
|
||||
'No central place to see all codes',
|
||||
'Hard to track which ones are active',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Edit3,
|
||||
title: 'Can\'t Update or Organize',
|
||||
issues: [
|
||||
'Printed QR codes are permanent (static)',
|
||||
'No way to organize by campaign/location',
|
||||
'Manual spreadsheet tracking',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: 'No Team Collaboration',
|
||||
issues: [
|
||||
'QR codes scattered across devices',
|
||||
'No centralized management system',
|
||||
'No permissions or access control',
|
||||
'Can\'t track who created/edited what',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: LayoutDashboard,
|
||||
title: 'Centralized Dashboard',
|
||||
description: 'All QR codes in one place. Search, filter, sort by campaign/date/type. Quick performance overview.',
|
||||
},
|
||||
{
|
||||
icon: FolderTree,
|
||||
title: 'Campaign Organization',
|
||||
description: 'Create folders and tags. Group by location, product, event. Archive old campaigns.',
|
||||
},
|
||||
{
|
||||
icon: Edit3,
|
||||
title: 'Bulk Editing',
|
||||
description: 'Edit multiple QR destinations at once. Bulk export, duplicate, archive. Schedule URL changes in advance.',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: 'Team Collaboration',
|
||||
description: 'Invite team members. Set roles (viewer, editor, admin). Activity log for accountability.',
|
||||
},
|
||||
{
|
||||
icon: BarChart3,
|
||||
title: 'Performance Tracking',
|
||||
description: 'Track scans per QR code. See locations, devices, timestamps. Export analytics to CSV.',
|
||||
},
|
||||
{
|
||||
icon: Bell,
|
||||
title: 'Smart Alerts',
|
||||
description: 'Get notified on high scan activity. Alert when errors occur. Weekly performance summaries.',
|
||||
},
|
||||
];
|
||||
|
||||
const useCases = [
|
||||
{
|
||||
icon: Store,
|
||||
title: 'Retail & E-Commerce',
|
||||
description: 'Manage product QR codes across locations. Track in-store vs online performance. Update promo URLs seasonally.',
|
||||
},
|
||||
{
|
||||
icon: UtensilsCrossed,
|
||||
title: 'Restaurants & Hospitality',
|
||||
description: 'Manage digital menu QR codes. Update locations/specials easily. Track table/location-specific scans.',
|
||||
},
|
||||
{
|
||||
icon: CalendarDays,
|
||||
title: 'Events & Conferences',
|
||||
description: 'Manage attendee/session QR codes. Track check-ins in real-time. Organize by event, date, venue.',
|
||||
},
|
||||
{
|
||||
icon: Megaphone,
|
||||
title: 'Marketing Agencies',
|
||||
description: 'Manage multiple client campaigns. Team permissions and white-label reports. Client-specific analytics.',
|
||||
},
|
||||
];
|
||||
|
||||
const comparison = [
|
||||
{ feature: 'Create QR Codes', free: 'Static only', qrMaster: 'Static + Dynamic' },
|
||||
{ feature: 'Central Dashboard', free: false, qrMaster: true },
|
||||
{ feature: 'Edit After Deploy', free: false, qrMaster: true },
|
||||
{ feature: 'Organize Campaigns', free: false, qrMaster: true },
|
||||
{ feature: 'Team Collaboration', free: false, qrMaster: true },
|
||||
{ feature: 'Bulk Operations', free: false, qrMaster: true },
|
||||
{ feature: 'Analytics', free: false, qrMaster: true },
|
||||
{ feature: 'API Access', free: false, qrMaster: true },
|
||||
];
|
||||
|
||||
const plans = [
|
||||
{
|
||||
name: 'Free',
|
||||
price: '€0',
|
||||
period: 'forever',
|
||||
features: [
|
||||
'3 Dynamic QR Codes',
|
||||
'Basic Dashboard',
|
||||
'Basic Analytics',
|
||||
'Perfect for trying',
|
||||
],
|
||||
cta: 'Start Free',
|
||||
href: '/signup',
|
||||
highlighted: false,
|
||||
},
|
||||
{
|
||||
name: 'Pro',
|
||||
price: '€9',
|
||||
period: 'per month',
|
||||
features: [
|
||||
'50 Dynamic QR Codes',
|
||||
'Advanced Analytics',
|
||||
'Team Collaboration (3 users)',
|
||||
'Priority Support',
|
||||
],
|
||||
cta: 'Start Free Trial',
|
||||
href: '/signup?plan=pro',
|
||||
highlighted: true,
|
||||
},
|
||||
{
|
||||
name: 'Business',
|
||||
price: '€29',
|
||||
period: 'per month',
|
||||
features: [
|
||||
'500 Dynamic QR Codes',
|
||||
'Full Analytics + CSV Export',
|
||||
'Unlimited Team Members',
|
||||
'API Access',
|
||||
],
|
||||
cta: 'Start Free Trial',
|
||||
href: '/signup?plan=business',
|
||||
highlighted: false,
|
||||
},
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'What does "manage QR codes" mean?',
|
||||
answer: 'QR code management means having a central dashboard to create, organize, edit, track, and analyze all your QR codes in one place instead of scattered files.',
|
||||
},
|
||||
{
|
||||
question: 'Can I edit a QR code after printing it?',
|
||||
answer: 'Yes, with dynamic QR codes. QR Master uses a redirect URL, so you can change the destination anytime. The printed QR code image stays the same.',
|
||||
},
|
||||
{
|
||||
question: 'How many QR codes can I manage?',
|
||||
answer: 'Pro plan: 50 dynamic QR codes. Business plan: 500. Enterprise: Unlimited. All plans include unlimited static QR codes.',
|
||||
},
|
||||
{
|
||||
question: 'Can my team access and edit QR codes?',
|
||||
answer: 'Yes! Invite team members on Business and Enterprise plans. Set permissions (viewer, editor, admin) for each member.',
|
||||
},
|
||||
{
|
||||
question: 'What analytics can I track?',
|
||||
answer: 'Total scans, unique scans, locations (city/country), devices (iOS/Android), browsers, timestamps, referrers. Export to CSV.',
|
||||
},
|
||||
{
|
||||
question: 'Can I organize QR codes by campaign?',
|
||||
answer: 'Absolutely. Use folders, tags, and custom labels to organize by campaign, location, product, or any criteria.',
|
||||
},
|
||||
{
|
||||
question: 'What happens to my dynamic QR codes if I cancel?',
|
||||
answer: 'Dynamic QR codes require an active subscription to redirect. Check our terms for data retention policies. You can always export your data and download static versions.',
|
||||
},
|
||||
{
|
||||
question: 'Is there an API for bulk management?',
|
||||
answer: 'Yes, Business and Enterprise plans include full API access for creating, editing, and tracking QR codes programmatically.',
|
||||
},
|
||||
];
|
||||
|
||||
const softwareSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
'@id': 'https://www.qrmaster.net/manage-qr-codes#software',
|
||||
name: 'QR Master - QR Code Management Platform',
|
||||
applicationCategory: 'BusinessApplication',
|
||||
offers: {
|
||||
'@type': 'AggregateOffer',
|
||||
lowPrice: '0',
|
||||
highPrice: '29',
|
||||
priceCurrency: 'EUR',
|
||||
},
|
||||
featureList: [
|
||||
'Centralized QR code dashboard',
|
||||
'Campaign organization with folders and tags',
|
||||
'Bulk editing and operations',
|
||||
'Team collaboration with permissions',
|
||||
'Real-time scan analytics',
|
||||
'API access for automation',
|
||||
],
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Manage QR Codes', url: '/manage-qr-codes' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-green-50 via-white to-blue-50 py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center mt-8">
|
||||
<div className="space-y-8">
|
||||
<div className="inline-flex items-center space-x-2 bg-green-100 text-green-800 px-4 py-2 rounded-full text-sm font-semibold">
|
||||
<span>✅</span>
|
||||
<span>Get Started Free</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
QR Code Management Software – Organize, Edit & Scale
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-600 leading-relaxed">
|
||||
The complete QR code management platform for businesses. Organize campaigns, edit in bulk, collaborate with teams, and track performance from one central dashboard.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
'No Credit Card Required',
|
||||
'Full Features',
|
||||
'Cancel Anytime',
|
||||
].map((feature, index) => (
|
||||
<div key={index} className="flex items-center space-x-3">
|
||||
<div className="flex-shrink-0 w-5 h-5 bg-green-500 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-3 h-3 text-white" />
|
||||
</div>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Get Started Free
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
href="#dashboard-preview"
|
||||
className="inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 border border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-gray-500 px-6 py-3 text-lg w-full sm:w-auto"
|
||||
>
|
||||
See Dashboard Demo
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dashboard Preview Card */}
|
||||
<div className="relative">
|
||||
<Card className="p-6 shadow-2xl">
|
||||
<h3 className="font-semibold text-lg mb-4">Dashboard Overview</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-semibold text-gray-700">All QR Codes</span>
|
||||
<span className="text-2xl font-bold text-blue-600">127</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-600">Across 12 campaigns</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-3">
|
||||
<div className="text-xs text-gray-600 mb-1">Active</div>
|
||||
<div className="text-xl font-bold text-green-600">94</div>
|
||||
</div>
|
||||
<div className="bg-gray-100 border border-gray-200 rounded-lg p-3">
|
||||
<div className="text-xs text-gray-600 mb-1">Archived</div>
|
||||
<div className="text-xl font-bold text-gray-600">33</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-lg p-3">
|
||||
<div className="text-xs text-gray-600 mb-2">Recent Activity</div>
|
||||
<div className="space-y-1 text-xs">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700">Campaign "Summer Sale" edited</span>
|
||||
<span className="text-gray-500">2m ago</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700">Bulk update: 12 codes</span>
|
||||
<span className="text-gray-500">1h ago</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 pt-4 border-t border-gray-200">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-600">Team Members</span>
|
||||
<div className="flex -space-x-2">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-400 to-purple-500 border-2 border-white" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Problem Statement */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Why Managing QR Codes Manually is Chaos
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{problems.map((problem, index) => (
|
||||
<Card key={index} className="p-6">
|
||||
<problem.icon className="w-12 h-12 text-red-600 mb-4" />
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4">{problem.title}</h3>
|
||||
<ul className="space-y-2">
|
||||
{problem.issues.map((issue, idx) => (
|
||||
<li key={idx} className="flex items-start space-x-2">
|
||||
<XCircle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-gray-600">{issue}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-12">
|
||||
<p className="text-2xl font-semibold text-gray-700">
|
||||
A Complete Management Platform Solves All of This ↓
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Core Features */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Everything You Need to Manage QR Codes at Scale
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{features.map((feature, index) => (
|
||||
<Card key={index} className="p-6 hover:shadow-lg transition-shadow">
|
||||
<feature.icon className="w-10 h-10 text-primary-600 mb-4" />
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-gray-600">{feature.description}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Workflow Diagram */}
|
||||
<section className="py-20 bg-blue-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Your QR Code Management Workflow
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{[
|
||||
{
|
||||
step: 1,
|
||||
title: 'Create & Customize',
|
||||
description: 'Upload CSV or create individually',
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: 'Organize',
|
||||
description: 'Tag, folder, assign to campaigns',
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: 'Deploy',
|
||||
description: 'Download, print, distribute',
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: 'Track & Analyze',
|
||||
description: 'Monitor scans, locations, devices',
|
||||
},
|
||||
{
|
||||
step: 5,
|
||||
title: 'Optimize',
|
||||
description: 'Edit URLs, A/B test, improve',
|
||||
},
|
||||
].map((step, index) => (
|
||||
<div key={index}>
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-shrink-0 w-12 h-12 bg-primary-600 text-white rounded-full flex items-center justify-center text-xl font-bold">
|
||||
{step.step}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-gray-900">{step.title}</h3>
|
||||
<p className="text-gray-600">{step.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
{index < 4 && (
|
||||
<div className="flex justify-center my-2">
|
||||
<div className="w-1 h-8 bg-primary-300" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Dashboard Preview */}
|
||||
<section id="dashboard-preview" className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
See Your Dashboard in Action
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Card className="p-8 bg-gradient-to-br from-gray-50 to-white">
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between pb-4 border-b border-gray-200">
|
||||
<h3 className="text-xl font-bold">QR Code Management</h3>
|
||||
<Link href="/signup">
|
||||
<Button size="sm">Sign Up</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4">
|
||||
{[
|
||||
{ name: 'Summer Campaign 2024', scans: 1247, status: 'Active', folder: 'Marketing' },
|
||||
{ name: 'Product Launch - Widget Pro', scans: 892, status: 'Active', folder: 'Products' },
|
||||
{ name: 'Event Check-in - TechConf', scans: 2341, status: 'Complete', folder: 'Events' },
|
||||
].map((qr, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-4 bg-white border border-gray-200 rounded-lg hover:shadow-md transition-shadow">
|
||||
<div className="flex-1">
|
||||
<div className="font-semibold text-gray-900">{qr.name}</div>
|
||||
<div className="text-sm text-gray-500">📁 {qr.folder}</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-6">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-primary-600">{qr.scans.toLocaleString()}</div>
|
||||
<div className="text-xs text-gray-500">scans</div>
|
||||
</div>
|
||||
<div className={`px-3 py-1 rounded-full text-xs font-semibold ${qr.status === 'Active'
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-gray-100 text-gray-600'
|
||||
}`}>
|
||||
{qr.status}
|
||||
</div>
|
||||
<Button variant="ghost" size="sm">
|
||||
<Edit3 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center space-x-8 pt-6 border-t border-gray-200 text-sm text-gray-600">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
||||
<span>View all codes at a glance</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
||||
<span>Click to edit destination instantly</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
||||
<span>Filter by campaign or folder</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use Cases by Industry */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
QR Code Management for Every Business
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{useCases.map((useCase, index) => (
|
||||
<Card key={index} className="p-8">
|
||||
<useCase.icon className="w-12 h-12 text-primary-600 mb-4" />
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-3">{useCase.title}</h3>
|
||||
<p className="text-gray-600">{useCase.description}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Feature Comparison */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Free QR Tools vs QR Code Management Platform
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-100">
|
||||
<tr>
|
||||
<th className="px-6 py-4 text-left text-gray-900 font-semibold">Feature</th>
|
||||
<th className="px-6 py-4 text-center text-gray-900 font-semibold">Free QR Tool</th>
|
||||
<th className="px-6 py-4 text-center text-primary-600 font-semibold">QR Master Manage</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{comparison.map((row, index) => (
|
||||
<tr key={index}>
|
||||
<td className="px-6 py-4 text-gray-900 font-medium">{row.feature}</td>
|
||||
<td className="px-6 py-4 text-center">
|
||||
{typeof row.free === 'boolean' ? (
|
||||
row.free ? (
|
||||
<span className="text-green-500 text-2xl">✓</span>
|
||||
) : (
|
||||
<span className="text-red-500 text-2xl">✗</span>
|
||||
)
|
||||
) : (
|
||||
<span className="text-gray-600">{row.free}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-center">
|
||||
{typeof row.qrMaster === 'boolean' ? (
|
||||
<span className="text-green-500 text-2xl">✓</span>
|
||||
) : (
|
||||
<span className="text-primary-600 font-semibold">{row.qrMaster}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{faqs.map((faq, index) => (
|
||||
<Card key={index} className="p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<p className="text-gray-600">{faq.answer}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Pricing Teaser */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Start Managing Your QR Codes Today
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{plans.map((plan, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`p-8 ${plan.highlighted
|
||||
? 'border-2 border-primary-600 shadow-xl relative'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
{plan.highlighted && (
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 bg-primary-600 text-white px-4 py-1 rounded-full text-sm font-semibold">
|
||||
Most Popular
|
||||
</div>
|
||||
)}
|
||||
<div className="text-center mb-6">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-2">{plan.name}</h3>
|
||||
<div className="flex items-baseline justify-center">
|
||||
<span className="text-4xl font-bold text-gray-900">{plan.price}</span>
|
||||
<span className="text-gray-600 ml-2">/{plan.period}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="space-y-3 mb-8">
|
||||
{plan.features.map((feature, idx) => (
|
||||
<li key={idx} className="flex items-start space-x-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-gray-600">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link href={plan.href}>
|
||||
<Button
|
||||
size="lg"
|
||||
variant={plan.highlighted ? undefined : 'outline'}
|
||||
className="w-full"
|
||||
>
|
||||
{plan.cta}
|
||||
</Button>
|
||||
</Link>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-8 text-sm text-gray-600">
|
||||
No credit card required • Full features • Cancel anytime
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Trust & CTA */}
|
||||
<section className="py-20 bg-gradient-to-r from-green-600 to-blue-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Built for Teams and Businesses
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-green-100">
|
||||
QR Master helps marketing teams, agencies, event organizers, and businesses manage their QR codes efficiently.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-8 mb-8">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle2 className="w-6 h-6" />
|
||||
<span>Built for retail, events, hospitality, and more</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle2 className="w-6 h-6" />
|
||||
<span>Secure data handling</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle2 className="w-6 h-6" />
|
||||
<span>Reliable uptime and performance</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
className="text-lg px-8 py-4 bg-white text-green-600 hover:bg-gray-100"
|
||||
>
|
||||
Start Free Trial
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
754
src/app/(main)/(marketing)/newsletter/NewsletterClient.tsx
Normal file
754
src/app/(main)/(marketing)/newsletter/NewsletterClient.tsx
Normal file
@@ -0,0 +1,754 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import {
|
||||
Mail,
|
||||
Users,
|
||||
QrCode,
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
Crown,
|
||||
Activity,
|
||||
Loader2,
|
||||
Lock,
|
||||
LogOut,
|
||||
Zap,
|
||||
Send,
|
||||
CheckCircle2,
|
||||
FileDown,
|
||||
DollarSign,
|
||||
} from 'lucide-react';
|
||||
|
||||
interface AdminStats {
|
||||
users: {
|
||||
total: number;
|
||||
premium: number;
|
||||
newThisWeek: number;
|
||||
newThisMonth: number;
|
||||
recent: Array<{
|
||||
email: string;
|
||||
name: string | null;
|
||||
plan: string;
|
||||
createdAt: string;
|
||||
}>;
|
||||
};
|
||||
qrCodes: {
|
||||
total: number;
|
||||
dynamic: number;
|
||||
static: number;
|
||||
active: number;
|
||||
};
|
||||
scans: {
|
||||
total: number;
|
||||
dynamicOnly: number;
|
||||
avgPerDynamicQR: string;
|
||||
};
|
||||
newsletter: {
|
||||
subscribers: number;
|
||||
};
|
||||
topQRCodes: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
type: string;
|
||||
scans: number;
|
||||
owner: string;
|
||||
createdAt: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default function NewsletterClient() {
|
||||
const router = useRouter();
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(true);
|
||||
const [loginError, setLoginError] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const [stats, setStats] = useState<AdminStats | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Newsletter management state
|
||||
const [newsletterData, setNewsletterData] = useState<{
|
||||
total: number;
|
||||
recent: Array<{ email: string; createdAt: string }>;
|
||||
} | null>(null);
|
||||
const [sendingBroadcast, setSendingBroadcast] = useState(false);
|
||||
const [broadcastResult, setBroadcastResult] = useState<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
} | null>(null);
|
||||
|
||||
// Lead management state
|
||||
const [leadData, setLeadData] = useState<{
|
||||
total: number;
|
||||
recent: Array<{
|
||||
id: string;
|
||||
email: string;
|
||||
source: string;
|
||||
reprintCost: number | null;
|
||||
updatesPerYear: number | null;
|
||||
annualSavings: number | null;
|
||||
createdAt: string;
|
||||
}>;
|
||||
} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
checkAuth();
|
||||
}, []);
|
||||
|
||||
const checkAuth = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/admin/stats');
|
||||
if (response.ok) {
|
||||
setIsAuthenticated(true);
|
||||
const data = await response.json();
|
||||
setStats(data);
|
||||
setLoading(false);
|
||||
// Also fetch newsletter and lead data
|
||||
fetchNewsletterData();
|
||||
fetchLeadsData();
|
||||
} else {
|
||||
setIsAuthenticated(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setIsAuthenticated(false);
|
||||
} finally {
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchNewsletterData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/newsletter/broadcast');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setNewsletterData(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch newsletter data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchLeadsData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/leads');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setLeadData(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch leads data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendBroadcast = async () => {
|
||||
if (!confirm(`Are you sure you want to send the AI Feature Launch email to all ${newsletterData?.total || 0} subscribers?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSendingBroadcast(true);
|
||||
setBroadcastResult(null);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/newsletter/broadcast', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
setBroadcastResult({
|
||||
success: true,
|
||||
message: data.message || `Successfully sent to ${data.sent} subscribers!`,
|
||||
});
|
||||
} else {
|
||||
setBroadcastResult({
|
||||
success: false,
|
||||
message: data.error || 'Failed to send broadcast',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
setBroadcastResult({
|
||||
success: false,
|
||||
message: 'Network error. Please try again.',
|
||||
});
|
||||
} finally {
|
||||
setSendingBroadcast(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoginError('');
|
||||
setIsAuthenticating(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/newsletter/admin-login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setIsAuthenticated(true);
|
||||
await checkAuth();
|
||||
} else {
|
||||
const data = await response.json();
|
||||
setLoginError(data.error || 'Invalid credentials');
|
||||
}
|
||||
} catch (error) {
|
||||
setLoginError('Login failed. Please try again.');
|
||||
} finally {
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
await fetch('/api/auth/logout', { method: 'POST' });
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
// Login Screen
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-purple-50 to-pink-50 dark:from-purple-950/20 dark:to-pink-950/20 px-4">
|
||||
<Card className="w-full max-w-md p-8">
|
||||
<div className="text-center mb-6">
|
||||
<div className="w-16 h-16 bg-gradient-to-br from-purple-100 to-pink-100 dark:from-purple-900/30 dark:to-pink-900/30 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Lock className="w-8 h-8 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold mb-2">Admin Dashboard</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Sign in to access admin panel
|
||||
</p>
|
||||
<Link href="/" className="text-sm text-slate-500 hover:text-slate-900 block mt-2">
|
||||
← Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleLogin} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="admin@example.com"
|
||||
required
|
||||
className="w-full px-4 py-3 rounded-xl bg-background border border-border focus:border-purple-500 focus:ring-2 focus:ring-purple-500/20 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
className="w-full px-4 py-3 rounded-xl bg-background border border-border focus:border-purple-500 focus:ring-2 focus:ring-purple-500/20 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{loginError && (
|
||||
<p className="text-sm text-red-600 dark:text-red-400">{loginError}</p>
|
||||
)}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isAuthenticating}
|
||||
className="w-full bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white"
|
||||
>
|
||||
{isAuthenticating ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Signing in...
|
||||
</>
|
||||
) : (
|
||||
'Sign In'
|
||||
)}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 pt-6 border-t text-center">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Admin credentials required
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Loading
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Admin Dashboard
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-purple-50/30 to-pink-50/30 dark:from-purple-950/10 dark:to-pink-950/10">
|
||||
<div className="container mx-auto px-4 py-8 max-w-7xl">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold mb-2">Admin Dashboard</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Platform overview and statistics
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleLogout}
|
||||
variant="outline"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
Logout
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Main Stats Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
{/* All Time Users */}
|
||||
<Card className="p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-900/20 rounded-lg flex items-center justify-center">
|
||||
<Users className="w-6 h-6 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<Badge className="bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300">
|
||||
All Time
|
||||
</Badge>
|
||||
</div>
|
||||
<h3 className="text-3xl font-bold mb-1">{stats?.users.total || 0}</h3>
|
||||
<p className="text-sm text-muted-foreground">Total Users</p>
|
||||
<div className="mt-3 pt-3 border-t space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-muted-foreground">This Month</span>
|
||||
<span className="text-sm font-semibold text-green-600 dark:text-green-400">
|
||||
+{stats?.users.newThisMonth || 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-muted-foreground">This Week</span>
|
||||
<span className="text-sm font-semibold text-green-600 dark:text-green-400">
|
||||
+{stats?.users.newThisWeek || 0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Dynamic QR Codes */}
|
||||
<Card className="p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="w-12 h-12 bg-purple-100 dark:bg-purple-900/20 rounded-lg flex items-center justify-center">
|
||||
<QrCode className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<Badge className="bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300">
|
||||
Dynamic
|
||||
</Badge>
|
||||
</div>
|
||||
<h3 className="text-3xl font-bold mb-1">{stats?.qrCodes.dynamic || 0}</h3>
|
||||
<p className="text-sm text-muted-foreground">Dynamic QR Codes</p>
|
||||
<div className="mt-3 pt-3 border-t flex items-center justify-between">
|
||||
<span className="text-xs text-muted-foreground">Static</span>
|
||||
<span className="text-sm font-semibold">{stats?.qrCodes.static || 0}</span>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Total Scans */}
|
||||
<Card className="p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="w-12 h-12 bg-green-100 dark:bg-green-900/20 rounded-lg flex items-center justify-center">
|
||||
<BarChart3 className="w-6 h-6 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<Badge className="bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300">
|
||||
All Time
|
||||
</Badge>
|
||||
</div>
|
||||
<h3 className="text-3xl font-bold mb-1">
|
||||
{stats?.scans.dynamicOnly.toLocaleString() || 0}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">Dynamic QR Scans</p>
|
||||
<div className="mt-3 pt-3 border-t flex items-center justify-between">
|
||||
<span className="text-xs text-muted-foreground">Avg per QR</span>
|
||||
<span className="text-sm font-semibold">{stats?.scans.avgPerDynamicQR || 0}</span>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Total QR Codes */}
|
||||
<Card className="p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="w-12 h-12 bg-amber-100 dark:bg-amber-900/20 rounded-lg flex items-center justify-center">
|
||||
<QrCode className="w-6 h-6 text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
<Badge className="bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300">
|
||||
All Time
|
||||
</Badge>
|
||||
</div>
|
||||
<h3 className="text-3xl font-bold mb-1">{stats?.qrCodes.total || 0}</h3>
|
||||
<p className="text-sm text-muted-foreground">Total QR Codes</p>
|
||||
<div className="mt-3 pt-3 border-t space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-muted-foreground">Dynamic</span>
|
||||
<span className="text-sm font-semibold">{stats?.qrCodes.dynamic || 0}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-muted-foreground">Static</span>
|
||||
<span className="text-sm font-semibold">{stats?.qrCodes.static || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Secondary Stats Row */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||
{/* Total All Scans */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-indigo-100 dark:bg-indigo-900/20 rounded-lg flex items-center justify-center">
|
||||
<Zap className="w-6 h-6 text-indigo-600 dark:text-indigo-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold">
|
||||
{stats?.scans.total.toLocaleString() || 0}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">Total All Scans</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Total QR Codes */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-pink-100 dark:bg-pink-900/20 rounded-lg flex items-center justify-center">
|
||||
<QrCode className="w-6 h-6 text-pink-600 dark:text-pink-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold">{stats?.qrCodes.total || 0}</h3>
|
||||
<p className="text-sm text-muted-foreground">Total QR Codes</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Premium Users */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-amber-100 dark:bg-amber-900/20 rounded-lg flex items-center justify-center">
|
||||
<Crown className="w-6 h-6 text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold">{stats?.users.premium || 0}</h3>
|
||||
<p className="text-sm text-muted-foreground">Premium Users</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Bottom Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Top QR Codes */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-purple-100 to-pink-100 dark:from-purple-900/30 dark:to-pink-900/30 rounded-lg flex items-center justify-center">
|
||||
<TrendingUp className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg">Top QR Codes</h3>
|
||||
<p className="text-xs text-muted-foreground">Most scanned</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{stats?.topQRCodes && stats.topQRCodes.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{stats.topQRCodes.map((qr, index) => (
|
||||
<div
|
||||
key={qr.id}
|
||||
className="flex items-center justify-between py-3 border-b border-border last:border-0"
|
||||
>
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-white text-sm font-bold">
|
||||
#{index + 1}
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium truncate">{qr.title}</p>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{qr.owner}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right flex-shrink-0 ml-4">
|
||||
<p className="text-lg font-bold">{qr.scans.toLocaleString()}</p>
|
||||
<p className="text-xs text-muted-foreground">scans</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">No QR codes yet</p>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Recent Users */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 bg-blue-100 dark:bg-blue-900/20 rounded-lg flex items-center justify-center">
|
||||
<Users className="w-5 h-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg">Recent Users</h3>
|
||||
<p className="text-xs text-muted-foreground">Latest signups</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{stats?.users.recent && stats.users.recent.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{stats.users.recent.map((user, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between py-3 border-b border-border last:border-0"
|
||||
>
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-cyan-500 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-white text-xs font-bold">
|
||||
{(user.name || user.email).charAt(0).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium truncate">
|
||||
{user.name || user.email}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{new Date(user.createdAt).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
className={
|
||||
user.plan === 'FREE'
|
||||
? 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-300'
|
||||
: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300'
|
||||
}
|
||||
>
|
||||
{user.plan === 'PRO' && <Crown className="w-3 h-3 mr-1" />}
|
||||
{user.plan}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">No users yet</p>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Newsletter Management Section */}
|
||||
<div className="mt-8">
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-purple-100 to-pink-100 dark:from-purple-900/30 dark:to-pink-900/30 rounded-lg flex items-center justify-center">
|
||||
<Users className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-lg">Newsletter Management</h3>
|
||||
<p className="text-xs text-muted-foreground">Manage AI feature launch notifications</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="text-2xl font-bold">{newsletterData?.total || 0}</span>
|
||||
<p className="text-xs text-muted-foreground">Total Subscribers</p>
|
||||
</div>
|
||||
<Badge className="bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300">
|
||||
Active
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Broadcast Section */}
|
||||
<div className="p-4 bg-gray-50 dark:bg-gray-900/50 rounded-xl mb-6">
|
||||
<div className="flex items-start gap-3 mb-3">
|
||||
<Send className="w-5 h-5 text-purple-600 dark:text-purple-400 mt-0.5" />
|
||||
<div>
|
||||
<h4 className="font-medium">Broadcast AI Feature Launch</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Send the AI feature launch announcement to all {newsletterData?.total || 0} subscribers.
|
||||
This will inform them that the features are now available.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Resend Free Tier Warning */}
|
||||
{(newsletterData?.total || 0) > 100 && (
|
||||
<div className="p-3 rounded-lg mb-3 bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-200 flex items-start gap-2">
|
||||
<Activity className="w-5 h-5 flex-shrink-0 mt-0.5" />
|
||||
<div className="text-sm">
|
||||
<strong>Warning: Resend Free Limit</strong>
|
||||
<p>You have more than 100 subscribers. The Resend Free Tier only allows 100 emails per day. Sending this broadcast might fail for some users or block your account.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{broadcastResult && (
|
||||
<div className={`p-3 rounded-lg mb-3 flex items-center gap-2 ${broadcastResult.success
|
||||
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
|
||||
: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
|
||||
}`}>
|
||||
{broadcastResult.success && <CheckCircle2 className="w-4 h-4" />}
|
||||
<span className="text-sm">{broadcastResult.message}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleSendBroadcast}
|
||||
disabled={sendingBroadcast || (newsletterData?.total || 0) === 0 || (newsletterData?.total || 0) > 100}
|
||||
className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white"
|
||||
>
|
||||
{sendingBroadcast ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Sending...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Mail className="w-4 h-4 mr-2" />
|
||||
Send Launch Notification to All
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Recent Subscribers */}
|
||||
<div>
|
||||
<h4 className="font-medium mb-3">Recent Subscribers</h4>
|
||||
{newsletterData?.recent && newsletterData.recent.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{newsletterData.recent.map((subscriber, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between py-2 border-b border-border last:border-0"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Mail className="w-4 h-4 text-muted-foreground" />
|
||||
<span className="text-sm">{subscriber.email}</span>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{new Date(subscriber.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">No subscribers yet</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tip */}
|
||||
<div className="mt-4 pt-4 border-t">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
💡 Tip: View all subscribers in{' '}
|
||||
<a
|
||||
href="http://localhost:5555"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-purple-600 dark:text-purple-400 hover:underline"
|
||||
>
|
||||
Prisma Studio
|
||||
</a>
|
||||
{' '}(NewsletterSubscription table)
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Lead Management Section */}
|
||||
<div className="mt-8">
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-emerald-100 to-teal-100 dark:from-emerald-900/30 dark:to-teal-900/30 rounded-lg flex items-center justify-center">
|
||||
<FileDown className="w-5 h-5 text-emerald-600 dark:text-emerald-400" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-lg">Lead Management</h3>
|
||||
<p className="text-xs text-muted-foreground">Reprint Calculator PDF downloads</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="text-2xl font-bold">{leadData?.total || 0}</span>
|
||||
<p className="text-xs text-muted-foreground">Total Leads</p>
|
||||
</div>
|
||||
<Badge className="bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300">
|
||||
Active
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Recent Leads */}
|
||||
<div>
|
||||
<h4 className="font-medium mb-3">Recent Leads</h4>
|
||||
{leadData?.recent && leadData.recent.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{leadData.recent.map((lead) => (
|
||||
<div
|
||||
key={lead.id}
|
||||
className="flex items-center justify-between py-3 px-4 border border-border rounded-lg bg-gray-50/50 dark:bg-gray-900/30"
|
||||
>
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<Mail className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<span className="text-sm font-medium block truncate">{lead.email}</span>
|
||||
{lead.annualSavings && (
|
||||
<span className="text-xs text-emerald-600 flex items-center gap-1">
|
||||
<DollarSign className="w-3 h-3" />
|
||||
€{lead.annualSavings.toLocaleString()} potential savings
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right flex-shrink-0 ml-4">
|
||||
<span className="text-xs text-muted-foreground block">
|
||||
{new Date(lead.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
{lead.reprintCost && lead.updatesPerYear && (
|
||||
<span className="text-xs text-slate-500">
|
||||
€{lead.reprintCost} × {lead.updatesPerYear}/yr
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">No leads yet. Leads appear when users download a PDF report from the Reprint Calculator.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tip */}
|
||||
<div className="mt-4 pt-4 border-t">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
💡 Tip: View all leads in{' '}
|
||||
<a
|
||||
href="http://localhost:5555"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-emerald-600 dark:text-emerald-400 hover:underline"
|
||||
>
|
||||
Prisma Studio
|
||||
</a>
|
||||
{' '}(Lead table)
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
19
src/app/(main)/(marketing)/newsletter/page.tsx
Normal file
19
src/app/(main)/(marketing)/newsletter/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import NewsletterClient from './NewsletterClient';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Newsletter Admin | QR Master',
|
||||
description: 'Administrative access for QR Master newsletter management. This area is restricted to authorized personnel only.',
|
||||
robots: {
|
||||
index: false,
|
||||
follow: false,
|
||||
},
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/newsletter',
|
||||
},
|
||||
};
|
||||
|
||||
export default function NewsletterPage() {
|
||||
return <NewsletterClient />;
|
||||
}
|
||||
63
src/app/(main)/(marketing)/not-found.tsx
Normal file
63
src/app/(main)/(marketing)/not-found.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="min-h-screen bg-white flex items-center justify-center px-4">
|
||||
<div className="max-w-2xl w-full text-center">
|
||||
{/* 404 Icon */}
|
||||
<div className="mb-8">
|
||||
<div className="inline-flex items-center justify-center w-24 h-24 bg-primary-100 rounded-full mb-6">
|
||||
<svg
|
||||
className="w-12 h-12 text-primary-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* 404 Text */}
|
||||
<h1 className="text-6xl md:text-8xl font-bold text-gray-900 mb-4">404</h1>
|
||||
<h2 className="text-2xl md:text-3xl font-semibold text-gray-700 mb-4">
|
||||
Page Not Found
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 mb-8 max-w-md mx-auto">
|
||||
Sorry, we couldn't find the page you're looking for. It might have been moved or deleted.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Action Button */}
|
||||
<div className="flex justify-center">
|
||||
<Link href="/">
|
||||
<Button size="lg">
|
||||
<svg
|
||||
className="w-5 h-5 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Back to Home
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
73
src/app/(main)/(marketing)/page.tsx
Normal file
73
src/app/(main)/(marketing)/page.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
import HomePageClient from '@/components/marketing/HomePageClient';
|
||||
|
||||
function truncateAtWord(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) return text;
|
||||
const truncated = text.slice(0, maxLength);
|
||||
const lastSpace = truncated.lastIndexOf(' ');
|
||||
return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated;
|
||||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const title = truncateAtWord('QR Master: Dynamic QR Generator', 60);
|
||||
const description = truncateAtWord(
|
||||
'Create dynamic QR codes, track scans, and scale campaigns with secure analytics. Free advanced features, bulk generation, and custom branding available.',
|
||||
160
|
||||
);
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
keywords: ['qr generator', 'free qr code generator', 'custom qr code generator', 'qr code maker', 'online qr code generator', 'dynamic qr code', 'qr code with logo'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/',
|
||||
en: 'https://www.qrmaster.net/',
|
||||
de: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
url: 'https://www.qrmaster.net/',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[organizationSchema(), websiteSchema()]} />
|
||||
|
||||
{/* Server-rendered SEO content for crawlers */}
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
|
||||
<p>
|
||||
Create professional QR codes for your business with QR Master. Our dynamic QR code generator
|
||||
lets you create trackable QR codes, edit destinations anytime, and view detailed analytics.
|
||||
Perfect for restaurants, retail, events, and marketing campaigns.
|
||||
</p>
|
||||
<p>
|
||||
Features include: Dynamic QR codes with real-time tracking, bulk QR code generation from Excel/CSV,
|
||||
custom branding with colors and logos, advanced scan analytics showing device types and locations,
|
||||
vCard QR codes for digital business cards, and restaurant menu QR codes.
|
||||
</p>
|
||||
<p>
|
||||
Start free with 3 active dynamic QR codes and unlimited static codes. Upgrade to Pro for 50 codes
|
||||
with advanced analytics, or Business for 500 codes with bulk creation and priority support.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<HomePageClient />
|
||||
</>
|
||||
);
|
||||
}
|
||||
269
src/app/(main)/(marketing)/pricing/PricingClient.tsx
Normal file
269
src/app/(main)/(marketing)/pricing/PricingClient.tsx
Normal file
@@ -0,0 +1,269 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { showToast } from '@/components/ui/Toast';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { BillingToggle } from '@/components/ui/BillingToggle';
|
||||
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
|
||||
|
||||
export default function PricingPage() {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState<string | null>(null);
|
||||
const [currentPlan, setCurrentPlan] = useState<string>('FREE');
|
||||
const [currentInterval, setCurrentInterval] = useState<'month' | 'year' | null>(null);
|
||||
const [billingPeriod, setBillingPeriod] = useState<'month' | 'year'>('month');
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch current user plan
|
||||
const fetchUserPlan = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/user/plan');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setCurrentPlan(data.plan || 'FREE');
|
||||
setCurrentInterval(data.interval || null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching user plan:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUserPlan();
|
||||
}, []);
|
||||
|
||||
const handleUpgrade = async (plan: 'PRO' | 'BUSINESS') => {
|
||||
setLoading(plan);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/stripe/create-checkout-session', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
plan,
|
||||
billingInterval: billingPeriod === 'month' ? 'month' : 'year',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to create checkout session');
|
||||
}
|
||||
|
||||
const { url } = await response.json();
|
||||
window.location.href = url;
|
||||
} catch (error) {
|
||||
console.error('Error creating checkout session:', error);
|
||||
showToast('Failed to start checkout. Please try again.', 'error');
|
||||
setLoading(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDowngrade = async () => {
|
||||
// Show confirmation dialog
|
||||
const confirmed = window.confirm(
|
||||
'Are you sure you want to downgrade to the Free plan? Your subscription will be canceled immediately and you will lose access to premium features.'
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading('FREE');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/stripe/cancel-subscription', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to cancel subscription');
|
||||
}
|
||||
|
||||
showToast('Successfully downgraded to Free plan', 'success');
|
||||
|
||||
// Refresh to update the plan
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} catch (error: any) {
|
||||
console.error('Error canceling subscription:', error);
|
||||
showToast(error.message || 'Failed to downgrade. Please try again.', 'error');
|
||||
setLoading(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to check if this is the user's exact current plan (plan + interval)
|
||||
const isCurrentPlanWithInterval = (planType: string, interval: 'month' | 'year') => {
|
||||
return currentPlan === planType && currentInterval === interval;
|
||||
};
|
||||
|
||||
// Helper function to check if user has this plan but different interval
|
||||
const hasPlanDifferentInterval = (planType: string) => {
|
||||
return currentPlan === planType && currentInterval && currentInterval !== billingPeriod;
|
||||
};
|
||||
|
||||
const selectedInterval = billingPeriod === 'month' ? 'month' : 'year';
|
||||
|
||||
const plans = [
|
||||
{
|
||||
key: 'free',
|
||||
name: 'Free',
|
||||
price: '€0',
|
||||
period: 'forever',
|
||||
showDiscount: false,
|
||||
features: [
|
||||
'3 active dynamic QR codes (8 types available)',
|
||||
'Unlimited static QR codes',
|
||||
'Basic scan tracking',
|
||||
'Standard QR design templates',
|
||||
'Download as SVG/PNG',
|
||||
],
|
||||
buttonText: currentPlan === 'FREE' ? 'Current Plan' : 'Downgrade to Free',
|
||||
buttonVariant: 'outline' as const,
|
||||
disabled: currentPlan === 'FREE',
|
||||
popular: false,
|
||||
onDowngrade: handleDowngrade,
|
||||
},
|
||||
{
|
||||
key: 'pro',
|
||||
name: 'Pro',
|
||||
price: billingPeriod === 'month' ? '€9' : '€90',
|
||||
period: billingPeriod === 'month' ? 'per month' : 'per year',
|
||||
showDiscount: billingPeriod === 'year',
|
||||
features: [
|
||||
'50 dynamic QR codes',
|
||||
'Unlimited static QR codes',
|
||||
'Advanced analytics (scans, devices, locations)',
|
||||
'Custom branding (colors & logos)',
|
||||
],
|
||||
buttonText: isCurrentPlanWithInterval('PRO', selectedInterval)
|
||||
? 'Current Plan'
|
||||
: hasPlanDifferentInterval('PRO')
|
||||
? `Switch to ${billingPeriod === 'month' ? 'Monthly' : 'Yearly'}`
|
||||
: 'Upgrade to Pro',
|
||||
buttonVariant: 'primary' as const,
|
||||
disabled: isCurrentPlanWithInterval('PRO', selectedInterval),
|
||||
popular: true,
|
||||
onUpgrade: () => handleUpgrade('PRO'),
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
name: 'Business',
|
||||
price: billingPeriod === 'month' ? '€29' : '€290',
|
||||
period: billingPeriod === 'month' ? 'per month' : 'per year',
|
||||
showDiscount: billingPeriod === 'year',
|
||||
features: [
|
||||
'500 dynamic QR codes',
|
||||
'Unlimited static QR codes',
|
||||
'Everything from Pro',
|
||||
'Bulk QR Creation (up to 1,000)',
|
||||
'Priority email support',
|
||||
'Advanced tracking & insights',
|
||||
],
|
||||
buttonText: isCurrentPlanWithInterval('BUSINESS', selectedInterval)
|
||||
? 'Current Plan'
|
||||
: hasPlanDifferentInterval('BUSINESS')
|
||||
? `Switch to ${billingPeriod === 'month' ? 'Monthly' : 'Yearly'}`
|
||||
: 'Upgrade to Business',
|
||||
buttonVariant: 'primary' as const,
|
||||
disabled: isCurrentPlanWithInterval('BUSINESS', selectedInterval),
|
||||
popular: false,
|
||||
onUpgrade: () => handleUpgrade('BUSINESS'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Choose Your Plan
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600">
|
||||
Select the perfect plan for your QR code needs
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mb-8">
|
||||
<BillingToggle value={billingPeriod} onChange={setBillingPeriod} />
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8 max-w-6xl 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">
|
||||
Most Popular
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CardHeader className="text-center pb-8">
|
||||
<CardTitle className="text-2xl mb-4">
|
||||
{plan.name}
|
||||
</CardTitle>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex items-baseline justify-center">
|
||||
<span className="text-4xl font-bold">
|
||||
{plan.price}
|
||||
</span>
|
||||
<span className="text-gray-600 ml-2">
|
||||
{plan.period}
|
||||
</span>
|
||||
</div>
|
||||
{plan.showDiscount && (
|
||||
<Badge variant="success" className="mt-2">
|
||||
Save 16%
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-6">
|
||||
<ul className="space-y-3">
|
||||
{plan.features.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.buttonVariant}
|
||||
className="w-full"
|
||||
size="lg"
|
||||
disabled={plan.disabled || loading === plan.key.toUpperCase()}
|
||||
onClick={plan.key === 'free' ? (plan as any).onDowngrade : (plan as any).onUpgrade}
|
||||
>
|
||||
{loading === plan.key.toUpperCase() ? 'Processing...' : plan.buttonText}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-12">
|
||||
<p className="text-gray-600">
|
||||
All plans include unlimited static QR codes and basic customization.
|
||||
</p>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Need help choosing? <ObfuscatedMailto email="support@qrmaster.net" className="text-primary-600 hover:text-primary-700 underline">Contact our team</ObfuscatedMailto>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
44
src/app/(main)/(marketing)/pricing/page.tsx
Normal file
44
src/app/(main)/(marketing)/pricing/page.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import PricingClient from './PricingClient';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Pricing Plans | QR Master'
|
||||
},
|
||||
description: 'Choose the perfect QR code plan for your needs. Free, Pro, and Business plans with dynamic QR codes, analytics, bulk generation, and custom branding.',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/pricing',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Pricing Plans | QR Master',
|
||||
description: 'Choose the perfect QR code plan for your needs.',
|
||||
url: 'https://www.qrmaster.net/pricing',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'QR Master Pricing Plans',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default function PricingPage() {
|
||||
return (
|
||||
<>
|
||||
|
||||
<div className="sr-only">
|
||||
<h2>Compare our plans</h2>
|
||||
<p>Find the best QR code solution for your business. From free personal tiers to enterprise-grade dynamic code management.</p>
|
||||
</div>
|
||||
<PricingClient />
|
||||
</>
|
||||
);
|
||||
}
|
||||
13
src/app/(main)/(marketing)/privacy/PrivacyEmailLink.tsx
Normal file
13
src/app/(main)/(marketing)/privacy/PrivacyEmailLink.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
|
||||
|
||||
export function PrivacyEmailLink() {
|
||||
return (
|
||||
<ObfuscatedMailto
|
||||
email="support@qrmaster.net"
|
||||
className="text-primary-600 hover:text-primary-700"
|
||||
/>
|
||||
);
|
||||
}
|
||||
132
src/app/(main)/(marketing)/privacy/page.tsx
Normal file
132
src/app/(main)/(marketing)/privacy/page.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Privacy Policy | QR Master',
|
||||
description: 'Read our Privacy Policy to understand how QR Master collects, uses, and protects your data. We are GDPR compliant and committed to user privacy and security.',
|
||||
};
|
||||
|
||||
export default function PrivacyPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-white py-12">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl">
|
||||
<div className="mb-8">
|
||||
<Link href="/" className="text-primary-600 hover:text-primary-700 font-medium">
|
||||
← Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">Privacy Policy</h1>
|
||||
<p className="text-gray-600 mb-8">Last updated: January 2025</p>
|
||||
|
||||
<div className="prose prose-lg max-w-none">
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">1. Introduction</h2>
|
||||
<p className="text-gray-700 mb-4">
|
||||
Welcome to QR Master ("we," "our," or "us"). We respect your privacy and are committed to protecting your personal data.
|
||||
This privacy policy explains how we collect, use, and protect your information when you use our services.
|
||||
</p>
|
||||
<p className="text-gray-700 mb-4">
|
||||
We implement appropriate security measures including secure HTTPS transmission, password hashing, database access controls,
|
||||
and CSRF protection to keep your data safe.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">2. Information We Collect</h2>
|
||||
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Information You Provide</h3>
|
||||
<ul className="list-disc pl-6 mb-4 text-gray-700 space-y-2">
|
||||
<li><strong>Account Information:</strong> Name, email address, and password</li>
|
||||
<li><strong>Payment Information:</strong> Processed securely through Stripe (we do not store credit card information)</li>
|
||||
<li><strong>QR Code Content:</strong> URLs, text, and customization settings for your QR codes</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Information Collected Automatically</h3>
|
||||
<ul className="list-disc pl-6 mb-4 text-gray-700 space-y-2">
|
||||
<li><strong>Usage Data:</strong> QR code scans and analytics</li>
|
||||
<li><strong>Technical Data:</strong> IP address, browser type, and device information</li>
|
||||
<li><strong>Cookies:</strong> Essential cookies for authentication and optional analytics cookies (PostHog) with your consent</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">3. How We Use Your Information</h2>
|
||||
<p className="text-gray-700 mb-4">We use your data to:</p>
|
||||
<ul className="list-disc pl-6 mb-4 text-gray-700 space-y-2">
|
||||
<li>Provide and maintain our QR code services</li>
|
||||
<li>Process payments and manage subscriptions</li>
|
||||
<li>Provide customer support</li>
|
||||
<li>Improve our services and develop new features</li>
|
||||
<li>Detect and prevent fraud</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mb-4">
|
||||
We retain your data while your account is active. Upon account deletion, most data is removed immediately,
|
||||
though some may be retained for legal compliance. Aggregated, anonymized analytics may be kept indefinitely.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">4. Data Sharing</h2>
|
||||
<p className="text-gray-700 mb-4">We may share your data with:</p>
|
||||
<ul className="list-disc pl-6 mb-4 text-gray-700 space-y-2">
|
||||
<li><strong>Stripe:</strong> Payment processing</li>
|
||||
<li><strong>PostHog:</strong> Analytics (only with your consent, respects Do Not Track)</li>
|
||||
<li><strong>Vercel:</strong> Cloud hosting provider</li>
|
||||
<li><strong>Legal Requirements:</strong> When required by law</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mb-4">
|
||||
We do not sell your personal data. Analytics are only activated if you accept optional cookies.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">5. Your Rights (GDPR)</h2>
|
||||
<p className="text-gray-700 mb-4">You have the right to:</p>
|
||||
<ul className="list-disc pl-6 mb-4 text-gray-700 space-y-2">
|
||||
<li><strong>Access:</strong> Request a copy of your personal data</li>
|
||||
<li><strong>Rectification:</strong> Correct inaccurate data (update in account settings)</li>
|
||||
<li><strong>Erasure:</strong> Delete your data (account deletion available in settings)</li>
|
||||
<li><strong>Data Portability:</strong> Receive your data in a portable format</li>
|
||||
<li><strong>Object:</strong> Object to processing based on legitimate interests</li>
|
||||
<li><strong>Withdraw Consent:</strong> Withdraw cookie consent at any time</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mb-4">
|
||||
To exercise these rights, contact us at{' '}
|
||||
<a href="mailto:support@qrmaster.net" className="text-primary-600 hover:text-primary-700">
|
||||
support@qrmaster.net
|
||||
</a>
|
||||
</p>
|
||||
<p className="text-gray-700 mb-4">
|
||||
Our service is for users 16 years and older. If you're in the EEA and have concerns,
|
||||
you may lodge a complaint with your local data protection authority.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">6. Contact Us</h2>
|
||||
<p className="text-gray-700 mb-4">
|
||||
If you have questions about this privacy policy, please contact us:
|
||||
</p>
|
||||
<div className="bg-gray-50 p-6 rounded-lg">
|
||||
<p className="text-gray-700 mb-2">
|
||||
<strong>Email:</strong>{' '}
|
||||
<ObfuscatedMailto email="support@qrmaster.net" className="text-primary-600 hover:text-primary-700" />
|
||||
</p>
|
||||
<p className="text-gray-700 mb-2"><strong>Website:</strong> <a href="/" className="text-primary-600 hover:text-primary-700">qrmaster.net</a></p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 pt-8 border-t border-gray-200">
|
||||
<p className="text-gray-600 text-center">
|
||||
<Link href="/" className="text-primary-600 hover:text-primary-700">
|
||||
Back to Home
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
400
src/app/(main)/(marketing)/qr-code-tracking/page.tsx
Normal file
400
src/app/(main)/(marketing)/qr-code-tracking/page.tsx
Normal file
@@ -0,0 +1,400 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'QR Code Tracking & Analytics - Track Every Scan',
|
||||
},
|
||||
description: 'Track QR code scans with real-time analytics. Monitor location, device, time, and user behavior. Free QR code tracking software with detailed reports.',
|
||||
keywords: 'qr code tracking, qr code analytics, track qr scans, qr code statistics, free qr tracking, qr code monitoring',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/qr-code-tracking',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/qr-code-tracking',
|
||||
en: 'https://www.qrmaster.net/qr-code-tracking',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'QR Code Tracking & Analytics - Track Every Scan',
|
||||
description: 'Track QR code scans with real-time analytics. Monitor location, device, time, and user behavior.',
|
||||
url: 'https://www.qrmaster.net/qr-code-tracking',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
title: 'QR Code Tracking & Analytics - Track Every Scan',
|
||||
description: 'Track QR code scans with real-time analytics. Monitor location, device, time, and user behavior.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function QRCodeTrackingPage() {
|
||||
const trackingFeatures = [
|
||||
{
|
||||
icon: '📊',
|
||||
title: 'Real-Time Analytics',
|
||||
description: 'See scan data instantly as it happens. Monitor your QR code performance in real-time with live dashboards.',
|
||||
},
|
||||
{
|
||||
icon: '🌍',
|
||||
title: 'Location Tracking',
|
||||
description: 'Know exactly where your QR codes are being scanned. Track by country, city, and region.',
|
||||
},
|
||||
{
|
||||
icon: '📱',
|
||||
title: 'Device Detection',
|
||||
description: 'Identify which devices scan your codes. Track iOS, Android, desktop, and browser types.',
|
||||
},
|
||||
{
|
||||
icon: '🕐',
|
||||
title: 'Time-Based Reports',
|
||||
description: 'Analyze scan patterns by hour, day, week, or month. Optimize your campaigns with timing insights.',
|
||||
},
|
||||
{
|
||||
icon: '👥',
|
||||
title: 'Unique vs Total Scans',
|
||||
description: 'Distinguish between unique users and repeat scans. Measure true reach and engagement.',
|
||||
},
|
||||
{
|
||||
icon: '📈',
|
||||
title: 'Campaign Performance',
|
||||
description: 'Track ROI with UTM parameters. Measure conversion rates and campaign effectiveness.',
|
||||
},
|
||||
];
|
||||
|
||||
const useCases = [
|
||||
{
|
||||
title: 'Marketing Campaigns',
|
||||
description: 'Track print ads, billboards, and product packaging to measure marketing ROI.',
|
||||
benefits: ['Measure ad performance', 'A/B test campaigns', 'Track conversions'],
|
||||
},
|
||||
{
|
||||
title: 'Event Management',
|
||||
description: 'Monitor event check-ins, booth visits, and attendee engagement in real-time.',
|
||||
benefits: ['Live attendance tracking', 'Booth analytics', 'Engagement metrics'],
|
||||
},
|
||||
{
|
||||
title: 'Product Labels',
|
||||
description: 'Track product authenticity scans, manual downloads, and warranty registrations.',
|
||||
benefits: ['Anti-counterfeiting', 'User registration tracking', 'Product analytics'],
|
||||
},
|
||||
{
|
||||
title: 'Restaurant Menus',
|
||||
description: 'See how many customers scan your menu QR codes and when peak times occur.',
|
||||
benefits: ['Customer insights', 'Peak time analysis', 'Menu engagement'],
|
||||
},
|
||||
];
|
||||
|
||||
const comparisonData = [
|
||||
{ feature: 'Real-Time Analytics', free: true, qrMaster: true },
|
||||
{ feature: 'Location Tracking', free: false, qrMaster: true },
|
||||
{ feature: 'Device Detection', free: false, qrMaster: true },
|
||||
{ feature: 'Unlimited Scans', free: false, qrMaster: true },
|
||||
{ feature: 'Historical Data', free: '7 days', qrMaster: 'Unlimited' },
|
||||
{ feature: 'Export Reports', free: false, qrMaster: true },
|
||||
{ feature: 'API Access', free: false, qrMaster: true },
|
||||
];
|
||||
|
||||
const softwareSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
'@id': 'https://www.qrmaster.net/qr-code-tracking#software',
|
||||
name: 'QR Master - QR Code Tracking & Analytics',
|
||||
applicationCategory: 'BusinessApplication',
|
||||
operatingSystem: 'Web Browser, iOS, Android',
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'USD',
|
||||
availability: 'https://schema.org/InStock',
|
||||
},
|
||||
aggregateRating: {
|
||||
'@type': 'AggregateRating',
|
||||
ratingValue: '4.8',
|
||||
ratingCount: '1250',
|
||||
},
|
||||
description: 'Track QR code scans with real-time analytics. Monitor location, device, time, and user behavior with our free QR code tracking software.',
|
||||
features: [
|
||||
'Real-time analytics dashboard',
|
||||
'Location tracking by country and city',
|
||||
'Device detection (iOS, Android, Desktop)',
|
||||
'Time-based scan reports',
|
||||
'Unique vs total scan tracking',
|
||||
'Campaign performance metrics',
|
||||
'Unlimited scans',
|
||||
'Export detailed reports',
|
||||
],
|
||||
};
|
||||
|
||||
const howToSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'HowTo',
|
||||
'@id': 'https://www.qrmaster.net/qr-code-tracking#howto',
|
||||
name: 'How to Track QR Code Scans',
|
||||
description: 'Learn how to track and analyze QR code scans with real-time analytics',
|
||||
totalTime: 'PT5M',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Create QR Code',
|
||||
text: 'Sign up for free and create a dynamic QR code with tracking enabled',
|
||||
url: 'https://www.qrmaster.net/signup',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Deploy QR Code',
|
||||
text: 'Download and place your QR code on marketing materials, products, or digital platforms',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Monitor Analytics',
|
||||
text: 'View real-time scan data including location, device, and time patterns in your dashboard',
|
||||
url: 'https://www.qrmaster.net/analytics',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Optimize Campaigns',
|
||||
text: 'Use insights to optimize placement, timing, and targeting of your QR code campaigns',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'QR Code Tracking', url: '/qr-code-tracking' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<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 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
<div className="space-y-8">
|
||||
<div className="inline-flex items-center space-x-2 bg-blue-100 text-blue-800 px-4 py-2 rounded-full text-sm font-semibold">
|
||||
<span>📊</span>
|
||||
<span>Free QR Code Tracking</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
Track Every QR Code Scan with Powerful Analytics
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-600 leading-relaxed">
|
||||
Monitor your QR code performance in real-time. Get detailed insights on location, device, time, and user behavior. Make data-driven decisions with our free tracking software.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Start Tracking Free
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/signup">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Create Trackable QR Code
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-6 text-sm text-gray-600">
|
||||
<div className="flex items-center space-x-2">
|
||||
<svg className="w-5 h-5 text-green-500" 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>No credit card required</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<svg className="w-5 h-5 text-green-500" 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>Unlimited scans</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Analytics Preview */}
|
||||
<div className="relative">
|
||||
<Card className="p-6 shadow-2xl">
|
||||
<h3 className="font-semibold text-lg mb-4">Live Analytics Dashboard</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center pb-3 border-b">
|
||||
<span className="text-gray-600">Total Scans</span>
|
||||
<span className="text-2xl font-bold text-primary-600">12,547</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pb-3 border-b">
|
||||
<span className="text-gray-600">Unique Users</span>
|
||||
<span className="text-2xl font-bold text-primary-600">8,392</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pb-3 border-b">
|
||||
<span className="text-gray-600">Top Location</span>
|
||||
<span className="font-semibold">🇩🇪 Germany</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600">Top Device</span>
|
||||
<span className="font-semibold">📱 iPhone</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="absolute -top-4 -right-4 bg-green-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg animate-pulse">
|
||||
Live Updates
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Tracking Features */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Powerful QR Code Tracking Features
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Get complete visibility into your QR code performance with our comprehensive analytics suite
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{trackingFeatures.map((feature, index) => (
|
||||
<Card key={index} className="p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="text-4xl mb-4">{feature.icon}</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
{feature.description}
|
||||
</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use Cases */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
QR Code Tracking Use Cases
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
See how businesses use QR code tracking to improve their operations
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{useCases.map((useCase, index) => (
|
||||
<Card key={index} className="p-8">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-3">
|
||||
{useCase.title}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-6">
|
||||
{useCase.description}
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{useCase.benefits.map((benefit, idx) => (
|
||||
<li key={idx} className="flex items-center space-x-2">
|
||||
<svg className="w-5 h-5 text-green-500 flex-shrink-0" 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">{benefit}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Comparison Table */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
QR Master vs Free Tools
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600">
|
||||
See why businesses choose QR Master for QR code tracking
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-100">
|
||||
<tr>
|
||||
<th className="px-6 py-4 text-left text-gray-900 font-semibold">Feature</th>
|
||||
<th className="px-6 py-4 text-center text-gray-900 font-semibold">Free Tools</th>
|
||||
<th className="px-6 py-4 text-center text-primary-600 font-semibold">QR Master</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{comparisonData.map((row, index) => (
|
||||
<tr key={index}>
|
||||
<td className="px-6 py-4 text-gray-900 font-medium">{row.feature}</td>
|
||||
<td className="px-6 py-4 text-center">
|
||||
{typeof row.free === 'boolean' ? (
|
||||
row.free ? (
|
||||
<span className="text-green-500 text-2xl">✓</span>
|
||||
) : (
|
||||
<span className="text-red-500 text-2xl">✗</span>
|
||||
)
|
||||
) : (
|
||||
<span className="text-gray-600">{row.free}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-center">
|
||||
{typeof row.qrMaster === 'boolean' ? (
|
||||
<span className="text-green-500 text-2xl">✓</span>
|
||||
) : (
|
||||
<span className="text-primary-600 font-semibold">{row.qrMaster}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 bg-gradient-to-r from-primary-600 to-purple-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Start Tracking Your QR Codes Today
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-primary-100">
|
||||
Join thousands of businesses using QR Master to track and optimize their QR code campaigns
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-primary-600 hover:bg-gray-100">
|
||||
Create Free Account
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
||||
View Pricing
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
117
src/app/(main)/(marketing)/reprint-calculator/page.tsx
Normal file
117
src/app/(main)/(marketing)/reprint-calculator/page.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import ReprintSavingsCalculator from '@/components/marketing/ReprintSavingsCalculator';
|
||||
import { ArrowDown, Check, ShieldCheck, Zap } from 'lucide-react';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Reprint Cost Calculator | QR Master',
|
||||
description:
|
||||
'Calculate how much you are wasting on QR code reprints. See your potential savings with dynamic QR codes that never need to be reprinted.',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/reprint-calculator',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Reprint Cost Calculator | QR Master',
|
||||
description: 'Stop wasting money on reprints. Calculate your savings now.',
|
||||
url: 'https://www.qrmaster.net/reprint-calculator',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'QR Master Reprint Cost Calculator',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default function ReprintCalculatorPage() {
|
||||
return (
|
||||
<>
|
||||
{/* Hero Section */}
|
||||
<section className="pt-24 pb-12 bg-white relative overflow-hidden">
|
||||
<div className="container mx-auto px-4 text-center max-w-3xl relative z-10">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-slate-100/80 backdrop-blur-sm border border-slate-200 text-slate-600 text-sm font-medium mb-8">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
|
||||
</span>
|
||||
Static QR codes are costing you money
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl lg:text-6xl font-black text-slate-900 mb-6 tracking-tight leading-[1.1]">
|
||||
Stop Burning Budget on <br className="hidden md:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-red-500 to-orange-600">Avoidable Reprints</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-slate-600 mb-8 leading-relaxed max-w-2xl mx-auto">
|
||||
Every time a URL changes, static QR codes become useless trash.
|
||||
Dynamic QR codes update instantly—keeping your print materials alive forever.
|
||||
</p>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<ArrowDown className="w-6 h-6 text-slate-400 animate-bounce" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Calculator Component */}
|
||||
<ReprintSavingsCalculator />
|
||||
|
||||
{/* Value Props */}
|
||||
<section className="py-24 bg-white border-t border-slate-100">
|
||||
<div className="container mx-auto px-4 max-w-6xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold text-slate-900 mb-4">
|
||||
Why Smart Companies Switched Years Ago
|
||||
</h2>
|
||||
<p className="text-slate-600 text-lg max-w-2xl mx-auto">
|
||||
The math is simple. One dynamic subscription costs less than a single batch of reprints.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8 lg:gap-12">
|
||||
{[
|
||||
{
|
||||
icon: Zap,
|
||||
color: "text-amber-500",
|
||||
bg: "bg-amber-50",
|
||||
title: "Update Instantly",
|
||||
desc: "Changed your menu? New promo link? Update the destination in seconds. Your printed codes keep working perfectly."
|
||||
},
|
||||
{
|
||||
icon: ShieldCheck,
|
||||
color: "text-blue-500",
|
||||
bg: "bg-blue-50",
|
||||
title: "Error Proofing",
|
||||
desc: "Printed the wrong link? With static codes, that's a disaster. With dynamic codes, it's a 5-second fix in the dashboard."
|
||||
},
|
||||
{
|
||||
icon: Check,
|
||||
color: "text-green-500",
|
||||
bg: "bg-green-50",
|
||||
title: "Real ROI Tracking",
|
||||
desc: "Stop guessing if your print ads work. Track every scan, location, and device to measure exactly what's driving value."
|
||||
}
|
||||
].map((feature, i) => (
|
||||
<div key={i} className="group p-8 rounded-2xl bg-slate-50 border border-slate-100 hover:bg-white hover:shadow-xl hover:shadow-slate-200/50 hover:border-slate-200 transition-all duration-300">
|
||||
<div className={`w-14 h-14 ${feature.bg} rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300`}>
|
||||
<feature.icon className={`w-7 h-7 ${feature.color}`} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-slate-900 mb-3">{feature.title}</h3>
|
||||
<p className="text-slate-600 leading-relaxed">
|
||||
{feature.desc}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Barcode from 'react-barcode';
|
||||
import Link from 'next/link';
|
||||
import { Download, Printer, Barcode as BarcodeIcon, Sparkles, Sliders, Check, Info, Copy } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Select } from '@/components/ui/Select';
|
||||
import { showToast } from '@/components/ui/Toast';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { toPng, toSvg, toBlob } from 'html-to-image';
|
||||
import { trackEvent } from '@/components/PostHogProvider';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
slate900: '#0f172a',
|
||||
};
|
||||
|
||||
const BARCODE_COLORS = [
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Dark Blue', value: '#1A1265' },
|
||||
{ name: 'Rich Indigo', value: '#4338CA' },
|
||||
{ name: 'Deep Emerald', value: '#065F46' },
|
||||
{ name: 'Crimson', value: '#991B1B' },
|
||||
{ name: 'Slate Gray', value: '#334155' },
|
||||
{ name: 'Business Navy', value: '#1E293B' },
|
||||
];
|
||||
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'product', label: 'Product' },
|
||||
{ id: 'serial', label: 'Serial' },
|
||||
];
|
||||
|
||||
const FORMAT_INFO: Record<string, string> = {
|
||||
'CODE128': 'High-density alphanumeric format. Best for general purpose use.',
|
||||
'EAN13': 'International retail standard for products worldwide.',
|
||||
'UPC': 'Standard retail format used primarily in North America.',
|
||||
'CODE39': 'Older industrial standard supporting uppercase letters and numbers.',
|
||||
'ITF14': 'Used on shipping containers and logistics packaging.',
|
||||
'MSI': 'Specialized format for retail shelf labeling and inventory.',
|
||||
'pharmacode': 'Pharmaceutical packaging control standard.',
|
||||
};
|
||||
|
||||
export default function BarcodeGeneratorClient() {
|
||||
const [value, setValue] = useState('123456789');
|
||||
const [format, setFormat] = useState('CODE128');
|
||||
const [width, setWidth] = useState(2);
|
||||
const [height, setHeight] = useState(100);
|
||||
const [displayValue, setDisplayValue] = useState(true);
|
||||
const [lineColor, setLineColor] = useState('#000000');
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const barcodeRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Validation Logic
|
||||
React.useEffect(() => {
|
||||
setError(null);
|
||||
if (!value) return;
|
||||
|
||||
if (format === 'EAN13' && !/^\d{12,13}$/.test(value)) {
|
||||
setError('EAN-13 requires 12 or 13 digits.');
|
||||
} else if (format === 'UPC' && !/^\d{11,12}$/.test(value)) {
|
||||
setError('UPC requires 11 or 12 digits.');
|
||||
} else if (format === 'CODE39' && !/^[0-9A-Z\-\.\ \$\/\+\%]+$/.test(value)) {
|
||||
setError('Code 39 only supports numbers, uppercase letters, and - . $ / + % spaces.');
|
||||
} else if ((format === 'ITF14' || format === 'MSI') && !/^\d+$/.test(value)) {
|
||||
setError('This format only supports numbers.');
|
||||
}
|
||||
|
||||
if (value && !error) {
|
||||
trackEvent('barcode_generated', {
|
||||
format: format,
|
||||
content_length: value.length,
|
||||
width: width,
|
||||
height: height,
|
||||
display_value: displayValue,
|
||||
line_color: lineColor,
|
||||
frame_type: frameType
|
||||
});
|
||||
}
|
||||
}, [value, format, width, height, displayValue, lineColor, frameType, error]);
|
||||
|
||||
const downloadBarcode = async (extension: 'png' | 'svg') => {
|
||||
if (!barcodeRef.current) return;
|
||||
|
||||
try {
|
||||
let dataUrl;
|
||||
if (extension === 'png') {
|
||||
dataUrl = await toPng(barcodeRef.current, {
|
||||
backgroundColor: '#ffffff',
|
||||
pixelRatio: 3,
|
||||
});
|
||||
} else {
|
||||
dataUrl = await toSvg(barcodeRef.current, {
|
||||
backgroundColor: '#ffffff',
|
||||
});
|
||||
}
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = dataUrl;
|
||||
link.download = `barcode-${value || 'generator'}.${extension}`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
showToast(`Barcode downloaded as ${extension.toUpperCase()}`, 'success');
|
||||
trackEvent('barcode_downloaded', {
|
||||
format: format,
|
||||
extension: extension,
|
||||
frame_type: frameType
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
showToast('Download failed', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const copyBarcode = async () => {
|
||||
if (!barcodeRef.current) return;
|
||||
try {
|
||||
// Use toBlob directly for better performance and compatibility
|
||||
const blob = await toBlob(barcodeRef.current, {
|
||||
backgroundColor: '#ffffff',
|
||||
pixelRatio: 3,
|
||||
});
|
||||
|
||||
if (!blob) {
|
||||
throw new Error('Failed to generate image blob');
|
||||
}
|
||||
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
'image/png': blob,
|
||||
}),
|
||||
]);
|
||||
showToast('Barcode copied to clipboard', 'success');
|
||||
trackEvent('barcode_copied', {
|
||||
format: format,
|
||||
frame_type: frameType
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Copy failed', err);
|
||||
showToast('Failed to copy barcode', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const formats = [
|
||||
{ value: 'CODE128', label: 'Code 128 (Standard)' },
|
||||
{ value: 'EAN13', label: 'EAN-13 (Retail)' },
|
||||
{ value: 'UPC', label: 'UPC-A (US Retail)' },
|
||||
{ value: 'CODE39', label: 'Code 39' },
|
||||
{ value: 'ITF14', label: 'ITF-14' },
|
||||
{ value: 'MSI', label: 'MSI' },
|
||||
{ value: 'pharmacode', label: 'Pharmacode' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Configuration */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sliders className="w-5 h-5 text-slate-900" aria-hidden="true" />
|
||||
Configuration
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="barcode-content" className="block text-sm font-medium text-slate-700 mb-2">Content</label>
|
||||
<Input
|
||||
id="barcode-content"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Enter barcode data (e.g. 12345678)"
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-slate-900 focus:ring-slate-900"
|
||||
aria-label="Barcode content"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-sm font-medium text-slate-700">Format</label>
|
||||
<div className="group relative">
|
||||
<Info className="w-4 h-4 text-slate-400 cursor-help" />
|
||||
<div className="absolute right-0 bottom-full mb-2 w-64 p-3 bg-slate-900 text-white text-[11px] rounded-xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none shadow-xl z-50">
|
||||
<p className="font-bold mb-1">Format Guide:</p>
|
||||
<p>{FORMAT_INFO[format]}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Select
|
||||
value={format}
|
||||
onChange={(e) => setFormat(e.target.value)}
|
||||
className="h-12 rounded-xl border-slate-200"
|
||||
options={formats}
|
||||
aria-label="Format"
|
||||
/>
|
||||
<p className="text-[10px] text-slate-500 mt-2 px-1">
|
||||
{FORMAT_INFO[format]}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-slate-900" aria-hidden="true" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<label htmlFor="width-range" className="text-sm font-medium text-slate-700">Width</label>
|
||||
<span className="text-xs text-slate-500 bg-slate-100 px-2 py-1 rounded-md font-bold">{width}px</span>
|
||||
</div>
|
||||
<input
|
||||
id="width-range"
|
||||
type="range"
|
||||
min="1"
|
||||
max="4"
|
||||
step="0.5"
|
||||
value={width}
|
||||
onChange={(e) => setWidth(parseFloat(e.target.value))}
|
||||
className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-slate-900"
|
||||
aria-label="Barcode width"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<label htmlFor="height-range" className="text-sm font-medium text-slate-700">Height</label>
|
||||
<span className="text-xs text-slate-500 bg-slate-100 px-2 py-1 rounded-md font-bold">{height}px</span>
|
||||
</div>
|
||||
<input
|
||||
id="height-range"
|
||||
type="range"
|
||||
min="30"
|
||||
max="200"
|
||||
step="5"
|
||||
value={height}
|
||||
onChange={(e) => setHeight(parseInt(e.target.value))}
|
||||
className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-slate-900"
|
||||
aria-label="Barcode height"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Line Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{BARCODE_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setLineColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
lineColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select color ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{lineColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} aria-hidden="true" />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-slate-900 text-white border-slate-900"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-3 cursor-pointer group p-3 border border-slate-200 rounded-xl hover:border-slate-900 transition-colors bg-slate-50/50">
|
||||
<div className={cn(
|
||||
"w-5 h-5 rounded border-2 flex items-center justify-center transition-all",
|
||||
displayValue ? "bg-slate-900 border-slate-900" : "border-slate-300 group-hover:border-slate-400"
|
||||
)}>
|
||||
{displayValue && <Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />}
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={displayValue}
|
||||
onChange={(e) => setDisplayValue(e.target.checked)}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span className="text-sm font-medium text-slate-700">Show Value Text</span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* Barcode Card */}
|
||||
<div
|
||||
className="bg-white rounded-3xl shadow-xl p-8 flex flex-col items-center justify-center min-h-[300px] w-full max-w-[400px] border border-slate-100 relative"
|
||||
>
|
||||
<div className="absolute top-4 right-4">
|
||||
<div className="flex items-center gap-1.5 px-2 py-1 bg-slate-100 rounded-md text-[10px] font-bold text-slate-500 uppercase tracking-wider">
|
||||
Live Preview
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref={barcodeRef} className="py-4 bg-white flex flex-col items-center justify-center overflow-hidden w-full">
|
||||
{frameType !== 'none' && !error && (
|
||||
<div
|
||||
className="mb-4 px-6 py-2 rounded-full text-white font-bold text-xs tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: lineColor }}
|
||||
>
|
||||
{FRAME_OPTIONS.find(f => f.id === frameType)?.label}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error ? (
|
||||
<div className="flex flex-col items-center text-center p-6 animate-in fade-in zoom-in duration-200">
|
||||
<div className="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mb-3">
|
||||
<Info className="w-6 h-6 text-red-500" />
|
||||
</div>
|
||||
<p className="text-red-500 font-bold text-sm">{error}</p>
|
||||
<p className="text-slate-400 text-xs mt-1">Please correct your input.</p>
|
||||
</div>
|
||||
) : value ? (
|
||||
<Barcode
|
||||
key={`${format}-${lineColor}-${value}-${width}-${height}-${displayValue}`}
|
||||
value={value}
|
||||
format={format as any}
|
||||
width={width}
|
||||
height={height}
|
||||
displayValue={displayValue}
|
||||
background="#ffffff"
|
||||
lineColor={lineColor}
|
||||
margin={10}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-center text-slate-400 p-6">
|
||||
<BarcodeIcon className="w-12 h-12 mx-auto mb-3 opacity-30" />
|
||||
<p className="text-sm font-medium">Enter data to generate</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center w-full">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<span className="truncate">{formats.find(f => f.value === format)?.label}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-600 mt-1 truncate px-2 font-mono">
|
||||
{value || 'Barcode Value'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex flex-col gap-4 mt-8 w-full max-w-[450px]">
|
||||
<div className="flex flex-col sm:flex-row items-center gap-3 w-full">
|
||||
<Button
|
||||
onClick={() => downloadBarcode('png')}
|
||||
className="w-full sm:flex-1 bg-slate-900 hover:bg-black text-white shadow-lg h-12 rounded-xl"
|
||||
aria-label="Download barcode as PNG"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<div className="relative w-full sm:w-auto">
|
||||
<div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-indigo-600 text-white text-[9px] font-bold px-2 py-0.5 rounded-full whitespace-nowrap shadow-sm z-10 pointer-events-none">
|
||||
BEST FOR PRINT
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => downloadBarcode('svg')}
|
||||
variant="outline"
|
||||
className="w-full sm:w-auto px-6 border-slate-300 hover:bg-white h-12 rounded-xl font-bold"
|
||||
aria-label="Download barcode as SVG"
|
||||
>
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
onClick={copyBarcode}
|
||||
variant="outline"
|
||||
className="w-full sm:w-auto px-4 border-slate-300 hover:bg-white h-12 rounded-xl"
|
||||
title="Copy to Clipboard"
|
||||
aria-label="Copy barcode image to clipboard"
|
||||
>
|
||||
<Copy className="w-4 h-4 text-slate-600" aria-hidden="true" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => window.print()}
|
||||
variant="outline"
|
||||
className="w-full sm:w-auto px-4 border-slate-300 hover:bg-white h-12 rounded-xl"
|
||||
title="Print"
|
||||
aria-label="Print barcode"
|
||||
>
|
||||
<Printer className="w-4 h-4 text-slate-600" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<Link href="/signup" className="text-xs font-medium text-slate-400 hover:text-indigo-600 transition-colors flex items-center justify-center gap-1 group">
|
||||
Need bulk generation?
|
||||
<span className="underline decoration-slate-300 group-hover:decoration-indigo-300 underline-offset-4">Available in Pro →</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-slate-900 to-slate-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Need Dynamic QR Codes?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Switch to QR codes to edit content later and track your scans.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-slate-900 hover:bg-slate-100 shrink-0 shadow-lg px-8">
|
||||
Get Started Free
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
import { BookOpen, CheckCircle, HelpCircle, Layers, Settings, ShoppingCart, Tag, Activity, Factory } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export function BarcodeGuide() {
|
||||
return (
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white" id="guide">
|
||||
<div className="max-w-3xl mx-auto prose prose-slate prose-lg">
|
||||
|
||||
<div className="flex items-center gap-3 mb-8 not-prose">
|
||||
<div className="p-3 bg-blue-100/50 rounded-xl">
|
||||
<BookOpen className="w-8 h-8 text-blue-600" />
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-slate-900 m-0">
|
||||
Barcode Generator – How Barcodes Work and Why They Matter
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<p className="lead text-xl text-slate-600">
|
||||
Barcodes are an essential part of modern commerce, logistics, and inventory management. A <strong>Barcode Generator</strong> allows businesses and individuals to create scannable barcodes quickly and efficiently for products, packaging, and internal systems. Whether you run an online shop, manage a warehouse, or sell products locally, understanding how barcodes work can save time and reduce errors.
|
||||
</p>
|
||||
<p>
|
||||
In this article, you will learn what barcodes are, how they work, and how a <strong>Barcode Generator</strong> helps you create professional barcodes in seconds.
|
||||
</p>
|
||||
|
||||
{/* SEO Image */}
|
||||
<div className="my-8 rounded-2xl overflow-hidden shadow-lg not-prose border border-slate-100">
|
||||
<img
|
||||
src="/barcode-generator-preview.png"
|
||||
alt="Free Online Barcode Generator Preview - Create EAN, UPC, and Code 128 Barcodes"
|
||||
className="w-full h-64 sm:h-80 object-cover"
|
||||
width="800"
|
||||
height="320"
|
||||
/>
|
||||
<div className="bg-slate-50 p-4 text-sm text-slate-500 text-center border-t border-slate-100">
|
||||
Use our <strong>free barcode generator</strong> to create scannable codes.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>What Is a Barcode?</h2>
|
||||
<p>
|
||||
A barcode is a visual representation of data that can be read by machines. It consists of vertical lines with different widths and spacing, which encode numbers or characters. When scanned with a barcode scanner or smartphone, the information is instantly translated into readable data.
|
||||
</p>
|
||||
<p>
|
||||
Barcodes are commonly used to identify products, track inventory, manage logistics, and speed up checkout processes. They reduce manual input and significantly lower the risk of human error.
|
||||
</p>
|
||||
|
||||
<h2>How Does a Barcode Generator Work?</h2>
|
||||
<p>
|
||||
A Barcode Generator converts text or numeric input into a barcode format that scanners can read. The process is simple:
|
||||
</p>
|
||||
<ul className="list-none pl-0 space-y-4 not-prose my-8">
|
||||
<li className="flex gap-4">
|
||||
<div className="w-8 h-8 mt-1 rounded-full bg-slate-100 flex items-center justify-center shrink-0 text-slate-600 font-bold text-sm">1</div>
|
||||
<div>
|
||||
<strong className="text-slate-900 block mb-1">Input Data</strong>
|
||||
<p className="text-slate-600 m-0 text-base">You enter a number or text (for example, a product ID).</p>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex gap-4">
|
||||
<div className="w-8 h-8 mt-1 rounded-full bg-slate-100 flex items-center justify-center shrink-0 text-slate-600 font-bold text-sm">2</div>
|
||||
<div>
|
||||
<strong className="text-slate-900 block mb-1">Select Format</strong>
|
||||
<p className="text-slate-600 m-0 text-base">You select a barcode format such as EAN-13 or Code 128.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex gap-4">
|
||||
<div className="w-8 h-8 mt-1 rounded-full bg-slate-100 flex items-center justify-center shrink-0 text-slate-600 font-bold text-sm">3</div>
|
||||
<div>
|
||||
<strong className="text-slate-900 block mb-1">Generate</strong>
|
||||
<p className="text-slate-600 m-0 text-base">The generator creates a scannable barcode image instantly.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex gap-4">
|
||||
<div className="w-8 h-8 mt-1 rounded-full bg-slate-100 flex items-center justify-center shrink-0 text-slate-600 font-bold text-sm">4</div>
|
||||
<div>
|
||||
<strong className="text-slate-900 block mb-1">Download</strong>
|
||||
<p className="text-slate-600 m-0 text-base">You download or print the barcode for use.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
A modern <strong>Barcode Generator</strong> works directly in the browser and does not require additional software.
|
||||
</p>
|
||||
|
||||
<h2>Common Types of Barcodes</h2>
|
||||
<p>
|
||||
Different barcode formats are used for different purposes. Choosing the right one is important for compatibility and scanning accuracy.
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6 not-prose my-8">
|
||||
{/* EAN-13 Card */}
|
||||
<div className="bg-white p-6 rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Tag className="w-5 h-5 text-blue-500" />
|
||||
<h4 className="text-lg font-bold text-slate-900 m-0">EAN-13</h4>
|
||||
</div>
|
||||
<div className="text-xs font-mono bg-slate-100 inline-block px-2 py-1 rounded text-slate-500 mb-3">Retail • Europe</div>
|
||||
<div className="mb-3 bg-slate-50 rounded border border-slate-100 p-2 flex justify-center">
|
||||
<img src="/barcode-generator-preview.png" alt="EAN-13 Barcode Sample for International Products" className="h-10 object-contain opacity-75 grayscale" width="200" height="40" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 m-0">
|
||||
EAN-13 is widely used in retail, especially in Europe. It is designed for consumer products sold in stores and supermarkets.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* UPC-A Card */}
|
||||
<div className="bg-white p-6 rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<ShoppingCart className="w-5 h-5 text-indigo-500" />
|
||||
<h4 className="text-lg font-bold text-slate-900 m-0">UPC-A</h4>
|
||||
</div>
|
||||
<div className="text-xs font-mono bg-slate-100 inline-block px-2 py-1 rounded text-slate-500 mb-3">Retail • USA/Canada</div>
|
||||
<div className="mb-3 bg-slate-50 rounded border border-slate-100 p-2 flex justify-center">
|
||||
<img src="/barcode-generator-preview.png" alt="UPC-A Barcode Example for Retail Products in USA" className="h-10 object-contain opacity-75 grayscale" width="200" height="40" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 m-0">
|
||||
UPC-A is similar to EAN-13 but is mainly used in the United States and Canada for retail products.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Code 128 Card */}
|
||||
<div className="bg-white p-6 rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Settings className="w-5 h-5 text-emerald-500" />
|
||||
<h4 className="text-lg font-bold text-slate-900 m-0">Code 128</h4>
|
||||
</div>
|
||||
<div className="text-xs font-mono bg-slate-100 inline-block px-2 py-1 rounded text-slate-500 mb-3">Logistics • Universal</div>
|
||||
<div className="mb-3 bg-slate-50 rounded border border-slate-100 p-2 flex justify-center">
|
||||
<img src="/barcode-generator-preview.png" alt="Code 128 Barcode for Inventory and Shipping Labels" className="h-10 object-contain opacity-75 grayscale" width="200" height="40" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 m-0">
|
||||
Code 128 is a flexible barcode format that supports letters and numbers. It is commonly used in logistics, shipping, and internal tracking systems.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Code 39 Card */}
|
||||
<div className="bg-white p-6 rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Factory className="w-5 h-5 text-orange-500" />
|
||||
<h4 className="text-lg font-bold text-slate-900 m-0">Code 39</h4>
|
||||
</div>
|
||||
<div className="text-xs font-mono bg-slate-100 inline-block px-2 py-1 rounded text-slate-500 mb-3">Industrial • Military</div>
|
||||
<div className="mb-3 bg-slate-50 rounded border border-slate-100 p-2 flex justify-center">
|
||||
<img src="/barcode-generator-preview.png" alt="Code 39 Barcode for Industrial Use" className="h-10 object-contain opacity-75 grayscale" width="200" height="40" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 m-0">
|
||||
The first alphanumeric barcode, Code 39 is still widely used in automotive and defense industries. It supports numbers and uppercase letters.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* MSI Card */}
|
||||
<div className="bg-white p-6 rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Layers className="w-5 h-5 text-purple-500" />
|
||||
<h4 className="text-lg font-bold text-slate-900 m-0">MSI</h4>
|
||||
</div>
|
||||
<div className="text-xs font-mono bg-slate-100 inline-block px-2 py-1 rounded text-slate-500 mb-3">Inventory • Shelves</div>
|
||||
<div className="mb-3 bg-slate-50 rounded border border-slate-100 p-2 flex justify-center">
|
||||
<img src="/barcode-generator-preview.png" alt="MSI Barcode for Inventory Management" className="h-10 object-contain opacity-75 grayscale" width="200" height="40" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 m-0">
|
||||
MSI (Modified Plessey) is often used for inventory control in retail environments, such as labeling shelves in supermarkets and warehouses.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Pharmacode Card */}
|
||||
<div className="bg-white p-6 rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Activity className="w-5 h-5 text-red-500" />
|
||||
<h4 className="text-lg font-bold text-slate-900 m-0">Pharmacode</h4>
|
||||
</div>
|
||||
<div className="text-xs font-mono bg-slate-100 inline-block px-2 py-1 rounded text-slate-500 mb-3">Pharma • Packaging</div>
|
||||
<div className="mb-3 bg-slate-50 rounded border border-slate-100 p-2 flex justify-center">
|
||||
<img src="/barcode-generator-preview.png" alt="Pharmacode for Pharmaceutical Packaging" className="h-10 object-contain opacity-75 grayscale" width="200" height="40" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 m-0">
|
||||
Pharmacode is a specialized barcode standard used in the pharmaceutical industry for packaging control to prevent medication errors.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<h2>Why Use a Barcode Generator?</h2>
|
||||
<p>Using a Barcode Generator offers several advantages:</p>
|
||||
<div className="not-prose grid gap-4 mb-8">
|
||||
<div className="flex gap-4 items-start">
|
||||
<CheckCircle className="w-5 h-5 text-green-500 mt-1 shrink-0" />
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 m-0">Speed</h5>
|
||||
<p className="text-slate-600 text-sm m-0">Create barcodes instantly without technical knowledge.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4 items-start">
|
||||
<CheckCircle className="w-5 h-5 text-green-500 mt-1 shrink-0" />
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 m-0">Accuracy</h5>
|
||||
<p className="text-slate-600 text-sm m-0">Reduce manual data entry errors.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4 items-start">
|
||||
<CheckCircle className="w-5 h-5 text-green-500 mt-1 shrink-0" />
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 m-0">Flexibility</h5>
|
||||
<p className="text-slate-600 text-sm m-0">Generate barcodes for different formats and use cases.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4 items-start">
|
||||
<CheckCircle className="w-5 h-5 text-green-500 mt-1 shrink-0" />
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 m-0">Cost-effective</h5>
|
||||
<p className="text-slate-600 text-sm m-0">No need for expensive software or hardware.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
For small businesses, online shops, and startups, a <strong>free Barcode Generator</strong> is often the easiest way to get started.
|
||||
</p>
|
||||
|
||||
<h2>Barcode vs QR Code</h2>
|
||||
<p>
|
||||
Although barcodes and QR codes are often confused, they serve different purposes. A barcode stores data horizontally and is mainly used for product identification. A <Link href="/tools/url-qr-code" className="text-blue-600 hover:underline">QR code</Link> stores data both horizontally and vertically and can contain more complex information such as URLs or <Link href="/tools/vcard-qr-code" className="text-blue-600 hover:underline">contact details</Link>.
|
||||
</p>
|
||||
<p>
|
||||
If you only need to identify products or inventory items, a classic barcode is usually the better choice.
|
||||
</p>
|
||||
|
||||
<h2>Are Barcodes Free to Use?</h2>
|
||||
<p>
|
||||
The barcode image itself can be generated for free using a Barcode Generator. However, for retail products sold internationally, the barcode number may need to be officially registered through organizations such as GS1. This ensures that the barcode is unique and recognized globally.
|
||||
</p>
|
||||
<p>
|
||||
For internal use, testing, or small projects, free barcode generation is usually sufficient.
|
||||
</p>
|
||||
|
||||
<h2>Use Cases for Barcodes</h2>
|
||||
<p>Barcodes are used in many industries, including:</p>
|
||||
<ul className="list-disc pl-6 space-y-2 mb-8">
|
||||
<li>Retail and e-commerce</li>
|
||||
<li>Inventory and warehouse management</li>
|
||||
<li>Shipping and logistics</li>
|
||||
<li>Libraries and document tracking</li>
|
||||
<li>Event tickets and labeling</li>
|
||||
</ul>
|
||||
<p>
|
||||
A reliable <strong>Barcode Generator</strong> helps streamline these processes and improves efficiency.
|
||||
</p>
|
||||
|
||||
<h2>Understanding Check Digits</h2>
|
||||
<p>
|
||||
Most barcodes (like EAN and UPC) include a "Check Digit"—the last number in the sequence. This digit is calculated mathematically from the other numbers to ensure the barcode is scanned correctly. Even if a barcode is slightly damaged or scratched, the scanner uses the check digit to verify the integrity of the data.
|
||||
</p>
|
||||
|
||||
<h2>Best Practices for Printing Barcodes</h2>
|
||||
<p>
|
||||
To ensure your barcodes scan instantly at the checkout or in the warehouse, follow these printing tips:
|
||||
</p>
|
||||
<ul className="list-disc pl-6 space-y-2 mb-8">
|
||||
<li><strong>High Contrast:</strong> Always print black bars on a white background. Reverse colors (white bars on black) often fail to scan.</li>
|
||||
<li><strong>Quiet Zone:</strong> Leave enough white space (margins) on the left and right sides of the barcode.</li>
|
||||
<li><strong>Resolution:</strong> For professional labels, use <strong>SVG format</strong> (vector) or high-resolution PNGs (at least 300 DPI) to avoid blurry edges.</li>
|
||||
<li><strong>Size:</strong> Do not scale the barcode too small. Standard EAN-13 codes should be at least 30mm wide for reliable scanning.</li>
|
||||
</ul>
|
||||
|
||||
<hr className="my-12 border-slate-200" />
|
||||
|
||||
<div className="flex items-center gap-3 mb-6 not-prose">
|
||||
<HelpCircle className="w-6 h-6 text-blue-500" />
|
||||
<h2 className="text-2xl font-bold text-slate-900 m-0">Frequently Asked Questions (FAQ)</h2>
|
||||
</div>
|
||||
|
||||
<div className="not-prose space-y-8">
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 text-lg mb-2">❓ What is a Barcode Generator?</h5>
|
||||
<p className="text-slate-600">A Barcode Generator is an online tool that converts numbers or text into scannable barcode images that can be used for products, labels, and inventory systems.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 text-lg mb-2">❓ Is this barcode generator free to use?</h5>
|
||||
<p className="text-slate-600">Yes, our online barcode generator is completely free to use with no hidden costs or sign-ups required. You can generate, download, and print barcodes instantly.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 text-lg mb-2">❓ Which barcode format should I use?</h5>
|
||||
<p className="text-slate-600">
|
||||
<strong>EAN-13:</strong> Standard for retail products in Europe and globally.<br />
|
||||
<strong>UPC-A:</strong> Standard for retail products in USA/Canada.<br />
|
||||
<strong>Code 128:</strong> Best for logistics, shipping, and internal tracking (supports letters & numbers).
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 text-lg mb-2">❓ Can I download barcodes in vector format (SVG)?</h5>
|
||||
<p className="text-slate-600">Yes! We offer <strong>SVG downloads</strong>. SVG files are vector-based, meaning they can be scaled to any size without losing quality—perfect for professional product packaging.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 text-lg mb-2">❓ How do I generate a barcode online?</h5>
|
||||
<p className="text-slate-600">To generate a barcode online, enter your product number or text, select the desired barcode format (such as EAN-13 or Code 128), and click the generate button. The barcode will be created instantly.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 text-lg mb-2">❓ Are generated barcodes scannable?</h5>
|
||||
<p className="text-slate-600">Yes, barcodes generated with a proper barcode generator are fully scannable. We generate standard-compliant barcodes readable by any standard optical or laser barcode scanner.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 text-lg mb-2">❓ Can I use these barcodes for Amazon (EAN/UPC)?</h5>
|
||||
<p className="text-slate-600">You can generate the <em>image</em> for Amazon here if you already have your EAN/UPC number. However, you cannot "create" a valid global EAN number here—you must purchase those official numbers from GS1 to sell on major platforms like Amazon.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-bold text-slate-900 text-lg mb-2">❓ What is the difference between a barcode and a QR code?</h5>
|
||||
<p className="text-slate-600">A barcode stores data horizontally (1D) and is mainly used for product IDs. A QR code stores data in 2D (matrix) and can hold much more information, such as URLs, vCards, or WiFi credentials.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 p-6 bg-slate-900 rounded-xl text-white not-prose">
|
||||
<h4 className="text-lg font-bold mb-2">Final Thoughts</h4>
|
||||
<p className="text-slate-300 m-0">
|
||||
A Barcode Generator is a simple yet powerful tool that helps businesses save time, reduce errors, and improve operational efficiency. By choosing the right barcode format and using a reliable generator, you can create professional barcodes that work across different systems and industries.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
303
src/app/(main)/(marketing)/tools/barcode-generator/page.tsx
Normal file
303
src/app/(main)/(marketing)/tools/barcode-generator/page.tsx
Normal file
@@ -0,0 +1,303 @@
|
||||
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import BarcodeGeneratorClient from './BarcodeGeneratorClient';
|
||||
import { BarcodeGuide } from './BarcodeGuide';
|
||||
import { Barcode as BarcodeIcon, Shield, Zap, Printer, Download, Share2, Sparkles, Sliders, Check } from 'lucide-react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Barcode Generator – Create Barcodes Online for Free',
|
||||
},
|
||||
description: 'Use a free Barcode Generator to create scannable barcodes online. Supports EAN, UPC and Code 128 for products, labels and inventory.',
|
||||
keywords: ['barcode generator', 'online barcode maker', 'create barcode free', 'ean-13 generator', 'upc-a generator', 'code 128 generator', 'barcode creator', 'printable barcodes'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Barcode Generator: Create EAN, UPC & Code 128',
|
||||
description: 'Barcode Generator: Create professional labels instantly. Free & Secured.',
|
||||
url: 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
siteName: 'QR Master',
|
||||
locale: 'en_US',
|
||||
type: 'website',
|
||||
images: [{ url: '/barcode-generator-preview.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Barcode Generator',
|
||||
description: 'Create custom barcodes in seconds. Download high-quality PNG/SVG.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Barcode Generator',
|
||||
'Generate custom printable barcodes instantly for EAN, UPC, Code 128 and more.',
|
||||
'/og-barcode-generator.png',
|
||||
'UtilitiesApplication'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a Barcode',
|
||||
description: 'Create custom barcodes for products or inventory.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Content',
|
||||
text: 'Type or paste the numeric or alphanumeric data for your barcode.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Select Format',
|
||||
text: 'Choose the appropriate barcode type (e.g., Code 128 for general use, EAN-13 for retail).',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Customize Design',
|
||||
text: 'Adjust the height and width of the barcode to fit your needs.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Toggle Text',
|
||||
text: 'Decide if you want the human-readable value to appear below the barcode.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Download & Print',
|
||||
text: 'Save your barcode as PNG or SVG and print it for labels or inventory.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT20S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'What is a Barcode Generator?': {
|
||||
question: 'What is a Barcode Generator?',
|
||||
answer: 'A Barcode Generator is an online tool that converts numbers or text into scannable barcode images that can be used for products, labels, and inventory systems.',
|
||||
},
|
||||
'Is this barcode generator free to use?': {
|
||||
question: 'Is this barcode generator free to use?',
|
||||
answer: 'Yes, our online barcode generator is completely free to use with no hidden costs or sign-ups required. You can generate, download, and print barcodes instantly.',
|
||||
},
|
||||
'Which barcode format should I use?': {
|
||||
question: 'Which barcode format should I use?',
|
||||
answer: 'EAN-13 is standard for retail in Europe/Global. UPC-A is standard for retail in USA/Canada. Code 128 is best for logistics and internal tracking as it supports letters and numbers.',
|
||||
},
|
||||
'Can I download barcodes in vector format (SVG)?': {
|
||||
question: 'Can I download barcodes in vector format (SVG)?',
|
||||
answer: 'Yes! We offer SVG downloads. SVG files are vector-based, meaning they can be scaled to any size without losing quality—perfect for professional product packaging.',
|
||||
},
|
||||
'How do I generate a barcode online?': {
|
||||
question: 'How do I generate a barcode online?',
|
||||
answer: 'To generate a barcode online, enter your product number or text, select the desired barcode format (such as EAN-13 or Code 128), and click the generate button. The barcode will be created instantly.',
|
||||
},
|
||||
'Are generated barcodes scannable?': {
|
||||
question: 'Are generated barcodes scannable?',
|
||||
answer: 'Yes, barcodes generated with a proper barcode generator are fully scannable. We generate standard-compliant barcodes readable by any standard optical or laser barcode scanner.',
|
||||
},
|
||||
'Can I use these barcodes for Amazon (EAN/UPC)?': {
|
||||
question: 'Can I use these barcodes for Amazon (EAN/UPC)?',
|
||||
answer: 'You can generate the image for Amazon here if you already have your EAN/UPC number. However, you cannot "create" a valid global EAN number here—you must purchase those official numbers from GS1 to sell on major platforms like Amazon.',
|
||||
},
|
||||
'What is the difference between a barcode and a QR code?': {
|
||||
question: 'What is the difference between a barcode and a QR code?',
|
||||
answer: 'A barcode stores data horizontally (1D) and is mainly used for product IDs. A QR code stores data in 2D (matrix) and can hold much more information, such as URLs, vCards, or WiFi credentials.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function BarcodeGeneratorPage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Barcode Generator" toolSlug="barcode-generator" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-slate-900">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
{/* Barcode Pattern */}
|
||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="barcode_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||
<path d="M5 0 V 60 M15 0 V 60 M20 0 V 60 M35 0 V 60 M40 0 V 60 M55 0 V 60" stroke="white" strokeWidth="2" strokeOpacity="0.5" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#barcode_pattern)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-400"></span>
|
||||
</span>
|
||||
Free Tool — Professional & Fast
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Free Online <span className="text-blue-400">Barcode Generator</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-slate-400 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Our <strong>barcode generator</strong> makes it easy to create and print high-quality labels for products and inventory.
|
||||
<span className="text-white block sm:inline mt-2 sm:mt-0"> Supports EAN, UPC, Code 128.</span>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Check className="w-4 h-4 text-blue-400" />
|
||||
Retail Ready
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Check className="w-4 h-4 text-blue-400" />
|
||||
Vector SVG Export
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Check className="w-4 h-4 text-blue-400" />
|
||||
No Registration
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-blue-500/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-2 hover:-rotate-1 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/5 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-gradient-to-br from-blue-400 to-indigo-600 rounded-xl shadow-lg p-5 mb-6 relative overflow-hidden text-white">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<BarcodeIcon className="w-8 h-8 opacity-80" />
|
||||
<div className="bg-white/20 px-2 py-1 rounded text-xs font-bold uppercase tracking-wider">Label</div>
|
||||
</div>
|
||||
<div className="text-xl font-bold tracking-wider mb-1">PROD-98234</div>
|
||||
<div className="text-xs opacity-70">Inventory ID</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-32 bg-white rounded-xl p-4 shadow-inner relative overflow-hidden flex flex-col items-center justify-center">
|
||||
<div className="w-full h-16 bg-black flex gap-1 mb-2">
|
||||
{[2, 4, 1, 3, 2, 1, 4, 2, 1, 3].map((w, i) => (
|
||||
<div key={i} className="bg-black flex-1" style={{ flex: w }} />
|
||||
))}
|
||||
</div>
|
||||
<div className="text-[10px] font-mono font-bold tracking-widest uppercase">98234001A</div>
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -right-6 bg-slate-900 border border-white/10 py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-blue-500/20 p-2 rounded-full">
|
||||
<Printer className="w-5 h-5 text-blue-500" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Ready</div>
|
||||
<div className="text-sm font-bold text-white">Print Instantly</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<BarcodeGeneratorClient />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Our Barcode Generator Works
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Sliders className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Configure</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Enter your data and select the format.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Sparkles className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Customize</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Adjust height, width and text display.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Zap className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Preview</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
See your barcode update in real-time.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save as professional PNG or SVG.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Printer className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Print</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Print labels directly from your browser.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* SEO GUIDE */}
|
||||
<BarcodeGuide />
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Phone,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Smartphone
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
richBlue: '#1A1265',
|
||||
richBlueLight: '#2A2275',
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
{ name: 'Teal', value: '#0D9488' },
|
||||
{ name: 'Coral', value: '#F43F5E' },
|
||||
{ name: 'Amber', value: '#D97706' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'call', label: 'Call Me' },
|
||||
{ id: 'contact', label: 'Contact' },
|
||||
];
|
||||
|
||||
export default function PhoneGenerator() {
|
||||
const [phone, setPhone] = useState('');
|
||||
const [qrColor, setQrColor] = useState(BRAND.richBlue);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const qrValue = `tel:${phone}`;
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `call-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `call-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Phone Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Phone className="w-5 h-5 text-[#1A1265]" />
|
||||
Call Number
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Number to Call</label>
|
||||
<Input
|
||||
placeholder="+1 (555) 123-4567"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#1A1265] focus:ring-[#1A1265]"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">Enter with country code for best results (e.g. +1).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#1A1265]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#1A1265] text-white border-[#1A1265]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={phone ? qrValue : "tel:+123456789"}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{phone || '+1 555 ...'}</span>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#1A1265] hover:bg-[#2A2275] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning initiates a direct call on any mobile phone.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#1A1265] to-[#2A2275] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Need to change contact info?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Dynamic QR Codes act as a digital business card that you can update anytime.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#1A1265] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Create vCard Plus
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
334
src/app/(main)/(marketing)/tools/call-qr-code-generator/page.tsx
Normal file
334
src/app/(main)/(marketing)/tools/call-qr-code-generator/page.tsx
Normal file
@@ -0,0 +1,334 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import PhoneGenerator from './PhoneGenerator';
|
||||
import { Phone, Shield, Zap, Smartphone, PhoneCall, Download } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata for "Call QR Code"
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Call QR Code Generator | Click-to-Call & Phone QR | QR Master',
|
||||
},
|
||||
description: 'Create Action-Oriented Call QR Codes. Scanners instantly dial your number. Perfect for "Call Now" buttons on print & flyers. Free & No Signup.',
|
||||
keywords: ['call qr code', 'click to call qr code', 'call now qr code', 'phone call qr generator', 'qr code for calling', 'call me qr', 'direct call qr code', 'dialer qr code', 'telefon qr code', 'anruf qr code'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/call-qr-code-generator',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Call QR Code Generator | Click-to-Call | QR Master',
|
||||
description: 'Generate Click-to-Call QR Codes. One scan to start a phone call instantly. Best for flyers and business cards.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/call-qr-code-generator',
|
||||
images: [{ url: '/og-phone-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Call QR Code Generator | Start Calls with a Scan',
|
||||
description: 'Create QR codes for instant calling. Free and reliable.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Call QR Code Generator',
|
||||
'Generate QR codes that trigger a phone call when scanned on a mobile device.',
|
||||
'/og-phone-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a Call QR Code',
|
||||
description: 'Create a QR code that dials a number automatically.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Number',
|
||||
text: 'Type your phone number with country code (e.g., +1 555-0199).',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Choose a color and add a label like "Call Me".',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Save the "Call Now" QR code and print it on your materials.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Test',
|
||||
text: 'Scan the code with your phone to ensure the number is correct.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Add to business cards, flyers, or supports desks.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Does it call automatically?': {
|
||||
question: 'Does it call automatically?',
|
||||
answer: 'Scanning the QR code opens the phone dialer with the number pre-filled. The user must tap the call button to initiate the call (security feature of phones).',
|
||||
},
|
||||
'Does it work internationally?': {
|
||||
question: 'Does it work internationally?',
|
||||
answer: 'Yes! We recommend entering your number in international format (starting with +) to ensure it works anywhere in the world.',
|
||||
},
|
||||
'Is my phone number private?': {
|
||||
question: 'Is my phone number private?',
|
||||
answer: 'Yes. We do not store your number. It is encoded directly into the QR code image.',
|
||||
},
|
||||
'Can I track calls?': {
|
||||
question: 'Can I track calls?',
|
||||
answer: 'This static Call QR Code cannot track calls. For tracking scans and analytics, consider using our VCard Plus solution.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, completely free. We do not charge for generating or scanning the code.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function CallQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Call QR Code Generator" toolSlug="call-qr-code-generator" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Create Instant <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">"Call Now" QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
The #1 Tool for Click-to-Call QR Codes. Scanners instantly open their phone dialer with your number pre-filled.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for print marketing.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<PhoneCall className="w-4 h-4 text-emerald-400" />
|
||||
One-Tap Call
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-400" />
|
||||
Instant Dial
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Shield className="w-4 h-4 text-purple-400" />
|
||||
No Data Saved
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center animate-pulse">
|
||||
<Phone className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="h-2 w-24 bg-slate-200 rounded-full mb-1" />
|
||||
<div className="h-2 w-16 bg-slate-100 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-emerald-100 p-2 rounded-full">
|
||||
<PhoneCall className="w-5 h-5 text-emerald-600" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||
<div className="text-sm font-bold text-slate-900">Calling...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<PhoneGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Call QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Phone className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Enter Number</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Type your phone number. Include the country code for international compatibility.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Customers scan the QR code with their mobile phone's camera.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<PhoneCall className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Call</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Their phone dialer opens automatically with your number.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save the QR code image.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Shield className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Add it to your marketing materials.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Call QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Do I need to include the country code?"
|
||||
answer="We highly recommend it. Adding the country code (e.g., +1 for USA/Canada, +44 for UK) ensures any phone from any region can dial your number correctly."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does this work on iPhone and Android?"
|
||||
answer="Yes, Call QR codes are a standard format supported natively by iOS and Android camera apps."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I change the number later?"
|
||||
answer="No. Static Call QR codes can't be edited. If your phone number changes, you'll need a new QR code. Use a Dynamic QR Code if you anticipate changes."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is there a cost per scan?"
|
||||
answer="No. There are no fees or limits on scanning. It works just like sharing your phone number."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is it free?"
|
||||
answer="Yes, completely free. We do not charge for generating or scanning the code."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Bitcoin,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Wallet,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Select } from '@/components/ui/Select';
|
||||
import { cn } from '@/lib/utils';
|
||||
import AdBanner from '@/components/ads/AdBanner';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
richBlue: '#1A1265',
|
||||
richBlueLight: '#2A2275',
|
||||
};
|
||||
|
||||
// Crypto Options
|
||||
const CRYPTO_CURRENCIES = [
|
||||
{ value: 'bitcoin', label: 'Bitcoin (BTC)', color: '#F7931A', prefix: 'bitcoin:' },
|
||||
{ value: 'ethereum', label: 'Ethereum (ETH)', color: '#627EEA', prefix: 'ethereum:' },
|
||||
{ value: 'usdt', label: 'Tether (USDT)', color: '#26A17B', prefix: '' }, // Commonly ERC20/TRC20 - keeping raw for safety
|
||||
{ value: 'solana', label: 'Solana (SOL)', color: '#14F195', prefix: 'solana:' },
|
||||
];
|
||||
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Bitcoin Orange', value: '#F7931A' },
|
||||
{ name: 'Ethereum Blue', value: '#627EEA' },
|
||||
{ name: 'Tether Green', value: '#26A17B' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Dark Blue', value: '#1A1265' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'pay', label: 'Pay Now' },
|
||||
{ id: 'donate', label: 'Donate' },
|
||||
];
|
||||
|
||||
export default function CryptoGenerator() {
|
||||
const [currency, setCurrency] = useState('bitcoin');
|
||||
const [address, setAddress] = useState('');
|
||||
const [amount, setAmount] = useState('');
|
||||
const [qrMode, setQrMode] = useState<'universal' | 'wallet'>('universal');
|
||||
const [qrColor, setQrColor] = useState('#F7931A');
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Generate URL based on selected mode
|
||||
const getUrl = () => {
|
||||
if (!address.trim()) return 'https://www.qrmaster.net';
|
||||
|
||||
const cleanAddr = address.trim();
|
||||
|
||||
if (qrMode === 'wallet') {
|
||||
// Wallet Direct Mode - Uses crypto URI scheme
|
||||
// Only works when scanning FROM a wallet app (Coinbase, Trust Wallet, etc.)
|
||||
const prefixes: Record<string, string> = {
|
||||
bitcoin: 'bitcoin:',
|
||||
ethereum: 'ethereum:',
|
||||
solana: 'solana:',
|
||||
usdt: '', // USDT doesn't have a standard URI
|
||||
};
|
||||
const prefix = prefixes[currency] || '';
|
||||
if (!prefix) return cleanAddr; // USDT fallback
|
||||
let uri = `${prefix}${cleanAddr}`;
|
||||
if (amount) uri += `?amount=${amount}`;
|
||||
return uri;
|
||||
} else {
|
||||
// Universal Mode - Blockchain explorer links
|
||||
// Works with ANY phone camera
|
||||
switch (currency) {
|
||||
case 'bitcoin':
|
||||
return `https://blockchair.com/bitcoin/address/${cleanAddr}`;
|
||||
case 'ethereum':
|
||||
return `https://etherscan.io/address/${cleanAddr}`;
|
||||
case 'solana':
|
||||
return `https://solscan.io/account/${cleanAddr}`;
|
||||
case 'usdt':
|
||||
return `https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7?a=${cleanAddr}`;
|
||||
default:
|
||||
return cleanAddr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `${currency}-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `${currency}-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Crypto Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Wallet className="w-5 h-5 text-slate-900" />
|
||||
Wallet Details
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Currency</label>
|
||||
<Select
|
||||
value={currency}
|
||||
options={CRYPTO_CURRENCIES}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
setCurrency(val);
|
||||
const col = CRYPTO_CURRENCIES.find(c => c.value === val)?.color;
|
||||
if (col) setQrColor(col);
|
||||
}}
|
||||
className="h-12 w-full rounded-xl border-slate-200"
|
||||
aria-label="Currency"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Wallet Address</label>
|
||||
<Input
|
||||
placeholder={`Enter ${currency} address`}
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-slate-900 focus:ring-slate-900 font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Amount (Optional)</label>
|
||||
<Input
|
||||
placeholder="0.00"
|
||||
type="number"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-slate-900 focus:ring-slate-900"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* QR Mode Toggle */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">QR Code Mode</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
onClick={() => setQrMode('universal')}
|
||||
className={cn(
|
||||
"py-3 px-4 rounded-xl text-sm font-medium transition-all border",
|
||||
qrMode === 'universal'
|
||||
? "bg-slate-900 text-white border-slate-900"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
Universal (Web)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setQrMode('wallet')}
|
||||
className={cn(
|
||||
"py-3 px-4 rounded-xl text-sm font-medium transition-all border",
|
||||
qrMode === 'wallet'
|
||||
? "bg-slate-900 text-white border-slate-900"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
Wallet Direct
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-slate-600 mt-2">
|
||||
{qrMode === 'universal'
|
||||
? "Works with any phone camera. Opens blockchain explorer."
|
||||
: "Requires scanning from a wallet app. Enables direct payment."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-slate-900" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-slate-900 text-white border-slate-900"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
{address.trim() ? (
|
||||
<QRCodeSVG
|
||||
value={getUrl()}
|
||||
size={240}
|
||||
level="Q"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="flex items-center justify-center border-2 border-dashed border-slate-200 rounded-xl"
|
||||
style={{ width: 240, height: 240 }}
|
||||
>
|
||||
<div className="text-center text-slate-400 p-6">
|
||||
<Wallet className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
||||
<p className="text-sm font-medium">Enter wallet address</p>
|
||||
<p className="text-xs mt-1">to generate QR code</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Bitcoin className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate capitalize">{currency}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-600 mt-1 truncate px-2">
|
||||
{address || 'Wallet Address'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-slate-900 hover:bg-black text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning copies the wallet address or opens a crypto app.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-slate-900 to-slate-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Accept Crypto for Business?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Create professional, branded payment pages for your store.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-slate-900 hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Get Business Tools
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
335
src/app/(main)/(marketing)/tools/crypto-qr-code/page.tsx
Normal file
335
src/app/(main)/(marketing)/tools/crypto-qr-code/page.tsx
Normal file
@@ -0,0 +1,335 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import CryptoGenerator from './CryptoGenerator';
|
||||
import { Bitcoin, Shield, Zap, Smartphone, Wallet, Coins, Sparkles, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Crypto QR Code Generator | Krypto QR Code Erstellen | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your Crypto wallet address. Erstelle Bitcoin & Ethereum QR Codes für einfache Zahlungen. Supports BTC, ETH, USDT & more.',
|
||||
keywords: ['crypto qr code', 'bitcoin qr generator', 'ethereum qr code', 'crypto wallet qr', 'donation qr code', 'krypto qr code', 'bitcoin qr code erstellen', 'kryptowährung qr code', 'wallet adresse qr code'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/crypto-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Crypto QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes to accept Crypto payments securely. Supports BTC, ETH, SOL.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/crypto-qr-code',
|
||||
images: [{ url: '/og-crypto-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Crypto QR Code Generator',
|
||||
description: 'Create secure QR codes for your crypto wallet.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Crypto QR Code Generator',
|
||||
'Generate QR codes that contain your cryptocurrency wallet address for easy payments.',
|
||||
'/og-crypto-generator.png',
|
||||
'FinanceApplication'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a Crypto QR Code',
|
||||
description: 'Create a QR code for your Bitcoin or Ethereum wallet.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Select Currency',
|
||||
text: 'Choose your cryptocurrency from the list (Bitcoin, Ethereum, USDT, etc.).',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Enter Address',
|
||||
text: 'Copy your public wallet address from your crypto app and paste it into the "Wallet Address" field.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Add Amount (Optional)',
|
||||
text: 'If you are requesting a specific payment, enter the amount to pre-fill the transaction.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Customize QR',
|
||||
text: 'Select a brand color (like Bitcoin Orange or Ethereum Blue) and add a frame like "Pay Now".',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Download',
|
||||
text: 'Download the QR code image and share it to receive funds securely.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Is it safe to share my wallet address?': {
|
||||
question: 'Is it safe to share my wallet address?',
|
||||
answer: 'Yes. Your public wallet address is designed to be shared so you can receive funds. Never share your private key.',
|
||||
},
|
||||
'Which currencies are supported?': {
|
||||
question: 'Which currencies are supported?',
|
||||
answer: 'Our generator supports standard URI schemes for Bitcoin, Ethereum, Solana, and can generally store any wallet string for other coins.',
|
||||
},
|
||||
'Can I add a specific amount?': {
|
||||
question: 'Can I add a specific amount?',
|
||||
answer: 'Yes, you can pre-fill an amount so when the user scans, their wallet app automatically suggests the correct payment value.',
|
||||
},
|
||||
'Does it work with all wallets?': {
|
||||
question: 'Does it work with all wallets?',
|
||||
answer: 'Yes, standard crypto QR codes are universally readable by almost all modern wallet apps (Coinbase, MetaMask, Trust Wallet, etc.).',
|
||||
},
|
||||
'Are there any fees?': {
|
||||
question: 'Are there any fees?',
|
||||
answer: 'No. This generator is completely free. We do not charge any fees for generating codes or for the transactions made using them.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function CryptoQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Crypto QR Code Generator" toolSlug="crypto-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-slate-900">
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
{/* Circuit Pattern */}
|
||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="circuit_pattern" width="100" height="100" patternUnits="userSpaceOnUse">
|
||||
<path d="M10 10 H 90 V 90 H 10 Z" stroke="none" fill="none" />
|
||||
<circle cx="20" cy="20" r="2" fill="#F7931A" />
|
||||
<circle cx="80" cy="80" r="2" fill="#627EEA" />
|
||||
<path d="M20 20 L 50 20 L 50 50" stroke="white" strokeWidth="1" strokeOpacity="0.1" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#circuit_pattern)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-orange-400"></span>
|
||||
</span>
|
||||
Free Tool — Secure & Private
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Accept Payments with <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-[#F7931A] to-[#F2A900]">Crypto QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-slate-400 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Share your wallet address securely. Supports Bitcoin, Ethereum, USDT, and more.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Error-free transfers.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Bitcoin className="w-4 h-4 text-[#F7931A]" />
|
||||
Bitcoin
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Coins className="w-4 h-4 text-[#627EEA]" />
|
||||
Ethereum & Altcoins
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Wallet className="w-4 h-4 text-white" />
|
||||
Wallet Connect
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-orange-500/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-2 hover:-rotate-1 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/5 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-gradient-to-br from-orange-400 to-orange-600 rounded-xl shadow-lg p-5 mb-6 relative overflow-hidden text-white">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<Bitcoin className="w-8 h-8 opacity-80" />
|
||||
<div className="bg-white/20 px-2 py-1 rounded text-xs">BTC</div>
|
||||
</div>
|
||||
<div className="text-2xl font-bold tracking-wider mb-1">0.05 BTC</div>
|
||||
<div className="text-xs opacity-70">$3,450.25 USD</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#333" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -right-6 bg-slate-900 border border-white/10 py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-orange-500/20 p-2 rounded-full">
|
||||
<Wallet className="w-5 h-5 text-orange-500" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Payment</div>
|
||||
<div className="text-sm font-bold text-white">Receive Crypto</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<CryptoGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Crypto QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Coins className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Select</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Choose your crypto currency (BTC, ETH, etc.).
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Wallet className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Paste</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Enter your public wallet address.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Zap className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Amount</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Optionally specify an amount to request.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Sparkles className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Style</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Customize colors and add a 'Pay' frame.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your secure QR code image.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Crypto QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Is it safe to share my wallet address?"
|
||||
answer="Yes. Your public wallet address is designed to be shared so you can receive funds. Never share your private key."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Which currencies are supported?"
|
||||
answer="Our generator supports standard URI schemes for Bitcoin, Ethereum, Solana, and can generally store any wallet string for other coins."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I add a specific amount?"
|
||||
answer="Yes, you can pre-fill an amount so when the user scans, their wallet app automatically suggests the correct payment value."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it work with all wallets?"
|
||||
answer="Yes, standard crypto QR codes are universally readable by almost all modern wallet apps (Coinbase, MetaMask, Trust Wallet, etc.)."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Are there any fees?"
|
||||
answer="No. This generator is completely free. We do not charge any fees for generating codes or for the transactions made using them."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Mail,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Type,
|
||||
FileText
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
richRed: '#dc2626',
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Email Red', value: '#dc2626' },
|
||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
{ name: 'Teal', value: '#0D9488' },
|
||||
{ name: 'Coral', value: '#F43F5E' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'email', label: 'Email Me' },
|
||||
{ id: 'contact', label: 'Contact' },
|
||||
{ id: 'send', label: 'Send Mail' },
|
||||
];
|
||||
|
||||
export default function EmailGenerator() {
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
subject: '',
|
||||
body: ''
|
||||
});
|
||||
|
||||
const [qrColor, setQrColor] = useState('#dc2626');
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Generate Mailto Link
|
||||
// Format: mailto:email?subject=...&body=...
|
||||
const getMailtoUrl = () => {
|
||||
const params = new URLSearchParams();
|
||||
if (formData.subject) params.append('subject', formData.subject);
|
||||
if (formData.body) params.append('body', formData.body);
|
||||
|
||||
const queryString = params.toString();
|
||||
return `mailto:${formData.email}${queryString ? `?${queryString}` : ''}`;
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `email-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const urlBlob = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = urlBlob;
|
||||
link.download = `email-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Input Fields */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Mail className="w-5 h-5 text-red-600" />
|
||||
Email Details
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Recipient Email</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-3 w-4 h-4 text-slate-400" />
|
||||
<Input
|
||||
name="email"
|
||||
placeholder="recipient@example.com"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className="h-11 rounded-xl pl-9"
|
||||
type="email"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Subject Line</label>
|
||||
<div className="relative">
|
||||
<Type className="absolute left-3 top-3 w-4 h-4 text-slate-400" />
|
||||
<Input
|
||||
name="subject"
|
||||
placeholder="e.g. Inquiry about services"
|
||||
value={formData.subject}
|
||||
onChange={handleChange}
|
||||
className="h-11 rounded-xl pl-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Body Message (Optional)</label>
|
||||
<textarea
|
||||
name="body"
|
||||
placeholder="Hi there, I would like to know more about..."
|
||||
value={formData.body}
|
||||
onChange={handleChange}
|
||||
className="w-full h-32 p-3 rounded-xl border border-slate-200 focus:border-red-600 focus:ring-1 focus:ring-red-600 focus:outline-none resize-none text-base"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-red-600" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-red-600 text-white border-red-600"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={getMailtoUrl() || 'mailto:hello@example.com'}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div className="mt-6 text-center">
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-red-50 mx-auto mb-3">
|
||||
<Mail className="w-6 h-6 text-red-600" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 text-lg truncate max-w-[260px] mx-auto">
|
||||
{formData.email || 'Email QR Code'}
|
||||
</h3>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-red-600 hover:bg-red-700 text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
100% free. No signup required.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-red-600 to-rose-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Change your email address often?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">Dynamic QR Codes allow you to update the recipient without reprinting.</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-red-700 hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Go Dynamic
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
269
src/app/(main)/(marketing)/tools/email-qr-code/page.tsx
Normal file
269
src/app/(main)/(marketing)/tools/email-qr-code/page.tsx
Normal file
@@ -0,0 +1,269 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import EmailGenerator from './EmailGenerator';
|
||||
import { Mail, Zap, Smartphone, Lock, Download, Sparkles } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Email QR Code Generator | Email QR Code Erstellen | QR Master',
|
||||
},
|
||||
description: 'Create an Email QR code to send emails instantly. Email QR Code erstellen mit Betreff und Text. 100% free and secure.',
|
||||
keywords: ['email qr code', 'mailto qr', 'email generator', 'free qr code', 'email qr code erstellen', 'email schreiben qr code', 'qr code für email', 'mailto qr code generator', 'email vorlage qr code'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/email-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Email QR Code Generator | QR Master',
|
||||
description: 'Send emails instantly with a custom QR code. Add recipient, subject, and body.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/email-qr-code',
|
||||
images: [{ url: '/og-email-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Email QR Code Generator',
|
||||
'Generate Email QR codes for mailto links with subject and body.',
|
||||
'/og-email-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create an Email QR Code',
|
||||
step: [
|
||||
{ '@type': 'HowToStep', position: 1, name: 'Enter Recipient', text: 'Type the email address you want to receive emails at.' },
|
||||
{ '@type': 'HowToStep', position: 2, name: 'Add Details', text: 'Optional: Add a pre-filled subject line and body text.' },
|
||||
{ '@type': 'HowToStep', position: 3, name: 'Customize', text: 'Choose a brand color and add a call-to-action frame.' },
|
||||
{ '@type': 'HowToStep', position: 4, name: 'Download', text: 'Download your QR code in PNG or SVG.' },
|
||||
{ '@type': 'HowToStep', position: 5, name: 'Share', text: 'Add to business cards or flyers.' },
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'How does it work?': {
|
||||
question: 'How does it work?',
|
||||
answer: 'When scanned, it opens the user\'s default email app (like Gmail or Outlook) with a new draft composed to your address.',
|
||||
},
|
||||
'Can I add a subject line?': {
|
||||
question: 'Can I add a subject line?',
|
||||
answer: 'Yes! You can pre-fill the subject line and the body content so the sender just has to hit send.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, 100% free with unlimited scans.',
|
||||
},
|
||||
'Does it work with attachments?': {
|
||||
question: 'Does it work with attachments?',
|
||||
answer: 'No. The standard mailto format does not support attaching files automatically. Users will have to attach files manually.',
|
||||
},
|
||||
'Is it private?': {
|
||||
question: 'Is it private?',
|
||||
answer: 'Yes. The data is encoded directly into the QR code. We do not store your email or message data.',
|
||||
},
|
||||
}),
|
||||
]
|
||||
};
|
||||
|
||||
export default function EmailPage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Email QR Code Generator" toolSlug="email-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#dc2626' }}>
|
||||
|
||||
{/* Background Pattern */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
|
||||
{/* Left: Text Content */}
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-300 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-300"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
The Smartest Way to <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-red-200 to-rose-200">Receive Emails</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-red-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Create a QR code that opens a pre-composed email instantly.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for feedback & inquiries.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Mail className="w-4 h-4 text-red-300" />
|
||||
Instant Draft
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-yellow-300" />
|
||||
Pre-filled Content
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-red-300" />
|
||||
Mobile Ready
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Visual Abstract Composition */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
{/* Decorative Glow */}
|
||||
<div className="absolute w-[500px] h-[500px] bg-red-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
{/* Floating Glass Card */}
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
{/* Mock QR */}
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#b91c1c" level="Q" />
|
||||
{/* Scan Line */}
|
||||
<div className="absolute top-1/2 left-0 w-full h-1 bg-red-500 shadow-[0_0_20px_rgba(220,38,38,1)] animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-3">
|
||||
<div className="h-2 w-32 bg-white/20 rounded-full mx-auto" />
|
||||
<div className="h-2 w-20 bg-white/10 rounded-full mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-red-100 p-2 rounded-full">
|
||||
<Mail className="w-5 h-5 text-red-600" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||
<div className="text-sm font-bold text-slate-900">Live</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<EmailGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Email QR Codes Work
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Mail className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Add Email</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Enter the address and subject.</p>
|
||||
</article>
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Sparkles className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Customize</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Pick a brand color.</p>
|
||||
</article>
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Zap className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Style</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Add a cool frame.</p>
|
||||
</article>
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Save your QR code.</p>
|
||||
</article>
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Print and get emails.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">Common questions about Email QR codes.</p>
|
||||
<div className="space-y-4">
|
||||
<FaqItem question="Does it work with Gmail?" answer="Yes, and Outlook, Apple Mail, Yahoo, etc. It opens the default mail app on the user's device." />
|
||||
<FaqItem question="Is it reversible?" answer="Yes, if you made a mistake you would need to generate a new code, as static QR codes cannot be edited after creation." />
|
||||
<FaqItem question="Is this tool free?" answer="Yes, completely free to use." />
|
||||
<FaqItem question="Can I attach files?" answer="No. The mailto standard does not support automatic attachment of files. Users must attach them manually." />
|
||||
<FaqItem question="Is it private?" answer="Yes. The data is encoded directly into the QR code. We do not store your email or message data." />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// FAQ Item Component
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Calendar,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Clock,
|
||||
MapPin,
|
||||
AlignLeft
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#F5F3FF', // Violet-50
|
||||
primary: '#7C3AED', // Violet-600
|
||||
primaryDark: '#6D28D9', // Violet-700
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
{ name: 'Purple', value: '#9333EA' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||
{ name: 'Pink', value: '#DB2777' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'event', label: 'Event' },
|
||||
{ id: 'save', label: 'Save Date' },
|
||||
];
|
||||
|
||||
export default function EventGenerator() {
|
||||
const [title, setTitle] = useState('');
|
||||
const [location, setLocation] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [startDate, setStartDate] = useState('');
|
||||
const [endDate, setEndDate] = useState('');
|
||||
|
||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Format Date for iCal: YYYYMMDDTHHMMSS
|
||||
const formatDate = (dateString: string) => {
|
||||
if (!dateString) return '';
|
||||
const d = new Date(dateString);
|
||||
// Basic formatting, assumes local time for simplicity in this static tool
|
||||
const year = d.getFullYear();
|
||||
const month = ('0' + (d.getMonth() + 1)).slice(-2);
|
||||
const day = ('0' + d.getDate()).slice(-2);
|
||||
const hours = ('0' + d.getHours()).slice(-2);
|
||||
const minutes = ('0' + d.getMinutes()).slice(-2);
|
||||
const seconds = ('0' + d.getSeconds()).slice(-2);
|
||||
return `${year}${month}${day}T${hours}${minutes}${seconds}`;
|
||||
};
|
||||
|
||||
const qrValue = [
|
||||
'BEGIN:VEVENT',
|
||||
`SUMMARY:${title}`,
|
||||
`LOCATION:${location}`,
|
||||
`DESCRIPTION:${description}`,
|
||||
`DTSTART:${formatDate(startDate)}`,
|
||||
`DTEND:${formatDate(endDate)}`,
|
||||
'END:VEVENT'
|
||||
].join('\n');
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `event-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `event-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Event Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Calendar className="w-5 h-5 text-[#7C3AED]" />
|
||||
Event Details
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Event Title</label>
|
||||
<Input
|
||||
placeholder="Summer Party"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#7C3AED] focus:ring-[#7C3AED]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Start Time</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
className="h-12 text-sm rounded-xl border-slate-200 focus:border-[#1A1265] focus:ring-[#1A1265]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">End Time</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
className="h-12 text-sm rounded-xl border-slate-200 focus:border-[#7C3AED] focus:ring-[#7C3AED]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Location</label>
|
||||
<div className="relative">
|
||||
<MapPin className="absolute left-3 top-3.5 w-5 h-5 text-slate-400" />
|
||||
<Input
|
||||
placeholder="123 Main St, New York"
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
className="pl-10 h-12 text-base rounded-xl border-slate-200 focus:border-[#1A1265] focus:ring-[#1A1265]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Description</label>
|
||||
<textarea
|
||||
className="w-full h-24 p-4 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#1A1265] resize-none text-slate-800 placeholder:text-slate-400"
|
||||
placeholder="Join us for a celebration..."
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#7C3AED]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#7C3AED] text-white border-[#7C3AED]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={(title || startDate) ? qrValue : "Title"}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Calendar className="w-4 h-4 text-[#7C3AED] shrink-0" />
|
||||
<span className="truncate">{title || 'Event Title'}</span>
|
||||
</h3>
|
||||
{(startDate) && (
|
||||
<div className="text-xs text-slate-600 mt-1 flex items-center justify-center gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
{new Date(startDate).toLocaleDateString()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#7C3AED] hover:bg-[#6D28D9] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning adds the event to the user's calendar.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#7C3AED] to-[#6D28D9] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Planning a big event?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Use a Dynamic QR Code to track RSVPs and update event details if the schedule changes.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#7C3AED] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Get Dynamic Events
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
325
src/app/(main)/(marketing)/tools/event-qr-code/page.tsx
Normal file
325
src/app/(main)/(marketing)/tools/event-qr-code/page.tsx
Normal file
@@ -0,0 +1,325 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import EventGenerator from './EventGenerator';
|
||||
import { Calendar, Shield, Zap, Smartphone, Clock, UserCheck, Download, Share2, Check } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Event QR Code Generator | Termin & Kalender QR | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your event. Verabredung & Termine direkt in den Kalender speichern. Save the date instantly. Free & Easy.',
|
||||
keywords: ['event qr code', 'calendar qr code', 'save the date qr', 'ical qr generator', 'invitation qr code', 'event qr code erstellen', 'veranstaltung qr code', 'kalender qr code', 'termin qr code', 'save the date qr code'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/event-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Event QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes to save events to calendars. Share dates easily.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/event-qr-code',
|
||||
images: [{ url: '/og-event-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Event QR Code Generator',
|
||||
description: 'Create QR codes for events. Instant save-to-calendar.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Event QR Code Generator',
|
||||
'Generate QR codes that add event details to the user\'s digital calendar.',
|
||||
'/og-event-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create an Event QR Code',
|
||||
description: 'Create a QR code that saves an event to a calendar.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Event Details',
|
||||
text: 'Fill in the Event Title, Location, Description, Start Time, and End Time.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Choose a color and frame style like "Save the Date".',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Save the QR code and add it to your invitations.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Test',
|
||||
text: 'Scan the code to ensure the event details and times are correct.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Distribute it via email, flyers, or social media.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT45S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Which calendars does it support?': {
|
||||
question: 'Which calendars does it support?',
|
||||
answer: 'The QR code uses the standard iCalendar (ICS) format. It works with Apple Calendar, Google Calendar, Outlook, and most other mobile calendar apps.',
|
||||
},
|
||||
'Can I change the date after printing?': {
|
||||
question: 'Can I change the date after printing?',
|
||||
answer: 'No. This is a static QR code, meaning the event details are permanently embedded in the image. If the date changes, you must create a new QR code. Use our Dynamic QR Code to edit events anytime.',
|
||||
},
|
||||
'Is there a limit to the description length?': {
|
||||
question: 'Is there a limit to the description length?',
|
||||
answer: 'Yes, because the data is stored in the QR code pattern. We recommend keeping descriptions concise (under 300 characters) to ensure the code remains easy to scan.',
|
||||
},
|
||||
'Do users need an app?': {
|
||||
question: 'Do users need an app?',
|
||||
answer: 'No special app is needed. Standard camera apps on iOS and Android can read the code and will prompt the user to "Add to Calendar".',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes. Creating and scanning the code is completely free and requires no signup.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function EventQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Event QR Code Generator" toolSlug="event-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#5B21B6' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-violet-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-violet-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Create Scannable <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-violet-300 to-fuchsia-300">Calendar Invites</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Share your event details instantly. Visitors scan to "Save the Date" directly to their phone calendar.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for invitations.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Calendar className="w-4 h-4 text-violet-300" />
|
||||
Instant Save
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Clock className="w-4 h-4 text-amber-400" />
|
||||
Timezone Smart
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<UserCheck className="w-4 h-4 text-purple-400" />
|
||||
Native Support
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-2 hover:rotate-1 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex flex-col items-center text-center">
|
||||
<div className="w-full h-2 bg-red-500 rounded-full mb-3" />
|
||||
<div className="text-xs uppercase font-bold text-red-500 tracking-widest mb-1">DECEMBER</div>
|
||||
<div className="text-4xl font-black text-slate-900 leading-none mb-1">25</div>
|
||||
<div className="text-xs text-slate-400">Saturday • 8:00 PM</div>
|
||||
</div>
|
||||
|
||||
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#0f172a" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-emerald-100 p-2 rounded-full">
|
||||
<Check className="w-5 h-5 text-emerald-600" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Event</div>
|
||||
<div className="text-sm font-bold text-slate-900">Added to Cal</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<EventGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Event QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Calendar className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Set Details</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Enter the event name, location, and start/end times.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Guests scan the code from your invite, poster, or flyer.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Clock className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your event QR code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Guests scan the code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<UserCheck className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Save</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
They tap "Add to Calendar."
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Event QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Does this work with Google Calendar?"
|
||||
answer="Yes, the generated QR code creates a standard .ics file event, which is compatible with Google Calendar, Apple Calendar, Outlook, and most others."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is the QR code reusable?"
|
||||
answer="No. Because the specific date and time are embedded in the code, you cannot change them later. If the event is rescheduled, you must generate a new QR code."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What happens if the event is in a different time zone?"
|
||||
answer="The user's calendar will usually convert the time to their local time zone automatically when they save it."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is it free?"
|
||||
answer="Yes. Creating and scanning the code is completely free."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Facebook,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
ThumbsUp,
|
||||
Globe
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
richBlue: '#1A1265',
|
||||
richBlueLight: '#2A2275',
|
||||
};
|
||||
|
||||
// QR Color Options - Facebook Theme
|
||||
const QR_COLORS = [
|
||||
{ name: 'Facebook Blue', value: '#1877F2' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Dark Blue', value: '#1A1265' },
|
||||
{ name: 'Teal', value: '#0D9488' },
|
||||
{ name: 'Coral', value: '#F43F5E' },
|
||||
{ name: 'Purple', value: '#7C3AED' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'follow', label: 'Follow Us' },
|
||||
{ id: 'like', label: 'Like Us' },
|
||||
];
|
||||
|
||||
export default function FacebookGenerator() {
|
||||
const [url, setUrl] = useState('');
|
||||
const [qrColor, setQrColor] = useState('#1877F2'); // Default to FB Blue
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `facebook-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `facebook-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Facebook Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Facebook className="w-5 h-5 text-[#1877F2]" />
|
||||
Facebook Page or Profile
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Facebook URL</label>
|
||||
<Input
|
||||
placeholder="https://facebook.com/yourpage"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#1877F2] focus:ring-[#1877F2]"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">Paste the full link to your profile, page, group, or post.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#1877F2]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#1877F2] text-white border-[#1877F2]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={url || "https://facebook.com"}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Facebook className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{url ? url.replace('https://', '') : 'facebook.com/...'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-600 mt-1">Opens in Facebook App</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#1877F2] hover:bg-[#155ebd] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning redirects directly to the Facebook profile or page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#1877F2] to-[#155ebd] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Running a Social Media Campaign?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Dynamic QR Codes allow you to track clicks, likes, and engagement rates in real-time.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#1877F2] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Get Social Analytics
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
337
src/app/(main)/(marketing)/tools/facebook-qr-code/page.tsx
Normal file
337
src/app/(main)/(marketing)/tools/facebook-qr-code/page.tsx
Normal file
@@ -0,0 +1,337 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import FacebookGenerator from './FacebookGenerator';
|
||||
import { Facebook, Shield, Zap, Smartphone, ThumbsUp, Users, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Facebook QR Code Generator | Get Likes & Follows | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your Facebook Page, Profile, or Group. Facebook QR Code erstellen. Scanners follow you instantly. Free & Easy.',
|
||||
keywords: ['facebook qr code', 'fb qr generator', 'facebook page qr', 'follow qr code', 'social media qr code', 'facebook qr code erstellen', 'facebook seite qr code', 'facebook gruppe qr code', 'facebook profil qr code', 'mehr likes qr code'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/facebook-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Facebook QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes to grow your Facebook audience. Instant app redirect.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/facebook-qr-code',
|
||||
images: [{ url: '/og-facebook-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Facebook QR Code Generator',
|
||||
description: 'Create QR codes for Facebook. Boost your engagement.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Facebook QR Code Generator',
|
||||
'Generate QR codes that direct users to a Facebook page, profile, or post.',
|
||||
'/og-facebook-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a Facebook QR Code',
|
||||
description: 'Create a QR code that opens a Facebook page.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Get Link',
|
||||
text: 'Copy the URL of your Facebook Page, Profile, or Group.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Paste Link',
|
||||
text: 'Paste the URL into the generator.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Customize',
|
||||
text: 'Choose your brand color and add a call-to-action frame.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download',
|
||||
text: 'Save the QR code and print it on your marketing materials.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Distribute it on flyers, business cards, or posters.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Does it open the Facebook app?': {
|
||||
question: 'Does it open the Facebook app?',
|
||||
answer: 'Yes! On most mobile devices, standard Facebook links are automatically detected and opened in the Facebook app if it is installed.',
|
||||
},
|
||||
'Can I link to a specific post?': {
|
||||
question: 'Can I link to a specific post?',
|
||||
answer: 'Absolutely. Just paste the direct link to the post (click the timestamp on the post to get the link).',
|
||||
},
|
||||
'Does it work for Facebook Events?': {
|
||||
question: 'Does it work for Facebook Events?',
|
||||
answer: 'Yes. Simply copy the full URL of your Facebook Event and paste it into the generator.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, this generator is 100% free to use for personal or business purposes.',
|
||||
},
|
||||
'Can I track scans?': {
|
||||
question: 'Can I track scans?',
|
||||
answer: 'This static QR code does not include analytics. To track how many people scan your code, you should use our Dynamic QR Code service.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function FacebookQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Facebook QR Code Generator" toolSlug="facebook-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1877F2' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
{/* Facebook Pattern */}
|
||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="fb_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||
<path d="M30 30L35 35M25 35L30 30" stroke="white" strokeWidth="2" strokeOpacity="0.2" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#fb_pattern)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-300 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-300"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Grow Your Audience with <br className="hidden lg:block" />
|
||||
<span className="text-white drop-shadow-md">Facebook QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-blue-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Make it easy for customers to find and follow you. A single scan opens your Page directly in the Facebook app.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Boost likes instantly.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<ThumbsUp className="w-4 h-4 text-blue-200" />
|
||||
Get Likes
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-yellow-300" />
|
||||
Instant Follow
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-green-300" />
|
||||
App Friendly
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-blue-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-full bg-gradient-to-tr from-blue-600 to-blue-400 p-0.5">
|
||||
<div className="w-full h-full bg-white rounded-full flex items-center justify-center">
|
||||
<Facebook className="w-6 h-6 text-[#1877F2]" fill="#1877F2" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="h-2.5 w-24 bg-slate-800 rounded-full mb-1.5" />
|
||||
<div className="h-2 w-16 bg-slate-300 rounded-full" />
|
||||
</div>
|
||||
<button className="ml-auto bg-[#1877F2] text-white px-3 py-1 rounded text-xs font-bold">
|
||||
Like
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#1877F2" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-blue-100 p-2 rounded-full">
|
||||
<Users className="w-5 h-5 text-blue-600" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Followers</div>
|
||||
<div className="text-sm font-bold text-slate-900">+1 New</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<FacebookGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Facebook QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Facebook className="w-7 h-7 text-[#1877F2]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Copy Link</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Go to your Facebook Page or Profile and copy the URL from the browser address bar.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#1877F2]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Your customers scan the code using their phone camera.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<ThumbsUp className="w-6 h-6 text-[#1877F2]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Engage</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
The Facebook app opens directly.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#1877F2]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your high-res QR code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1877F2]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-[#1877F2]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Print it and start getting likes.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Facebook QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Will this work for Facebook Groups?"
|
||||
answer="Yes! You can paste the link to your Facebook Group, and the QR code will direcr users to join."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What if the user doesn't have the Facebook app?"
|
||||
answer="The link will open in their mobile web browser instead, so they can still see your page and log in."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I customize the color?"
|
||||
answer="Yes. While Facebook Blue is recommended for recognition, you can choose any color to match your brand."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is the QR code permanent?"
|
||||
answer="Yes. As long as your Facebook URL doesn't change, this QR code will work forever."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it work for Facebook Events?"
|
||||
answer="Yes. Simply copy the full URL of your Facebook Event and paste it into the generator."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
MapPin,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Navigation,
|
||||
Globe
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#ECFDF5', // Emerald-50
|
||||
primary: '#10B981', // Emerald-500
|
||||
primaryDark: '#047857', // Emerald-700
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Teal', value: '#0D9488' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Navy', value: '#1E3A8A' },
|
||||
{ name: 'Sky', value: '#0EA5E9' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'location', label: 'Location' },
|
||||
{ id: 'map', label: 'View Map' },
|
||||
];
|
||||
|
||||
export default function GeolocationGenerator() {
|
||||
const [latitude, setLatitude] = useState('');
|
||||
const [longitude, setLongitude] = useState('');
|
||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Using Google Maps Universal Link for best compatibility
|
||||
const qrValue = `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`;
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `location-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `location-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
const getCurrentLocation = () => {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
setLatitude(position.coords.latitude.toFixed(6));
|
||||
setLongitude(position.coords.longitude.toFixed(6));
|
||||
},
|
||||
(error) => {
|
||||
console.error("Error getting location: ", error);
|
||||
alert("Could not access location. Please enter manually.");
|
||||
}
|
||||
);
|
||||
} else {
|
||||
alert("Geolocation is not supported by this browser.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Location Details */}
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<MapPin className="w-5 h-5 text-[#10B981]" />
|
||||
Coordinates
|
||||
</h2>
|
||||
<Button
|
||||
onClick={getCurrentLocation}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-[#047857] border-[#047857]/20 hover:bg-[#047857]/5"
|
||||
>
|
||||
<Navigation className="w-3 h-3 mr-2" />
|
||||
Get Current Location
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Latitude</label>
|
||||
<Input
|
||||
placeholder="40.712776"
|
||||
value={latitude}
|
||||
onChange={(e) => setLatitude(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#10B981] focus:ring-[#10B981]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Longitude</label>
|
||||
<Input
|
||||
placeholder="-74.005974"
|
||||
value={longitude}
|
||||
onChange={(e) => setLongitude(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#10B981] focus:ring-[#10B981]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-slate-600">
|
||||
Tip: You can copy-paste coordinates directly from Google Maps.
|
||||
(Right-click a location on standard Maps, then click the coordinates to copy).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#10B981]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#10B981] text-white border-[#10B981]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={(latitude && longitude) ? qrValue : "https://maps.google.com"}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<MapPin className="w-4 h-4 text-[#10B981] shrink-0" />
|
||||
<span className="truncate">{latitude || 'Lat'}, {longitude || 'Long'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-600 mt-1">Google Maps Location</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#10B981] hover:bg-[#047857] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning opens the location directly in Google Maps.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#10B981] to-[#047857] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Need a Business Map?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Create a Dynamic QR Code for your business location. If you move, just update the location without reprinting.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#047857] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Get Dynamic Maps
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
332
src/app/(main)/(marketing)/tools/geolocation-qr-code/page.tsx
Normal file
332
src/app/(main)/(marketing)/tools/geolocation-qr-code/page.tsx
Normal file
@@ -0,0 +1,332 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import GeolocationGenerator from './GeolocationGenerator';
|
||||
import { MapPin, Shield, Zap, Smartphone, Navigation, Map, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Geolocation QR Code Generator | Standort & Map Links | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for a specific location. Erstelle einen Map QR Code für Google Maps. Coordinates & Directions instantly. Standort teilen leicht gemacht.',
|
||||
keywords: ['location qr code', 'maps qr code', 'google maps qr generator', 'geolocation qr', 'coordinates qr code', 'standort qr code', 'google maps qr code erstellen', 'koordinaten qr code', 'wegbeschreibung qr code', 'maps qr code generator'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/geolocation-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Geolocation QR Code Generator | QR Master',
|
||||
description: 'Navigate users to any location with a QR code. Opens directly in Google Maps.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/geolocation-qr-code',
|
||||
images: [{ url: '/og-geolocation-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Geolocation QR Code Generator',
|
||||
description: 'Create QR codes for maps and locations. Instant and free.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Geolocation QR Code Generator',
|
||||
'Generate QR codes that open specific geographic coordinates in map applications.',
|
||||
'/og-geolocation-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a Location QR Code',
|
||||
description: 'Create a QR code that points to a specific map location.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Get Coordinates',
|
||||
text: 'Find the Latitude and Longitude of your location (e.g., from Google Maps).',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Enter Data',
|
||||
text: 'Paste the coordinates into the generator.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Customize',
|
||||
text: 'Choose a color and style for your map QR code.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download',
|
||||
text: 'Save your QR code as a high-quality image.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Place it on invitations, signs, or your website.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT45S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Which map app does it open?': {
|
||||
question: 'Which map app does it open?',
|
||||
answer: 'Our generator creates a universal Google Maps link. On most devices, this will open the Google Maps app if installed, or the browser version if not. It is the most compatible method.',
|
||||
},
|
||||
'How do I find my Latitude and Longitude?': {
|
||||
question: 'How do I find my Latitude and Longitude?',
|
||||
answer: 'On Google Maps desktop: Right-click any spot on the map. The first item in the menu is the coordinates. Click to copy them.',
|
||||
},
|
||||
'Does it work offline?': {
|
||||
question: 'Does it work offline?',
|
||||
answer: 'The QR code itself can be scanned offline, but the user will likely need an internet connection to load the map and get directions.',
|
||||
},
|
||||
'Can I use an address instead?': {
|
||||
question: 'Can I use an address instead?',
|
||||
answer: 'For precise results, we use coordinates. However, you can use our URL Generator and paste a link to your Google Maps address search result if you prefer.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, generating this location QR code is completely free and requires no signup.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function GeolocationQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Location QR Code Generator" toolSlug="geolocation-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#047857' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Share Perfect Locations with <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-300 to-teal-300">Map QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Provide exact directions to your event, store, or secret spot.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Opens directly in Google Maps.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Navigation className="w-4 h-4 text-emerald-400" />
|
||||
Exact Directions
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-400" />
|
||||
Instant Load
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Shield className="w-4 h-4 text-purple-400" />
|
||||
No Data Saved
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-white rounded-xl shadow-lg h-32 mb-6 relative overflow-hidden grayscale group-hover:grayscale-0 transition-all duration-500">
|
||||
<div className="absolute inset-0 opacity-20 bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:16px_16px]"></div>
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<MapPin className="w-8 h-8 text-red-500 drop-shadow-lg animate-bounce" />
|
||||
</div>
|
||||
<div className="absolute bottom-2 left-2 right-2 bg-white/90 p-2 rounded text-[10px] text-slate-600 font-mono text-center">
|
||||
40.7128° N, 74.0060° W
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#0f172a" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-emerald-100 p-2 rounded-full">
|
||||
<Map className="w-5 h-5 text-emerald-600" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Map</div>
|
||||
<div className="text-sm font-bold text-slate-900">Open</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<GeolocationGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Geolocation QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<MapPin className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Pinpoint</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Enter exact GPS coordinates. This ensures users go to the precise spot (e.g., a specific building entrance).
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Users scan the code. It is encoded with a universal map link.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your high-quality QR image.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Users scan the code to load coordinates.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Go</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
They get instant directions to your spot.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Map QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Why not just use an address?"
|
||||
answer="Addresses can be ambiguous or cover large areas (like a park or stadium). Coordinates point to an exact geographic spot, ensuring visitors find the specific meeting point or parking entrance."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it work on Apple Maps?"
|
||||
answer="Yes. While the underlying link is a Google Maps link, iOS devices usually handle these gracefully, either opening them in the Google Maps app (if installed) or the browser, where Apple Maps can often intercept directions."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is it free?"
|
||||
answer="Yes, generating this location QR code is completely free and requires no signup."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I track who scanned it?"
|
||||
answer="Not with this static tool. If you need scan analytics (e.g., how many people scanned your storefront QR), you should use our Dynamic QR Code service."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is it free?"
|
||||
answer="Yes, generating this location QR code is completely free and requires no signup."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Instagram,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Camera
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
richBlue: '#1A1265',
|
||||
richBlueLight: '#2A2275',
|
||||
};
|
||||
|
||||
// QR Color Options - Insta Theme
|
||||
const QR_COLORS = [
|
||||
{ name: 'Insta Pink', value: '#E1306C' },
|
||||
{ name: 'Insta Purple', value: '#833AB4' },
|
||||
{ name: 'Insta Orange', value: '#F77737' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Rich Blue', value: '#1A1265' },
|
||||
{ name: 'Teal', value: '#0D9488' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'follow', label: 'Follow Us' },
|
||||
{ id: 'insta', label: 'Instagram' },
|
||||
];
|
||||
|
||||
export default function InstagramGenerator() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [qrColor, setQrColor] = useState('#E1306C');
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Instagram URL construction: https://instagram.com/username
|
||||
const getUrl = () => {
|
||||
const cleanUser = username.replace(/^@/, '').replace(/https?:\/\/(www\.)?instagram\.com\//, '').replace(/\/$/, '');
|
||||
return cleanUser ? `https://instagram.com/${cleanUser}` : 'https://instagram.com';
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `instagram-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `instagram-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Instagram Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Instagram className="w-5 h-5 text-[#E1306C]" />
|
||||
Instagram Username
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Username or Link</label>
|
||||
<Input
|
||||
placeholder="@username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#E1306C] focus:ring-[#E1306C]"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">Enter your username (without @) or paste full profile link.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#E1306C]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#E1306C] text-white border-[#E1306C]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={getUrl()}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Instagram className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{username || '@username'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-600 mt-1">Opens in Instagram</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#E1306C] hover:bg-[#C13584] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning redirects directly to your Instagram profile.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#833AB4] via-[#FD1D1D] to-[#FCA145] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Want a "Link in Bio" QR?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Create a digital landing page with links to all your socials using Dynamic Codes.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#E1306C] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Create Bio Link
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
325
src/app/(main)/(marketing)/tools/instagram-qr-code/page.tsx
Normal file
325
src/app/(main)/(marketing)/tools/instagram-qr-code/page.tsx
Normal file
@@ -0,0 +1,325 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import InstagramGenerator from './InstagramGenerator';
|
||||
import { Instagram, Shield, Zap, Smartphone, Camera, Heart, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Instagram QR Code Generator | Get More Followers | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your Instagram profile. Erstelle einen Insta QR Code. Scanners follow you instantly. Free & Customizable.',
|
||||
keywords: ['instagram qr code', 'insta qr generator', 'ig nametag generator', 'instagram follow qr', 'social media qr code', 'instagram qr code erstellen', 'instagram profil qr code', 'insta qr code', 'mehr follower qr code', 'instagram nametag generator'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/instagram-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Instagram QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes to grow your Instagram following. Instant app redirect.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/instagram-qr-code',
|
||||
images: [{ url: '/og-instagram-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Instagram QR Code Generator',
|
||||
description: 'Create QR codes for Instagram. Boost your followers.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Instagram QR Code Generator',
|
||||
'Generate QR codes that direct users to an Instagram profile or post.',
|
||||
'/og-instagram-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create an Instagram QR Code',
|
||||
description: 'Create a QR code that opens an Instagram profile.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Username',
|
||||
text: 'Type your Instagram handle (e.g. @yourbrand) or paste your profile link.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Choose a gradient color that matches the Instagram vibe or your own brand.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Save the QR code image.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Test',
|
||||
text: 'Scan the code to ensure it opens the correct profile.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Put it on your packaging, business cards, or storefront.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Is this an Instagram Nametag?': {
|
||||
question: 'Is this an Instagram Nametag?',
|
||||
answer: 'It works similarly! While Instagram has its own internal "Nametag" or "QR Code" feature, our generator allows you to create a standard QR code that is more customizable and can be tracked with our Dynamic plans.',
|
||||
},
|
||||
'Does it open the Instagram app?': {
|
||||
question: 'Does it open the Instagram app?',
|
||||
answer: 'Yes. When scanned on a mobile device with Instagram installed, it will deep-link directly to the profile in the app.',
|
||||
},
|
||||
'Can I link to a specific photo or reel?': {
|
||||
question: 'Can I link to a specific photo or reel?',
|
||||
answer: 'Yes! Instead of your username, just paste the full link to the specific post or reel.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, generating this QR code is 100% free.',
|
||||
},
|
||||
'Can I track scans?': {
|
||||
question: 'Can I track scans?',
|
||||
answer: 'Not with this static tool. If you need scan analytics, consider using our Dynamic QR Code solution.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function InstagramQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Instagram QR Code Generator" toolSlug="instagram-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-gradient-to-br from-[#833AB4] via-[#FD1D1D] to-[#FCA145]">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<circle cx="0" cy="0" r="40" fill="white" fillOpacity="0.1" />
|
||||
<circle cx="100" cy="100" r="50" fill="white" fillOpacity="0.1" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-pink-300 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-pink-300"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Boost Your Following with <br className="hidden lg:block" />
|
||||
<span className="text-white drop-shadow-md">Instagram QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-pink-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Connect physically to digitally. Let customers scan to follow your Instagram profile instantly.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your brand effortlessly.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Heart className="w-4 h-4 text-pink-200" />
|
||||
More Likes
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-yellow-200" />
|
||||
Instant Follow
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-white" />
|
||||
App Deep Link
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-white/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex flex-col items-center">
|
||||
<div className="w-16 h-16 rounded-full p-[2px] bg-gradient-to-tr from-[#FCA145] via-[#FD1D1D] to-[#833AB4] mb-2">
|
||||
<div className="w-full h-full rounded-full bg-white p-1">
|
||||
<div className="w-full h-full rounded-full bg-slate-200" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm font-bold text-slate-900">@yourbrand</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#E1306C" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-gradient-to-tr from-[#FCA145] to-[#E1306C] p-2 rounded-full text-white">
|
||||
<Camera className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Profile</div>
|
||||
<div className="text-sm font-bold text-slate-900">Following</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<InstagramGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Instagram QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Instagram className="w-7 h-7 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Username</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Enter your Instagram handle. No need to login or connect your account.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Print</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Add the QR code to your packaging, business cards, or table tents.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your custom QR code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Heart className="w-6 h-6 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Fans scan to instantly visit your profile.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Grow</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Convert offline traffic into followers.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Instagram QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Does this work for private accounts?"
|
||||
answer="Yes, the link will take users to your profile. If your account is private, they will still have to request to follow you."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I link to a Story?"
|
||||
answer="Yes, but Stories expire after 24 hours (unless saved as a Highlight). Linking to a Highlight or your main Profile is usually better for printed materials."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I customize the frame?"
|
||||
answer="Yes, we offer several frame options like 'Follow Us' or 'Scan Me' to encourage action."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it expire?"
|
||||
answer="No. The QR code will work as long as your Instagram username remains the same."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I track scans?"
|
||||
answer="Not with this static tool. If you need scan analytics, consider using our Dynamic QR Code solution."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
30
src/app/(main)/(marketing)/tools/layout.tsx
Normal file
30
src/app/(main)/(marketing)/tools/layout.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import AdBanner from '@/components/ads/AdBanner';
|
||||
|
||||
export default function ToolsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
{/* AdSense script - managed by global AdSenseScript component */}
|
||||
<div className="flex-grow relative">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{/* Footer Ad Placement - Appears on ALL tool pages */}
|
||||
{/* AdBanner handles its own visibility - only shows when an ad is filled */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl pb-8">
|
||||
<AdBanner
|
||||
dataAdSlot="1234567890" // Placeholder
|
||||
dataAdFormat="auto"
|
||||
fullWidthResponsive={true}
|
||||
className="bg-slate-50 rounded-xl p-4 border border-slate-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
CreditCard,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
DollarSign
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Select } from '@/components/ui/Select';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors - PayPal Blue
|
||||
const BRAND = {
|
||||
paleGrey: '#EFF6FF', // Blue-50
|
||||
primary: '#003087', // PayPal Dark Blue
|
||||
primaryDark: '#001F5C',
|
||||
accent: '#0070BA', // PayPal Light Blue
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'PayPal Blue', value: '#003087' },
|
||||
{ name: 'PayPal Light', value: '#0070BA' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Indigo', value: '#4F46E5' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'pay', label: 'Pay Now' },
|
||||
{ id: 'donate', label: 'Donate' },
|
||||
{ id: 'tip', label: 'Tip Me' },
|
||||
];
|
||||
|
||||
// Currency Options
|
||||
const CURRENCIES = [
|
||||
{ value: 'EUR', label: 'EUR (€)' },
|
||||
{ value: 'USD', label: 'USD ($)' },
|
||||
{ value: 'GBP', label: 'GBP (£)' },
|
||||
{ value: 'CHF', label: 'CHF' },
|
||||
];
|
||||
|
||||
// Input type options
|
||||
const INPUT_TYPES = [
|
||||
{ id: 'username', label: 'PayPal.me Username' },
|
||||
{ id: 'email', label: 'PayPal Email' },
|
||||
];
|
||||
|
||||
export default function PayPalGenerator() {
|
||||
const [inputType, setInputType] = useState('email');
|
||||
const [paypalId, setPaypalId] = useState('');
|
||||
const [amount, setAmount] = useState('');
|
||||
const [currency, setCurrency] = useState('EUR');
|
||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Generate PayPal payment link
|
||||
const generatePayPalLink = () => {
|
||||
if (!paypalId.trim()) return 'https://paypal.com';
|
||||
|
||||
if (inputType === 'username') {
|
||||
// PayPal.me link
|
||||
let link = `https://paypal.me/${paypalId.trim()}`;
|
||||
if (amount && parseFloat(amount) > 0) {
|
||||
link += `/${amount}`;
|
||||
}
|
||||
return link;
|
||||
} else {
|
||||
// PayPal email payment link (donation/payment format)
|
||||
const params = new URLSearchParams({
|
||||
cmd: '_donations',
|
||||
business: paypalId.trim(),
|
||||
currency_code: currency,
|
||||
...(amount && parseFloat(amount) > 0 ? { amount } : {}),
|
||||
});
|
||||
return `https://www.paypal.com/cgi-bin/webscr?${params.toString()}`;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `paypal-qr-${paypalId || 'code'}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `paypal-qr-${paypalId || 'code'}.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* PayPal Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<CreditCard className="w-5 h-5 text-[#003087]" />
|
||||
PayPal Details
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Input Type Toggle */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Payment Method</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{INPUT_TYPES.map((type) => (
|
||||
<button
|
||||
key={type.id}
|
||||
onClick={() => setInputType(type.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
inputType === type.id
|
||||
? "bg-[#003087] text-white border-[#003087]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{type.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
{inputType === 'username' ? 'PayPal.me Username' : 'PayPal Email Address'}
|
||||
</label>
|
||||
<Input
|
||||
type={inputType === 'email' ? 'email' : 'text'}
|
||||
placeholder={inputType === 'username' ? 'e.g. johndoe' : 'e.g. mail@example.com'}
|
||||
value={paypalId}
|
||||
onChange={(e) => setPaypalId(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#003087] focus:ring-[#003087]"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">
|
||||
{inputType === 'username'
|
||||
? <>Find yours at <a href="https://paypal.me" target="_blank" rel="noopener noreferrer" className="text-[#003087] underline">paypal.me</a></>
|
||||
: 'The email address linked to your PayPal account'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Amount (Optional)</label>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="25.00"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#003087] focus:ring-[#003087]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Currency</label>
|
||||
<Select
|
||||
value={currency}
|
||||
onChange={(e) => setCurrency(e.target.value)}
|
||||
className="h-12 rounded-xl border-slate-200"
|
||||
aria-label="Currency"
|
||||
options={CURRENCIES}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#003087]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-5 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-2 rounded-lg text-xs font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#003087] text-white border-[#003087]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={generatePayPalLink()}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* PayPal Info */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<DollarSign className="w-4 h-4 text-[#003087] shrink-0" />
|
||||
<span className="truncate">{paypalId || 'Your PayPal'}</span>
|
||||
</h3>
|
||||
{amount && (
|
||||
<p className="text-sm text-slate-600 mt-1">{amount} {currency}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#003087] hover:bg-[#001F5C] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Your PayPal link is encoded directly. Static and forever free.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#003087] to-[#0070BA] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Need payment analytics?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">Track how many people scan your payment QR code with Dynamic QR Codes.</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#003087] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Get Analytics
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
331
src/app/(main)/(marketing)/tools/paypal-qr-code/page.tsx
Normal file
331
src/app/(main)/(marketing)/tools/paypal-qr-code/page.tsx
Normal file
@@ -0,0 +1,331 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import PayPalGenerator from './PayPalGenerator';
|
||||
import { CreditCard, Shield, Zap, Smartphone, DollarSign, Download, Share2, Banknote } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free PayPal QR Code Generator | Accept Payments Instantly | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your PayPal.me link. PayPal QR Code erstellen. Receive payments instantly. Support tips, donations, and fixed amounts.',
|
||||
keywords: ['paypal qr code', 'paypal.me qr generator', 'payment qr code', 'accept payments qr', 'paypal qr generator', 'tip qr code', 'donation qr code', 'paypal qr code erstellen', 'zahlungs qr code', 'spenden qr code', 'paypal bezahlen qr'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/paypal-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free PayPal QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes for PayPal payments. Perfect for tips, donations, and invoices.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/paypal-qr-code',
|
||||
images: [{ url: '/og-paypal-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free PayPal QR Code Generator',
|
||||
description: 'Create PayPal payment QR codes. Instant and free.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'PayPal QR Code Generator',
|
||||
'Generate QR codes that link to your PayPal.me page for instant payments.',
|
||||
'/og-paypal-generator.png',
|
||||
'FinanceApplication'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a PayPal QR Code',
|
||||
description: 'Create a QR code for receiving PayPal payments.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Username',
|
||||
text: 'Type your PayPal.me username (the part after paypal.me/).',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Set Amount (Optional)',
|
||||
text: 'Enter a pre-filled amount and currency for fixed payments.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Customize Design',
|
||||
text: 'Choose PayPal brand colors and add a frame like "Pay Now" or "Tip Me".',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download QR Code',
|
||||
text: 'Download your high-quality QR code in PNG or SVG format.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Print it on invoices, display at your shop, or share digitally.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'How does the PayPal QR code work?': {
|
||||
question: 'How does the PayPal QR code work?',
|
||||
answer: 'When scanned, it opens the PayPal app or website with your PayPal.me link. If you set an amount, it will be pre-filled for the payer.',
|
||||
},
|
||||
'Do I need a PayPal Business account?': {
|
||||
question: 'Do I need a PayPal Business account?',
|
||||
answer: 'No. Any PayPal account with a PayPal.me link can use this generator. Personal accounts work fine for tips and donations.',
|
||||
},
|
||||
'Is there a fee for using the QR code?': {
|
||||
question: 'Is there a fee for using the QR code?',
|
||||
answer: 'This generator is 100% free. PayPal may charge their standard transaction fees when you receive payments.',
|
||||
},
|
||||
'Can I change the amount later?': {
|
||||
question: 'Can I change the amount later?',
|
||||
answer: 'No, this is a static QR code. The amount is encoded permanently. For variable amounts, leave the amount field empty.',
|
||||
},
|
||||
'What currencies are supported?': {
|
||||
question: 'What currencies are supported?',
|
||||
answer: 'We support EUR, USD, GBP, and CHF. PayPal handles currency conversion automatically.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function PayPalQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="PayPal QR Code Generator" toolSlug="paypal-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#003087' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-sky-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-sky-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Accept Payments with <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-sky-300 to-blue-200">PayPal QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-blue-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Let customers pay you by scanning. Perfect for tips, donations, and invoices.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Instant payments.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<CreditCard className="w-4 h-4 text-sky-300" />
|
||||
PayPal.me Links
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-300" />
|
||||
Pre-fill Amount
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Shield className="w-4 h-4 text-emerald-300" />
|
||||
Secure Payments
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-blue-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
{/* Payment Card Mock */}
|
||||
<div className="w-full bg-gradient-to-br from-[#0070BA] to-[#003087] rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden text-white">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<Banknote className="w-6 h-6 opacity-80" />
|
||||
<div className="bg-white/20 px-2 py-1 rounded text-xs">EUR</div>
|
||||
</div>
|
||||
<div className="text-2xl font-bold tracking-wider">€25.00</div>
|
||||
<div className="text-xs opacity-70 mt-1">Payment Request</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#003087" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-blue-100 p-2 rounded-full">
|
||||
<DollarSign className="w-5 h-5 text-[#003087]" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">PayPal</div>
|
||||
<div className="text-sm font-bold text-slate-900">Ready</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<PayPalGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How PayPal QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<CreditCard className="w-6 h-6 text-[#003087]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Username</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Enter your PayPal.me username.</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<DollarSign className="w-6 h-6 text-[#003087]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Amount</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Optional: Set a fixed payment amount.</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Sparkles className="w-6 h-6 text-[#003087]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Design</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Pick colors and add a frame.</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#003087]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Save as PNG or SVG file.</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#003087]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-[#003087]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Print or share to receive payments.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about PayPal QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="How does the PayPal QR code work?"
|
||||
answer="When scanned, it opens the PayPal app or website with your PayPal.me link. If you set an amount, it will be pre-filled for the payer."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Do I need a PayPal Business account?"
|
||||
answer="No. Any PayPal account with a PayPal.me link can use this generator. Personal accounts work fine for tips and donations."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is there a fee for using the QR code?"
|
||||
answer="This generator is 100% free. PayPal may charge their standard transaction fees when you receive payments."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I change the amount later?"
|
||||
answer="No, this is a static QR code. The amount is encoded permanently. For variable amounts, leave the amount field empty."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What if I don't have a PayPal.me link?"
|
||||
answer="You can create one for free in your PayPal account settings. Go to paypal.me to set up your personalized link."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
function Sparkles({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
266
src/app/(main)/(marketing)/tools/sms-qr-code/SMSGenerator.tsx
Normal file
266
src/app/(main)/(marketing)/tools/sms-qr-code/SMSGenerator.tsx
Normal file
@@ -0,0 +1,266 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
MessageSquare,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Phone
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
primary: '#ea580c', // Orange-600
|
||||
primaryDark: '#c2410c', // Orange-700
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
{ name: 'Teal', value: '#0D9488' },
|
||||
{ name: 'Coral', value: '#F43F5E' },
|
||||
{ name: 'Amber', value: '#D97706' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'sms', label: 'SMS Us' },
|
||||
{ id: 'text', label: 'Text Us' },
|
||||
];
|
||||
|
||||
export default function SMSGenerator() {
|
||||
const [phone, setPhone] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// sms:number?body=message
|
||||
const qrValue = `sms:${phone}?body=${encodeURIComponent(message)}`;
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `sms-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `sms-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* SMS Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<MessageSquare className="w-5 h-5 text-orange-600" />
|
||||
SMS Details
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Phone Number</label>
|
||||
<Input
|
||||
placeholder="+1 555 123 4567"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-orange-600 focus:ring-orange-600"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Pre-filled Message</label>
|
||||
<textarea
|
||||
className="w-full h-32 p-4 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-orange-600 resize-none text-slate-800 placeholder:text-slate-400"
|
||||
placeholder="I'm interested in..."
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
maxLength={160}
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2 text-right">{message.length}/160</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-orange-600" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-orange-600 text-white border-orange-600"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={phone ? qrValue : "sms:+123456789?body=Hello"}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{phone || 'Number'}</span>
|
||||
</h3>
|
||||
<div className="flex items-center justify-center gap-2 mt-2 text-slate-600 text-xs">
|
||||
<MessageSquare className="w-3 h-3" />
|
||||
<span className="italic truncate">{message || 'Your message...'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-orange-600 hover:bg-orange-700 text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Opens the messaging app with text pre-filled.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-orange-600 to-orange-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Use SMS for marketing?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Dynamic QR Codes offer better tracking and allow you to change the campaign message later.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-orange-700 hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Try Dynamic Codes
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
291
src/app/(main)/(marketing)/tools/sms-qr-code/page.tsx
Normal file
291
src/app/(main)/(marketing)/tools/sms-qr-code/page.tsx
Normal file
@@ -0,0 +1,291 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SMSGenerator from './SMSGenerator';
|
||||
import { MessageSquare, Shield, Zap, Smartphone, Send } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free SMS QR Code Generator | SMS QR Code Erstellen | QR Master',
|
||||
},
|
||||
description: 'Create a QR code to send an SMS text message instantly. SMS QR Code erstellen mit vorgefertigtem Text. Free, private, and works on all phones.',
|
||||
keywords: ['sms qr code', 'text message qr code', 'send sms qr', 'sms generator', 'text qr', 'sms qr code erstellen', 'qr code für sms', 'nachricht qr code', 'sms vorlage qr code', 'sms versenden qr'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/sms-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free SMS QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes for instant SMS messages. Pre-fill text and number.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/sms-qr-code',
|
||||
images: [{ url: '/og-sms-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free SMS QR Code Generator',
|
||||
description: 'Create QR codes to send texts. Instant and free.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'SMS QR Code Generator',
|
||||
'Generate QR codes that open the user\'s SMS app with a pre-filled message.',
|
||||
'/og-sms-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create an SMS QR Code',
|
||||
description: 'Create a QR code that prepares a text message.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Phone Number',
|
||||
text: 'Type the destination phone number.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Enter Message',
|
||||
text: 'Type the message you want pre-filled (e.g., "Send me info!").',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Save the QR code and share it.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Does the text send automatically?': {
|
||||
question: 'Does the text send automatically?',
|
||||
answer: 'No. The QR code opens the messaging app with the text typed out. The user must simply tap "Send". This is a security feature of all smartphones.',
|
||||
},
|
||||
'Is there a cost?': {
|
||||
question: 'Is there a cost?',
|
||||
answer: 'Generating the code is free. Standard SMS rates apply for the person sending the text message, depending on their carrier plan.',
|
||||
},
|
||||
'Can I change the message later?': {
|
||||
question: 'Can I change the message later?',
|
||||
answer: 'No. Static QR codes have the message embedded in them. To change the message, you need a new QR code.',
|
||||
},
|
||||
'What uses are there for SMS QR codes?': {
|
||||
question: 'What uses are there for SMS QR codes?',
|
||||
answer: 'They are great for SMS marketing opt-ins ("Text JOIN to 12345"), customer support requests, or voting via text.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function SMSQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="SMS QR Code Generator" toolSlug="sms-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#ea580c' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-amber-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Make Texting Easy with <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-amber-200 to-orange-100">SMS QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-orange-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Let users send you a pre-written text with one scan. Ideal for opt-ins and support.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Universal compatibility.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<MessageSquare className="w-4 h-4 text-amber-300" />
|
||||
Pre-fill Texts
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-300" />
|
||||
Instant Open
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Shield className="w-4 h-4 text-amber-300" />
|
||||
Zero Friction
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-orange-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-white rounded-xl shadow-lg p-3 mb-6 relative overflow-hidden flex gap-3 items-center">
|
||||
<div className="w-10 h-10 rounded-full bg-orange-100 flex items-center justify-center shrink-0">
|
||||
<MessageSquare className="w-5 h-5 text-orange-600" />
|
||||
</div>
|
||||
<div className="bg-slate-100 rounded-2xl rounded-tl-none p-3 text-xs text-slate-600 w-full">
|
||||
Hi, I want to join the club!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-orange-100 p-2 rounded-full">
|
||||
<Send className="w-5 h-5 text-orange-600" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">SMS</div>
|
||||
<div className="text-sm font-bold text-slate-900">Sent!</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<SMSGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How SMS QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-orange-50 flex items-center justify-center mx-auto mb-4">
|
||||
<MessageSquare className="w-7 h-7 text-orange-600" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Compose</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Enter the number and the message you want your customers to send.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-orange-50 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-orange-600" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
User scans the code. The messages app opens automatically.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-orange-50 flex items-center justify-center mx-auto mb-4">
|
||||
<Send className="w-7 h-7 text-orange-600" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Send</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
User hits "Send" to trigger the text. Perfect for quick sign-ups or votes.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about SMS QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Does the user need an internet connection?"
|
||||
answer="No. The QR code contains all the info offline. However, the user needs a cellular signal to actually send the SMS message."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is it free for the user to send?"
|
||||
answer="It depends on their mobile plan. Standard SMS rates apply, though most modern plans include unlimited texting."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I use shortcodes?"
|
||||
answer="Yes. You can enter a shortcode (e.g. 55555) in the phone number field instead of a regular number."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is my phone number visible?"
|
||||
answer="Yes. Since the user is sending a text to you, they will see your number (or shortcode) in their messaging app."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Users,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Video,
|
||||
MessageCircle
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors - Microsoft Teams Purple
|
||||
const BRAND = {
|
||||
paleGrey: '#F3F2F1',
|
||||
primary: '#6264A7',
|
||||
primaryDark: '#464775',
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Teams Purple', value: '#6264A7' },
|
||||
{ name: 'Teams Dark', value: '#464775' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Indigo', value: '#4F46E5' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'join', label: 'Join Meeting' },
|
||||
{ id: 'teams', label: 'Teams' },
|
||||
];
|
||||
|
||||
// Link Type Options
|
||||
const LINK_TYPES = [
|
||||
{ id: 'meeting', label: 'Meeting Link', icon: Video },
|
||||
{ id: 'chat', label: 'Chat with User', icon: MessageCircle },
|
||||
];
|
||||
|
||||
export default function TeamsGenerator() {
|
||||
const [linkType, setLinkType] = useState('meeting');
|
||||
const [meetingUrl, setMeetingUrl] = useState('');
|
||||
const [userEmail, setUserEmail] = useState('');
|
||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Generate Teams link
|
||||
const generateTeamsLink = () => {
|
||||
if (linkType === 'meeting') {
|
||||
// If user pastes full Teams meeting URL, use it directly
|
||||
if (meetingUrl.trim().includes('teams.microsoft.com') || meetingUrl.trim().includes('teams.live.com')) {
|
||||
return meetingUrl.trim();
|
||||
}
|
||||
// Otherwise return placeholder
|
||||
return meetingUrl.trim() || 'https://teams.microsoft.com';
|
||||
} else {
|
||||
// Chat link with email
|
||||
if (!userEmail.trim()) return 'https://teams.microsoft.com';
|
||||
return `https://teams.microsoft.com/l/chat/0/0?users=${encodeURIComponent(userEmail.trim())}`;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `teams-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `teams-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Teams Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Users className="w-5 h-5 text-[#6264A7]" />
|
||||
Microsoft Teams
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Link Type Toggle */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">What do you want to share?</label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{LINK_TYPES.map((type) => (
|
||||
<button
|
||||
key={type.id}
|
||||
onClick={() => setLinkType(type.id)}
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-2 py-3 px-4 rounded-xl font-medium transition-all border",
|
||||
linkType === type.id
|
||||
? "bg-[#6264A7] text-white border-[#6264A7]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
<type.icon className="w-4 h-4" />
|
||||
{type.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{linkType === 'meeting' ? (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Teams Meeting URL</label>
|
||||
<Input
|
||||
placeholder="Paste your Teams meeting link here"
|
||||
value={meetingUrl}
|
||||
onChange={(e) => setMeetingUrl(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#6264A7] focus:ring-[#6264A7]"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">
|
||||
Copy the meeting link from your Teams calendar invite.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">User Email Address</label>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="e.g. colleague@company.com"
|
||||
value={userEmail}
|
||||
onChange={(e) => setUserEmail(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#6264A7] focus:ring-[#6264A7]"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">
|
||||
The person's work email to start a Teams chat.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#6264A7]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-2 rounded-lg text-xs font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#6264A7] text-white border-[#6264A7]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={generateTeamsLink()}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Teams Info */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2">
|
||||
{linkType === 'meeting' ? (
|
||||
<Video className="w-4 h-4 text-[#6264A7] shrink-0" />
|
||||
) : (
|
||||
<MessageCircle className="w-4 h-4 text-[#6264A7] shrink-0" />
|
||||
)}
|
||||
<span className="truncate">
|
||||
{linkType === 'meeting' ? 'Teams Meeting' : (userEmail || 'Teams Chat')}
|
||||
</span>
|
||||
</h3>
|
||||
<p className="text-sm text-slate-600 mt-1">
|
||||
{linkType === 'meeting' ? 'Join Meeting' : 'Start Chat'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#6264A7] hover:bg-[#464775] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Works with Microsoft Teams desktop and mobile apps.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#6264A7] to-[#464775] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Need to update meeting links?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">Dynamic QR Codes let you change the destination without reprinting.</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#6264A7] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Create Dynamic QR
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
308
src/app/(main)/(marketing)/tools/teams-qr-code/page.tsx
Normal file
308
src/app/(main)/(marketing)/tools/teams-qr-code/page.tsx
Normal file
@@ -0,0 +1,308 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import TeamsGenerator from './TeamsGenerator';
|
||||
import { Users, Shield, Zap, Video, MessageCircle, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Microsoft Teams QR Code Generator | Join Meetings | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your Microsoft Teams meeting. Teams QR Code erstellen. Attendees scan to join instantly. For hybrid meetings & office displays.',
|
||||
keywords: ['teams qr code', 'microsoft teams meeting qr', 'join teams qr code', 'meeting room qr', 'teams invitation qr', 'hybrid meeting qr code', 'microsoft teams qr code erstellen', 'teams meeting qr code', 'teams besprechung qr', 'teams beitreten qr'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/teams-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Microsoft Teams QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes for Teams meetings. One scan to join instantly.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/teams-qr-code',
|
||||
images: [{ url: '/og-teams-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Microsoft Teams QR Code Generator',
|
||||
description: 'Create Teams meeting QR codes. Instant and free.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Microsoft Teams QR Code Generator',
|
||||
'Generate QR codes that let people join your Microsoft Teams meeting with one scan.',
|
||||
'/og-teams-generator.png',
|
||||
'BusinessApplication'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a Microsoft Teams QR Code',
|
||||
description: 'Create a QR code for joining Teams meetings.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Copy Meeting Link',
|
||||
text: 'Copy the Teams meeting URL from your calendar invitation.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Paste Link',
|
||||
text: 'Paste the meeting link into the generator.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Customize',
|
||||
text: 'Choose Teams colors and add a frame label.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download',
|
||||
text: 'Download your QR code and display it in your meeting room.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'What happens when someone scans the QR code?': {
|
||||
question: 'What happens when someone scans the QR code?',
|
||||
answer: 'Microsoft Teams opens and the user is prompted to join the meeting. Works on desktop, mobile, and web.',
|
||||
},
|
||||
'Does it work for recurring meetings?': {
|
||||
question: 'Does it work for recurring meetings?',
|
||||
answer: 'Yes! If your recurring meeting uses the same meeting link, the QR code will work for all sessions.',
|
||||
},
|
||||
'Can guests without Teams accounts join?': {
|
||||
question: 'Can guests without Teams accounts join?',
|
||||
answer: 'Yes. Guests can join Teams meetings via the web browser without needing a Microsoft account.',
|
||||
},
|
||||
'Is this for personal or business Teams?': {
|
||||
question: 'Is this for personal or business Teams?',
|
||||
answer: 'Both! Works with Microsoft Teams for work, school, and personal accounts.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function TeamsQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Teams QR Code Generator" toolSlug="teams-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#6264A7' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Join Meetings with <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-violet-200 to-white">Teams QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-violet-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Create QR codes for Microsoft Teams meetings. Attendees scan to join instantly.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for hybrid workplaces.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Video className="w-4 h-4 text-white" />
|
||||
Meeting Links
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<MessageCircle className="w-4 h-4 text-white" />
|
||||
Chat Links
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-300" />
|
||||
Instant Join
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-violet-400/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
{/* Meeting Card Mock */}
|
||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="w-10 h-10 bg-[#6264A7] rounded-lg flex items-center justify-center">
|
||||
<Users className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-slate-900 text-sm">Team Standup</div>
|
||||
<div className="text-xs text-slate-600">Daily at 9:00 AM</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="bg-green-100 text-green-700 text-xs px-2 py-1 rounded-full">Live Now</div>
|
||||
<div className="bg-slate-100 text-slate-600 text-xs px-2 py-1 rounded-full">8 attending</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#6264A7" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-violet-100 p-2 rounded-full">
|
||||
<Video className="w-5 h-5 text-[#6264A7]" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||
<div className="text-sm font-bold text-slate-900">Ready to Join</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<TeamsGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Teams QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-4 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Video className="w-6 h-6 text-[#6264A7]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Get Link</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Copy your Teams meeting URL.</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Shield className="w-6 h-6 text-[#6264A7]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Paste</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Paste into the generator.</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#6264A7]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Save your QR code.</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#6264A7]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-[#6264A7]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Display</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Put in meeting rooms or invites.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Teams QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="What happens when someone scans the QR code?"
|
||||
answer="Microsoft Teams opens and the user is prompted to join the meeting. Works on desktop, mobile, and web browser."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it work for recurring meetings?"
|
||||
answer="Yes! If your recurring meeting uses the same meeting link, the QR code will work for all sessions."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can guests without Teams accounts join?"
|
||||
answer="Yes. Guests can join Teams meetings via the web browser without needing a Microsoft account."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What about meeting rooms with digital displays?"
|
||||
answer="Perfect for that! Display the QR code on your room's screen so attendees can scan to join from their devices."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
245
src/app/(main)/(marketing)/tools/text-qr-code/TextGenerator.tsx
Normal file
245
src/app/(main)/(marketing)/tools/text-qr-code/TextGenerator.tsx
Normal file
@@ -0,0 +1,245 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Type,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Copy,
|
||||
FileText
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
richBlue: '#1A1265',
|
||||
richBlueLight: '#2A2275',
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
{ name: 'Teal', value: '#0D9488' },
|
||||
{ name: 'Coral', value: '#F43F5E' },
|
||||
{ name: 'Amber', value: '#D97706' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'text', label: 'Text' },
|
||||
{ id: 'message', label: 'Message' },
|
||||
];
|
||||
|
||||
export default function TextGenerator() {
|
||||
const [text, setText] = useState('');
|
||||
const [qrColor, setQrColor] = useState(BRAND.richBlue);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `text-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `text-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Text Input */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Type className="w-5 h-5 text-[#1A1265]" />
|
||||
Enter Content
|
||||
</h2>
|
||||
|
||||
<div className="relative">
|
||||
<textarea
|
||||
className="w-full h-40 p-4 pb-8 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#1A1265] resize-none text-slate-800 placeholder:text-slate-400"
|
||||
placeholder="Type your text here (up to 300 characters)..."
|
||||
maxLength={300}
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
<div className="absolute bottom-3 right-3 text-xs text-slate-400 font-medium">
|
||||
{text.length}/300
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#1A1265]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#1A1265] text-white border-[#1A1265]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={text || "Your Text Here"}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Text Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2">
|
||||
<FileText className="w-4 h-4 text-slate-400" />
|
||||
Plain Text
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#1A1265] hover:bg-[#2A2275] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Your text stays on your device. Nothing is sent to servers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#1A1265] to-[#2A2275] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Want to track who scans your QR code?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">Dynamic QR codes give you scan analytics and let you edit content anytime.</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#1A1265] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Try Dynamic Codes
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
324
src/app/(main)/(marketing)/tools/text-qr-code/page.tsx
Normal file
324
src/app/(main)/(marketing)/tools/text-qr-code/page.tsx
Normal file
@@ -0,0 +1,324 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import TextGenerator from './TextGenerator';
|
||||
import { Type, Shield, Zap, Smartphone, FileText, QrCode, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Text QR Code Generator | Text zu QR Code | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for any plain text message. Erstelle einen Text QR Code kostenlos. Works offline & 100% private. Nachricht als QR Code teilen.',
|
||||
keywords: ['text qr code', 'qr code text generator', 'message to qr code', 'offline qr code', 'text qr generator', 'free qr code', 'text zu qr code', 'qr code text erstellen', 'nachricht in qr code', 'textnachricht qr code', 'geheimnachricht qr code'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/text-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Text QR Code Generator | QR Master',
|
||||
description: 'Turn any text into a QR code instantly. No signup required. 100% text privacy.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/text-qr-code',
|
||||
images: [{ url: '/og-text-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Text QR Code Generator',
|
||||
description: 'Create QR codes for text. Instant, free, and private.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Text QR Code Generator',
|
||||
'Generate QR codes for plain text messages. Works offline once generated. No data collection.',
|
||||
'/og-text-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a Text QR Code',
|
||||
description: 'Turn any plain text into a scannable QR code.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Content',
|
||||
text: 'Type or paste your text message into the input field.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize Design',
|
||||
text: 'Choose a color and add a frame label like "Scan Me" or "Read".',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download QR Code',
|
||||
text: 'Download your high-quality QR code in PNG or SVG format.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Test',
|
||||
text: 'Scan the code to ensure the text appears correctly.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Print it or display it where you want people to read the message.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Is there a character limit?': {
|
||||
question: 'Is there a character limit?',
|
||||
answer: 'Yes, we recommend keeping it under 300 characters for optimal scanning. While QR codes can hold more, more text makes the code denser and harder to scan.',
|
||||
},
|
||||
'Do I need internet to scan a Text QR code?': {
|
||||
question: 'Do I need internet to scan a Text QR code?',
|
||||
answer: 'No. Text QR codes work completely offline. The text content is embedded directly into the QR code pattern.',
|
||||
},
|
||||
'Is my text private?': {
|
||||
question: 'Is my text private?',
|
||||
answer: 'Yes. This generator runs 100% in your browser. We do not store or see the text you type.',
|
||||
},
|
||||
'How do I scan a text QR code?': {
|
||||
question: 'How do I scan a text QR code?',
|
||||
answer: 'Open your phone camera or a QR scanner app and point it at the code. The text will appear on your screen automatically.',
|
||||
},
|
||||
'Can I edit the text later?': {
|
||||
question: 'Can I edit the text later?',
|
||||
answer: 'No, this is a static QR code. The text is permanent. If you need to change it, you must create a new QR code.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function TextQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Text QR Code Generator" toolSlug="text-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Turn Text content into <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Scannable QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Share notes, codes, keys, or messages instantly. Scan to read without internet.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> 100% Private.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Shield className="w-4 h-4 text-emerald-400" />
|
||||
No Data Storage
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-400" />
|
||||
Instant Create
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-purple-400" />
|
||||
Offline Readable
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||
<div className="absolute top-1/2 left-0 w-full h-1 bg-emerald-500 shadow-[0_0_20px_rgba(16,185,129,1)] animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-white/10 rounded-xl p-4 backdrop-blur-sm border border-white/10">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-indigo-500/20 flex items-center justify-center">
|
||||
<FileText className="w-4 h-4 text-indigo-300" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-1.5 w-20 bg-white/30 rounded-full" />
|
||||
<div className="h-1.5 w-12 bg-white/20 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<TextGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Text QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Type className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Enter Text</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Type your message, code, or note. It is instantly encoded into the QR pattern.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Zap className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Customize</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Choose a color and add a call-to-action frame.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Get your ready-to-use QR code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Test</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Point camera to read text.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Share</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Pass information instantly.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Text QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="How do I scan a text QR code?"
|
||||
answer="Open your phone camera or a QR scanner app and point it at the code. The text will appear on your screen automatically."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is there a character limit?"
|
||||
answer="We recommend keeping it under 300 characters for the best scanning experience. Theoretically, QR codes can hold up to 4,296 characters, but the code becomes very complex and harder to scan with standard phone cameras."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Do I need internet to scan a Text QR code?"
|
||||
answer="No. Text QR codes are 'static' codes, meaning the data is encoded directly into the image pattern. You can scan and read them completely offline, making them perfect for remote locations or secure environments."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is my text private?"
|
||||
answer="Yes. We prioritize your privacy. The generation process happens entirely in your browser using JavaScript. Your text data is never sent to our servers or stored anywhere."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I change the text after printing?"
|
||||
answer="No. Static QR codes are permanent. If you need to change the text later, you must generate a new QR code. For editable content, you would need a Dynamic QR Code (which we also offer)."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Music,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Video,
|
||||
Share2
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
richBlue: '#1A1265',
|
||||
richBlueLight: '#2A2275',
|
||||
};
|
||||
|
||||
// QR Color Options - TikTok Theme
|
||||
const QR_COLORS = [
|
||||
{ name: 'TikTok Black', value: '#000000' },
|
||||
{ name: 'TikTok Pink', value: '#FE2C55' },
|
||||
{ name: 'TikTok Cyan', value: '#25F4EE' },
|
||||
{ name: 'Deep Blue', value: '#1A1265' },
|
||||
{ name: 'Purple', value: '#7C3AED' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'follow', label: 'Follow' },
|
||||
{ id: 'watch', label: 'Watch' },
|
||||
];
|
||||
|
||||
export default function TiktokGenerator() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [qrColor, setQrColor] = useState('#000000');
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// TikTok URL: https://www.tiktok.com/@username
|
||||
const getUrl = () => {
|
||||
const cleanUser = username.replace(/^@/, '').replace(/https?:\/\/(www\.)?tiktok\.com\/@?/, '').replace(/\/$/, '');
|
||||
return cleanUser ? `https://www.tiktok.com/@${cleanUser}` : 'https://www.tiktok.com';
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `tiktok-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `tiktok-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* TikTok Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Music className="w-5 h-5 text-black" />
|
||||
TikTok Username
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Username</label>
|
||||
<Input
|
||||
placeholder="@username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-black focus:ring-black"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">Enter your TikTok handle (e.g. @charlidamelio).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-black" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-black text-white border-black"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={getUrl()}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Music className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{username || '@username'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-600 mt-1">Opens in TikTok</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-black hover:bg-slate-800 text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning redirects directly to your TikTok profile.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#000000] via-[#25F4EE] to-[#FE2C55] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Cross-promote on Socials?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Use one Dynamic Link to share your TikTok, Insta, and YouTube all at once.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-black hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Create All-in-One Link
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
333
src/app/(main)/(marketing)/tools/tiktok-qr-code/page.tsx
Normal file
333
src/app/(main)/(marketing)/tools/tiktok-qr-code/page.tsx
Normal file
@@ -0,0 +1,333 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import TiktokGenerator from './TikTokGenerator';
|
||||
import { Music, Shield, Zap, Smartphone, Video, Heart, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free TikTok QR Code Generator | Get Followers | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your TikTok profile. TikTok QR Code erstellen. Scanners follow you instantly. Customize with colors and frames.',
|
||||
keywords: ['tiktok qr code', 'tik tok qr generator', 'tiktok follow qr', 'social media qr code', 'tiktok profile qr', 'tiktok qr code erstellen', 'tiktok profil qr code', 'mehr tiktok follower', 'tiktok scanncode'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/tiktok-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free TikTok QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes to grow your TikTok following. Instant app redirect.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/tiktok-qr-code',
|
||||
images: [{ url: '/og-tiktok-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free TikTok QR Code Generator',
|
||||
description: 'Create QR codes for TikTok. Get more followers.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'TikTok QR Code Generator',
|
||||
'Generate QR codes that direct users to a TikTok profile.',
|
||||
'/og-tiktok-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a TikTok QR Code',
|
||||
description: 'Create a QR code that opens a TikTok profile.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Username',
|
||||
text: 'Type your TikTok handle (e.g. @user).',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Select colors like Cyan or Pink to match the TikTok brand.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Save the QR code.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Test',
|
||||
text: 'Scan the code to ensure it links to your profile.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Share it on other social media or print it out.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Does this replace the in-app QR code?': {
|
||||
question: 'Does this replace the in-app QR code?',
|
||||
answer: 'You can use either! The advantage of our generator is that you can print high-resolution versions for large posters, customize the color/frame, and it works with any standard QR scanner.',
|
||||
},
|
||||
'Can I link to a specific video?': {
|
||||
question: 'Can I link to a specific video?',
|
||||
answer: 'Yes, just paste the full video URL (e.g. tiktok.com/@user/video/123...) instead of your username.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, completely free from start to finish.',
|
||||
},
|
||||
'Can I track who scanned my code?': {
|
||||
question: 'Can I track who scanned my code?',
|
||||
answer: 'No, this is a static QR code. For analytics, you need a Dynamic QR Code.',
|
||||
},
|
||||
'Is it safe?': {
|
||||
question: 'Is it safe?',
|
||||
answer: 'Yes. The QR code simply contains a link to your TikTok profile. No personal data is collected.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function TiktokQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="TikTok QR Code Generator" toolSlug="tiktok-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-black">
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
{/* TikTok Pattern */}
|
||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="tt_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||
<circle cx="30" cy="30" r="2" fill="cyan" />
|
||||
<circle cx="40" cy="40" r="2" fill="magenta" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#tt_pattern)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-cyan-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Go Viral with <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-[#25F4EE] to-[#FE2C55]">TikTok QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-slate-400 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Share your TikTok immediately. A quick scan sends fans straight to your profile to follow and watch.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your audience.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Music className="w-4 h-4 text-[#25F4EE]" />
|
||||
Get Followers
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Video className="w-4 h-4 text-[#FE2C55]" />
|
||||
Share Videos
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-white" />
|
||||
Deep Link
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
{/* Glow Effects */}
|
||||
<div className="absolute w-[300px] h-[300px] bg-[#25F4EE]/20 rounded-full blur-[80px] -top-10 -right-10 animate-pulse" />
|
||||
<div className="absolute w-[300px] h-[300px] bg-[#FE2C55]/20 rounded-full blur-[80px] bottom-10 left-10 animate-pulse delay-75" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-20 h-20 rounded-full bg-black border-2 border-[#25F4EE] p-1 mb-6 shadow-[#FE2C55]/50 shadow-lg relative">
|
||||
<div className="w-full h-full rounded-full bg-slate-800 flex items-center justify-center overflow-hidden">
|
||||
<Music className="w-10 h-10 text-white animate-bounce" />
|
||||
</div>
|
||||
<div className="absolute -bottom-1 -right-1 bg-[#FE2C55] w-6 h-6 rounded-full border-2 border-black flex items-center justify-center">
|
||||
<Heart className="w-3 h-3 text-white fill-current" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#000000" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -right-6 bg-black border border-white/10 py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-white/10 p-2 rounded-full">
|
||||
<Music className="w-5 h-5 text-[#25F4EE]" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">TikTok</div>
|
||||
<div className="text-sm font-bold text-white">Following</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<TiktokGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How TikTok QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Music className="w-7 h-7 text-[#25F4EE]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Enter Handle</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Type in your username. No password required.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#FE2C55]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Fans scan the code to instantly find you in the app.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your custom QR code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Heart className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Follow</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Fans scan to find you instantly.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Viral</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Grow your audience everywhere.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about TikTok QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Does this replace the in-app QR code?"
|
||||
answer="You can use either! The advantage of our generator is that you can print high-resolution versions for large posters, customize the color/frame, and it works with any standard QR scanner."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I link to a specific video?"
|
||||
answer="Yes, just paste the full video URL (e.g. tiktok.com/@user/video/123...) instead of your username."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is it free?"
|
||||
answer="Yes, completely free from start to finish."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I track who scanned my code?"
|
||||
answer="No, this is a static QR code. For analytics, you need a Dynamic QR Code."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is it safe?"
|
||||
answer="Yes. The QR code simply contains a link to your TikTok profile. No personal data is collected."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Twitter,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
MessageCircle
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
richBlue: '#1A1265',
|
||||
richBlueLight: '#2A2275',
|
||||
};
|
||||
|
||||
// QR Color Options - X Theme
|
||||
const QR_COLORS = [
|
||||
{ name: 'X Black', value: '#000000' },
|
||||
{ name: 'X Blue', value: '#1DA1F2' },
|
||||
{ name: 'Dark Blue', value: '#1A1265' },
|
||||
{ name: 'Teal', value: '#0D9488' },
|
||||
{ name: 'Coral', value: '#F43F5E' },
|
||||
{ name: 'Grey', value: '#374151' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'follow', label: 'Follow' },
|
||||
{ id: 'connect', label: 'Connect' },
|
||||
];
|
||||
|
||||
export default function TwitterGenerator() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [qrColor, setQrColor] = useState('#000000');
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Twitter URL construction
|
||||
const getUrl = () => {
|
||||
const cleanUser = username.replace(/^@/, '').replace(/https?:\/\/(www\.)?(twitter|x)\.com\//, '').replace(/\/$/, '');
|
||||
return cleanUser ? `https://x.com/${cleanUser}` : 'https://x.com';
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `twitter-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `twitter-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Twitter Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Twitter className="w-5 h-5 text-black" />
|
||||
X (Twitter) Username
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Username or Link</label>
|
||||
<Input
|
||||
placeholder="@username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-black focus:ring-black"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">Enter your X (Twitter) handle to create a profile link.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-black" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-slate-900 text-white border-slate-900"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={getUrl()}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Twitter className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{username || '@username'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-600 mt-1">Opens in X (Twitter)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-slate-900 hover:bg-black text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning redirects directly to the X profile.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-slate-900 to-slate-700 rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Cross-promote your channels</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Use a single Dynamic QR Code to link to all your social media profiles at once.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-slate-900 hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Create Smart Link
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
335
src/app/(main)/(marketing)/tools/twitter-qr-code/page.tsx
Normal file
335
src/app/(main)/(marketing)/tools/twitter-qr-code/page.tsx
Normal file
@@ -0,0 +1,335 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import TwitterGenerator from './TwitterGenerator';
|
||||
import { Twitter, Shield, Zap, Smartphone, MessageCircle, UserPlus, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Twitter (X) QR Code Generator | Follow & Connect | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your X (Twitter) profile. Twitter QR Code erstellen. Scanners follow you instantly. Free & Customizable.',
|
||||
keywords: ['twitter qr code', 'x qr generator', 'twitter follow qr', 'social media qr code', 'x profile qr', 'twitter qr code erstellen', 'x qr code erstellen', 'twitter profil qr code', 'x profil qr code'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/twitter-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Twitter (X) QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes to grow your X (Twitter) following. Instant app redirect.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/twitter-qr-code',
|
||||
images: [{ url: '/og-twitter-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Twitter (X) QR Code Generator',
|
||||
description: 'Create QR codes for X. Boost your following.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Twitter (X) QR Code Generator',
|
||||
'Generate QR codes that direct users to an X (Twitter) profile or tweet.',
|
||||
'/og-twitter-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a Twitter QR Code',
|
||||
description: 'Create a QR code that opens an X profile.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Username',
|
||||
text: 'Enter your X handle (e.g. @elonmusk).',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Design',
|
||||
text: 'Choose a black frame or custom color.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Save the QR code.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Test',
|
||||
text: 'Scan to verify it goes to the correct profile.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Add to your business cards or conference badges.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Does it work for both Twitter and X?': {
|
||||
question: 'Does it work for both Twitter and X?',
|
||||
answer: 'Yes, they are the same platform. The QR code links to x.com, which is the current standard, but works for twitter.com links too.',
|
||||
},
|
||||
'Can I link to a specific tweet?': {
|
||||
question: 'Can I link to a specific tweet?',
|
||||
answer: 'Yes! Just paste the full URL of the tweet into the input field instead of your username.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, generating this QR code is completely free and requires no signup.',
|
||||
},
|
||||
'Can I track scans?': {
|
||||
question: 'Can I track scans?',
|
||||
answer: 'This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics.',
|
||||
},
|
||||
'What if I change my handle?': {
|
||||
question: 'What if I change my handle?',
|
||||
answer: 'If you change your handle, the link in the QR code will break. You will need to generate a new QR code.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function TwitterQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Twitter QR Code Generator" toolSlug="twitter-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-slate-950">
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
{/* X Pattern */}
|
||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="x_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||
<path d="M20 20L40 40M40 20L20 40" stroke="white" strokeWidth="1" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#x_pattern)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-500 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-500"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Connect on X with <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-white">Twitter QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-slate-400 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Share your X profile instantly. A quick scan takes users directly to your timeline to follow and interact.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your community.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<UserPlus className="w-4 h-4 text-white" />
|
||||
Get Followers
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<MessageCircle className="w-4 h-4 text-white" />
|
||||
Start Conversions
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-white" />
|
||||
Instant Connect
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-blue-500/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-2 hover:-rotate-1 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/5 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-black rounded-xl shadow-lg p-5 mb-6 relative overflow-hidden flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-white rounded-full flex items-center justify-center">
|
||||
<Twitter className="w-6 h-6 text-black" fill="black" />
|
||||
</div>
|
||||
<div className="text-white">
|
||||
<div className="font-bold text-sm">QR Master</div>
|
||||
<div className="text-xs text-slate-400">@qrmaster</div>
|
||||
</div>
|
||||
<button className="ml-auto bg-white text-black px-4 py-1.5 rounded-full text-xs font-bold hover:bg-slate-200">
|
||||
Follow
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#000000" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -right-6 bg-black border border-white/10 py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-white/10 p-2 rounded-full">
|
||||
<Twitter className="w-5 h-5 text-white" fill="white" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Twitter / X</div>
|
||||
<div className="text-sm font-bold text-white">Profile Link</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<TwitterGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How X (Twitter) QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Twitter className="w-7 h-7 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Input Handle</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Simply type your @handle or paste your profile link.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Snap</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Put the code on your networking gear. People scan it in seconds.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your X QR code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<UserPlus className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
People scan to find you.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-black flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Connect</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
They are instantly on your profile.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Twitter QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Why create a QR code for X?"
|
||||
answer="It's much faster than telling someone your handle and hoping they spell it right. A scan is instant and error-proof."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Will links to twitter.com still work?"
|
||||
answer="Yes, twitter.com links redirect to x.com, so both work perfectly fine."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I change the destination later?"
|
||||
answer="No, this is a static QR code. If you change your handle, you will need a new QR code. Our Dynamic QR codes allow you to edit the link anytime."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I track scans?"
|
||||
answer="This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What if I change my handle?"
|
||||
answer="If you change your handle, the link in the QR code will break. You will need to generate a new QR code."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
245
src/app/(main)/(marketing)/tools/url-qr-code/URLGenerator.tsx
Normal file
245
src/app/(main)/(marketing)/tools/url-qr-code/URLGenerator.tsx
Normal file
@@ -0,0 +1,245 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { toPng } from 'html-to-image';
|
||||
|
||||
import {
|
||||
Link as LinkIcon,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Globe
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EEF2FF', // Indigo-50
|
||||
primary: '#4F46E5', // Indigo-600
|
||||
primaryDark: '#4338CA', // Indigo-700
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Indigo', value: '#4F46E5' },
|
||||
{ name: 'Blue', value: '#2563EB' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
{ name: 'Pink', value: '#DB2777' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'website', label: 'Website' },
|
||||
{ id: 'visit', label: 'Visit' },
|
||||
];
|
||||
|
||||
export default function URLGenerator() {
|
||||
const [url, setUrl] = useState('');
|
||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `url-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `url-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* URL Input */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<LinkIcon className="w-5 h-5 text-[#4F46E5]" />
|
||||
Website URL
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Enter URL</label>
|
||||
<Input
|
||||
placeholder="https://www.yourwebsite.com"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#4F46E5] focus:ring-[#4F46E5]"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">Include https:// for best results.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#4F46E5]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#4F46E5] text-white border-[#4F46E5]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={url || "https://qrmaster.io"}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* URL Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<LinkIcon className="w-4 h-4 text-indigo-600 shrink-0" />
|
||||
<span className="truncate">{url || 'Your Website'}</span>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#4F46E5] hover:bg-[#4338CA] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Your link is encoded directly. Static and forever free.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#4F46E5] to-[#4338CA] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Need to change this link later?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
If your URL changes, this QR code will stop working. Use Dynamic QR Codes to edit links anytime.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#4F46E5] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Create Dynamic QR
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
282
src/app/(main)/(marketing)/tools/url-qr-code/page.tsx
Normal file
282
src/app/(main)/(marketing)/tools/url-qr-code/page.tsx
Normal file
@@ -0,0 +1,282 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import URLGenerator from './URLGenerator';
|
||||
import { Link as LinkIcon, Shield, Zap, Smartphone, Globe } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free URL QR Code Generator | Link to Any Website | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your website. Erstelle kostenlos einen QR Code für deine Webseite. Static and free forever. Link zu jeder URL.',
|
||||
keywords: ['url qr code', 'website qr code', 'link qr generator', 'free qr code generator', 'url to qr', 'qr code erstellen', 'link qr code erstellen', 'website qr code generator', 'kostenlos qr code erstellen', 'url zu qr code', 'webseite verlinken qr'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/url-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free URL QR Code Generator | QR Master',
|
||||
description: 'Turn any URL into a QR code. Share websites instantly.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/url-qr-code',
|
||||
images: [{ url: '/og-url-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free URL QR Code Generator',
|
||||
description: 'Create QR codes for any link. Instant and free.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'URL QR Code Generator',
|
||||
'Generate QR codes for URLs and websites. Direct linking, no redirects.',
|
||||
'/og-url-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a URL QR Code',
|
||||
description: 'Turn a website link into a scannable QR code.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter URL',
|
||||
text: 'Copy and paste your website address (e.g., https://example.com).',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Select a color and add a call-to-action frame like "Scan Me".',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Save your QR code as a PNG or SVG image.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT20S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Do these QR codes expire?': {
|
||||
question: 'Do these QR codes expire?',
|
||||
answer: 'No. These are static QR codes. They directly encode your URL and will work forever as long as your website is online.',
|
||||
},
|
||||
'Can I track how many people scan it?': {
|
||||
question: 'Can I track how many people scan it?',
|
||||
answer: 'No, static QR codes cannot be tracked. If you need scan usage analytics (location, device, time), you should use our Dynamic QR Code generator.',
|
||||
},
|
||||
'Can I change the destination URL later?': {
|
||||
question: 'Can I change the destination URL later?',
|
||||
answer: 'No. Once a static QR code is printed, it cannot be changed. If you change your website URL, you will need to print a new code. Use Dynamic QR Codes if you need flexibility.',
|
||||
},
|
||||
'Is there a scan limit?': {
|
||||
question: 'Is there a scan limit?',
|
||||
answer: 'No. There are zero limits on how many times your QR code can be scanned.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function URLQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="URL QR Code Generator" toolSlug="url-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Link to Any Website with <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Instant QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Create a QR code for your website, portfolio, or menu.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Free forever. No expirations.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Globe className="w-4 h-4 text-emerald-400" />
|
||||
Universal Links
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-400" />
|
||||
Instant Redirect
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Shield className="w-4 h-4 text-purple-400" />
|
||||
Direct Encoding
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-white/10 rounded-xl p-4 backdrop-blur-sm border border-white/10">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-indigo-500/20 flex items-center justify-center">
|
||||
<LinkIcon className="w-4 h-4 text-indigo-300" />
|
||||
</div>
|
||||
<div className="space-y-1 w-full">
|
||||
<div className="h-1.5 w-3/4 bg-white/30 rounded-full" />
|
||||
<div className="h-1.5 w-1/2 bg-white/20 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<URLGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How URL QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<LinkIcon className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Paste Link</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Copy the URL of the webpage you want to link to and paste it into the generator.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Users scan the code and a notification appears to open the link in their browser.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Globe className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Visit</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
They are instantly directed to your website, restaurant menu, or social profile.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about URL QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Do these QR codes expire?"
|
||||
answer="No. Static URL QR codes do not expire. They contain the direct link to your website. As long as your website is active, the QR code will work."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I track scans?"
|
||||
answer="No, static QR codes cannot be tracked. If you need analytics to see who is scanning your code and from where, you need a Dynamic QR Code."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What happens if I change my website URL?"
|
||||
answer="If you change your URL, this static QR code will no longer work (unless you set up a redirect on your own server). With a Dynamic QR Code, you can update the destination URL anytime without reprinting the code."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Are there ads on the QR code?"
|
||||
answer="No. We do not insert ads before redirecting users. The scan goes directly to your URL."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
User,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Briefcase,
|
||||
Phone,
|
||||
Mail,
|
||||
Globe,
|
||||
MapPin,
|
||||
Contact
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#FFF1F2', // Rose-50
|
||||
primary: '#E11D48', // Rose-600
|
||||
primaryDark: '#BE123C', // Rose-700
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Rose', value: '#E11D48' },
|
||||
{ name: 'Pink', value: '#DB2777' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Navy', value: '#1E3A8A' },
|
||||
{ name: 'Purple', value: '#7C3AED' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'contact', label: 'Save Contact' },
|
||||
{ id: 'vcard', label: 'vCard' },
|
||||
];
|
||||
|
||||
export default function VCardGenerator() {
|
||||
// Personal Info
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [website, setWebsite] = useState('');
|
||||
const [jobTitle, setJobTitle] = useState('');
|
||||
const [company, setCompany] = useState('');
|
||||
|
||||
// Address
|
||||
const [street, setStreet] = useState('');
|
||||
const [city, setCity] = useState('');
|
||||
const [zip, setZip] = useState('');
|
||||
const [country, setCountry] = useState('');
|
||||
|
||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Generate VCard String
|
||||
const generateVCard = () => {
|
||||
const vcard = [
|
||||
'BEGIN:VCARD',
|
||||
'VERSION:3.0',
|
||||
`N:${lastName};${firstName};;;`,
|
||||
`FN:${firstName} ${lastName}`,
|
||||
`ORG:${company}`,
|
||||
`TITLE:${jobTitle}`,
|
||||
`TEL;TYPE=CELL:${phone}`,
|
||||
`EMAIL;TYPE=WORK:${email}`,
|
||||
`URL:${website}`,
|
||||
`ADR;TYPE=WORK:;;${street};${city};;${zip};${country}`,
|
||||
'END:VCARD'
|
||||
].join('\n');
|
||||
return vcard;
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `vcard-${firstName || 'contact'}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `vcard-${firstName || 'contact'}.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-6xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-12">
|
||||
|
||||
{/* LEFT: Input Section (Wider for VCard) */}
|
||||
<div className="lg:col-span-7 p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Personal Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<User className="w-5 h-5 text-[#E11D48]" />
|
||||
Contact Information
|
||||
</h2>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">First Name</label>
|
||||
<Input placeholder="John" value={firstName} onChange={(e) => setFirstName(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Last Name</label>
|
||||
<Input placeholder="Doe" value={lastName} onChange={(e) => setLastName(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Phone</label>
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
||||
<Input placeholder="+1 555 000 0000" value={phone} onChange={(e) => setPhone(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Email</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
||||
<Input placeholder="john@company.com" value={email} onChange={(e) => setEmail(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Website</label>
|
||||
<div className="relative">
|
||||
<Globe className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
||||
<Input placeholder="https://..." value={website} onChange={(e) => setWebsite(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Job Title</label>
|
||||
<div className="relative">
|
||||
<Briefcase className="absolute left-3 top-3 w-5 h-5 text-slate-400" />
|
||||
<Input placeholder="Manager" value={jobTitle} onChange={(e) => setJobTitle(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Company</label>
|
||||
<Input placeholder="Acme Corp" value={company} onChange={(e) => setCompany(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Address Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<MapPin className="w-5 h-5 text-[#E11D48]" />
|
||||
Address
|
||||
</h2>
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<div className="sm:col-span-2">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Street</label>
|
||||
<Input placeholder="123 Business Rd" value={street} onChange={(e) => setStreet(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">City</label>
|
||||
<Input placeholder="New York" value={city} onChange={(e) => setCity(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Zip/Postcode</label>
|
||||
<Input placeholder="10001" value={zip} onChange={(e) => setZip(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Country</label>
|
||||
<Input placeholder="USA" value={country} onChange={(e) => setCountry(e.target.value)} className="h-11 rounded-xl border-slate-200 focus:border-[#E11D48] focus:ring-[#E11D48]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#E11D48]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-8">
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2 px-3 rounded-lg text-sm font-medium transition-all border text-center",
|
||||
frameType === frame.id
|
||||
? "bg-[#E11D48] text-white border-[#E11D48]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="lg:col-span-5 p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[300px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={generateVCard()}
|
||||
size={220}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Contact className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{firstName || 'First'} {lastName || 'Last'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-600 mt-1 truncate">{company || 'Company Name'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex flex-col sm:flex-row items-center gap-3 mt-8 w-full max-w-[320px]">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#E11D48] hover:bg-[#BE123C] text-white shadow-lg w-full"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white w-full"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning adds this contact to the address book instantly.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#E11D48] to-[#BE123C] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Want a Digital Business Card?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Upgrade to Dynamic vCard to include a profile photo, social links, and update your info anytime.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#E11D48] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Create Digital Card
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
296
src/app/(main)/(marketing)/tools/vcard-qr-code/page.tsx
Normal file
296
src/app/(main)/(marketing)/tools/vcard-qr-code/page.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import VCardGenerator from './VCardGenerator';
|
||||
import { User, Shield, Zap, Smartphone, Contact, Share2, Check, UserPlus } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free vCard QR Code Generator | QR Master',
|
||||
},
|
||||
description: 'Create a vCard QR code for your business card. Erstelle deine elektronische Visitenkarte kostenlos. Share contact details instantly. Free & No App Required.',
|
||||
keywords: ['vcard qr code', 'business card qr code', 'contact qr generator', 'digital business card', 'add to contacts qr', 'visitenkarte qr code', 'digitale visitenkarte erstellen', 'kontakt qr code', 'elektronische visitenkarte', 'vcard erstellen kostenlos'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/vcard-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free vCard QR Code Generator | QR Master',
|
||||
description: 'Turn your contact info into a QR code. The modern way to share your business card.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/vcard-qr-code',
|
||||
images: [{ url: '/og-vcard-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free vCard QR Code Generator',
|
||||
description: 'Create QR codes for contact sharing. Instant and free.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'vCard QR Code Generator',
|
||||
'Generate vCard (VCF) QR codes for business cards. Scanners can save contact info instantly.',
|
||||
'/og-vcard-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a vCard QR Code',
|
||||
description: 'Create a QR code that saves your contact details.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Details',
|
||||
text: 'Fill in your Name, Phone, Email, Company, and Address.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Select a color that matches your brand and add a frame.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Download the QR code image and place it on your physical business card.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT1M',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'How does a vCard QR code work?': {
|
||||
question: 'How does a vCard QR code work?',
|
||||
answer: 'A vCard QR code contains your contact information in a standardized format (VCF). When scanned, the phone recognizes it as a contact card and prompts the user to "Save Contact" to their address book.',
|
||||
},
|
||||
'Is there a limit to how much info I can add?': {
|
||||
question: 'Is there a limit to how much info I can add?',
|
||||
answer: 'Static QR codes hold data directly in the pattern. The more data you add (long addresses, bio), the denser and harder to scan the QR code becomes. We recommend sticking to essential contact info for static codes.',
|
||||
},
|
||||
'Can I update my info later?': {
|
||||
question: 'Can I update my info later?',
|
||||
answer: 'No. This is a static vCard QR code. Once created, the info cannot be changed. If you move jobs or change numbers, you must print a new code. For editable cards, use our Dynamic vCard Plus.',
|
||||
},
|
||||
'Does it work on iPhone and Android?': {
|
||||
question: 'Does it work on iPhone and Android?',
|
||||
answer: 'Yes. Both iOS (Camera app) and Android (Camera or Google Lens) natively support vCard QR codes and correctly import the contact data.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function VCardQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="vCard QR Code Generator" toolSlug="vcard-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#9F1239' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-rose-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-rose-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
The Modern Way to <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-rose-300 to-pink-300">Share Your Contact</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Create a scannable Digital Business Card. One scan saves your name, phone, email, and address instantly.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Free & Professional.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<UserPlus className="w-4 h-4 text-rose-300" />
|
||||
Instant Save
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Share2 className="w-4 h-4 text-amber-400" />
|
||||
Easy Share
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Shield className="w-4 h-4 text-purple-400" />
|
||||
No Data Stored
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-96 h-60 bg-white/10 backdrop-blur-2xl border border-white/30 rounded-2xl shadow-2xl p-6 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent" />
|
||||
|
||||
<div className="flex justify-between items-start relative z-10">
|
||||
<div className="space-y-4">
|
||||
<div className="w-16 h-16 rounded-full bg-white/20 border-2 border-white/30 flex items-center justify-center">
|
||||
<Contact className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-4 w-32 bg-white/90 rounded-sm" />
|
||||
<div className="h-3 w-20 bg-emerald-400/90 rounded-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-24 h-24 bg-white rounded-lg p-1.5 shadow-lg">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={84} fgColor="#1A1265" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-6 left-6 space-y-2 z-10">
|
||||
<div className="h-2 w-48 bg-white/40 rounded-full" />
|
||||
<div className="h-2 w-40 bg-white/30 rounded-full" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-4 -left-4 bg-white py-2 px-4 rounded-lg shadow-xl flex items-center gap-2 transform scale-90">
|
||||
<div className="bg-emerald-100 p-1.5 rounded-full">
|
||||
<Check className="w-3 h-3 text-emerald-600" />
|
||||
</div>
|
||||
<span className="text-xs font-bold text-slate-900">Saved to Contacts</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<VCardGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How vCard QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Contact className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Enter Details</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Fill in your professional contact information. Only add what you want to share.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
A potential client or partner scans your card with their phone camera.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<UserPlus className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Save</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
They tap "Create New Contact" to save your details instantly. No typing errors.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about vCard QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Can I add a profile picture?"
|
||||
answer="Not on a static vCard QR code. Static codes store data in the pixels, so adding an image would make the code too complex to scan. For profile pictures, social links, and rich media, use our Dynamic vCard Plus solution."
|
||||
/>
|
||||
<FaqItem
|
||||
question="How long does the QR code last?"
|
||||
answer="Forever. Static vCard QR codes do not expire because the data is embedded directly in the image."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What information is required?"
|
||||
answer="Nothing is strictly required, but we recommend at least a First Name and either a Phone Number or Email so the contact is useful."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is my data safe?"
|
||||
answer="Yes. This tool operates 100% in your browser. We do not store, see, or optimize your contact data. It goes directly from your input to the QR code."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Phone,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
MessageCircle,
|
||||
Send
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Textarea } from '@/components/ui/Textarea';
|
||||
|
||||
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
richBlue: '#1A1265',
|
||||
richBlueLight: '#2A2275',
|
||||
};
|
||||
|
||||
// QR Color Options - WhatsApp Theme
|
||||
const QR_COLORS = [
|
||||
{ name: 'WhatsApp Green', value: '#25D366' },
|
||||
{ name: 'Teal', value: '#128C7E' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Rich Blue', value: '#1A1265' },
|
||||
{ name: 'Purple', value: '#7C3AED' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'chat', label: 'Chat With Us' },
|
||||
{ id: 'support', label: 'Support' },
|
||||
];
|
||||
|
||||
export default function WhatsappGenerator() {
|
||||
const [phone, setPhone] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const [qrColor, setQrColor] = useState('#25D366');
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// WhatsApp URL: https://wa.me/number?text=message
|
||||
const getUrl = () => {
|
||||
const cleanPhone = phone.replace(/\D/g, ''); // Remove non-digits
|
||||
const encodedMessage = encodeURIComponent(message);
|
||||
return `https://wa.me/${cleanPhone}?text=${encodedMessage}`;
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `whatsapp-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `whatsapp-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* WhatsApp Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<MessageCircle className="w-5 h-5 text-[#25D366]" />
|
||||
WhatsApp Details
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Phone Number</label>
|
||||
<Input
|
||||
placeholder="15551234567"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#25D366] focus:ring-[#25D366]"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">Include country code (e.g. 1 for US, 44 for UK). No '+' symbol.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Pre-filled Message (Optional)</label>
|
||||
<Textarea
|
||||
placeholder="Hi, I'm interested in your services..."
|
||||
value={message}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setMessage(e.target.value)}
|
||||
className="h-24 p-4 text-base rounded-xl border-slate-200 focus:border-[#25D366] focus:ring-[#25D366] resize-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#25D366]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#25D366] text-white border-[#25D366]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={getUrl()}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{phone ? `+${phone}` : 'Number'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-600 mt-1">Starts WhatsApp Chat</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#25D366] hover:bg-[#128C7E] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning starts a chat with this number instantly.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#128C7E] to-[#25D366] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Using WhatsApp for Business?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Track how many customers contact you via QR code analytics with our Pro plan.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#128C7E] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Get Business Analytics
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
335
src/app/(main)/(marketing)/tools/whatsapp-qr-code/page.tsx
Normal file
335
src/app/(main)/(marketing)/tools/whatsapp-qr-code/page.tsx
Normal file
@@ -0,0 +1,335 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import WhatsappGenerator from './WhatsAppGenerator';
|
||||
import { MessageCircle, Shield, Zap, Smartphone, Send, Phone, Download, Check } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free WhatsApp QR Code Generator | Start Chats Instantly | QR Master',
|
||||
},
|
||||
description: 'Create a QR code that opens a WhatsApp chat. WhatsApp QR Code erstellen mit Nachricht. Perfect for support & sales.',
|
||||
keywords: ['whatsapp qr code', 'wa.me generator', 'whatsapp chat qr', 'whatsapp link generator', 'contact qr code', 'whatsapp qr code erstellen', 'wa.me link erstellen', 'whatsapp chat starten', 'whatsapp kontakt qr code', 'whatsapp nachricht qr code'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/whatsapp-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free WhatsApp QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes to start WhatsApp chats. Add a pre-filled message.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/whatsapp-qr-code',
|
||||
images: [{ url: '/og-whatsapp-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free WhatsApp QR Code Generator',
|
||||
description: 'Create QR codes for WhatsApp. Chat instantly.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'WhatsApp QR Code Generator',
|
||||
'Generate QR codes that start a WhatsApp conversation with a specific number.',
|
||||
'/og-whatsapp-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a WhatsApp QR Code',
|
||||
description: 'Create a QR code that opens a WhatsApp chat.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Number',
|
||||
text: 'Type your WhatsApp phone number with country code (e.g. 1555...).',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Add a pre-written message and choose your brand color.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Save the QR code.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Test',
|
||||
text: 'Scan the code to ensure it opens the correct chat.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Add it to your website, business cards, or support materials.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT45S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Do users need to save my number first?': {
|
||||
question: 'Do users need to save my number first?',
|
||||
answer: 'No! That is the best part. Scanning the code opens the chat immediately without them needing to save you as a contact first.',
|
||||
},
|
||||
'Can I use this for WhatsApp Business?': {
|
||||
question: 'Can I use this for WhatsApp Business?',
|
||||
answer: 'Yes, it works perfectly for both personal and business accounts.',
|
||||
},
|
||||
'What is the Pre-filled Message?': {
|
||||
question: 'What is the Pre-filled Message?',
|
||||
answer: 'It is text that automatically appears in the user\'s typing field when they scan the code (e.g., "Hello, I want to order pizza"). They just have to hit send.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, this generator is completely free.',
|
||||
},
|
||||
'Can I track scans?': {
|
||||
question: 'Can I track scans?',
|
||||
answer: 'This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function WhatsappQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="WhatsApp QR Code Generator" toolSlug="whatsapp-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-[#128C7E]">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
{/* WhatsApp Pattern */}
|
||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="wa_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||
<path d="M30 30L35 35M25 35L30 30" stroke="white" strokeWidth="2" strokeOpacity="0.2" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#wa_pattern)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-300 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-300"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Start Chats Instantly with <br className="hidden lg:block" />
|
||||
<span className="text-white drop-shadow-md">WhatsApp QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-green-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Let customers message you without saving your number.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for support, sales, and bookings.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Send className="w-4 h-4 text-green-200" />
|
||||
Instant Chat
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<MessageCircle className="w-4 h-4 text-white" />
|
||||
Pre-filled Msg
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Phone className="w-4 h-4 text-white" />
|
||||
No Save Contact
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-green-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-[#ECE5DD] rounded-xl shadow-lg h-40 mb-6 relative overflow-hidden flex flex-col justify-end p-4">
|
||||
{/* Chat Bubble Right */}
|
||||
<div className="bg-[#DCF8C6] p-2 rounded-lg self-end mb-2 max-w-[80%] text-[10px] text-slate-800 shadow-sm">
|
||||
Hi! I'd like to book an appointment.
|
||||
<div className="text-[8px] text-slate-600 text-right mt-0.5">10:42 AM <span className="text-blue-500">✓✓</span></div>
|
||||
</div>
|
||||
{/* Chat Bubble Left */}
|
||||
<div className="bg-white p-2 rounded-lg self-start max-w-[80%] text-[10px] text-slate-800 shadow-sm">
|
||||
Sure! What time works for you?
|
||||
<div className="text-[8px] text-slate-600 text-right mt-0.5">10:43 AM</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#128C7E" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-green-100 p-2 rounded-full">
|
||||
<MessageCircle className="w-5 h-5 text-[#25D366]" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">WhatsApp</div>
|
||||
<div className="text-sm font-bold text-slate-900">Start Chat</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<WhatsappGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How WhatsApp QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Phone className="w-7 h-7 text-[#25D366]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Your Number</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Enter your WhatsApp phone number. Ensure it includes the country code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<MessageCircle className="w-6 h-6 text-[#25D366]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Customize</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Add a message and choose your color.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#25D366]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your QR code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Check className="w-6 h-6 text-[#25D366]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Test</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Scan to ensure it works.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#25D366]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Send className="w-6 h-6 text-[#25D366]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Chat</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Start chatting instantly.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about WhatsApp QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Does it work internationally?"
|
||||
answer="Yes. Since you include the country code, it works for anyone, anywhere in the world."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I use this on my website?"
|
||||
answer="Yes, you can display the QR code on your contact page so desktop users can easily scan it to chat on their phone."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is my phone number visible?"
|
||||
answer="Yes, the phone number is embedded in the link. It is the same visibility as putting your phone number on a business card."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is it free?"
|
||||
answer="Yes, this generator is completely free."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I track scans?"
|
||||
answer="This is a static QR code, so tracking is not included. Use our Dynamic QR Code generator for analytics."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
307
src/app/(main)/(marketing)/tools/wifi-qr-code/WiFiGenerator.tsx
Normal file
307
src/app/(main)/(marketing)/tools/wifi-qr-code/WiFiGenerator.tsx
Normal file
@@ -0,0 +1,307 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
|
||||
import {
|
||||
Wifi,
|
||||
Download,
|
||||
Printer,
|
||||
Check,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Sparkles
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Select } from '@/components/ui/Select';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#F0F9FF', // Sky-50
|
||||
primary: '#0EA5E9', // Sky-500
|
||||
primaryDark: '#0284C7', // Sky-600
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Sky Blue', value: '#0EA5E9' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Deep Blue', value: '#1E40AF' },
|
||||
{ name: 'Cyan', value: '#06B6D4' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'wifi', label: 'WiFi' },
|
||||
{ id: 'connect', label: 'Connect' },
|
||||
{ id: 'free', label: 'Free WiFi' },
|
||||
];
|
||||
|
||||
export default function WiFiGenerator() {
|
||||
const [ssid, setSsid] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [encryption, setEncryption] = useState('WPA');
|
||||
const [hidden, setHidden] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
// Customization
|
||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const wifiString = `WIFI:T:${encryption};S:${ssid};P:${password};H:${hidden};;`;
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `wifi-qr-${ssid || 'code'}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `wifi-qr-${ssid || 'code'}.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Network Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Wifi className="w-5 h-5 text-[#0EA5E9]" />
|
||||
Network Details
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Network Name (SSID)</label>
|
||||
<Input
|
||||
placeholder="e.g. MyHomeWiFi"
|
||||
value={ssid}
|
||||
onChange={(e) => setSsid(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#0EA5E9] focus:ring-[#0EA5E9]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Password</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="Your WiFi Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#0EA5E9] focus:ring-[#0EA5E9] pr-12"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 p-1.5 text-slate-400 hover:text-slate-600 transition-colors"
|
||||
aria-label={showPassword ? "Hide password" : "Show password"}
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Security</label>
|
||||
<Select
|
||||
value={encryption}
|
||||
onChange={(e) => setEncryption(e.target.value)}
|
||||
className="h-12 rounded-xl border-slate-200"
|
||||
aria-label="Security"
|
||||
options={[
|
||||
{ value: 'WPA', label: 'WPA / WPA2' },
|
||||
{ value: 'WEP', label: 'WEP' },
|
||||
{ value: 'nopass', label: 'No Password' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end pb-1">
|
||||
<label className="flex items-center gap-3 cursor-pointer group">
|
||||
<div className={cn(
|
||||
"w-5 h-5 rounded border-2 flex items-center justify-center transition-all",
|
||||
hidden ? "bg-[#1A1265] border-[#1A1265]" : "border-slate-300 group-hover:border-slate-400"
|
||||
)}>
|
||||
{hidden && <Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />}
|
||||
</div>
|
||||
<input type="checkbox" checked={hidden} onChange={(e) => setHidden(e.target.checked)} className="sr-only" />
|
||||
<span className="text-sm font-medium text-slate-700">Hidden Network</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#0EA5E9]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#0EA5E9] text-white border-[#0EA5E9]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label - CTA Button Style */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={wifiString}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Network Info */}
|
||||
<div className="mt-6 text-center">
|
||||
<h3 className="font-bold text-slate-900 text-xl truncate max-w-[260px] mx-auto">
|
||||
{ssid || 'Network Name'}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#0EA5E9] hover:bg-[#0284C7] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Your credentials stay on your device. Nothing is sent to servers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#0EA5E9] to-[#0284C7] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Hospitality or Business WiFi?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">Get scan analytics and collect customer reviews with our Pro plan.</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#0EA5E9] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Get Business Tools
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
341
src/app/(main)/(marketing)/tools/wifi-qr-code/page.tsx
Normal file
341
src/app/(main)/(marketing)/tools/wifi-qr-code/page.tsx
Normal file
@@ -0,0 +1,341 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import WiFiGenerator from './WiFiGenerator';
|
||||
import { Wifi, Shield, Zap, Smartphone, Lock, QrCode, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free WiFi QR Code Generator | WLAN QR Code | QR Master',
|
||||
},
|
||||
description: 'Create a WiFi QR code in seconds. Erstelle kostenlos deinen WLAN QR Code ohne Passwort-Eingabe. Guests scan to connect instantly. 100% Secure & Free.',
|
||||
keywords: ['wifi qr code', 'qr code generator', 'wifi qr code generator', 'share wifi', 'wifi password qr', 'guest wifi', 'wlan qr code', 'wlan qr code erstellen', 'wifi passwort qr code', 'wlan zugang teilen', 'wifi qr code kostenlos'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/wifi-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free WiFi QR Code Generator | QR Master',
|
||||
description: 'Share your WiFi without sharing your password. Guests scan the QR code to connect instantly.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/wifi-qr-code',
|
||||
images: [{ url: '/og-wifi-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free WiFi QR Code Generator',
|
||||
description: 'Share WiFi instantly with a QR code. No typing passwords.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
// SoftwareApplication Schema
|
||||
generateSoftwareAppSchema(
|
||||
'WiFi QR Code Generator',
|
||||
'Generate QR codes for WiFi networks. Guests scan to connect without typing passwords.',
|
||||
'/og-wifi-generator.png'
|
||||
),
|
||||
// HowTo Schema for Featured Snippets
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a WiFi QR Code',
|
||||
description: 'Create a QR code that connects devices to your WiFi network automatically.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Network Name',
|
||||
text: 'Type your WiFi network name (SSID) in the Network Name field.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Enter Password',
|
||||
text: 'Enter your WiFi password. This is processed locally and never sent to any server.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Select Security Type',
|
||||
text: 'Choose WPA/WPA2 (most common), WEP, or No Password for open networks.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download QR Code',
|
||||
text: 'Click Download PNG or SVG to save your QR code. Print it or share digitally.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Connect',
|
||||
text: 'Print the code. Guests can scan it to join your network instantly.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT1M',
|
||||
},
|
||||
// FAQPage Schema
|
||||
generateFaqSchema({
|
||||
'Is it safe to enter my WiFi password?': {
|
||||
question: 'Is it safe to enter my WiFi password?',
|
||||
answer: 'Yes, completely safe. This tool processes everything in your browser (client-side). Your password never leaves your device and is not sent to any server.',
|
||||
},
|
||||
'Do WiFi QR codes work on iPhone and Android?': {
|
||||
question: 'Do WiFi QR codes work on iPhone and Android?',
|
||||
answer: 'Yes. Both iOS (11+) and Android devices can scan WiFi QR codes using their built-in camera app. No additional apps required.',
|
||||
},
|
||||
'What happens if I change my WiFi password?': {
|
||||
question: 'What happens if I change my WiFi password?',
|
||||
answer: 'If you change your WiFi password, the old QR code will stop working. You\'ll need to generate a new QR code with the updated credentials.For frequently changing passwords, consider using dynamic QR codes.',
|
||||
},
|
||||
'Can I customize the QR code design?': {
|
||||
question: 'Can I customize the QR code design?',
|
||||
answer: 'Yes. You can change the QR code color and add frame labels like "Scan Me" or "WiFi" to make it more recognizable and user-friendly.',
|
||||
},
|
||||
'Does it work for hidden networks?': {
|
||||
question: 'Does it work for hidden networks?',
|
||||
answer: 'Yes, just check the "Hidden Network" box if your SSID is hidden. The QR code contains the standard WiFi string configuration.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function WiFiQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Script */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="WiFi QR Code Generator" toolSlug="wifi-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
||||
{/* Background Pattern */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
|
||||
{/* Left: Text Content */}
|
||||
<div className="text-center lg:text-left">
|
||||
{/* Badge */}
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
The Safest Way to <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Share Your WiFi</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Generate a secure QR code in seconds. No more spelling out complicated passwords.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> 100% Client-Side & Private.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Lock className="w-4 h-4 text-emerald-400" />
|
||||
No Server Uploads
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-400" />
|
||||
Instant Connect
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-purple-400" />
|
||||
iOS & Android
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Visual Abstract Composition */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
{/* Decorative Glow */}
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
{/* Floating Glass Card */}
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
{/* Mock QR */}
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||
{/* Scan Line */}
|
||||
<div className="absolute top-1/2 left-0 w-full h-1 bg-emerald-500 shadow-[0_0_20px_rgba(16,185,129,1)] animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-3">
|
||||
<div className="h-2 w-32 bg-white/20 rounded-full mx-auto" />
|
||||
<div className="h-2 w-20 bg-white/10 rounded-full mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-emerald-100 p-2 rounded-full">
|
||||
<Wifi className="w-5 h-5 text-emerald-600" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||
<div className="text-sm font-bold text-slate-900">Connected</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<WiFiGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS - AEO/GEO Content */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How WiFi QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Wifi className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Network</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Enter your WiFi SSID and password.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Shield className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Security</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Select WPA/WPA2 encryption.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Zap className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Style</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Customize colors and add a frame.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Get your high-quality QR image.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Connect</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Print it out. Guests scan to join!
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION - Featured Snippet Optimized */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Everything you need to know about WiFi QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Is it safe to enter my WiFi password here?"
|
||||
answer="Yes, completely safe. This tool uses client-side processing, meaning your WiFi password never leaves your device. It's processed locally in your browser to generate the QR code—no data is sent to any server."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Do WiFi QR codes work on iPhone and Android?"
|
||||
answer="Yes. iOS 11 and later, as well as all modern Android devices, can scan WiFi QR codes using the built-in camera app. Simply point the camera at the QR code and tap the notification to connect."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What happens if I change my WiFi password?"
|
||||
answer="If you change your WiFi password, the old QR code will stop working. You'll need to generate a new QR code with the updated credentials. For frequently changing passwords, consider using dynamic QR codes."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I customize the QR code design?"
|
||||
answer="Yes. You can change the foreground color of the QR code and add frame labels such as 'Scan Me', 'WiFi', or 'Connect' to make your QR code more recognizable and user-friendly."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it work for hidden networks?"
|
||||
answer="Yes, just check the 'Hidden Network' box if your SSID is hidden. The QR code contains the standard WiFi string configuration."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// FAQ Item Component
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Youtube,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Play
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors
|
||||
const BRAND = {
|
||||
paleGrey: '#EBEBDF',
|
||||
richBlue: '#1A1265',
|
||||
richBlueLight: '#2A2275',
|
||||
};
|
||||
|
||||
// QR Color Options - YT Theme
|
||||
const QR_COLORS = [
|
||||
{ name: 'YouTube Red', value: '#FF0000' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Dark Blue', value: '#1A1265' },
|
||||
{ name: 'Teal', value: '#0D9488' },
|
||||
{ name: 'Grey', value: '#374151' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'watch', label: 'Watch' },
|
||||
{ id: 'subscribe', label: 'Subscribe' },
|
||||
];
|
||||
|
||||
export default function YoutubeGenerator() {
|
||||
const [url, setUrl] = useState('');
|
||||
const [qrColor, setQrColor] = useState('#FF0000');
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `youtube-qr-code.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `youtube-qr-code.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* YouTube Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Youtube className="w-5 h-5 text-[#FF0000]" />
|
||||
YouTube Video or Channel
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Video/Channel URL</label>
|
||||
<Input
|
||||
placeholder="https://youtube.com/watch?v=..."
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#FF0000] focus:ring-[#FF0000]"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">Paste a link to any video, channel, or playlist.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#FF0000]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-3 rounded-lg text-sm font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#FF0000] text-white border-[#FF0000]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={url || "https://youtube.com"}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Preview */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2 truncate">
|
||||
<Youtube className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{url ? 'YouTube Content' : 'youtube.com'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-600 mt-1">Opens in YouTube App</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#FF0000] hover:bg-[#cc0000] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning redirects directly to the video or channel.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#FF0000] to-[#cc0000] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Promoting a Video Channel?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Dynamic QR Codes give you stats on scans, locations, and time of day.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#FF0000] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Get Video Stats
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
330
src/app/(main)/(marketing)/tools/youtube-qr-code/page.tsx
Normal file
330
src/app/(main)/(marketing)/tools/youtube-qr-code/page.tsx
Normal file
@@ -0,0 +1,330 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import YoutubeGenerator from './YouTubeGenerator';
|
||||
import { Youtube, Shield, Zap, Smartphone, Play, Radio, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free YouTube QR Code Generator | Get Views & Subscribers | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your YouTube video or channel. YouTube QR Code erstellen. Scanners watch instantly. Free & Fast.',
|
||||
keywords: ['youtube qr code', 'video qr code', 'youtube channel qr', 'youtube subscribe qr', 'social media qr code', 'youtube qr code erstellen', 'video qr code erstellen', 'kanal qr code', 'youtube abonnenten qr code', 'youtube video teilen qr'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/youtube-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free YouTube QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes to grow your YouTube channel. Instant video play.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/youtube-qr-code',
|
||||
images: [{ url: '/og-youtube-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free YouTube QR Code Generator',
|
||||
description: 'Create QR codes for YouTube videos. Get more views.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'YouTube QR Code Generator',
|
||||
'Generate QR codes that direct users to a YouTube video or channel.',
|
||||
'/og-youtube-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a YouTube QR Code',
|
||||
description: 'Create a QR code that opens a YouTube video.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Copy URL',
|
||||
text: 'Copy the link of your YouTube video or channel.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Paste',
|
||||
text: 'Paste the link into the generator input.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Customize',
|
||||
text: 'Add a "Watch Now" frame or change the color to YouTube Red.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download',
|
||||
text: 'Save your QR code image for printing.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Place it on posters, merch, or video end screens.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Does it open the YouTube app?': {
|
||||
question: 'Does it open the YouTube app?',
|
||||
answer: 'Yes! If the user has the YouTube app installed, the QR code will automatically launch the app and play the video.',
|
||||
},
|
||||
'Can I link to a specific timestamp?': {
|
||||
question: 'Can I link to a specific timestamp?',
|
||||
answer: 'Yes. If you include the timestamp in your YouTube link (e.g., ?t=60s), the video will start playing from that exact moment.',
|
||||
},
|
||||
'Can I use this for a playlist?': {
|
||||
question: 'Can I use this for a playlist?',
|
||||
answer: 'Absolutely. Just paste the playlist URL, and users will be taken to the full list of videos.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, this tool is 100% free forever.',
|
||||
},
|
||||
'Does it work for YouTube Shorts?': {
|
||||
question: 'Does it work for YouTube Shorts?',
|
||||
answer: 'Yes, just paste the "Share" link from any YouTube Short.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function YoutubeQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="YouTube QR Code Generator" toolSlug="youtube-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-[#FF0000]">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
{/* Play Button Pattern */}
|
||||
<svg className="w-full h-full" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="yt_pattern" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||
<path d="M20 20 L40 30 L20 40 Z" fill="white" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#yt_pattern)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-300 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-300"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Get More Views with <br className="hidden lg:block" />
|
||||
<span className="text-white drop-shadow-md">YouTube QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-red-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
From print to play in one scan. Direct your audience to your latest video, channel, or playlist instantly.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Boost subscriber growth.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Play className="w-4 h-4 text-white" />
|
||||
Instant Play
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Radio className="w-4 h-4 text-white" />
|
||||
Grow Channel
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-white" />
|
||||
App Friendly
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-red-600/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-black rounded-xl shadow-lg h-40 mb-6 relative overflow-hidden group-hover:scale-105 transition-transform flex items-center justify-center">
|
||||
<div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1611162617474-5b21e879e113?q=80&w=1000&auto=format&fit=crop')] bg-cover bg-center opacity-70"></div>
|
||||
<div className="w-12 h-12 bg-red-600 rounded-full flex items-center justify-center relative z-10 shadow-xl">
|
||||
<Play className="w-6 h-6 text-white ml-1" fill="white" />
|
||||
</div>
|
||||
<div className="absolute bottom-2 right-2 bg-black/80 px-2 rounded text-xs text-white font-bold">10:24</div>
|
||||
</div>
|
||||
|
||||
<div className="w-44 h-44 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={160} fgColor="#FF0000" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-red-100 p-2 rounded-full">
|
||||
<Youtube className="w-5 h-5 text-red-600" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Subscribers</div>
|
||||
<div className="text-sm font-bold text-slate-900">+10 New</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<YoutubeGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How YouTube QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Youtube className="w-7 h-7 text-[#FF0000]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Paste Link</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Copy the URL of your video, channel, or playlist.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#FF0000]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Print Code</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Place the QR code on flyers, posters, or merchandise.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#FF0000]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your high-quality QR code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Play className="w-6 h-6 text-[#FF0000]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Customers scan the code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#FF0000]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-[#FF0000]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Watch</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
The video plays instantly.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about YouTube QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Can I link to my Live Stream?"
|
||||
answer="Yes! Paste your channel's live link (e.g., youtube.com/c/YourChannel/live) and it will always go to your current live stream."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does the video auto-play?"
|
||||
answer="Most smartphones will open the YouTube app and auto-play the video, but it depends on the user's specific settings."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I change the video later?"
|
||||
answer="Only if you use our Dynamic QR Code service. This static code will always point to the original link you entered."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is it safe?"
|
||||
answer="Yes. The QR code simply contains your video link. No personal data is stored or tracked by this free tool."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it work for YouTube Shorts?"
|
||||
answer="Yes, just paste the 'Share' link from any YouTube Short."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
302
src/app/(main)/(marketing)/tools/zoom-qr-code/ZoomGenerator.tsx
Normal file
302
src/app/(main)/(marketing)/tools/zoom-qr-code/ZoomGenerator.tsx
Normal file
@@ -0,0 +1,302 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
Video,
|
||||
Download,
|
||||
Check,
|
||||
Sparkles,
|
||||
Users
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Brand Colors - Zoom Blue
|
||||
const BRAND = {
|
||||
paleGrey: '#EFF6FF',
|
||||
primary: '#2D8CFF',
|
||||
primaryDark: '#0B5CDB',
|
||||
};
|
||||
|
||||
// QR Color Options
|
||||
const QR_COLORS = [
|
||||
{ name: 'Zoom Blue', value: '#2D8CFF' },
|
||||
{ name: 'Dark Blue', value: '#0B5CDB' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Indigo', value: '#4F46E5' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
];
|
||||
|
||||
// Frame Options
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'join', label: 'Join Meeting' },
|
||||
{ id: 'zoom', label: 'Zoom' },
|
||||
];
|
||||
|
||||
export default function ZoomGenerator() {
|
||||
const [meetingId, setMeetingId] = useState('');
|
||||
const [passcode, setPasscode] = useState('');
|
||||
const [useDirectLink, setUseDirectLink] = useState(false); // Default to web URL for compatibility
|
||||
const [qrColor, setQrColor] = useState(BRAND.primary);
|
||||
const [frameType, setFrameType] = useState('none');
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Format meeting ID for display (xxx xxxx xxxx)
|
||||
const formatMeetingId = (id: string) => {
|
||||
const cleaned = id.replace(/\D/g, '');
|
||||
if (cleaned.length <= 3) return cleaned;
|
||||
if (cleaned.length <= 7) return `${cleaned.slice(0, 3)} ${cleaned.slice(3)}`;
|
||||
return `${cleaned.slice(0, 3)} ${cleaned.slice(3, 7)} ${cleaned.slice(7, 11)}`;
|
||||
};
|
||||
|
||||
// Generate Zoom link
|
||||
const generateZoomLink = () => {
|
||||
const cleanId = meetingId.replace(/\D/g, '');
|
||||
if (!cleanId) return 'https://zoom.us/j/1234567890';
|
||||
|
||||
if (useDirectLink) {
|
||||
// zoommtg protocol for direct app open
|
||||
let link = `zoommtg://zoom.us/join?confno=${cleanId}`;
|
||||
if (passcode) {
|
||||
link += `&pwd=${passcode}`;
|
||||
}
|
||||
return link;
|
||||
} else {
|
||||
// Regular web link
|
||||
let link = `https://zoom.us/j/${cleanId}`;
|
||||
if (passcode) {
|
||||
link += `?pwd=${passcode}`;
|
||||
}
|
||||
return link;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const { toPng } = await import('html-to-image');
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = `zoom-qr-${meetingId.replace(/\D/g, '') || 'meeting'}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `zoom-qr-${meetingId.replace(/\D/g, '') || 'meeting'}.svg`;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = FRAME_OPTIONS.find(f => f.id === frameType);
|
||||
return frame?.id !== 'none' ? frame?.label : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
|
||||
{/* Main Generator Card */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
{/* Meeting Details */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Video className="w-5 h-5 text-[#2D8CFF]" />
|
||||
Meeting Details
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Meeting ID</label>
|
||||
<Input
|
||||
placeholder="123 4567 8901"
|
||||
value={formatMeetingId(meetingId)}
|
||||
onChange={(e) => setMeetingId(e.target.value.replace(/\D/g, ''))}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#2D8CFF] focus:ring-[#2D8CFF]"
|
||||
/>
|
||||
<p className="text-xs text-slate-600 mt-2">The 10-11 digit meeting ID from your Zoom invite.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">Passcode (Optional)</label>
|
||||
<Input
|
||||
placeholder="abc123"
|
||||
value={passcode}
|
||||
onChange={(e) => setPasscode(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#2D8CFF] focus:ring-[#2D8CFF]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="flex items-center gap-3 cursor-pointer group">
|
||||
<div className={cn(
|
||||
"w-5 h-5 rounded border-2 flex items-center justify-center transition-all",
|
||||
useDirectLink ? "bg-[#2D8CFF] border-[#2D8CFF]" : "border-slate-300 group-hover:border-slate-400"
|
||||
)}>
|
||||
{useDirectLink && <Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />}
|
||||
</div>
|
||||
<input type="checkbox" checked={useDirectLink} onChange={(e) => setUseDirectLink(e.target.checked)} className="sr-only" />
|
||||
<span className="text-sm font-medium text-slate-700">Open Zoom app directly</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100"></div>
|
||||
|
||||
{/* Design Options */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-[#2D8CFF]" />
|
||||
Design Options
|
||||
</h2>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">QR Code Color</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((c) => (
|
||||
<button
|
||||
key={c.name}
|
||||
onClick={() => setQrColor(c.value)}
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full border-2 flex items-center justify-center transition-all hover:scale-110",
|
||||
qrColor === c.value ? "border-slate-900 ring-2 ring-offset-2 ring-slate-200" : "border-white shadow-md"
|
||||
)}
|
||||
style={{ backgroundColor: c.value }}
|
||||
aria-label={`Select ${c.name}`}
|
||||
title={c.name}
|
||||
>
|
||||
{qrColor === c.value && <Check className="w-4 h-4 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame Selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">Frame Label</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={cn(
|
||||
"py-2.5 px-2 rounded-lg text-xs font-medium transition-all border",
|
||||
frameType === frame.id
|
||||
? "bg-[#2D8CFF] text-white border-[#2D8CFF]"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200 hover:border-slate-300"
|
||||
)}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview Section */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center" style={{ backgroundColor: BRAND.paleGrey }}>
|
||||
|
||||
{/* QR Card with Frame */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="bg-white rounded-3xl shadow-xl p-6 sm:p-8 flex flex-col items-center w-full max-w-[320px]"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
className="mb-5 px-8 py-2.5 rounded-full text-white font-bold text-sm tracking-widest uppercase shadow-md"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{getFrameLabel()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code */}
|
||||
<div className="bg-white">
|
||||
<QRCodeSVG
|
||||
value={generateZoomLink()}
|
||||
size={240}
|
||||
level="M"
|
||||
includeMargin={false}
|
||||
fgColor={qrColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Meeting Info */}
|
||||
<div className="mt-6 text-center max-w-[260px]">
|
||||
<h3 className="font-bold text-slate-900 text-lg flex items-center justify-center gap-2">
|
||||
<Video className="w-4 h-4 text-[#2D8CFF] shrink-0" />
|
||||
<span className="truncate">{formatMeetingId(meetingId) || 'Meeting ID'}</span>
|
||||
</h3>
|
||||
{passcode && (
|
||||
<p className="text-sm text-slate-600 mt-1">Passcode: {passcode}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex items-center gap-3 mt-8">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
className="bg-[#2D8CFF] hover:bg-[#0B5CDB] text-white shadow-lg"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownload('svg')}
|
||||
variant="outline"
|
||||
className="border-slate-300 hover:bg-white"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
SVG
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Your meeting ID is encoded directly. Static and forever free.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upsell Banner */}
|
||||
<div className="mt-8 bg-gradient-to-r from-[#2D8CFF] to-[#0B5CDB] rounded-2xl p-6 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-white text-center sm:text-left">
|
||||
<h3 className="font-bold text-lg">Need to update meeting details?</h3>
|
||||
<p className="text-white/80 text-sm mt-1">Dynamic QR Codes let you change the meeting link without reprinting.</p>
|
||||
</div>
|
||||
<Link href="/signup">
|
||||
<Button className="bg-white text-[#2D8CFF] hover:bg-slate-100 shrink-0 shadow-lg">
|
||||
Create Dynamic QR
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
308
src/app/(main)/(marketing)/tools/zoom-qr-code/page.tsx
Normal file
308
src/app/(main)/(marketing)/tools/zoom-qr-code/page.tsx
Normal file
@@ -0,0 +1,308 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import ZoomGenerator from './ZoomGenerator';
|
||||
import { Video, Shield, Zap, Smartphone, Users, Download, Share2 } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Zoom QR Code Generator | Join Meetings Instantly | QR Master',
|
||||
},
|
||||
description: 'Create a QR code for your Zoom meeting. Zoom QR Code erstellen. Attendees scan to join instantly. Perfect for conference rooms & invites.',
|
||||
keywords: ['zoom qr code', 'zoom meeting qr', 'join zoom qr code', 'meeting room qr', 'zoom invitation qr', 'conference qr code', 'zoom qr code erstellen', 'zoom meeting qr code', 'video konferenz qr', 'zoom beitreten qr'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/zoom-qr-code',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Zoom QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes for Zoom meetings. One scan to join instantly.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/zoom-qr-code',
|
||||
images: [{ url: '/og-zoom-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Zoom QR Code Generator',
|
||||
description: 'Create Zoom meeting QR codes. Instant and free.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Zoom QR Code Generator',
|
||||
'Generate QR codes that let people join your Zoom meeting with one scan.',
|
||||
'/og-zoom-generator.png',
|
||||
'BusinessApplication'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a Zoom QR Code',
|
||||
description: 'Create a QR code for joining Zoom meetings.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Meeting ID',
|
||||
text: 'Copy the 10-11 digit meeting ID from your Zoom invitation.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Add Passcode',
|
||||
text: 'If your meeting has a passcode, enter it to include in the QR.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Choose Link Type',
|
||||
text: 'Select whether to open Zoom app directly or use a web link.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download',
|
||||
text: 'Download your QR code and display it in your meeting room or invitation.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'What happens when someone scans the QR code?': {
|
||||
question: 'What happens when someone scans the QR code?',
|
||||
answer: 'The Zoom app opens directly with your meeting ID and passcode pre-filled. They just tap "Join" to enter the meeting.',
|
||||
},
|
||||
'Does it work for recurring meetings?': {
|
||||
question: 'Does it work for recurring meetings?',
|
||||
answer: 'Yes! If your recurring meeting uses a fixed Personal Meeting ID (PMI), the QR code will work for all sessions.',
|
||||
},
|
||||
'What if the meeting ID changes?': {
|
||||
question: 'What if the meeting ID changes?',
|
||||
answer: 'Static QR codes cannot be updated. You\'ll need to generate a new code. For changeable meetings, consider our Dynamic QR Codes.',
|
||||
},
|
||||
'Does it work on all devices?': {
|
||||
question: 'Does it work on all devices?',
|
||||
answer: 'Yes. The QR code works on iOS, Android, and can also open Zoom on desktop computers if the Zoom app is installed.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function ZoomQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Zoom QR Code Generator" toolSlug="zoom-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#2D8CFF' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Join Meetings with <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-200 to-cyan-100">Zoom QR Codes</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-blue-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Create QR codes for your Zoom meetings. Attendees scan to join instantly.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Perfect for conference rooms.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Video className="w-4 h-4 text-white" />
|
||||
Direct Join
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-300" />
|
||||
Instant Open
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Users className="w-4 h-4 text-emerald-300" />
|
||||
Any Device
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-blue-400/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
{/* Meeting Card Mock */}
|
||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="w-10 h-10 bg-[#2D8CFF] rounded-full flex items-center justify-center">
|
||||
<Video className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-slate-900 text-sm">Team Standup</div>
|
||||
<div className="text-xs text-slate-600">ID: 123 456 7890</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="bg-green-100 text-green-700 text-xs px-2 py-1 rounded-full">Live</div>
|
||||
<div className="bg-slate-100 text-slate-600 text-xs px-2 py-1 rounded-full">12 attending</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#2D8CFF" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -right-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-blue-100 p-2 rounded-full">
|
||||
<Users className="w-5 h-5 text-[#2D8CFF]" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||
<div className="text-sm font-bold text-slate-900">Ready to Join</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<ZoomGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Zoom QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-4 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Video className="w-6 h-6 text-[#2D8CFF]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Meeting ID</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Enter your Zoom meeting ID.</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Shield className="w-6 h-6 text-[#2D8CFF]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Passcode</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Add passcode if required.</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#2D8CFF]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Save your QR code.</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#2D8CFF]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-[#2D8CFF]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Display</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">Put in meeting rooms or invites.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Zoom QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="What happens when someone scans the QR code?"
|
||||
answer="The Zoom app opens directly with your meeting ID and passcode pre-filled. They just tap 'Join' to enter the meeting."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it work for recurring meetings?"
|
||||
answer="Yes! If your recurring meeting uses a fixed Personal Meeting ID (PMI), the QR code will work for all sessions."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What if the meeting ID changes?"
|
||||
answer="Static QR codes cannot be updated. You'll need to generate a new code. For changeable meetings, consider our Dynamic QR Codes."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it work without the Zoom app installed?"
|
||||
answer="If 'Open Zoom app directly' is unchecked, the QR links to join.zoom.us which works in any browser."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user