feat: implement pricing strategy, subscription tiers, and core infrastructure for QR code management

This commit is contained in:
Timo Knuth
2026-04-14 19:34:47 +02:00
parent 82101ca08f
commit 6b73ac5c50
16 changed files with 1718 additions and 1344 deletions

View File

@@ -15,8 +15,9 @@ import { useTranslation } from '@/hooks/useTranslation';
import { useCsrf } from '@/hooks/useCsrf';
import { showToast } from '@/components/ui/Toast';
import {
Globe, User, MapPin, Phone, FileText, Smartphone, Ticket, Star, HelpCircle, Upload
Globe, User, MapPin, Phone, FileText, Smartphone, Ticket, Star, HelpCircle, Upload, Barcode as BarcodeIcon
} from 'lucide-react';
import Barcode from 'react-barcode';
// Tooltip component for form field help
const Tooltip = ({ text }: { text: string }) => (
@@ -140,6 +141,7 @@ export default function CreatePage() {
{ value: 'APP', label: 'App Download', icon: Smartphone },
{ value: 'COUPON', label: 'Coupon / Discount', icon: Ticket },
{ value: 'FEEDBACK', label: 'Feedback / Review', icon: Star },
{ value: 'BARCODE', label: 'Barcode', icon: BarcodeIcon },
];
// Get QR content based on content type
@@ -170,6 +172,8 @@ export default function CreatePage() {
return `Coupon: ${content.code || 'SAVE20'} - ${content.discount || '20% OFF'}`;
case 'FEEDBACK':
return content.feedbackUrl || 'https://example.com/feedback';
case 'BARCODE':
return content.value || '123456789';
default:
return 'https://example.com';
}
@@ -642,6 +646,68 @@ export default function CreatePage() {
/>
</>
);
case 'BARCODE':
return (
<>
{isDynamic ? (
<>
<div className="rounded-lg bg-blue-50 border border-blue-200 p-3 text-sm text-blue-800">
<strong>How dynamic barcodes work:</strong> The barcode encodes a short redirect URL
(e.g. <span className="font-mono text-xs">qrmaster.net/r/</span>). When scanned with a
smartphone camera, it opens the browser and redirects to your destination which you
can update anytime. Works with smartphone cameras, not POS laser scanners.
</div>
<Input
label="Destination URL"
value={content.url || ''}
onChange={(e) => setContent({ ...content, url: e.target.value })}
placeholder="https://example.com"
required
/>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Barcode Format</label>
<select
value={['CODE128', 'CODE39'].includes(content.format) ? content.format : 'CODE128'}
onChange={(e) => setContent({ ...content, format: e.target.value })}
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<option value="CODE128">CODE128 General purpose (recommended)</option>
<option value="CODE39">CODE39 Industrial / logistics</option>
</select>
<p className="text-xs text-gray-500 mt-1">
Only URL-capable formats available. EAN-13, UPC, and ITF-14 encode numbers only and cannot embed a redirect URL.
</p>
</div>
</>
) : (
<>
<Input
label="Barcode Value"
value={content.value || ''}
onChange={(e) => setContent({ ...content, value: e.target.value })}
placeholder="123456789012"
required
/>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Barcode Format</label>
<select
value={content.format || 'CODE128'}
onChange={(e) => setContent({ ...content, format: e.target.value })}
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<option value="CODE128">CODE128 General purpose (recommended)</option>
<option value="EAN13">EAN-13 Retail products (international)</option>
<option value="UPC">UPC Retail products (USA/Canada)</option>
<option value="CODE39">CODE39 Industrial / logistics</option>
<option value="ITF14">ITF-14 Shipping containers</option>
<option value="MSI">MSI Shelf labeling / inventory</option>
<option value="pharmacode">Pharmacode Pharmaceutical packaging</option>
</select>
</div>
</>
)}
</>
);
default:
return null;
}
@@ -992,7 +1058,25 @@ export default function CreatePage() {
</div>
)}
{qrContent ? (
{contentType === 'BARCODE' ? (
qrContent ? (
<div className="p-2 bg-white">
<Barcode
value={qrContent}
format={content.format || 'CODE128'}
lineColor={foregroundColor}
background={backgroundColor}
width={2}
height={100}
displayValue={true}
/>
</div>
) : (
<div className="w-[200px] h-[200px] bg-gray-100 rounded flex items-center justify-center text-gray-500">
Enter barcode value
</div>
)
) : qrContent ? (
<div className={cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''}>
<QRCodeSVG
value={qrContent}