SEO: Fix structured data validation errors, delete static sitemap, and update indexing scripts

This commit is contained in:
Timo Knuth 2026-01-23 23:10:22 +01:00
parent f3637fc2fe
commit eef4855c1b
147 changed files with 24590 additions and 27027 deletions

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://www.qrmaster.net/</loc>
<lastmod>2025-10-16T00:00:00Z</lastmod>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://www.qrmaster.net/blog</loc>
<lastmod>2025-10-16T00:00:00Z</lastmod>
<changefreq>daily</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.qrmaster.net/pricing</loc>
<lastmod>2025-10-16T00:00:00Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.qrmaster.net/faq</loc>
<lastmod>2025-10-16T00:00:00Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://www.qrmaster.net/blog/qr-code-analytics</loc>
<lastmod>2025-10-16T00:00:00Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
</urlset>

View File

@ -1,11 +0,0 @@
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white">
{children}
</div>
);
}

View File

@ -1,187 +0,0 @@
'use client';
import React, { useState, useEffect } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { useTranslation } from '@/hooks/useTranslation';
import { useCsrf } from '@/hooks/useCsrf';
export default function LoginPage() {
const router = useRouter();
const searchParams = useSearchParams();
const { t } = useTranslation();
const { fetchWithCsrf, loading: csrfLoading } = useCsrf();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
try {
const response = await fetchWithCsrf('/api/auth/simple-login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (response.ok && data.success) {
// Store user in localStorage for client-side
localStorage.setItem('user', JSON.stringify(data.user));
// Track successful login with PostHog
try {
const { identifyUser, trackEvent } = await import('@/components/PostHogProvider');
identifyUser(data.user.id, {
email: data.user.email,
name: data.user.name,
plan: data.user.plan || 'FREE',
});
trackEvent('user_login', {
method: 'email',
email: data.user.email,
});
} catch (error) {
console.error('PostHog tracking error:', error);
}
// Check for redirect parameter
const redirectUrl = searchParams.get('redirect') || '/dashboard';
router.push(redirectUrl);
router.refresh();
} else {
setError(data.error || 'Invalid email or password');
}
} catch (err) {
setError('An error occurred. Please try again.');
} finally {
setLoading(false);
}
};
const handleGoogleSignIn = () => {
// Redirect to Google OAuth API route
window.location.href = '/api/auth/google';
};
return (
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white flex items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<Link href="/" className="inline-flex items-center space-x-2 mb-6">
<img src="/logo.svg" alt="QR Master" className="w-10 h-10" />
<span className="text-2xl font-bold text-gray-900">QR Master</span>
</Link>
<h1 className="text-3xl font-bold text-gray-900">Welcome Back</h1>
<p className="text-gray-600 mt-2">Sign in to your account</p>
<Link href="/" className="text-sm text-primary-600 hover:text-primary-700 font-medium mt-2 inline-block border border-primary-600 hover:border-primary-700 px-4 py-2 rounded-lg transition-colors">
Back to Home
</Link>
</div>
<Card>
<CardContent className="p-6">
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="bg-red-50 text-red-600 p-3 rounded-lg text-sm">
{error}
</div>
)}
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
/>
<Input
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
/>
<div className="flex items-center justify-between">
<label className="flex items-center">
<input type="checkbox" className="mr-2" />
<span className="text-sm text-gray-600">Remember me</span>
</label>
<Link href="/forgot-password" className="text-sm text-primary-600 hover:text-primary-700">
Forgot password?
</Link>
</div>
<Button type="submit" className="w-full" loading={loading} disabled={csrfLoading || loading}>
{csrfLoading ? 'Loading...' : 'Sign In'}
</Button>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">Or continue with</span>
</div>
</div>
<Button
type="button"
variant="outline"
className="w-full"
onClick={handleGoogleSignIn}
>
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
<path
fill="#4285F4"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="#34A853"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="#FBBC05"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="#EA4335"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
Sign in with Google
</Button>
</form>
<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
Don't have an account?{' '}
<Link href="/signup" className="text-primary-600 hover:text-primary-700 font-medium">
Sign up
</Link>
</p>
</div>
</CardContent>
</Card>
<p className="text-center text-sm text-gray-500 mt-6">
By signing in, you agree to our{' '}
<Link href="/privacy" className="text-primary-600 hover:text-primary-700">
Privacy Policy
</Link>
</p>
</div>
</div>
);
}

