Files
QR-Code-Generator/index.jsx
2025-06-27 23:50:13 +02:00

698 lines
27 KiB
JavaScript

import React, { useState, useEffect, useRef } from 'react';
import { QrCode, Link, MessageSquare, User, Wifi, Download, Copy, Check } from 'lucide-react';
const TRANSLATIONS = {
"de-DE": {
"appTitle": "QR-Code Generator",
"appDescription": "Erstelle QR-Codes für URLs, Text und Kontaktinformationen",
"urlTab": "URL",
"textTab": "Text",
"contactTab": "Kontakt",
"wifiTab": "WLAN",
"enterUrl": "URL eingeben",
"enterText": "Text eingeben",
"contactInformation": "Kontaktinformationen",
"wifiConfiguration": "WLAN-Konfiguration",
"websiteUrl": "Website-URL",
"urlPlaceholder": "beispiel.de oder https://beispiel.de",
"urlHelp": "Geben Sie eine Website-URL ein. Falls Sie http:// weglassen, fügen wir automatisch https:// hinzu.",
"textContent": "Textinhalt",
"textPlaceholder": "Beliebigen Text eingeben um QR-Code zu erstellen...",
"firstName": "Vorname",
"firstNamePlaceholder": "Max",
"lastName": "Nachname",
"lastNamePlaceholder": "Mustermann",
"phoneNumber": "Telefonnummer",
"phonePlaceholder": "+49 (0) 123 456789",
"emailAddress": "E-Mail-Adresse",
"emailPlaceholder": "max.mustermann@beispiel.de",
"organization": "Organisation",
"organizationPlaceholder": "Firmenname",
"website": "Website",
"websitePlaceholder": "https://beispiel.de",
"clearAllFields": "Alle Felder löschen",
"networkName": "Netzwerkname (SSID)",
"networkNamePlaceholder": "Mein WLAN-Netzwerk",
"password": "Passwort",
"passwordPlaceholder": "WLAN-Passwort",
"securityType": "Sicherheitstyp",
"hidden": "Verstecktes Netzwerk",
"hiddenHelp": "Ankreuzen, falls dies ein verstecktes Netzwerk ist",
"generatedQrCode": "Generierter QR-Code",
"scanQrCode": "Scannen Sie diesen QR-Code mit Ihrem Gerät",
"fillFormPrompt": "Füllen Sie das Formular aus, um Ihren QR-Code zu erstellen",
"download": "Herunterladen",
"copyData": "Daten kopieren",
"copied": "Kopiert!",
"qrCodeData": "QR-Code Daten:",
"footerText": "QR-Codes sofort erstellen • Keine Datenspeicherung • Kostenlos",
"qrCodeAlt": "Generierter QR-Code"
},
"en-US": {
"appTitle": "QR Code Generator",
"appDescription": "Generate QR codes for URLs, text, and contact information",
"urlTab": "URL",
"textTab": "Text",
"contactTab": "Contact",
"wifiTab": "WiFi",
"enterUrl": "Enter URL",
"enterText": "Enter Text",
"contactInformation": "Contact Information",
"wifiConfiguration": "WiFi Configuration",
"websiteUrl": "Website URL",
"urlPlaceholder": "example.com or https://example.com",
"urlHelp": "Enter a website URL. If you don't include http://, we'll add https:// automatically.",
"textContent": "Text Content",
"textPlaceholder": "Enter any text to generate QR code...",
"firstName": "First Name",
"firstNamePlaceholder": "John",
"lastName": "Last Name",
"lastNamePlaceholder": "Doe",
"phoneNumber": "Phone Number",
"phonePlaceholder": "+1 (555) 123-4567",
"emailAddress": "Email Address",
"emailPlaceholder": "john.doe@example.com",
"organization": "Organization",
"organizationPlaceholder": "Company Name",
"website": "Website",
"websitePlaceholder": "https://example.com",
"clearAllFields": "Clear All Fields",
"networkName": "Network Name (SSID)",
"networkNamePlaceholder": "My WiFi Network",
"password": "Password",
"passwordPlaceholder": "WiFi password",
"securityType": "Security Type",
"hidden": "Hidden Network",
"hiddenHelp": "Check if this is a hidden network",
"generatedQrCode": "Generated QR Code",
"scanQrCode": "Scan this QR code with your device",
"fillFormPrompt": "Fill in the form to generate your QR code",
"download": "Download",
"copyData": "Copy Data",
"copied": "Copied!",
"qrCodeData": "QR Code Data:",
"footerText": "Generate QR codes instantly • No data stored • Free to use",
"qrCodeAlt": "Generated QR Code"
},
"es-ES": {
"appTitle": "Generador de Códigos QR",
"appDescription": "Genera códigos QR para URLs, texto e información de contacto",
"urlTab": "URL",
"textTab": "Texto",
"contactTab": "Contacto",
"wifiTab": "WiFi",
"enterUrl": "Ingresa URL",
"enterText": "Ingresa Texto",
"contactInformation": "Información de Contacto",
"wifiConfiguration": "Configuración WiFi",
"websiteUrl": "URL del Sitio Web",
"urlPlaceholder": "ejemplo.com o https://ejemplo.com",
"urlHelp": "Ingresa una URL de sitio web. Si no incluyes http://, agregaremos https:// automáticamente.",
"textContent": "Contenido de Texto",
"textPlaceholder": "Ingresa cualquier texto para generar código QR...",
"firstName": "Nombre",
"firstNamePlaceholder": "Juan",
"lastName": "Apellido",
"lastNamePlaceholder": "Pérez",
"phoneNumber": "Número de Teléfono",
"phonePlaceholder": "+1 (555) 123-4567",
"emailAddress": "Dirección de Correo",
"emailPlaceholder": "juan.perez@ejemplo.com",
"organization": "Organización",
"organizationPlaceholder": "Nombre de la Empresa",
"website": "Sitio Web",
"websitePlaceholder": "https://ejemplo.com",
"clearAllFields": "Limpiar Todos los Campos",
"networkName": "Nombre de Red (SSID)",
"networkNamePlaceholder": "Mi Red WiFi",
"password": "Contraseña",
"passwordPlaceholder": "Contraseña WiFi",
"securityType": "Tipo de Seguridad",
"hidden": "Red Oculta",
"hiddenHelp": "Marcar si esta es una red oculta",
"generatedQrCode": "Código QR Generado",
"scanQrCode": "Escanea este código QR con tu dispositivo",
"fillFormPrompt": "Completa el formulario para generar tu código QR",
"download": "Descargar",
"copyData": "Copiar Datos",
"copied": "¡Copiado!",
"qrCodeData": "Datos del Código QR:",
"footerText": "Genera códigos QR al instante • No se almacenan datos • Gratis",
"qrCodeAlt": "Código QR Generado"
}
};
const appLocale = '{{APP_LOCALE}}';
const browserLocale = navigator.languages?.[0] || navigator.language || 'en-US';
const findMatchingLocale = (locale) => {
if (TRANSLATIONS[locale]) return locale;
const lang = locale.split('-')[0];
const match = Object.keys(TRANSLATIONS).find(key => key.startsWith(lang + '-'));
return match || 'en-US';
};
const locale = (appLocale !== '{{APP_LOCALE}}') ? findMatchingLocale(appLocale) : findMatchingLocale(browserLocale);
// Standardmäßig auf Deutsch setzen
const defaultLocale = 'de-DE';
const t = (key) => TRANSLATIONS[locale]?.[key] || TRANSLATIONS[defaultLocale]?.[key] || TRANSLATIONS['en-US']?.[key] || key;
const QRCodeGenerator = () => {
const [activeTab, setActiveTab] = useState('url');
const [qrData, setQrData] = useState('');
const [copied, setCopied] = useState(false);
const qrContainerRef = useRef(null);
// Form states for different types
const [urlInput, setUrlInput] = useState('');
const [textInput, setTextInput] = useState('');
const [contactInfo, setContactInfo] = useState({
firstName: '',
lastName: '',
phone: '',
email: '',
organization: '',
url: ''
});
const [wifiInfo, setWifiInfo] = useState({
ssid: '',
password: '',
security: 'WPA',
hidden: false
});
// QR Code generation using QRious library via CDN
const generateQRCode = async (text) => {
if (!text.trim()) {
if (qrContainerRef.current) {
qrContainerRef.current.innerHTML = '';
}
return;
}
try {
// Load QRious library dynamically
if (!window.QRious) {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js';
script.onload = () => {
createQR(text);
};
document.head.appendChild(script);
} else {
createQR(text);
}
} catch (error) {
console.error('Error loading QR library:', error);
// Fallback to Google Charts API
generateFallbackQR(text);
}
};
const createQR = (text) => {
if (!qrContainerRef.current) return;
try {
// Clear previous QR code
qrContainerRef.current.innerHTML = '';
// Create canvas element
const canvas = document.createElement('canvas');
qrContainerRef.current.appendChild(canvas);
// Generate QR code
const qr = new window.QRious({
element: canvas,
value: text,
size: 300,
background: 'white',
foreground: 'black',
level: 'M'
});
// Style the canvas
canvas.className = 'w-full h-auto rounded-xl shadow-lg bg-white';
canvas.style.maxWidth = '300px';
canvas.style.height = 'auto';
} catch (error) {
console.error('Error creating QR code:', error);
generateFallbackQR(text);
}
};
const generateFallbackQR = (text) => {
if (!qrContainerRef.current) return;
// Clear previous content
qrContainerRef.current.innerHTML = '';
// Create img element for fallback
const img = document.createElement('img');
const encodedData = encodeURIComponent(text);
img.src = `https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl=${encodedData}&choe=UTF-8`;
img.alt = t('qrCodeAlt');
img.className = 'w-full h-auto rounded-xl shadow-lg bg-white p-4';
img.style.maxWidth = '300px';
img.style.height = 'auto';
// Add error handling for the fallback image
img.onerror = () => {
// If Google Charts also fails, try QR Server API
img.src = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodedData}&format=png&margin=10`;
};
qrContainerRef.current.appendChild(img);
};
const formatUrl = (url) => {
if (!url.trim()) return '';
// Add protocol if missing
if (!url.startsWith('http://') && !url.startsWith('https://')) {
return 'https://' + url;
}
return url;
};
const generateVCard = (contact) => {
const vcard = `BEGIN:VCARD
VERSION:3.0
FN:${contact.firstName} ${contact.lastName}
N:${contact.lastName};${contact.firstName};;;
ORG:${contact.organization}
TEL:${contact.phone}
EMAIL:${contact.email}
URL:${contact.url}
END:VCARD`;
return vcard;
};
const generateWiFiQR = (wifi) => {
// WiFi QR code format: WIFI:T:WPA;S:mynetwork;P:mypass;H:false;;
const security = wifi.security || 'WPA';
const hidden = wifi.hidden ? 'true' : 'false';
return `WIFI:T:${security};S:${wifi.ssid};P:${wifi.password};H:${hidden};;`;
};
useEffect(() => {
let data = '';
switch (activeTab) {
case 'url':
data = formatUrl(urlInput);
break;
case 'text':
data = textInput;
break;
case 'contact':
if (contactInfo.firstName || contactInfo.lastName || contactInfo.phone || contactInfo.email) {
data = generateVCard(contactInfo);
}
break;
case 'wifi':
if (wifiInfo.ssid) {
data = generateWiFiQR(wifiInfo);
}
break;
default:
data = '';
}
setQrData(data);
generateQRCode(data);
}, [activeTab, urlInput, textInput, contactInfo, wifiInfo]);
const downloadQRCode = () => {
if (!qrData) return;
const canvas = qrContainerRef.current?.querySelector('canvas');
const img = qrContainerRef.current?.querySelector('img');
if (canvas) {
// Download from canvas
const link = document.createElement('a');
link.download = `qr-code-${activeTab}.png`;
link.href = canvas.toDataURL();
link.click();
} else if (img) {
// Download from image
const link = document.createElement('a');
link.download = `qr-code-${activeTab}.png`;
link.href = img.src;
link.click();
}
};
const copyToClipboard = async () => {
if (qrData) {
try {
await navigator.clipboard.writeText(qrData);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
};
const resetForm = () => {
setUrlInput('');
setTextInput('');
setContactInfo({
firstName: '',
lastName: '',
phone: '',
email: '',
organization: '',
url: ''
});
setWifiInfo({
ssid: '',
password: '',
security: 'WPA',
hidden: false
});
setQrData('');
if (qrContainerRef.current) {
qrContainerRef.current.innerHTML = '';
}
};
const tabs = [
{ id: 'url', label: t('urlTab'), icon: Link },
{ id: 'text', label: t('textTab'), icon: MessageSquare },
{ id: 'contact', label: t('contactTab'), icon: User },
{ id: 'wifi', label: t('wifiTab'), icon: Wifi }
];
return (
<div className="min-h-screen bg-white p-4">
<div className="max-w-4xl mx-auto">
<div className="text-center mb-8">
<div className="inline-flex items-center justify-center w-16 h-16 bg-red-800 rounded-lg mb-4">
<QrCode className="w-8 h-8 text-white" />
</div>
<h1 className="text-4xl font-bold text-red-800 mb-2">
{t('appTitle')}
</h1>
<p className="text-gray-600 text-lg">{t('appDescription')}</p>
</div>
<div className="bg-white rounded-lg shadow-lg border border-red-800 overflow-hidden">
{/* Tab Navigation */}
<div className="border-b border-gray-200">
<nav className="flex">
{tabs.map((tab) => {
const IconComponent = tab.icon;
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`flex-1 flex items-center justify-center gap-2 px-6 py-4 text-sm font-medium transition-all duration-200 ${
activeTab === tab.id
? 'text-red-800 border-b-2 border-red-800 bg-red-50'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<IconComponent className="w-4 h-4" />
{tab.label}
</button>
);
})}
</nav>
</div>
<div className="p-8">
<div className="grid lg:grid-cols-2 gap-8">
{/* Input Section */}
<div className="space-y-6">
<h2 className="text-2xl font-semibold text-gray-800 mb-4">
{activeTab === 'url' && t('enterUrl')}
{activeTab === 'text' && t('enterText')}
{activeTab === 'contact' && t('contactInformation')}
{activeTab === 'wifi' && t('wifiConfiguration')}
</h2>
{/* URL Input */}
{activeTab === 'url' && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('websiteUrl')}
</label>
<input
type="url"
value={urlInput}
onChange={(e) => setUrlInput(e.target.value)}
placeholder={t('urlPlaceholder')}
className="w-full px-4 py-3 border border-red-800 rounded-lg focus:ring-2 focus:ring-red-800 focus:border-transparent transition-all duration-200"
/>
<p className="text-xs text-gray-500 mt-1">
{t('urlHelp')}
</p>
</div>
)}
{/* WiFi Input */}
{activeTab === 'wifi' && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('networkName')}
</label>
<input
type="text"
value={wifiInfo.ssid}
onChange={(e) => setWifiInfo({...wifiInfo, ssid: e.target.value})}
placeholder={t('networkNamePlaceholder')}
className="w-full px-4 py-3 border border-red-800 rounded-lg focus:ring-2 focus:ring-red-800 focus:border-transparent transition-all duration-200"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('password')}
</label>
<input
type="password"
value={wifiInfo.password}
onChange={(e) => setWifiInfo({...wifiInfo, password: e.target.value})}
placeholder={t('passwordPlaceholder')}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('securityType')}
</label>
<select
value={wifiInfo.security}
onChange={(e) => setWifiInfo({...wifiInfo, security: e.target.value})}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
>
<option value="WPA">WPA/WPA2</option>
<option value="WEP">WEP</option>
<option value="nopass">Offen (Kein Passwort)</option>
</select>
</div>
<div className="flex items-center space-x-3">
<input
type="checkbox"
id="hidden"
checked={wifiInfo.hidden}
onChange={(e) => setWifiInfo({...wifiInfo, hidden: e.target.checked})}
className="w-4 h-4 text-red-800 bg-gray-100 border-red-800 rounded focus:ring-red-800 focus:ring-2"
/>
<label htmlFor="hidden" className="text-sm font-medium text-gray-700">
{t('hidden')}
</label>
</div>
<p className="text-xs text-gray-500">
{t('hiddenHelp')}
</p>
</div>
)}
{/* Text Input */}
{activeTab === 'text' && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('textContent')}
</label>
<textarea
value={textInput}
onChange={(e) => setTextInput(e.target.value)}
placeholder={t('textPlaceholder')}
rows={4}
className="w-full px-4 py-3 border border-red-800 rounded-lg focus:ring-2 focus:ring-red-800 focus:border-transparent transition-all duration-200 resize-none"
/>
</div>
)}
{/* Contact Input */}
{activeTab === 'contact' && (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('firstName')}
</label>
<input
type="text"
value={contactInfo.firstName}
onChange={(e) => setContactInfo({...contactInfo, firstName: e.target.value})}
placeholder={t('firstNamePlaceholder')}
className="w-full px-4 py-3 border border-red-800 rounded-lg focus:ring-2 focus:ring-red-800 focus:border-transparent transition-all duration-200"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('lastName')}
</label>
<input
type="text"
value={contactInfo.lastName}
onChange={(e) => setContactInfo({...contactInfo, lastName: e.target.value})}
placeholder={t('lastNamePlaceholder')}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('phoneNumber')}
</label>
<input
type="tel"
value={contactInfo.phone}
onChange={(e) => setContactInfo({...contactInfo, phone: e.target.value})}
placeholder={t('phonePlaceholder')}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('emailAddress')}
</label>
<input
type="email"
value={contactInfo.email}
onChange={(e) => setContactInfo({...contactInfo, email: e.target.value})}
placeholder={t('emailPlaceholder')}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('organization')}
</label>
<input
type="text"
value={contactInfo.organization}
onChange={(e) => setContactInfo({...contactInfo, organization: e.target.value})}
placeholder={t('organizationPlaceholder')}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('website')}
</label>
<input
type="url"
value={contactInfo.url}
onChange={(e) => setContactInfo({...contactInfo, url: e.target.value})}
placeholder={t('websitePlaceholder')}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
/>
</div>
</div>
)}
<button
onClick={resetForm}
className="w-full px-6 py-3 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-all duration-200 font-medium"
>
{t('clearAllFields')}
</button>
</div>
{/* QR Code Display Section */}
<div className="flex flex-col items-center space-y-6">
<h2 className="text-2xl font-semibold text-gray-800">{t('generatedQrCode')}</h2>
<div className="bg-red-50 border border-red-800 rounded-lg p-8 w-full max-w-sm">
{qrData ? (
<div className="text-center">
<div ref={qrContainerRef} className="flex justify-center">
{/* QR code will be dynamically inserted here */}
</div>
<p className="text-sm text-gray-600 mt-4">
{t('scanQrCode')}
</p>
</div>
) : (
<div className="text-center py-16">
<QrCode className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<p className="text-gray-500">
{t('fillFormPrompt')}
</p>
</div>
)}
</div>
{qrData && (
<div className="flex gap-4 w-full max-w-sm">
<button
onClick={downloadQRCode}
className="flex-1 flex items-center justify-center gap-2 px-6 py-3 bg-red-800 text-white rounded-lg hover:bg-red-700 transition-all duration-200 font-medium shadow-lg"
>
<Download className="w-4 h-4" />
{t('download')}
</button>
<button
onClick={copyToClipboard}
className="flex-1 flex items-center justify-center gap-2 px-6 py-3 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-all duration-200 font-medium"
>
{copied ? (
<>
<Check className="w-4 h-4 text-green-600" />
{t('copied')}
</>
) : (
<>
<Copy className="w-4 h-4" />
{t('copyData')}
</>
)}
</button>
</div>
)}
{qrData && (
<div className="w-full max-w-sm">
<h3 className="text-sm font-medium text-gray-700 mb-2">{t('qrCodeData')}</h3>
<div className="bg-gray-100 rounded-lg p-3 text-xs text-gray-600 max-h-32 overflow-y-auto">
<pre className="whitespace-pre-wrap break-words">{qrData}</pre>
</div>
</div>
)}
</div>
</div>
</div>
</div>
<div className="text-center mt-8 text-gray-500 text-sm">
<p>&copy; {new Date().getFullYear()} <a href="https://wachtel-it.de" target="_blank" rel="noopener noreferrer" className="text-red-800 hover:underline">Philipp Wachtel</a></p>
</div>
</div>
</div>
);
};
export default QRCodeGenerator;