Paar fehler
This commit is contained in:
parent
e44dc1c6bb
commit
59131a54f0
|
|
@ -0,0 +1,24 @@
|
|||
# Neue Features und Updates für QR Master (DE)
|
||||
|
||||
## Übersicht
|
||||
Wir haben unser Angebot aktualisiert, um noch mehr Wert für unsere Nutzer zu bieten. Hier sind die neuesten Ergänzungen und Verbesserungen:
|
||||
|
||||
### 1. Erweiterte QR Code Typen
|
||||
Wir haben spezifische QR Code Lösungen für verschiedene Anwendungsfälle hinzugefügt:
|
||||
|
||||
- **Feedback QR Code**: Sammeln Sie direkt Kundenfeedback. Scans führen zu einem anpassbaren Feedback-Formular.
|
||||
- **PDF QR Code**: Teilen Sie Dokumente, Speisekarten oder Broschüren als PDF. Ideal für Restaurants und Unternehmen.
|
||||
- **Coupon QR Code**: Bieten Sie Rabatte und Gutscheine via QR Code an. Perfekt für Marketingkampagnen im Einzelhandel.
|
||||
- **App Store QR Code**: Ein intelligenter QR Code, der Nutzer basierend auf ihrem Gerät (iOS oder Android) automatisch zum richtigen App Store leitet.
|
||||
|
||||
### 2. Mehr Dynamik im Kostenlosen Plan
|
||||
Um den Einstieg zu erleichtern, haben wir das Limit für den kostenlosen Plan erhöht:
|
||||
- **Neu**: 8 Dynamische QR Codes kostenlos (statt bisher 3).
|
||||
- **Vorteil**: Mehr Flexibilität für kleine Unternehmen und Startups, um verschiedene Kampagnen gleichzeitig zu testen.
|
||||
|
||||
### 3. SEO Optimierung
|
||||
Alle neuen QR Code Typen sind jetzt vollständig in unsere Plattform integriert und für Suchmaschinen optimiert, damit Nutzer die richtige Lösung für ihr Problem finden.
|
||||
|
||||
---
|
||||
|
||||
*Erstellt am 22.01.2026*
|
||||
|
|
@ -17,22 +17,16 @@ export const metadata: Metadata = {
|
|||
},
|
||||
};
|
||||
|
||||
export default function RootAppLayout({
|
||||
export default function AppGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="font-sans">
|
||||
<Providers>
|
||||
<Suspense fallback={null}>
|
||||
<AppLayout>
|
||||
{children}
|
||||
</AppLayout>
|
||||
</Suspense>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
<Suspense fallback={null}>
|
||||
<AppLayout>
|
||||
{children}
|
||||
</AppLayout>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,30 +47,24 @@ export const metadata: Metadata = {
|
|||
},
|
||||
};
|
||||
|
||||
export default function RootMarketingLayout({
|
||||
export default function MarketingGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }}
|
||||
/>
|
||||
</head>
|
||||
<body className="font-sans">
|
||||
<Providers>
|
||||
<MarketingLayout>
|
||||
{children}
|
||||
</MarketingLayout>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }}
|
||||
/>
|
||||
<MarketingLayout>
|
||||
{children}
|
||||
</MarketingLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ 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 = {
|
||||
|
|
@ -70,7 +71,19 @@ export default function BarcodeGeneratorClient() {
|
|||
} else if ((format === 'ITF14' || format === 'MSI') && !/^\d+$/.test(value)) {
|
||||
setError('This format only supports numbers.');
|
||||
}
|
||||
}, [value, format]);
|
||||
|
||||
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;
|
||||
|
|
@ -96,6 +109,11 @@ export default function BarcodeGeneratorClient() {
|
|||
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');
|
||||
|
|
@ -121,6 +139,10 @@ export default function BarcodeGeneratorClient() {
|
|||
}),
|
||||
]);
|
||||
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');
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export const metadata: Metadata = {
|
|||
default: 'QR Master – QR Code Generator & Analytics',
|
||||
template: '%s | QR Master',
|
||||
},
|
||||
description: 'Erstellen Sie dynamische QR Codes, verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
|
||||
description: 'Erstellen Sie dynamische QR Codes für Feedback, PDF, Coupons und App Stores. Verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
|
||||
metadataBase: new URL('https://www.qrmaster.net'),
|
||||
icons: {
|
||||
icon: [
|
||||
|
|
@ -22,7 +22,7 @@ export const metadata: Metadata = {
|
|||
type: 'website',
|
||||
siteName: 'QR Master',
|
||||
title: 'QR Master – QR Code Generator & Analytics',
|
||||
description: 'Erstellen Sie dynamische QR Codes, verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
|
||||
description: 'Erstellen Sie dynamische QR Codes für Feedback, PDF, Coupons und App Stores. Verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
|
||||
url: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
locale: 'de_DE',
|
||||
images: [
|
||||
|
|
@ -42,30 +42,24 @@ export const metadata: Metadata = {
|
|||
robots: { index: true, follow: true },
|
||||
};
|
||||
|
||||
export default function RootMarketingDeLayout({
|
||||
export default function MarketingDeGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="de">
|
||||
<head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }}
|
||||
/>
|
||||
</head>
|
||||
<body className="font-sans">
|
||||
<Providers>
|
||||
<MarketingDeLayout>
|
||||
{children}
|
||||
</MarketingDeLayout>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }}
|
||||
/>
|
||||
<MarketingDeLayout>
|
||||
{children}
|
||||
</MarketingDeLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ function truncateAtWord(text: string, maxLength: number): string {
|
|||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const title = 'QR Code Erstellen – Kostenlos | QR Master';
|
||||
const description = 'Erstellen Sie QR Codes kostenlos in Sekunden. Dynamische QR-Codes mit Tracking, Branding und Massen-Erstellung. Für immer kostenlos.';
|
||||
const description = 'Erstellen Sie QR Codes kostenlos in Sekunden. Dynamische QR-Codes für Feedback, PDF, Coupons & App Stores. Mit Tracking, Branding und Massen-Erstellung. Für immer kostenlos.';
|
||||
|
||||
return {
|
||||
title: {
|
||||
|
|
@ -37,7 +37,11 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||
'qr codes erstellen',
|
||||
'qr code erstellen kostenlos',
|
||||
'dynamischer qr code',
|
||||
'qr code mit logo'
|
||||
'qr code mit logo',
|
||||
'feedback qr code',
|
||||
'pdf qr code',
|
||||
'coupon qr code',
|
||||
'app store qr code'
|
||||
],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
|
|
@ -84,7 +88,7 @@ export default function QRCodeErstellenPage() {
|
|||
<div className="sr-only" aria-hidden="false">
|
||||
<p>
|
||||
Erstellen Sie professionelle QR Codes für Ihr Unternehmen mit QR Master. Unser dynamischer QR Code Generator
|
||||
ermöglicht es Ihnen, trackbare QR Codes zu erstellen, Ziel-URLs jederzeit zu ändern und detaillierte Statistiken einzusehen.
|
||||
ermöglicht es Ihnen, trackbare QR Codes für Feedback, PDF-Dateien, Coupons und App Stores zu erstellen, Ziel-URLs jederzeit zu ändern und detaillierte Statistiken einzusehen.
|
||||
Perfekt für Restaurants, Einzelhandel, Events und Marketing-Kampagnen.
|
||||
</p>
|
||||
<p>
|
||||
|
|
@ -93,7 +97,7 @@ export default function QRCodeErstellenPage() {
|
|||
vCard QR Codes für digitale Visitenkarten und QR Codes für Restaurant-Speisekarten.
|
||||
</p>
|
||||
<p>
|
||||
Starten Sie kostenlos mit 3 dynamischen QR Codes und unbegrenzten statischen Codes. Upgrade auf Pro für 50 Codes
|
||||
Starten Sie kostenlos mit 8 dynamischen QR Codes und unbegrenzten statischen Codes. Upgrade auf Pro für 50 Codes
|
||||
mit erweiterten Statistiken, oder Business für 500 Codes mit Massen-Erstellung und Prioritäts-Support.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ export default function CouponPage() {
|
|||
{/* Redeem Button */}
|
||||
{coupon.redeemUrl && !isExpired && (
|
||||
<a
|
||||
href={coupon.redeemUrl}
|
||||
href={ensureAbsoluteUrl(coupon.redeemUrl)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block w-full py-4 rounded-xl font-semibold text-center bg-gradient-to-r from-[#076653] to-[#0C342C] text-white hover:from-[#087861] hover:to-[#0E4036] transition-all shadow-lg shadow-emerald-200"
|
||||
|
|
@ -165,3 +165,11 @@ export default function CouponPage() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ensureAbsoluteUrl(url: string | undefined): string | undefined {
|
||||
if (!url) return undefined;
|
||||
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) {
|
||||
return url;
|
||||
}
|
||||
return `https://${url}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ export default function FeedbackPage() {
|
|||
|
||||
if (rating >= 4 && feedback?.googleReviewUrl) {
|
||||
setTimeout(() => {
|
||||
window.location.href = feedback.googleReviewUrl!;
|
||||
const url = ensureAbsoluteUrl(feedback.googleReviewUrl);
|
||||
if (url) window.location.href = url;
|
||||
}, 2000);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -119,9 +120,9 @@ export default function FeedbackPage() {
|
|||
{/* Card */}
|
||||
<div className="bg-white rounded-3xl shadow-xl overflow-hidden">
|
||||
{/* Colored Header */}
|
||||
<div className="bg-gradient-to-r from-[#4C5F4E] via-[#C6C0B3] to-[#FAF8F5] p-8 text-center">
|
||||
<div className="w-14 h-14 bg-white/20 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<Star className="w-7 h-7 text-white" />
|
||||
<div className="bg-gradient-to-r from-[#FAF8F5] via-[#C6C0B3] to-[#4C5F4E] p-8 text-center">
|
||||
<div className="w-14 h-14 bg-[#4C5F4E]/10 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<Star className="w-7 h-7 text-[#4C5F4E]" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold mb-1 text-gray-900">How was your experience?</h1>
|
||||
<p className="text-gray-700">{feedback.businessName}</p>
|
||||
|
|
@ -193,3 +194,11 @@ export default function FeedbackPage() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ensureAbsoluteUrl(url: string | undefined): string | undefined {
|
||||
if (!url) return undefined;
|
||||
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) {
|
||||
return url;
|
||||
}
|
||||
return `https://${url}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import type { Metadata } from 'next';
|
||||
import { Suspense } from 'react';
|
||||
import '@/styles/globals.css';
|
||||
import { ToastContainer } from '@/components/ui/Toast';
|
||||
import AuthProvider from '@/components/SessionProvider';
|
||||
import { PostHogProvider } from '@/components/PostHogProvider';
|
||||
import CookieBanner from '@/components/CookieBanner';
|
||||
import { Providers } from '@/components/Providers';
|
||||
|
||||
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
|
||||
|
||||
|
|
@ -58,13 +55,9 @@ export default function RootLayout({
|
|||
<html lang="en">
|
||||
<body className="font-sans">
|
||||
<Suspense fallback={null}>
|
||||
<PostHogProvider>
|
||||
<AuthProvider>
|
||||
{children}
|
||||
</AuthProvider>
|
||||
<CookieBanner />
|
||||
<ToastContainer />
|
||||
</PostHogProvider>
|
||||
<Providers>
|
||||
{children}
|
||||
</Providers>
|
||||
</Suspense>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export async function GET(
|
|||
|
||||
switch (qrCode.contentType) {
|
||||
case 'URL':
|
||||
destination = content.url || 'https://example.com';
|
||||
destination = ensureAbsoluteUrl(content.url);
|
||||
break;
|
||||
case 'PHONE':
|
||||
destination = `tel:${content.phone}`;
|
||||
|
|
@ -61,7 +61,7 @@ export async function GET(
|
|||
break;
|
||||
case 'PDF':
|
||||
// Direct link to file
|
||||
destination = content.fileUrl || 'https://example.com/file.pdf';
|
||||
destination = ensureAbsoluteUrl(content.fileUrl);
|
||||
break;
|
||||
case 'APP':
|
||||
// Smart device detection for app stores
|
||||
|
|
@ -70,11 +70,11 @@ export async function GET(
|
|||
const isAndroid = /android/i.test(userAgent);
|
||||
|
||||
if (isIOS && content.iosUrl) {
|
||||
destination = content.iosUrl;
|
||||
destination = ensureAbsoluteUrl(content.iosUrl);
|
||||
} else if (isAndroid && content.androidUrl) {
|
||||
destination = content.androidUrl;
|
||||
destination = ensureAbsoluteUrl(content.androidUrl);
|
||||
} else {
|
||||
destination = content.fallbackUrl || content.iosUrl || content.androidUrl || 'https://example.com';
|
||||
destination = ensureAbsoluteUrl(content.fallbackUrl || content.iosUrl || content.androidUrl);
|
||||
}
|
||||
break;
|
||||
case 'COUPON':
|
||||
|
|
@ -234,7 +234,16 @@ async function trackScan(qrId: string, request: NextRequest) {
|
|||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error tracking scan:', error);
|
||||
// Don't throw - this is fire and forget
|
||||
}
|
||||
}
|
||||
|
||||
function ensureAbsoluteUrl(url: string): string {
|
||||
if (!url) return 'https://example.com';
|
||||
// Check if it already has a protocol (http://, https://, myapp://, mailto:, etc.)
|
||||
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) {
|
||||
return url;
|
||||
}
|
||||
// Default to https for web URLs
|
||||
return `https://${url}`;
|
||||
}
|
||||
|
|
@ -4,17 +4,36 @@ import { useEffect, useState, useRef } from 'react';
|
|||
import { usePathname, useSearchParams } from 'next/navigation';
|
||||
import posthog from 'posthog-js';
|
||||
|
||||
export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export function PostHogPageView() {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
const initializationAttempted = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const cookieConsent = localStorage.getItem('cookieConsent');
|
||||
if (cookieConsent === 'accepted' && pathname && (posthog as any)._loaded) {
|
||||
let url = window.origin + pathname;
|
||||
if (searchParams && searchParams.toString()) {
|
||||
url = url + `?${searchParams.toString()}`;
|
||||
}
|
||||
|
||||
posthog.capture('$pageview', {
|
||||
$current_url: url,
|
||||
});
|
||||
}
|
||||
}, [pathname, searchParams]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
||||
const [initializationAttempted, setInitializationAttempted] = useState(false);
|
||||
|
||||
// Initialize PostHog once
|
||||
useEffect(() => {
|
||||
// Prevent double initialization in React Strict Mode
|
||||
if (initializationAttempted.current) return;
|
||||
initializationAttempted.current = true;
|
||||
if (initializationAttempted) return;
|
||||
setInitializationAttempted(true);
|
||||
|
||||
const cookieConsent = localStorage.getItem('cookieConsent');
|
||||
|
||||
|
|
@ -27,50 +46,32 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Check if already initialized (using _loaded property)
|
||||
if (!(posthog as any)._loaded) {
|
||||
posthog.init(apiKey, {
|
||||
api_host: apiHost || 'https://us.i.posthog.com',
|
||||
person_profiles: 'identified_only',
|
||||
capture_pageview: false, // Manual pageview tracking
|
||||
capture_pageview: false,
|
||||
capture_pageleave: true,
|
||||
autocapture: true,
|
||||
respect_dnt: true,
|
||||
opt_out_capturing_by_default: false,
|
||||
});
|
||||
|
||||
// Enable debug mode in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
posthog.debug();
|
||||
}
|
||||
|
||||
// Set initialized immediately after init
|
||||
setIsInitialized(true);
|
||||
} else {
|
||||
setIsInitialized(true); // Already loaded
|
||||
}
|
||||
}
|
||||
}, [initializationAttempted]);
|
||||
|
||||
// NO cleanup function - PostHog should persist across page navigation
|
||||
}, []);
|
||||
|
||||
// Track page views ONLY after PostHog is initialized
|
||||
useEffect(() => {
|
||||
const cookieConsent = localStorage.getItem('cookieConsent');
|
||||
|
||||
if (cookieConsent === 'accepted' && pathname && isInitialized) {
|
||||
let url = window.origin + pathname;
|
||||
if (searchParams && searchParams.toString()) {
|
||||
url = url + `?${searchParams.toString()}`;
|
||||
}
|
||||
|
||||
posthog.capture('$pageview', {
|
||||
$current_url: url,
|
||||
});
|
||||
}
|
||||
}, [pathname, searchParams, isInitialized]); // Added isInitialized dependency
|
||||
|
||||
return <>{children}</>;
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={null}>
|
||||
<PostHogPageView />
|
||||
</Suspense>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,15 +3,12 @@
|
|||
import { Suspense } from 'react';
|
||||
import { ToastContainer } from '@/components/ui/Toast';
|
||||
import AuthProvider from '@/components/SessionProvider';
|
||||
import { PostHogProvider, PostHogPageView } from '@/components/PostHogProvider';
|
||||
import { PostHogProvider } from '@/components/PostHogProvider';
|
||||
import CookieBanner from '@/components/CookieBanner';
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<PostHogProvider>
|
||||
<Suspense fallback={null}>
|
||||
<PostHogPageView />
|
||||
</Suspense>
|
||||
<AuthProvider>
|
||||
{children}
|
||||
</AuthProvider>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { Input } from '@/components/ui/Input';
|
|||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { calculateContrast } from '@/lib/utils';
|
||||
import { trackEvent } from '@/components/PostHogProvider';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
interface InstantGeneratorProps {
|
||||
t: any; // i18n translation function
|
||||
|
|
@ -20,6 +22,18 @@ export const InstantGenerator: React.FC<InstantGeneratorProps> = ({ t }) => {
|
|||
const [cornerStyle, setCornerStyle] = useState('square');
|
||||
const [size, setSize] = useState(200);
|
||||
|
||||
useEffect(() => {
|
||||
if (url) {
|
||||
trackEvent('instant_qr_generated', {
|
||||
url_length: url.length,
|
||||
foreground: foregroundColor,
|
||||
background: backgroundColor,
|
||||
corner_style: cornerStyle,
|
||||
size: size
|
||||
});
|
||||
}
|
||||
}, [url, foregroundColor, backgroundColor, cornerStyle, size]);
|
||||
|
||||
const contrast = calculateContrast(foregroundColor, backgroundColor);
|
||||
const hasGoodContrast = contrast >= 4.5;
|
||||
|
||||
|
|
@ -38,6 +52,7 @@ export const InstantGenerator: React.FC<InstantGeneratorProps> = ({ t }) => {
|
|||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
trackEvent('instant_qr_downloaded', { format: 'svg' });
|
||||
} else {
|
||||
// Convert SVG to PNG using Canvas
|
||||
const canvas = document.createElement('canvas');
|
||||
|
|
@ -65,6 +80,7 @@ export const InstantGenerator: React.FC<InstantGeneratorProps> = ({ t }) => {
|
|||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
trackEvent('instant_qr_downloaded', { format: 'png' });
|
||||
}
|
||||
});
|
||||
URL.revokeObjectURL(url);
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@
|
|||
"price": "€0",
|
||||
"period": "für immer",
|
||||
"features": [
|
||||
"3 dynamische QR-Codes",
|
||||
"8 dynamische QR-Codes",
|
||||
"Unbegrenzte statische QR-Codes",
|
||||
"Basis-Scan-Tracking",
|
||||
"Standard QR-Design-Vorlagen"
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@
|
|||
"price": "€0",
|
||||
"period": "forever",
|
||||
"features": [
|
||||
"3 dynamic QR codes",
|
||||
"8 dynamic QR codes",
|
||||
"Unlimited static QR codes",
|
||||
"Basic scan tracking",
|
||||
"Standard QR design templates",
|
||||
|
|
|
|||
Loading…
Reference in New Issue