View File

@ -1,208 +0,0 @@
'use client';
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { useTranslation } from '@/hooks/useTranslation';
import { useCsrf } from '@/hooks/useCsrf';
export default function SignupPage() {
const router = useRouter();
const { t } = useTranslation();
const { fetchWithCsrf } = useCsrf();
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
if (password !== confirmPassword) {
setError('Passwords do not match');
setLoading(false);
return;
}
if (password.length < 8) {
setError('Password must be at least 8 characters');
setLoading(false);
return;
}
try {
const response = await fetchWithCsrf('/api/auth/signup', {
method: 'POST',
body: JSON.stringify({ name, email, password }),
});
const data = await response.json();
if (response.ok && data.success) {
// Store user in localStorage for client-side
localStorage.setItem('user', JSON.stringify(data.user));
// Track successful signup with PostHog
try {
const { identifyUser, trackEvent } = await import('@/components/PostHogProvider');
identifyUser(data.user.id, {
email: data.user.email,
name: data.user.name,
plan: data.user.plan || 'FREE',
signupMethod: 'email',
});
trackEvent('user_signup', {
method: 'email',
email: data.user.email,
});
} catch (error) {
console.error('PostHog tracking error:', error);
}
// Redirect to dashboard
router.push('/dashboard');
router.refresh();
} else {
setError(data.error || 'Failed to create account');
}
} catch (err) {
setError('An error occurred. Please try again.');
} finally {
setLoading(false);
}
};
const handleGoogleSignIn = () => {
// Redirect to Google OAuth API route
window.location.href = '/api/auth/google';
};
return (
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white flex items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<Link href="/" className="inline-flex items-center space-x-2 mb-6">
<img src="/logo.svg" alt="QR Master" className="w-10 h-10" />
<span className="text-2xl font-bold text-gray-900">QR Master</span>
</Link>
<h1 className="text-3xl font-bold text-gray-900">Create Account</h1>
<p className="text-gray-600 mt-2">Start creating QR codes in seconds</p>
<Link href="/" className="text-sm text-primary-600 hover:text-primary-700 font-medium mt-2 inline-block border border-primary-600 hover:border-primary-700 px-4 py-2 rounded-lg transition-colors">
Back to Home
</Link>
</div>
<Card>
<CardContent className="p-6">
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="bg-red-50 text-red-600 p-3 rounded-lg text-sm">
{error}
</div>
)}
<Input
label="Full Name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="John Doe"
required
/>
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
/>
<Input
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
/>
<Input
label="Confirm Password"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="••••••••"
required
/>
<Button type="submit" className="w-full" loading={loading}>
Create Account
</Button>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">Or continue with</span>
</div>
</div>
<Button
type="button"
variant="outline"
className="w-full"
onClick={handleGoogleSignIn}
>
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
<path
fill="#4285F4"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="#34A853"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="#FBBC05"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="#EA4335"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
Sign up with Google
</Button>
</form>
<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
Already have an account?{' '}
<Link href="/login" className="text-primary-600 hover:text-primary-700 font-medium">
Sign in
</Link>
</p>
</div>
</CardContent>
</Card>
<p className="text-center text-sm text-gray-500 mt-6">
By signing up, you agree to our{' '}
<Link href="/privacy" className="text-primary-600 hover:text-primary-700">
Privacy Policy
</Link>
</p>
</div>
</div>
);
}

View File

@ -0,0 +1,25 @@
import Link from 'next/link';
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white flex flex-col">
<div className="flex-grow">
{children}
</div>
<footer className="py-6 text-center text-sm text-gray-500 bg-transparent">
<div className="space-x-6 mb-2">
<Link href="/" className="hover:text-gray-900 transition-colors">Home</Link>
<Link href="/pricing" className="hover:text-gray-900 transition-colors">Pricing</Link>
<Link href="/privacy" className="hover:text-gray-900 transition-colors">Privacy</Link>
<Link href="/faq" className="hover:text-gray-900 transition-colors">FAQ</Link>
</div>
<p>&copy; {new Date().getFullYear()} QR Master</p>
</footer>
</div>
);
}

View File

