qrmaster.net

This commit is contained in:
Timo Knuth
2025-12-09 22:22:36 +01:00
parent 424c61a176
commit 8c5e2fa58e
37 changed files with 549 additions and 915 deletions

View File

@@ -15,20 +15,17 @@ interface QRCodeCardProps {
contentType: string;
content?: any;
slug: string;
status: 'ACTIVE' | 'PAUSED';
createdAt: string;
scans?: number;
style?: any;
};
onEdit: (id: string) => void;
onPause: (id: string) => void;
onDelete: (id: string) => void;
}
export const QRCodeCard: React.FC<QRCodeCardProps> = ({
qr,
onEdit,
onPause,
onDelete,
}) => {
// For dynamic QR codes, use the redirect URL for tracking
@@ -172,9 +169,6 @@ END:VCARD`;
<Badge variant={qr.type === 'DYNAMIC' ? 'info' : 'default'}>
{qr.type}
</Badge>
<Badge variant={qr.status === 'ACTIVE' ? 'success' : 'warning'}>
{qr.status}
</Badge>
</div>
</div>
@@ -193,11 +187,6 @@ END:VCARD`;
{qr.type === 'DYNAMIC' && (
<DropdownItem onClick={() => onEdit(qr.id)}>Edit</DropdownItem>
)}
{qr.type === 'DYNAMIC' && (
<DropdownItem onClick={() => onPause(qr.id)}>
{qr.status === 'ACTIVE' ? 'Pause' : 'Resume'}
</DropdownItem>
)}
<DropdownItem onClick={() => onDelete(qr.id)} className="text-red-600">
Delete
</DropdownItem>

View File

@@ -20,7 +20,22 @@ export const Hero: React.FC<HeroProps> = ({ t }) => {
return (
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 pt-12 pb-20">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
{/* Animated Background Orbs */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
{/* Orb 1 - Blue (top-left) */}
<div className="absolute -top-24 -left-24 w-96 h-96 bg-blue-400/30 rounded-full blur-3xl animate-blob" />
{/* Orb 2 - Purple (top-right) */}
<div className="absolute -top-12 -right-12 w-96 h-96 bg-purple-400/30 rounded-full blur-3xl animate-blob animation-delay-2000" />
{/* Orb 3 - Pink (bottom-left) */}
<div className="absolute -bottom-24 -left-12 w-96 h-96 bg-pink-400/20 rounded-full blur-3xl animate-blob animation-delay-4000" />
{/* Orb 4 - Cyan (center-right) */}
<div className="absolute top-1/2 -right-24 w-80 h-80 bg-cyan-400/20 rounded-full blur-3xl animate-blob animation-delay-6000" />
</div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl relative z-10">
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Left Content */}
<div className="space-y-8">
@@ -83,6 +98,9 @@ export const Hero: React.FC<HeroProps> = ({ t }) => {
</div>
</div>
</div>
{/* Smooth Gradient Fade Transition */}
<div className="absolute bottom-0 left-0 w-full h-32 bg-gradient-to-b from-transparent to-gray-50 pointer-events-none" />
</section>
);
};

View File

@@ -1,16 +1,19 @@
'use client';
import React from 'react';
import React, { useState } from 'react';
import Link from 'next/link';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { BillingToggle } from '@/components/ui/BillingToggle';
interface PricingProps {
t: any; // i18n translation function
}
export const Pricing: React.FC<PricingProps> = ({ t }) => {
const [billingPeriod, setBillingPeriod] = useState<'month' | 'year'>('month');
const plans = [
{
key: 'free',
@@ -38,6 +41,10 @@ export const Pricing: React.FC<PricingProps> = ({ t }) => {
</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-5xl mx-auto">
{plans.map((plan) => (
<Card
@@ -56,13 +63,30 @@ export const Pricing: React.FC<PricingProps> = ({ t }) => {
<CardTitle className="text-2xl mb-4">
{t.pricing[plan.key].title}
</CardTitle>
<div className="flex items-baseline justify-center">
<span className="text-4xl font-bold">
{t.pricing[plan.key].price}
</span>
<span className="text-gray-600 ml-2">
{t.pricing[plan.key].period}
</span>
<div className="flex flex-col items-center">
<div className="flex items-baseline justify-center">
<span className="text-4xl font-bold">
{plan.key === 'free'
? t.pricing[plan.key].price
: billingPeriod === 'month'
? t.pricing[plan.key].price
: plan.key === 'pro'
? '€90'
: '€290'}
</span>
<span className="text-gray-600 ml-2">
{plan.key === 'free'
? t.pricing[plan.key].period
: billingPeriod === 'month'
? t.pricing[plan.key].period
: 'per year'}
</span>
</div>
{billingPeriod === 'year' && plan.key !== 'free' && (
<Badge variant="success" className="mt-2">
Save 16%
</Badge>
)}
</div>
</CardHeader>

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { cn } from '@/lib/utils';
interface BillingToggleProps {
value: 'month' | 'year';
onChange: (value: 'month' | 'year') => void;
}
export const BillingToggle: React.FC<BillingToggleProps> = ({ value, onChange }) => {
return (
<div className="inline-flex items-center rounded-lg border border-gray-300 bg-gray-50 p-1">
<button
type="button"
onClick={() => onChange('month')}
className={cn(
'px-6 py-2 text-sm font-medium rounded-md transition-all duration-200',
value === 'month'
? 'bg-primary-600 text-white shadow-sm'
: 'bg-transparent text-gray-700 hover:bg-gray-100'
)}
>
Monthly
</button>
<button
type="button"
onClick={() => onChange('year')}
className={cn(
'px-6 py-2 text-sm font-medium rounded-md transition-all duration-200',
value === 'year'
? 'bg-primary-600 text-white shadow-sm'
: 'bg-transparent text-gray-700 hover:bg-gray-100'
)}
>
Yearly
</button>
</div>
);
};