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
882 lines (874 loc) • 1.67 MB
JavaScript
'use strict';
var core = require('@capacitor/core');
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
/**
* 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' },
[exports.QRType.MENU]: { icon: '🍽️', description: 'Create a restaurant menu' },
[exports.QRType.BUSINESS]: { icon: '🏢', description: 'Share business information' },
[exports.QRType.VCARD]: { icon: '👤', description: 'Share a digital business card' },
[exports.QRType.MP3]: { icon: '🎵', description: 'Share an audio file' },
[exports.QRType.APPS]: { icon: '📱', description: 'Redirect to app stores' },
[exports.QRType.LINKS_LIST]: { icon: '🔗', description: 'Share multiple links' },
[exports.QRType.COUPON]: { icon: '🎟️', description: 'Share a coupon or discount' },
[exports.QRType.FACEBOOK]: { icon: '👍', description: 'Share your Facebook page' },
[exports.QRType.INSTAGRAM]: { icon: '📷', description: 'Share your Instagram profile' },
[exports.QRType.SOCIAL_MEDIA]: { icon: '👥', description: 'Share all social channels' },
[exports.QRType.WHATSAPP]: { icon: '💬', description: 'Start a WhatsApp chat' },
[exports.QRType.TEXT]: { icon: '📝', description: 'Share plain text' },
[exports.QRType.EMAIL]: { icon: '✉️', description: 'Send an email' },
[exports.QRType.SMS]: { icon: '💭', description: 'Send an SMS' },
[exports.QRType.PHONE]: { icon: '📞', description: 'Make a phone call' },
[exports.QRType.LOCATION]: { icon: '📍', description: 'Share a location' },
[exports.QRType.EVENT]: { icon: '📅', description: 'Share an event' },
};
const ArrayFieldEditor = ({ fieldName: _fieldName, label, value = [], onChange, itemTemplate, renderItem, }) => {
const [collapsed, setCollapsed] = React.useState(false);
const handleAddItem = () => {
const newItem = Object.assign(Object.assign({}, itemTemplate), { id: Date.now() });
onChange([...value, newItem]);
};
const handleUpdateItem = (index, newItem) => {
const updated = [...value];
updated[index] = newItem;
onChange(updated);
};
const handleRemoveItem = (index) => {
const updated = value.filter((_, i) => i !== index);
onChange(updated);
};
return (jsxRuntime.jsxs("div", { className: "array-field-editor", children: [jsxRuntime.jsxs("div", { className: "array-field-header", children: [jsxRuntime.jsx("label", { children: label }), jsxRuntime.jsxs("div", { className: "array-field-actions", children: [jsxRuntime.jsxs("span", { className: "item-count", children: [value.length, " items"] }), jsxRuntime.jsx("button", { type: "button", className: "toggle-button", onClick: () => setCollapsed(!collapsed), children: collapsed ? '▸' : '▾' })] })] }), !collapsed && (jsxRuntime.jsxs("div", { className: "array-field-content", children: [value.length === 0 ? (jsxRuntime.jsxs("p", { className: "empty-message", children: ["No ", label.toLowerCase(), " added yet"] })) : (jsxRuntime.jsx("div", { className: "array-items", children: value.map((item, index) => (jsxRuntime.jsxs("div", { className: "array-item", children: [jsxRuntime.jsxs("div", { className: "item-header", children: [jsxRuntime.jsx("span", { className: "item-number", children: index + 1 }), jsxRuntime.jsx("button", { type: "button", className: "remove-button", onClick: () => handleRemoveItem(index), children: "\u2715" })] }), jsxRuntime.jsx("div", { className: "item-content", children: renderItem(item, index, (new