@ -0,0 +1,187 @@
'use client';
import React, { useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { Card, CardContent } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { useTranslation } from '@/hooks/useTranslation';
import { useCsrf } from '@/hooks/useCsrf';
export default function LoginClient() {
const router = useRouter();
const searchParams = useSearchParams();
const { t } = useTranslation();
const { fetchWithCsrf, loading: csrfLoading } = useCsrf();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
try {
const response = await fetchWithCsrf('/api/auth/simple-login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (response.ok && data.success) {
// Store user in localStorage for client-side
localStorage.setItem('user', JSON.stringify(data.user));
// Track successful login with PostHog
try {
const { identifyUser, trackEvent } = await import('@/components/PostHogProvider');
identifyUser(data.user.id, {
email: data.user.email,
name: data.user.name,
plan: data.user.plan || 'FREE',
});
trackEvent('user_login', {
method: 'email',
email: data.user.email,
});
} catch (error) {
console.error('PostHog tracking error:', error);
}
// Check for redirect parameter
const redirectUrl = searchParams.get('redirect') || '/dashboard';
router.push(redirectUrl);
router.refresh();
} else {
setError(data.error || 'Invalid email or password');
}
} catch (err) {
setError('An error occurred. Please try again.');
} finally {
setLoading(false);
}
};
const handleGoogleSignIn = () => {
// Redirect to Google OAuth API route
window.location.href = '/api/auth/google';
};
return (
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white flex items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<Link href="/" className="inline-flex items-center space-x-2 mb-6">
<img src="/logo.svg" alt="QR Master" className="w-10 h-10" />
<span className="text-2xl font-bold text-gray-900">QR Master</span>
</Link>
<h1 className="text-3xl font-bold text-gray-900">Welcome Back</h1>
<p className="text-gray-600 mt-2">Sign in to your account</p>
<Link href="/" className="text-sm text-primary-600 hover:text-primary-700 font-medium mt-2 inline-block border border-primary-600 hover:border-primary-700 px-4 py-2 rounded-lg transition-colors">
Back to Home
</Link>
</div>
<Card>
<CardContent className="p-6">
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="bg-red-50 text-red-600 p-3 rounded-lg text-sm">
{error}
</div>
)}
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
/>
<Input
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
/>
<div className="flex items-center justify-between">
<label className="flex items-center">
<input type="checkbox" className="mr-2" />
<span className="text-sm text-gray-600">Remember me</span>
</label>
<Link href="/forgot-password" className="text-sm text-primary-600 hover:text-primary-700">
Forgot password?
</Link>
</div>
<Button type="submit" className="w-full" loading={loading} disabled={csrfLoading || loading}>
{csrfLoading ? 'Loading...' : 'Sign In'}
</Button>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">Or continue with</span>
</div>
</div>
<Button
type="button"
variant="outline"
className="w-full"
onClick={handleGoogleSignIn}
>
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
<path
fill="#4285F4"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="#34A853"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="#FBBC05"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="#EA4335"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
Sign in with Google
</Button>
</form>
<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
Don't have an account?{' '}
<Link href="/signup" className="text-primary-600 hover:text-primary-700 font-medium">
Sign up
</Link>
</p>
</div>
</CardContent>
</Card>
<p className="text-center text-sm text-gray-500 mt-6">
By signing in, you agree to our{' '}
<Link href="/privacy" className="text-primary-600 hover:text-primary-700">
Privacy Policy
</Link>
</p>
</div>
</div>
);
}

View File

@ -0,0 +1,11 @@
import type { Metadata } from 'next';
import LoginClient from './LoginClient';
export const metadata: Metadata = {
title: 'QR Master Smart QR Generator & Analytics',
description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics. Free advanced features, bulk generation, and custom branding available.',
};
export default function LoginPage() {
return <LoginClient />;
}

View File

