UNPKG

qrcode-studio

Version:

A comprehensive Capacitor plugin for QR code and barcode scanning/generation. Supports 22+ QR data types and 14+ barcode formats (EAN, UPC, Code 128, etc.), with customizable designs, analytics, and React components. Works seamlessly across web, iOS, and

845 lines (838 loc) 1.82 MB
var QrCodeStudio = (function (exports, core, jsxRuntime, React) { 'use strict'; /** * Barcode formats */ exports.BarcodeFormat = void 0; (function (BarcodeFormat) { // 2D Codes BarcodeFormat["QR_CODE"] = "QR_CODE"; BarcodeFormat["DATA_MATRIX"] = "DATA_MATRIX"; BarcodeFormat["AZTEC"] = "AZTEC"; BarcodeFormat["PDF_417"] = "PDF_417"; BarcodeFormat["MAXICODE"] = "MAXICODE"; // 1D Product Codes BarcodeFormat["EAN_13"] = "EAN_13"; BarcodeFormat["EAN_8"] = "EAN_8"; BarcodeFormat["UPC_A"] = "UPC_A"; BarcodeFormat["UPC_E"] = "UPC_E"; // 1D Industrial Codes BarcodeFormat["CODE_128"] = "CODE_128"; BarcodeFormat["CODE_39"] = "CODE_39"; BarcodeFormat["CODE_93"] = "CODE_93"; BarcodeFormat["CODABAR"] = "CODABAR"; BarcodeFormat["ITF"] = "ITF"; BarcodeFormat["ITF_14"] = "ITF_14"; // Specialty Codes BarcodeFormat["MSI"] = "MSI"; BarcodeFormat["MSI_PLESSEY"] = "MSI_PLESSEY"; BarcodeFormat["PHARMACODE"] = "PHARMACODE"; BarcodeFormat["RSS_14"] = "RSS_14"; BarcodeFormat["RSS_EXPANDED"] = "RSS_EXPANDED"; })(exports.BarcodeFormat || (exports.BarcodeFormat = {})); /** * QR code types */ exports.QRType = void 0; (function (QRType) { QRType["WEBSITE"] = "website"; QRType["PDF"] = "pdf"; QRType["IMAGES"] = "images"; QRType["VIDEO"] = "video"; QRType["WIFI"] = "wifi"; QRType["MENU"] = "menu"; QRType["BUSINESS"] = "business"; QRType["VCARD"] = "vcard"; QRType["MP3"] = "mp3"; QRType["APPS"] = "apps"; QRType["LINKS_LIST"] = "links_list"; QRType["COUPON"] = "coupon"; QRType["FACEBOOK"] = "facebook"; QRType["INSTAGRAM"] = "instagram"; QRType["SOCIAL_MEDIA"] = "social_media"; QRType["WHATSAPP"] = "whatsapp"; QRType["TEXT"] = "text"; QRType["EMAIL"] = "email"; QRType["SMS"] = "sms"; QRType["PHONE"] = "phone"; QRType["LOCATION"] = "location"; QRType["EVENT"] = "event"; })(exports.QRType || (exports.QRType = {})); exports.Directory = void 0; (function (Directory) { Directory["Documents"] = "DOCUMENTS"; Directory["Downloads"] = "DOWNLOADS"; Directory["External"] = "EXTERNAL"; Directory["ExternalStorage"] = "EXTERNAL_STORAGE"; })(exports.Directory || (exports.Directory = {})); // import './QRScanner.css'; // CSS should be imported by the consuming app const QRScanner = ({ onScan, onError, options, className = '', style, showOverlay = true, overlayComponent, }) => { const [isScanning, setIsScanning] = React.useState(false); const [permissionStatus, setPermissionStatus] = React.useState('prompt'); const [error, setError] = React.useState(null); const containerRef = React.useRef(null); const scanningRef = React.useRef(false); // Check permissions on mount React.useEffect(() => { checkPermissions(); }, []); // Handle scan results React.useEffect(() => { if (!isScanning) return; const handleScanResult = (result) => { onScan(result); }; const handleScanError = (error) => { setError(error.message); onError === null || onError === void 0 ? void 0 : onError(error); }; const resultListener = QRCodeStudio.addListener('scanResult', handleScanResult); const errorListener = QRCodeStudio.addListener('scanError', handleScanError); return () => { resultListener.then(l => l.remove()); errorListener.then(l => l.remove()); }; }, [isScanning, onScan, onError]); const checkPermissions = async () => { try { const result = await QRCodeStudio.checkPermissions(); setPermissionStatus(result.camera); } catch (_a) { setError('Failed to check camera permissions'); } }; const requestPermissions = async () => { try { const result = await QRCodeStudio.requestPermissions(); setPermissionStatus(result.camera); if (result.camera === 'granted') { startScanning(); } } catch (_a) { setError('Failed to request camera permissions'); } }; const startScanning = React.useCallback(async () => { if (scanningRef.current) return; try { scanningRef.current = true; setError(null); await QRCodeStudio.startScan(options); setIsScanning(true); } catch (err) { scanningRef.current = false; setError(err instanceof Error ? err.message : 'Failed to start scanner'); onError === null || onError === void 0 ? void 0 : onError({ code: 'START_SCAN_ERROR', message: err instanceof Error ? err.message : 'Failed to start scanner', }); } }, [options, onError]); const stopScanning = React.useCallback(async () => { if (!scanningRef.current) return; try { await QRCodeStudio.stopScan(); setIsScanning(false); scanningRef.current = false; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to stop scanner'); } }, []); // Cleanup on unmount React.useEffect(() => { return () => { if (scanningRef.current) { QRCodeStudio.stopScan().catch(console.error); } }; }, []); const renderPermissionRequest = () => (jsxRuntime.jsxs("div", { className: "qr-scanner-permission", children: [jsxRuntime.jsx("div", { className: "permission-icon", children: "\uD83D\uDCF7" }), jsxRuntime.jsx("h3", { children: "Camera Permission Required" }), jsxRuntime.jsx("p", { children: "This app needs camera access to scan QR codes" }), jsxRuntime.jsx("button", { className: "permission-button", onClick: requestPermissions, children: "Grant Permission" })] })); const renderError = () => (jsxRuntime.jsxs("div", { className: "qr-scanner-error", children: [jsxRuntime.jsx("div", { className: "error-icon", children: "\u26A0\uFE0F" }), jsxRuntime.jsx("h3", { children: "Scanner Error" }), jsxRuntime.jsx("p", { children: error }), jsxRuntime.jsx("button", { className: "retry-button", onClick: () => { setError(null); if (permissionStatus === 'granted') { startScanning(); } else { requestPermissions(); } }, children: "Retry" })] })); const renderScanner = () => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "qr-scanner-video" }), showOverlay && (overlayComponent || (jsxRuntime.jsxs("div", { className: "qr-scanner-overlay", children: [jsxRuntime.jsxs("div", { className: "scan-region", children: [jsxRuntime.jsx("div", { className: "corner top-left" }), jsxRuntime.jsx("div", { className: "corner top-right" }), jsxRuntime.jsx("div", { className: "corner bottom-left" }), jsxRuntime.jsx("div", { className: "corner bottom-right" })] }), jsxRuntime.jsx("div", { className: "scan-line" }), jsxRuntime.jsx("p", { className: "scan-hint", children: "Position QR code within the frame" })] }))), jsxRuntime.jsx("button", { className: "stop-scan-button", onClick: stopScanning, children: "Stop Scanning" })] })); const renderControls = () => (jsxRuntime.jsx("div", { className: "qr-scanner-controls", children: jsxRuntime.jsx("button", { className: "start-scan-button", onClick: () => { if (permissionStatus === 'granted') { startScanning(); } else { requestPermissions(); } }, children: "Start Scanner" }) })); return (jsxRuntime.jsxs("div", { ref: containerRef, className: `qr-scanner-container ${className}`, style: style, children: [error && renderError(), !error && permissionStatus === 'denied' && renderPermissionRequest(), !error && permissionStatus === 'granted' && !isScanning && renderControls(), !error && isScanning && renderScanner()] })); }; class QRValidationError extends Error { constructor(message, field) { super(message); this.field = field; this.name = 'QRValidationError'; } } const validators = { [exports.QRType.WEBSITE]: (data) => { if (!data.url) throw new QRValidationError('URL is required', 'url'); if (!isValidUrl$1(data.url)) throw new QRValidationError('Invalid URL format', 'url'); }, [exports.QRType.PDF]: (data) => { if (!data.url) throw new QRValidationError('PDF URL is required', 'url'); if (!isValidUrl$1(data.url)) throw new QRValidationError('Invalid URL format', 'url'); if (!data.url.toLowerCase().endsWith('.pdf') && !data.url.includes('pdf')) { console.warn('URL does not appear to be a PDF file'); } }, [exports.QRType.IMAGES]: (data) => { if (!data.images || !Array.isArray(data.images) || data.images.length === 0) { throw new QRValidationError('At least one image is required', 'images'); } data.images.forEach((img, index) => { if (!img.url) throw new QRValidationError(`Image ${index + 1} URL is required`, `images[${index}].url`); if (!isValidUrl$1(img.url)) throw new QRValidationError(`Invalid URL format for image ${index + 1}`, `images[${index}].url`); }); }, [exports.QRType.VIDEO]: (data) => { if (!data.url) throw new QRValidationError('Video URL is required', 'url'); if (!isValidUrl$1(data.url)) throw new QRValidationError('Invalid URL format', 'url'); }, [exports.QRType.WIFI]: (data) => { if (!data.ssid) throw new QRValidationError('Network name (SSID) is required', 'ssid'); if (!data.security) throw new QRValidationError('Security type is required', 'security'); if (!['WEP', 'WPA', 'WPA2', 'WPA3', 'nopass'].includes(data.security)) { throw new QRValidationError('Invalid security type', 'security'); } if (data.security !== 'nopass' && !data.password) { throw new QRValidationError('Password is required for secured networks', 'password'); } }, [exports.QRType.MENU]: (data) => { if (!data.restaurantName) throw new QRValidationError('Restaurant name is required', 'restaurantName'); if (!data.categories || !Array.isArray(data.categories) || data.categories.length === 0) { throw new QRValidationError('At least one menu category is required', 'categories'); } data.categories.forEach((cat, catIndex) => { if (!cat.name) throw new QRValidationError(`Category ${catIndex + 1} name is required`, `categories[${catIndex}].name`); if (!cat.items || !Array.isArray(cat.items) || cat.items.length === 0) { throw new QRValidationError(`Category "${cat.name}" must have at least one item`, `categories[${catIndex}].items`); } cat.items.forEach((item, itemIndex) => { if (!item.name) throw new QRValidationError(`Item name is required`, `categories[${catIndex}].items[${itemIndex}].name`); if (!item.price) throw new QRValidationError(`Item price is required`, `categories[${catIndex}].items[${itemIndex}].price`); }); }); }, [exports.QRType.BUSINESS]: (data) => { if (!data.name) throw new QRValidationError('Business name is required', 'name'); if (data.email && !isValidEmail$1(data.email)) { throw new QRValidationError('Invalid email format', 'email'); } if (data.website && !isValidUrl$1(data.website)) { throw new QRValidationError('Invalid website URL', 'website'); } }, [exports.QRType.VCARD]: (data) => { if (!data.firstName && !data.lastName && !data.organization) { throw new QRValidationError('At least one of firstName, lastName, or organization is required'); } if (data.email && !isValidEmail$1(data.email)) { throw new QRValidationError('Invalid email format', 'email'); } if (data.website && !isValidUrl$1(data.website)) { throw new QRValidationError('Invalid website URL', 'website'); } }, [exports.QRType.MP3]: (data) => { if (!data.url) throw new QRValidationError('Audio URL is required', 'url'); if (!isValidUrl$1(data.url)) throw new QRValidationError('Invalid URL format', 'url'); }, [exports.QRType.APPS]: (data) => { if (!data.appStoreUrl && !data.playStoreUrl && !data.windowsStoreUrl && !data.customUrl) { throw new QRValidationError('At least one app store URL is required'); } if (data.appStoreUrl && !isValidUrl$1(data.appStoreUrl)) { throw new QRValidationError('Invalid App Store URL', 'appStoreUrl'); } if (data.playStoreUrl && !isValidUrl$1(data.playStoreUrl)) { throw new QRValidationError('Invalid Play Store URL', 'playStoreUrl'); } if (data.windowsStoreUrl && !isValidUrl$1(data.windowsStoreUrl)) { throw new QRValidationError('Invalid Windows Store URL', 'windowsStoreUrl'); } if (data.customUrl && !isValidUrl$1(data.customUrl)) { throw new QRValidationError('Invalid custom URL', 'customUrl'); } }, [exports.QRType.LINKS_LIST]: (data) => { if (!data.links || !Array.isArray(data.links) || data.links.length === 0) { throw new QRValidationError('At least one link is required', 'links'); } data.links.forEach((link, index) => { if (!link.title) throw new QRValidationError(`Link ${index + 1} title is required`, `links[${index}].title`); if (!link.url) throw new QRValidationError(`Link ${index + 1} URL is required`, `links[${index}].url`); if (!isValidUrl$1(link.url)) throw new QRValidationError(`Invalid URL format for link ${index + 1}`, `links[${index}].url`); }); }, [exports.QRType.COUPON]: (data) => { if (!data.code) throw new QRValidationError('Coupon code is required', 'code'); if (data.validUntil && !isValidDate(data.validUntil)) { throw new QRValidationError('Invalid date format for validUntil', 'validUntil'); } }, [exports.QRType.FACEBOOK]: (data) => { if (!data.pageUrl) throw new QRValidationError('Facebook page URL is required', 'pageUrl'); if (!isValidUrl$1(data.pageUrl) || !data.pageUrl.includes('facebook.com')) { throw new QRValidationError('Invalid Facebook URL', 'pageUrl'); } }, [exports.QRType.INSTAGRAM]: (data) => { if (!data.profileUrl) throw new QRValidationError('Instagram profile URL is required', 'profileUrl'); if (!isValidUrl$1(data.profileUrl) || !data.profileUrl.includes('instagram.com')) { throw new QRValidationError('Invalid Instagram URL', 'profileUrl'); } }, [exports.QRType.SOCIAL_MEDIA]: (data) => { const socialNetworks = Object.keys(data).filter(key => data[key]); if (socialNetworks.length === 0) { throw new QRValidationError('At least one social media link is required'); } socialNetworks.forEach(network => { if (data[network] && !isValidUrl$1(data[network])) { throw new QRValidationError(`Invalid URL for ${network}`, network); } }); }, [exports.QRType.WHATSAPP]: (data) => { if (!data.phoneNumber) throw new QRValidationError('Phone number is required', 'phoneNumber'); if (!isValidPhoneNumber$1(data.phoneNumber)) { throw new QRValidationError('Invalid phone number format', 'phoneNumber'); } }, [exports.QRType.TEXT]: (data) => { if (!data.text) throw new QRValidationError('Text content is required', 'text'); if (data.text.length > 2000) { throw new QRValidationError('Text is too long (max 2000 characters)', 'text'); } }, [exports.QRType.EMAIL]: (data) => { if (!data.to) throw new QRValidationError('Recipient email is required', 'to'); if (!isValidEmail$1(data.to)) throw new QRValidationError('Invalid email format', 'to'); if (data.cc && !isValidEmail$1(data.cc)) { throw new QRValidationError('Invalid CC email format', 'cc'); } if (data.bcc && !isValidEmail$1(data.bcc)) { throw new QRValidationError('Invalid BCC email format', 'bcc'); } }, [exports.QRType.SMS]: (data) => { if (!data.phoneNumber) throw new QRValidationError('Phone number is required', 'phoneNumber'); if (!isValidPhoneNumber$1(data.phoneNumber)) { throw new QRValidationError('Invalid phone number format', 'phoneNumber'); } }, [exports.QRType.PHONE]: (data) => { if (!data.phoneNumber) throw new QRValidationError('Phone number is required', 'phoneNumber'); if (!isValidPhoneNumber$1(data.phoneNumber)) { throw new QRValidationError('Invalid phone number format', 'phoneNumber'); } }, [exports.QRType.LOCATION]: (data) => { if (typeof data.latitude !== 'number') { throw new QRValidationError('Latitude must be a number', 'latitude'); } if (typeof data.longitude !== 'number') { throw new QRValidationError('Longitude must be a number', 'longitude'); } if (data.latitude < -90 || data.latitude > 90) { throw new QRValidationError('Latitude must be between -90 and 90', 'latitude'); } if (data.longitude < -180 || data.longitude > 180) { throw new QRValidationError('Longitude must be between -180 and 180', 'longitude'); } }, [exports.QRType.EVENT]: (data) => { if (!data.title) throw new QRValidationError('Event title is required', 'title'); if (!data.startDate) throw new QRValidationError('Start date is required', 'startDate'); if (!isValidDate(data.startDate)) { throw new QRValidationError('Invalid start date format', 'startDate'); } if (data.endDate && !isValidDate(data.endDate)) { throw new QRValidationError('Invalid end date format', 'endDate'); } if (data.endDate && new Date(data.endDate) < new Date(data.startDate)) { throw new QRValidationError('End date must be after start date', 'endDate'); } }, }; function validateQRData(type, data) { const validator = validators[type]; if (!validator) { throw new QRValidationError(`Unknown QR type: ${type}`); } validator(data); } // Helper validation functions function isValidUrl$1(url) { try { new URL(url); return true; } catch (_a) { return false; } } function isValidEmail$1(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } function isValidPhoneNumber$1(phone) { // Basic phone validation - accepts digits, spaces, +, -, (, ) const phoneRegex = /^[\d\s+\-()]+$/; const cleaned = phone.replace(/[\s\-()]/g, ''); return phoneRegex.test(phone) && cleaned.length >= 7 && cleaned.length <= 15; } function isValidDate(dateStr) { const date = new Date(dateStr); return date instanceof Date && !isNaN(date.getTime()); } // import './QRGenerator.css'; // CSS should be imported by the consuming app const QRGenerator = ({ type, data, design = {}, size = 300, format = 'png', onGenerate, className = '', style, showDownload = true, showShare = true, errorCorrectionLevel = 'M', version, maskPattern, margin, scale, width, toSJISFunc, }) => { const [qrCode, setQrCode] = React.useState(null); const [isGenerating, setIsGenerating] = React.useState(false); const [error, setError] = React.useState(null); const [selectedFormat, setSelectedFormat] = React.useState(format); const canvasRef = React.useRef(null); const containerRef = React.useRef(null); // Generate QR code when props change React.useEffect(() => { generateQRCode(); }, [type, data, design, size]); const generateQRCode = React.useCallback(async () => { try { // Validate data validateQRData(type, data); setIsGenerating(true); setError(null); const result = await QRCodeStudio.generate({ type, data, design, size, errorCorrectionLevel, version, maskPattern, margin, scale, width, toSJISFunc, }); setQrCode(result); onGenerate === null || onGenerate === void 0 ? void 0 : onGenerate(result); // Apply custom design if needed if (design.logo || design.frame) { applyCustomDesign(result); } } catch (err) { if (err instanceof QRValidationError) { setError(`Validation Error: ${err.message}`); } else { setError(err instanceof Error ? err.message : 'Failed to generate QR code'); } } finally { setIsGenerating(false); } }, [type, data, design, size, onGenerate]); const applyCustomDesign = async (qrResult) => { if (!canvasRef.current || !qrResult.dataUrl) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); if (!ctx) return; canvas.width = size; canvas.height = size; // Draw QR code const img = new Image(); img.onload = () => { ctx.drawImage(img, 0, 0, size, size); // Apply logo if provided if (design.logo) { const logoImg = new Image(); logoImg.crossOrigin = 'anonymous'; logoImg.onload = () => { var _a; const logoSize = design.logo.size || size * 0.2; const logoMargin = design.logo.margin || 10; const x = (size - logoSize) / 2; const y = (size - logoSize) / 2; // Draw white background for logo ctx.fillStyle = ((_a = design.colors) === null || _a === void 0 ? void 0 : _a.light) || '#FFFFFF'; ctx.fillRect(x - logoMargin, y - logoMargin, logoSize + logoMargin * 2, logoSize + logoMargin * 2); // Draw logo if (design.logo.borderRadius) { ctx.save(); ctx.beginPath(); ctx.roundRect(x, y, logoSize, logoSize, design.logo.borderRadius); ctx.clip(); ctx.drawImage(logoImg, x, y, logoSize, logoSize); ctx.restore(); } else { ctx.drawImage(logoImg, x, y, logoSize, logoSize); } }; logoImg.src = design.logo.src; } }; img.src = qrResult.dataUrl; }; const handleDownload = async () => { if (!qrCode) return; try { await QRCodeStudio.saveQRCode({ qrCode, fileName: `qrcode_${type}_${Date.now()}`, format: selectedFormat, }); } catch (_a) { setError('Failed to download QR code'); } }; const handleShare = async () => { if (!qrCode || !qrCode.dataUrl) return; if (navigator.share) { try { const blob = await (await fetch(qrCode.dataUrl)).blob(); const file = new File([blob], `qrcode.${selectedFormat}`, { type: `image/${selectedFormat}` }); await navigator.share({ title: 'QR Code', text: `QR Code for ${type}`, files: [file], }); } catch (err) { if (err.name !== 'AbortError') { setError('Failed to share QR code'); } } } else { // Fallback: Copy to clipboard try { await navigator.clipboard.writeText(qrCode.shortUrl || qrCode.landingPageUrl || qrCode.dataUrl); alert('Link copied to clipboard!'); } catch (_a) { setError('Sharing not supported on this device'); } } }; const exportFormats = ['png', 'jpg', 'svg', 'pdf', 'json', 'webp', 'gif', 'eps', 'wmf']; return (jsxRuntime.jsxs("div", { ref: containerRef, className: `qr-generator-container ${className}`, style: style, children: [error && (jsxRuntime.jsxs("div", { className: "qr-generator-error", children: [jsxRuntime.jsx("span", { className: "error-icon", children: "\u26A0\uFE0F" }), jsxRuntime.jsx("p", { children: error }), jsxRuntime.jsx("button", { onClick: generateQRCode, children: "Retry" })] })), isGenerating && (jsxRuntime.jsxs("div", { className: "qr-generator-loading", children: [jsxRuntime.jsx("div", { className: "loading-spinner" }), jsxRuntime.jsx("p", { children: "Generating QR Code..." })] })), !isGenerating && qrCode && !error && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "qr-code-preview", children: [qrCode.svg ? (jsxRuntime.jsx("div", { className: "qr-code-svg", dangerouslySetInnerHTML: { __html: qrCode.svg }, style: { width: size, height: size } })) : qrCode.dataUrl ? (jsxRuntime.jsx("img", { src: qrCode.dataUrl, alt: "QR Code", className: "qr-code-image", style: { width: size, height: size } })) : null, jsxRuntime.jsx("canvas", { ref: canvasRef, style: { display: 'none' } })] }), jsxRuntime.jsxs("div", { className: "qr-generator-actions", children: [showDownload && (jsxRuntime.jsxs("div", { className: "download-section", children: [jsxRuntime.jsx("select", { value: selectedFormat, onChange: (e) => setSelectedFormat(e.target.value), className: "format-select", children: exportFormats.map(fmt => (jsxRuntime.jsx("option", { value: fmt, children: fmt.toUpperCase() }, fmt))) }), jsxRuntime.jsxs("button", { onClick: handleDownload, className: "download-button", children: [jsxRuntime.jsx("span", { className: "button-icon", children: "\u2B07\uFE0F" }), "Download"] })] })), showShare && (jsxRuntime.jsxs("button", { onClick: handleShare, className: "share-button", children: [jsxRuntime.jsx("span", { className: "button-icon", children: "\uD83D\uDD17" }), "Share"] }))] }), qrCode.shortUrl && (jsxRuntime.jsxs("div", { className: "qr-code-url", children: [jsxRuntime.jsx("label", { children: "Short URL:" }), jsxRuntime.jsx("input", { type: "text", value: qrCode.shortUrl, readOnly: true, onClick: (e) => e.target.select() })] })), qrCode.landingPageUrl && (jsxRuntime.jsxs("div", { className: "qr-code-url", children: [jsxRuntime.jsx("label", { children: "Landing Page:" }), jsxRuntime.jsx("input", { type: "text", value: qrCode.landingPageUrl, readOnly: true, onClick: (e) => e.target.select() })] }))] }))] })); }; const BarcodeScanner = ({ onScan, onError, formats, showTorchButton = true, showFormatSelector = false, multiple = false, scanOptions = {}, className = '', style = {}, showOverlay = true, overlayComponent, showProductInfo = true, }) => { const [isScanning, setIsScanning] = React.useState(false); const [torchEnabled, setTorchEnabled] = React.useState(false); const [torchAvailable, setTorchAvailable] = React.useState(false); const [selectedFormats, setSelectedFormats] = React.useState(formats || []); const [lastScan, setLastScan] = React.useState(null); const scannerRef = React.useRef(null); React.useEffect(() => { let scanListener; let errorListener; let torchListener; const initScanner = async () => { try { // Check permissions const permissions = await QRCodeStudio.checkPermissions(); if (permissions.camera !== 'granted') { const requestResult = await QRCodeStudio.requestPermissions(); if (requestResult.camera !== 'granted') { onError === null || onError === void 0 ? void 0 : onError({ code: 'PERMISSION_DENIED', message: 'Camera permission denied', }); return; } } // Check torch availability const torch = await QRCodeStudio.isTorchAvailable(); setTorchAvailable(torch.available); // Setup listeners scanListener = await QRCodeStudio.addListener('scanResult', (result) => { setLastScan(result); onScan(result); }); errorListener = await QRCodeStudio.addListener('scanError', (error) => { onError === null || onError === void 0 ? void 0 : onError(error); }); torchListener = await QRCodeStudio.addListener('torchStateChanged', (state) => { setTorchEnabled(state.isEnabled); }); // Start scanning await QRCodeStudio.startScan(Object.assign(Object.assign({}, scanOptions), { formats: selectedFormats.length > 0 ? selectedFormats : undefined, multiple, showTorchButton: false })); setIsScanning(true); } catch (error) { onError === null || onError === void 0 ? void 0 : onError({ code: 'INIT_ERROR', message: error instanceof Error ? error.message : 'Failed to initialize scanner', }); } }; initScanner(); return () => { if (isScanning) { QRCodeStudio.stopScan(); } scanListener === null || scanListener === void 0 ? void 0 : scanListener.remove(); errorListener === null || errorListener === void 0 ? void 0 : errorListener.remove(); torchListener === null || torchListener === void 0 ? void 0 : torchListener.remove(); }; }, [selectedFormats, multiple]); const handleTorchToggle = async () => { try { if (torchEnabled) { await QRCodeStudio.disableTorch(); } else { await QRCodeStudio.enableTorch(); } } catch (error) { console.error('Failed to toggle torch:', error); } }; const handleFormatChange = (format, checked) => { setSelectedFormats(prev => { if (checked) { return [...prev, format]; } else { return prev.filter(f => f !== format); } }); }; const formatGroups = { '2D Codes': ['QR_CODE', 'DATA_MATRIX', 'AZTEC', 'PDF_417', 'MAXICODE'], 'Product Codes': ['EAN_13', 'EAN_8', 'UPC_A', 'UPC_E'], 'Industrial Codes': ['CODE_128', 'CODE_39', 'CODE_93', 'CODABAR', 'ITF', 'ITF_14'], 'Specialty': ['MSI', 'MSI_PLESSEY', 'PHARMACODE', 'RSS_14', 'RSS_EXPANDED'], }; return (jsxRuntime.jsxs("div", { ref: scannerRef, className: `barcode-scanner ${className}`, style: Object.assign({ position: 'relative', width: '100%', height: '100%' }, style), children: [showOverlay && !overlayComponent && (jsxRuntime.jsxs("div", { className: "barcode-scanner-overlay", children: [jsxRuntime.jsxs("div", { className: "scanning-frame", children: [jsxRuntime.jsx("div", { className: "corner corner-tl" }), jsxRuntime.jsx("div", { className: "corner corner-tr" }), jsxRuntime.jsx("div", { className: "corner corner-bl" }), jsxRuntime.jsx("div", { className: "corner corner-br" })] }), jsxRuntime.jsxs("div", { className: "scanner-controls", children: [showTorchButton && torchAvailable && (jsxRuntime.jsx("button", { className: `torch-button ${torchEnabled ? 'enabled' : ''}`, onClick: handleTorchToggle, "aria-label": torchEnabled ? 'Turn off flashlight' : 'Turn on flashlight', children: jsxRuntime.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: torchEnabled ? (jsxRuntime.jsx("path", { d: "M6,14L7,14.05L13,14.05L14,14L14,7L13,6.95L7,6.95L6,7L6,14M8,2L16,2L16,7L20,7L20,12L16,12L16,22L8,22L8,12L4,12L4,7L8,7L8,2M10,4L10,10L14,10L14,4L10,4Z" })) : (jsxRuntime.jsx("path", { d: "M6,14L7,14.05L13,14.05L14,14L14,7L13,6.95L7,6.95L6,7L6,14M8,2L16,2L16,7L20,7L20,12L16,12L16,22L8,22L8,12L4,12L4,7L8,7L8,2Z" })) }) })), showFormatSelector && (jsxRuntime.jsxs("div", { className: "format-selector", children: [jsxRuntime.jsx("button", { className: "format-selector-toggle", children: jsxRuntime.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "M3,4H7V8H3V4M9,5V7H21V5H9M3,10H7V14H3V10M9,11V13H21V11H9M3,16H7V20H3V16M9,17V19H21V17H9" }) }) }), jsxRuntime.jsx("div", { className: "format-dropdown", children: Object.entries(formatGroups).map(([group, groupFormats]) => (jsxRuntime.jsxs("div", { className: "format-group", children: [jsxRuntime.jsx("div", { className: "format-group-label", children: group }), groupFormats.map(format => (jsxRuntime.jsxs("label", { className: "format-option", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: selectedFormats.includes(format), onChange: (e) => handleFormatChange(format, e.target.checked) }), jsxRuntime.jsx("span", { children: format.replace(/_/g, ' ') })] }, format)))] }, group))) })] }))] }), lastScan && showProductInfo && lastScan.productInfo && (jsxRuntime.jsx("div", { className: "product-info-overlay", children: jsxRuntime.jsxs("div", { className: "product-info", children: [jsxRuntime.jsx("h3", { children: lastScan.productInfo.name }), lastScan.productInfo.brand && jsxRuntime.jsx("p", { className: "brand", children: lastScan.productInfo.brand }), lastScan.productInfo.price && (jsxRuntime.jsxs("p", { className: "price", children: [lastScan.productInfo.price.currency, " ", lastScan.productInfo.price.value] })), jsxRuntime.jsx("p", { className: "barcode", children: lastScan.content }), jsxRuntime.jsx("p", { className: "format", children: lastScan.format.replace(/_/g, ' ') })] }) })), jsxRuntime.jsx("div", { className: "scanner-instructions", children: multiple ? 'Scan multiple barcodes' : 'Position barcode within frame' })] })), overlayComponent] })); }; const qrFormFields = { [exports.QRType.WEBSITE]: [ { label: 'URL', name: 'url', type: 'url', placeholder: 'https://example.com', required: true }, { label: 'Title', name: 'title', type: 'text', placeholder: 'Optional page title' }, { label: 'Description', name: 'description', type: 'textarea', placeholder: 'Optional description' }, ], [exports.QRType.PDF]: [ { label: 'PDF URL', name: 'url', type: 'url', placeholder: 'https://example.com/document.pdf', required: true }, { label: 'Title', name: 'title', type: 'text', placeholder: 'Document title' }, { label: 'Description', name: 'description', type: 'textarea', placeholder: 'Document description' }, ], [exports.QRType.IMAGES]: [ { label: 'Images', name: 'images', type: 'array', required: true }, { label: 'Gallery Title', name: 'title', type: 'text', placeholder: 'My Image Gallery' }, { label: 'Description', name: 'description', type: 'textarea', placeholder: 'Gallery description' }, ], [exports.QRType.VIDEO]: [ { label: 'Video URL', name: 'url', type: 'url', placeholder: 'https://youtube.com/watch?v=...', required: true }, { label: 'Title', name: 'title', type: 'text', placeholder: 'Video title' }, { label: 'Description', name: 'description', type: 'textarea', placeholder: 'Video description' }, { label: 'Thumbnail URL', name: 'thumbnail', type: 'url', placeholder: 'https://example.com/thumb.jpg' }, ], [exports.QRType.WIFI]: [ { label: 'Network Name (SSID)', name: 'ssid', type: 'text', placeholder: 'MyNetwork', required: true }, { label: 'Security Type', name: 'security', type: 'select', required: true, options: [ { value: 'WPA2', label: 'WPA2 (Recommended)' }, { value: 'WPA3', label: 'WPA3' }, { value: 'WPA', label: 'WPA' }, { value: 'WEP', label: 'WEP' }, { value: 'nopass', label: 'No Password' }, ] }, { label: 'Password', name: 'password', type: 'password', placeholder: 'Network password' }, { label: 'Hidden Network', name: 'hidden', type: 'checkbox' }, ], [exports.QRType.MENU]: [ { label: 'Restaurant Name', name: 'restaurantName', type: 'text', required: true }, { label: 'Categories', name: 'categories', type: 'array', required: true }, { label: 'Currency', name: 'currency', type: 'select', options: [ { value: 'USD', label: '$ USD' }, { value: 'EUR', label: '€ EUR' }, { value: 'GBP', label: '£ GBP' }, { value: 'JPY', label: '¥ JPY' }, { value: 'CNY', label: '¥ CNY' }, ] }, ], [exports.QRType.BUSINESS]: [ { label: 'Business Name', name: 'name', type: 'text', required: true }, { label: 'Industry', name: 'industry', type: 'text', placeholder: 'e.g., Restaurant, Retail, Services' }, { label: 'Phone', name: 'phone', type: 'tel', placeholder: '+1234567890' }, { label: 'Email', name: 'email', type: 'email', placeholder: 'contact@business.com' }, { label: 'Website', name: 'website', type: 'url', placeholder: 'https://business.com' }, { label: 'Address', name: 'address', type: 'textarea', placeholder: 'Full business address' }, { label: 'Business Hours', name: 'hours', type: 'textarea', placeholder: 'Mon-Fri: 9AM-5PM' }, { label: 'Description', name: 'description', type: 'textarea', placeholder: 'About your business' }, { label: 'Logo URL', name: 'logo', type: 'url', placeholder: 'https://example.com/logo.png' }, ], [exports.QRType.VCARD]: [ { label: 'First Name', name: 'firstName', type: 'text' }, { label: 'Last Name', name: 'lastName', type: 'text' }, { label: 'Organization', name: 'organization', type: 'text' }, { label: 'Job Title', name: 'title', type: 'text' }, { label: 'Phone', name: 'phone', type: 'tel', placeholder: '+1234567890' }, { label: 'Mobile', name: 'mobile', type: 'tel', placeholder: '+1234567890' }, { label: 'Email', name: 'email', type: 'email', placeholder: 'email@example.com' }, { label: 'Website', name: 'website', type: 'url', placeholder: 'https://example.com' }, { label: 'Address', name: 'address', type: 'textarea' }, { label: 'Note', name: 'note', type: 'textarea' }, ], [exports.QRType.MP3]: [ { label: 'Audio URL', name: 'url', type: 'url', placeholder: 'https://example.com/audio.mp3', required: true }, { label: 'Title', name: 'title', type: 'text', placeholder: 'Song title' }, { label: 'Artist', name: 'artist', type: 'text', placeholder: 'Artist name' }, { label: 'Album', name: 'album', type: 'text', placeholder: 'Album name' }, { label: 'Cover Art URL', name: 'coverArt', type: 'url', placeholder: 'https://example.com/cover.jpg' }, ], [exports.QRType.APPS]: [ { label: 'App Store URL', name: 'appStoreUrl', type: 'url', placeholder: 'https://apps.apple.com/...' }, { label: 'Play Store URL', name: 'playStoreUrl', type: 'url', placeholder: 'https://play.google.com/...' }, { label: 'Windows Store URL', name: 'windowsStoreUrl', type: 'url', placeholder: 'https://microsoft.com/...' }, { label: 'Custom URL', name: 'customUrl', type: 'url', placeholder: 'https://example.com/app' }, { label: 'App Name', name: 'appName', type: 'text', placeholder: 'My Amazing App' }, { label: 'Description', name: 'description', type: 'textarea', placeholder: 'App description' }, { label: 'Icon URL', name: 'icon', type: 'url', placeholder: 'https://example.com/icon.png' }, ], [exports.QRType.LINKS_LIST]: [ { label: 'List Title', name: 'title', type: 'text', placeholder: 'My Important Links' }, { label: 'Links', name: 'links', type: 'array', required: true }, ], [exports.QRType.COUPON]: [ { label: 'Coupon Code', name: 'code', type: 'text', placeholder: 'SAVE20', required: true }, { label: 'Description', name: 'description', type: 'textarea', placeholder: 'Get 20% off your purchase' }, { label: 'Discount', name: 'discount', type: 'text', placeholder: '20% OFF' }, { label: 'Valid Until', name: 'validUntil', type: 'date' }, { label: 'Terms & Conditions', name: 'terms', type: 'textarea', placeholder: 'Coupon terms' }, { label: 'Logo URL', name: 'logo', type: 'url', placeholder: 'https://example.com/logo.png' }, ], [exports.QRType.FACEBOOK]: [ { label: 'Facebook Page URL', name: 'pageUrl', type: 'url', placeholder: 'https://facebook.com/yourpage', required: true }, { label: 'Page Name', name: 'pageName', type: 'text', placeholder: 'Your Page Name' }, ], [exports.QRType.INSTAGRAM]: [ { label: 'Instagram Profile URL', name: 'profileUrl', type: 'url', placeholder: 'https://instagram.com/username', required: true }, { label: 'Username', name: 'username', type: 'text', placeholder: '@username' }, ], [exports.QRType.SOCIAL_MEDIA]: [ { label: 'Facebook', name: 'facebook', type: 'url', placeholder: 'https://facebook.com/...' }, { label: 'Instagram', name: 'instagram', type: 'url', placeholder: 'https://instagram.com/...' }, { label: 'Twitter/X', name: 'twitter', type: 'url', placeholder: 'https://twitter.com/...' }, { label: 'LinkedIn', name: 'linkedin', type: 'url', placeholder: 'https://linkedin.com/...' }, { label: 'YouTube', name: 'youtube', type: 'url', placeholder: 'https://youtube.com/...' }, { label: 'TikTok', name: 'tiktok', type: 'url', placeholder: 'https://tiktok.com/...' }, { label: 'Pinterest', name: 'pinterest', type: 'url', placeholder: 'https://pinterest.com/...' }, { label: 'Snapchat', name: 'snapchat', type: 'url', placeholder: 'https://snapchat.com/...' }, { label: 'Reddit', name: 'reddit', type: 'url', placeholder: 'https://reddit.com/...' }, ], [exports.QRType.WHATSAPP]: [ { label: 'Phone Number', name: 'phoneNumber', type: 'tel', placeholder: '+1234567890', required: true, helper: 'Include country code' }, { label: 'Pre-filled Message', name: 'message', type: 'textarea', placeholder: 'Hello! I found your contact through QR code...' }, ], [exports.QRType.TEXT]: [ { label: 'Text Content', name: 'text', type: 'textarea', placeholder: 'Enter your text here...', required: true, maxLength: 2000 }, ], [exports.QRType.EMAIL]: [ { label: 'To Email', name: 'to', type: 'email', placeholder: 'recipient@example.com', required: true }, { label: 'Subject', name: 'subject', type: 'text', placeholder: 'Email subject' }, { label: 'Message', name: 'body', type: 'textarea', placeholder: 'Email message' }, { label: 'CC', name: 'cc', type: 'email', placeholder: 'cc@example.com' }, { label: 'BCC', name: 'bcc', type: 'email', placeholder: 'bcc@example.com' }, ], [exports.QRType.SMS]: [ { label: 'Phone Number', name: 'phoneNumber', type: 'tel', placeholder: '+1234567890', required: true }, { label: 'Message', name: 'message', type: 'textarea', placeholder: 'SMS message text' }, ], [exports.QRType.PHONE]: [ { label: 'Phone Number', name: 'phoneNumber', type: 'tel', placeholder: '+1234567890', required: true }, ], [exports.QRType.LOCATION]: [ { label: 'Latitude', name: 'latitude', type: 'number', placeholder: '37.7749', required: true, min: '-90', max: '90' }, { label: 'Longitude', name: 'longitude', type: 'number', placeholder: '-122.4194', required: true, min: '-180', max: '180' }, { label: 'Location Name', name: 'name', type: 'text', placeholder: 'Golden Gate Bridge' }, { label: 'Address', name: 'address', type: 'text', placeholder: 'Full address' }, ], [exports.QRType.EVENT]: [ { label: 'Event Title', name: 'title', type: 'text', placeholder: 'Annual Conference', required: true }, { label: 'Location', name: 'location', type: 'text', placeholder: 'Conference Center, New York' }, { label: 'Start Date & Time', name: 'startDate', type: 'datetime-local', required: true }, { label: 'End Date & Time', name: 'endDate', type: 'datetime-local' }, { label: 'Description', name: 'description', type: 'textarea', placeholder: 'Event details' }, { label: 'Event URL', name: 'url', type: 'url', placeholder: 'https://example.com/event' }, ], }; const qrTypeInfo = { [exports.QRType.WEBSITE]: { icon: '🌐', description: 'Link to any website URL' }, [exports.QRType.PDF]: { icon: '📄', description: 'Share a PDF document' }, [exports.QRType.IMAGES]: { icon: '🖼️', description: 'Share multiple images' }, [exports.QRType.VIDEO]: { icon: '🎥', description: 'Share a video' }, [exports.QRType.WIFI]: { icon: '📶', description: 'Connect to a Wi-Fi network' },