MVP ready to test

This commit is contained in:
Timo Knuth
2025-10-28 17:20:37 +01:00
parent 91b78cb284
commit 2f0208ebf9
48 changed files with 6258 additions and 110 deletions

View File

@@ -11,6 +11,7 @@ import { Select } from '@/components/ui/Select';
import { QRCodeSVG } from 'qrcode.react';
import { showToast } from '@/components/ui/Toast';
import { useTranslation } from '@/hooks/useTranslation';
import { useCsrf } from '@/hooks/useCsrf';
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
@@ -27,6 +28,7 @@ interface GeneratedQR {
export default function BulkCreationPage() {
const { t } = useTranslation();
const { fetchWithCsrf } = useCsrf();
const [step, setStep] = useState<'upload' | 'preview' | 'complete'>('upload');
const [data, setData] = useState<BulkQRData[]>([]);
const [mapping, setMapping] = useState<Record<string, string>>({});
@@ -200,11 +202,8 @@ export default function BulkCreationPage() {
// Save each QR code to the database
const savePromises = qrCodesToSave.map((qr) =>
fetch('/api/qrs', {
fetchWithCsrf('/api/qrs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(qr),
})
);

View File

@@ -11,11 +11,13 @@ import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { calculateContrast } from '@/lib/utils';
import { useTranslation } from '@/hooks/useTranslation';
import { useCsrf } from '@/hooks/useCsrf';
import { showToast } from '@/components/ui/Toast';
export default function CreatePage() {
const router = useRouter();
const { t } = useTranslation();
const { fetchWithCsrf } = useCsrf();
const [loading, setLoading] = useState(false);
const [userPlan, setUserPlan] = useState<string>('FREE');
@@ -168,9 +170,8 @@ export default function CreatePage() {
console.log('SENDING QR DATA:', qrData);
const response = await fetch('/api/qrs', {
const response = await fetchWithCsrf('/api/qrs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(qrData),
});

View File

@@ -108,22 +108,28 @@ export default function DashboardPage() {
const blogPosts = [
{
title: 'QR-Codes im Restaurant: Die digitale Revolution der Speisekarte',
excerpt: 'Erfahren Sie, wie QR-Codes die Gastronomie revolutionieren...',
readTime: '5 Min',
slug: 'qr-codes-im-restaurant',
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.',
readTime: '12 Min',
slug: 'qr-code-tracking-guide',
},
{
title: 'Dynamische vs. Statische QR-Codes: Was ist der Unterschied?',
excerpt: 'Ein umfassender Vergleich zwischen dynamischen und statischen QR-Codes...',
readTime: '3 Min',
slug: 'dynamische-vs-statische-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.',
readTime: '10 Min',
slug: 'dynamic-vs-static-qr-codes',
},
{
title: 'QR-Code Marketing-Strategien für 2025',
excerpt: 'Die besten Marketing-Strategien mit QR-Codes für Ihr Unternehmen...',
readTime: '7 Min',
slug: 'qr-code-marketing-strategien',
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.',
readTime: '13 Min',
slug: 'bulk-qr-code-generator-excel',
},
{
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.',
readTime: '15 Min',
slug: 'qr-code-analytics-guide',
},
];
@@ -398,7 +404,7 @@ export default function DashboardPage() {
{/* Blog & Resources */}
<div>
<h2 className="text-xl font-semibold text-gray-900 mb-6">{t('dashboard.blog_resources')}</h2>
<div className="grid md:grid-cols-3 gap-6">
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
{blogPosts.map((post) => (
<Card key={post.slug} hover>
<CardHeader>
@@ -426,34 +432,34 @@ export default function DashboardPage() {
<DialogContent>
<DialogHeader>
<DialogTitle className="text-2xl text-center">
Upgrade erfolgreich!
Upgrade Successful!
</DialogTitle>
<DialogDescription className="text-center text-base pt-4">
Willkommen im <strong>{upgradedPlan} Plan</strong>! Ihr Konto wurde erfolgreich aktualisiert.
Welcome to the <strong>{upgradedPlan} Plan</strong>! Your account has been successfully upgraded.
</DialogDescription>
</DialogHeader>
<div className="py-6 space-y-4">
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 p-6 rounded-lg border border-blue-200">
<h3 className="font-semibold text-gray-900 mb-3">Ihre neuen Features:</h3>
<h3 className="font-semibold text-gray-900 mb-3">Your New Features:</h3>
<ul className="space-y-2 text-sm text-gray-700">
{upgradedPlan === 'PRO' && (
<>
<li className="flex items-start">
<span className="text-green-600 mr-2"></span>
<span>50 dynamische QR-Codes</span>
<span>50 Dynamic QR Codes</span>
</li>
<li className="flex items-start">
<span className="text-green-600 mr-2"></span>
<span>Branding (Farben anpassen)</span>
<span>Custom Branding (Colors & Logo)</span>
</li>
<li className="flex items-start">
<span className="text-green-600 mr-2"></span>
<span>Detaillierte Analytics (Devices, Locations, Time-Series)</span>
<span>Detailed Analytics (Devices, Locations, Time-Series)</span>
</li>
<li className="flex items-start">
<span className="text-green-600 mr-2"></span>
<span>CSV-Export</span>
<span>CSV Export</span>
</li>
<li className="flex items-start">
<span className="text-green-600 mr-2"></span>
@@ -465,19 +471,19 @@ export default function DashboardPage() {
<>
<li className="flex items-start">
<span className="text-green-600 mr-2"></span>
<span>500 dynamische QR-Codes</span>
<span>500 Dynamic QR Codes</span>
</li>
<li className="flex items-start">
<span className="text-green-600 mr-2"></span>
<span>Alles von Pro</span>
<span>Everything from Pro</span>
</li>
<li className="flex items-start">
<span className="text-green-600 mr-2"></span>
<span>Bulk QR-Generierung (bis 1,000)</span>
<span>Bulk QR Generation (up to 1,000)</span>
</li>
<li className="flex items-start">
<span className="text-green-600 mr-2"></span>
<span>Prioritäts-Support</span>
<span>Priority Support</span>
</li>
</>
)}
@@ -494,7 +500,7 @@ export default function DashboardPage() {
}}
className="w-full"
>
Ersten QR-Code erstellen
Create First QR Code
</Button>
</DialogFooter>
</DialogContent>

View File

@@ -4,12 +4,14 @@ 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 { useCsrf } from '@/hooks/useCsrf';
import { showToast } from '@/components/ui/Toast';
import ChangePasswordModal from '@/components/settings/ChangePasswordModal';
type TabType = 'profile' | 'subscription';
export default function SettingsPage() {
const { fetchWithCsrf } = useCsrf();
const [activeTab, setActiveTab] = useState<TabType>('profile');
const [loading, setLoading] = useState(false);
const [showPasswordModal, setShowPasswordModal] = useState(false);
@@ -64,9 +66,8 @@ export default function SettingsPage() {
try {
// Save to backend API
const response = await fetch('/api/user/profile', {
const response = await fetchWithCsrf('/api/user/profile', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
});
@@ -97,9 +98,8 @@ export default function SettingsPage() {
setLoading(true);
try {
const response = await fetch('/api/stripe/portal', {
const response = await fetchWithCsrf('/api/stripe/portal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
const data = await response.json();
@@ -134,9 +134,8 @@ export default function SettingsPage() {
setLoading(true);
try {
const response = await fetch('/api/user/delete', {
const response = await fetchWithCsrf('/api/user/delete', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
});
const data = await response.json();