@ -0,0 +1,208 @@
'use client';
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { useTranslation } from '@/hooks/useTranslation';
import { useCsrf } from '@/hooks/useCsrf';
export default function SignupClient() {
const router = useRouter();
const { t } = useTranslation();
const { fetchWithCsrf } = useCsrf();
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
if (password !== confirmPassword) {
setError('Passwords do not match');
setLoading(false);
return;
}
if (password.length < 8) {
setError('Password must be at least 8 characters');
setLoading(false);
return;
}
try {
const response = await fetchWithCsrf('/api/auth/signup', {
method: 'POST',
body: JSON.stringify({ name, email, password }),
});
const data = await response.json();
if (response.ok && data.success) {
// Store user in localStorage for client-side
localStorage.setItem('user', JSON.stringify(data.user));
// Track successful signup with PostHog
try {
const { identifyUser, trackEvent } = await import('@/components/PostHogProvider');
identifyUser(data.user.id, {
email: data.user.email,
name: data.user.name,
plan: data.user.plan || 'FREE',
signupMethod: 'email',
});
trackEvent('user_signup', {
method: 'email',
email: data.user.email,
});
} catch (error) {
console.error('PostHog tracking error:', error);
}
// Redirect to dashboard
router.push('/dashboard');
router.refresh();
} else {
setError(data.error || 'Failed to create account');
}
} catch (err) {
setError('An error occurred. Please try again.');
} finally {
setLoading(false);
}
};
const handleGoogleSignIn = () => {
// Redirect to Google OAuth API route
window.location.href = '/api/auth/google';
};
return (
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-white flex items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<Link href="/" className="inline-flex items-center space-x-2 mb-6">
<img src="/logo.svg" alt="QR Master" className="w-10 h-10" />
<span className="text-2xl font-bold text-gray-900">QR Master</span>
</Link>
<h1 className="text-3xl font-bold text-gray-900">Create Account</h1>
<p className="text-gray-600 mt-2">Start creating QR codes in seconds</p>
<Link href="/" className="text-sm text-primary-600 hover:text-primary-700 font-medium mt-2 inline-block border border-primary-600 hover:border-primary-700 px-4 py-2 rounded-lg transition-colors">
Back to Home
</Link>
</div>
<Card>
<CardContent className="p-6">
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="bg-red-50 text-red-600 p-3 rounded-lg text-sm">
{error}
</div>
)}
<Input
label="Full Name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="John Doe"
required
/>
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
/>
<Input
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
/>
<Input
label="Confirm Password"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="••••••••"
required
/>
<Button type="submit" className="w-full" loading={loading}>
Create Account
</Button>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">Or continue with</span>
</div>
</div>
<Button
type="button"
variant="outline"
className="w-full"
onClick={handleGoogleSignIn}
>
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
<path
fill="#4285F4"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="#34A853"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="#FBBC05"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="#EA4335"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
Sign up with Google
</Button>
</form>
<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
Already have an account?{' '}
<Link href="/login" className="text-primary-600 hover:text-primary-700 font-medium">
Sign in
</Link>
</p>
</div>
</CardContent>
</Card>
<p className="text-center text-sm text-gray-500 mt-6">
By signing up, you agree to our{' '}
<Link href="/privacy" className="text-primary-600 hover:text-primary-700">
Privacy Policy
</Link>
</p>
</div>
</div>
);
}

View File

@ -0,0 +1,14 @@
import { Metadata } from 'next';
import SignupClient from './SignupClient';
export const metadata: Metadata = {
title: 'Create Account | QR Master',
description: 'Start creating dynamic QR codes in seconds. Join thousands of businesses using QR Master.',
alternates: {
canonical: 'https://www.qrmaster.net/signup',
},
};
export default function SignupPage() {
return <SignupClient />;
}

View 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>
</>
);
}

View File

@ -18,7 +18,7 @@ function truncateAtWord(text: string, maxLength: number): string {
export async function generateMetadata(): Promise<Metadata> {
const title = truncateAtWord('QR Insights: Latest QR Strategies', 60);
const description = truncateAtWord(
'Expert guides on QR analytics, dynamic codes & smart marketing uses.',
'Expert guides on QR code strategies, marketing campaigns, tracking analytics, and best practices for small businesses and enterprises.',
160
);
@ -91,7 +91,7 @@ const blogPosts = [
date: 'October 18, 2025',
readTime: '12 Min',
category: 'Tracking & Analytics',
image: '/blog/1-hero.png',
image: '/blog/1-hero.webp',
},
{
slug: 'dynamic-vs-static-qr-codes',
@ -100,7 +100,7 @@ const blogPosts = [
date: 'October 17, 2025',
readTime: '10 Min',
category: 'QR Code Basics',
image: '/blog/2-hero.png',
image: '/blog/2-hero.webp',
},
{
slug: 'bulk-qr-code-generator-excel',
@ -109,7 +109,7 @@ const blogPosts = [
date: 'October 16, 2025',
readTime: '13 Min',
category: 'Bulk Generation',
image: '/blog/3-hero.png',
image: '/blog/bulk-qr-events-hero.png',
},
{
slug: 'qr-code-analytics',
@ -118,7 +118,7 @@ const blogPosts = [
date: 'October 16, 2025',
readTime: '15 Min',
category: 'Analytics',
image: '/blog/4-hero.png',
image: '/blog/qr-code-analytics-hero.webp',
},
];

View File

@ -8,8 +8,10 @@ import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
import { breadcrumbSchema } from '@/lib/schema';
export const metadata: Metadata = {
title: 'Bulk QR Code Generator - Create 1000s of QR Codes from Excel | QR Master',
description: 'Generate hundreds of QR codes at once from CSV or Excel files. Create URLs, vCards, locations, phone numbers, and text QR codes in bulk. Perfect for products, events, inventory management.',
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',
@ -19,14 +21,14 @@ export const metadata: Metadata = {
},
},
openGraph: {
title: 'Bulk QR Code Generator - Create 1000s of QR Codes from Excel',
description: 'Generate hundreds of QR codes at once from CSV or Excel files. Perfect for products, events, and inventory.',
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 of QR Codes from Excel',
description: 'Generate hundreds of QR codes at once from CSV or Excel files. Perfect for products, events, and inventory.',
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.',
},
};
@ -46,7 +48,7 @@ export default function BulkQRCodeGeneratorPage() {
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@example.com,+1234567890,Company Inc,CEO',
example: 'John Doe,VCARD,John,Doe,john[at]example.com,+1234567890,Company Inc,CEO',
},
{
type: 'GEO',
@ -333,7 +335,7 @@ export default function BulkQRCodeGeneratorPage() {
Start Bulk Generation
</Button>
</Link>
<Link href="/create">
<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>
@ -440,7 +442,7 @@ export default function BulkQRCodeGeneratorPage() {
<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@example.com,+1234567890,Company,CEO</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">

View File

@ -331,7 +331,7 @@ export default function CustomQRCodeGeneratorPage() {
</div>
<div className="flex flex-col sm:flex-row gap-4">
<Link href="/create">
<Link href="/signup">
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
Try Designer Now
</Button>
@ -452,7 +452,7 @@ export default function CustomQRCodeGeneratorPage() {
</div>
<h3 className="font-bold text-gray-900">{example.name}</h3>
<p className="text-sm text-gray-600">{example.description}</p>
<Link href="/create">
<Link href="/signup">
<Button variant="outline" size="sm" className="w-full mt-3">
Use This Style
</Button>
@ -634,7 +634,7 @@ export default function CustomQRCodeGeneratorPage() {
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="/create">
<Link href="/signup">
<Button
size="lg"
variant="secondary"

View File

@ -8,8 +8,10 @@ import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
import { breadcrumbSchema } from '@/lib/schema';
export const metadata: Metadata = {
title: 'Dynamic QR Code Generator - Edit QR Codes Anytime | QR Master',
description: 'Create dynamic QR codes that can be edited after printing. Change destination URL, track scans, and update content without reprinting. Free dynamic QR code generator.',
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',
@ -19,13 +21,13 @@ export const metadata: Metadata = {
},
},
openGraph: {
title: 'Dynamic QR Code Generator - Edit QR Codes Anytime | QR Master',
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 QR Codes Anytime | QR Master',
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.',
},
};
@ -180,7 +182,7 @@ export default function DynamicQRCodeGeneratorPage() {
position: 2,
name: 'Generate QR Code',
text: 'Enter your destination URL and customize the design with your branding',
url: 'https://www.qrmaster.net/create',
url: 'https://www.qrmaster.net/signup',
},
{
'@type': 'HowToStep',
@ -504,7 +506,7 @@ export default function DynamicQRCodeGeneratorPage() {
Get Started Free
</Button>
</Link>
<Link href="/create">
<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>

View File

@ -3,6 +3,7 @@ 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;
@ -129,9 +130,7 @@ export default function FAQPage() {
</h2>
<p className="text-lg text-gray-700 mb-6 leading-relaxed">
Our support team is here to help. Contact us at{' '}
<a href="mailto:support@qrmaster.net" className="text-blue-600 hover:text-blue-700 font-semibold">
support@qrmaster.net
</a>{' '}
<ObfuscatedMailto email="support@qrmaster.net" className="text-blue-600 hover:text-blue-700 font-semibold" />{' '}
or reach out through our live chat.
</p>
</div>

View File

@ -22,7 +22,9 @@ import {
} from 'lucide-react';
export const metadata: Metadata = {
title: 'QR Code Management Software Organize, Edit & Scale | QR Master',
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',

View File

@ -14,7 +14,7 @@ function truncateAtWord(text: string, maxLength: number): string {
export async function generateMetadata(): Promise<Metadata> {
const title = truncateAtWord('QR Master: Dynamic QR Generator', 60);
const description = truncateAtWord(
'Dynamic QR, branding, bulk generation & analytics for all campaigns.',
'Create dynamic QR codes, track scans, and scale campaigns with secure analytics. Free advanced features, bulk generation, and custom branding available.',
160
);
@ -27,6 +27,7 @@ export async function generateMetadata(): Promise<Metadata> {
languages: {
'x-default': 'https://www.qrmaster.net/',
en: 'https://www.qrmaster.net/',
de: 'https://www.qrmaster.net/qr-code-erstellen',
},
},
openGraph: {
@ -49,7 +50,7 @@ export default function HomePage() {
{/* Server-rendered SEO content for crawlers */}
<div className="sr-only" aria-hidden="false">
<h1>QR Master: Free Dynamic QR Code Generator with Tracking & Analytics</h1>
<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.

View File

@ -7,6 +7,7 @@ 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();
@ -260,7 +261,7 @@ export default function PricingPage() {
All plans include unlimited static QR codes and basic customization.
</p>
<p className="text-gray-600 mt-2">
Need help choosing? <a href="mailto:support@qrmaster.net" className="text-primary-600 hover:text-primary-700 underline">Contact our team</a>
Need help choosing? <ObfuscatedMailto email="support@qrmaster.net" className="text-primary-600 hover:text-primary-700 underline">Contact our team</ObfuscatedMailto>
</p>
</div>
</div>

View File

@ -33,8 +33,7 @@ export const metadata: Metadata = {
export default function PricingPage() {
return (
<>
{/* Server-rendered H1 for SEO */}
<h1 className="sr-only">QR Master Pricing Choose Your QR Code Plan</h1>
<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>

View File

@ -1,9 +1,10 @@
import React from 'react';
import Link from 'next/link';
import { ObfuscatedMailto } from '@/components/ui/ObfuscatedMailto';
export const metadata = {
title: 'Privacy Policy | QR Master',
description: 'Privacy Policy and data protection information for 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() {
@ -111,9 +112,7 @@ export default function PrivacyPage() {
<div className="bg-gray-50 p-6 rounded-lg">
<p className="text-gray-700 mb-2">
<strong>Email:</strong>{' '}
<a href="mailto:support@qrmaster.net" className="text-primary-600 hover:text-primary-700">
support@qrmaster.net
</a>
<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>

View File

@ -8,7 +8,9 @@ import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
import { breadcrumbSchema } from '@/lib/schema';
export const metadata: Metadata = {
title: 'QR Code Tracking & Analytics - Track Every Scan | QR Master',
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: {
@ -19,13 +21,13 @@ export const metadata: Metadata = {
},
},
openGraph: {
title: 'QR Code Tracking & Analytics - Track Every Scan | QR Master',
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 | QR Master',
title: 'QR Code Tracking & Analytics - Track Every Scan',
description: 'Track QR code scans with real-time analytics. Monitor location, device, time, and user behavior.',
},
};
@ -199,7 +201,7 @@ export default function QRCodeTrackingPage() {
Start Tracking Free
</Button>
</Link>
<Link href="/create">
<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>
@ -319,79 +321,79 @@ export default function QRCodeTrackingPage() {
</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>
<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' ? (
<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-primary-600 font-semibold">{row.qrMaster}</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</Card>
</div>
</section>
<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>
{/* 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>
</section>
</div>
</section>
</div>
</>
);

Some files were not shown because too many files have changed in this diff Show More