Files
app_umbrix/components/QRCodeModal.tsx

168 lines
5.1 KiB
TypeScript
Raw Permalink Normal View History

'use client';
import { useState, useEffect } from 'react';
import { X, Download, Share2, Copy, Check } from 'lucide-react';
interface QRCodeModalProps {
isOpen: boolean;
onClose: () => void;
url: string;
title?: string;
}
export default function QRCodeModal({
isOpen,
onClose,
url,
title = 'QR Код подписки',
}: QRCodeModalProps) {
const [copied, setCopied] = useState(false);
const [qrDataUrl, setQrDataUrl] = useState<string>('');
useEffect(() => {
if (isOpen && url) {
// Generate QR code using qrcode library
import('qrcode').then((QRCode) => {
QRCode.toDataURL(url, {
width: 300,
margin: 2,
color: {
dark: '#000000',
light: '#FFFFFF',
},
errorCorrectionLevel: 'H',
})
.then((dataUrl) => {
setQrDataUrl(dataUrl);
})
.catch((err) => {
console.error('Failed to generate QR code:', err);
});
});
}
}, [isOpen, url]);
if (!isOpen) return null;
const downloadQR = () => {
const a = document.createElement('a');
a.href = qrDataUrl;
a.download = 'vpn-subscription-qr.png';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
const shareQR = async () => {
if (!navigator.share) {
// Fallback: copy URL to clipboard
await copyUrl();
return;
}
try {
// Convert data URL to blob
const response = await fetch(qrDataUrl);
const blob = await response.blob();
const file = new File([blob], 'vpn-qr-code.png', { type: 'image/png' });
await navigator.share({
title: 'VPN Подписка',
text: 'QR код для подключения к VPN',
files: [file],
});
} catch (err) {
console.error('Failed to share QR code:', err);
// Fallback to copying URL
await copyUrl();
}
};
const copyUrl = async () => {
try {
await navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy URL:', err);
}
};
return (
<div
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
onClick={onClose}
>
<div
className="bg-[var(--bg-card)] border border-slate-700 rounded-lg p-6 max-w-sm w-full"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-semibold text-white">{title}</h3>
<button
onClick={onClose}
className="p-2 hover:bg-slate-700 rounded-lg transition-colors text-white"
>
<X className="h-5 w-5" />
</button>
</div>
{/* QR Code */}
<div className="bg-white p-4 rounded-lg mb-6 flex items-center justify-center">
{qrDataUrl ? (
<img src={qrDataUrl} alt="QR Code" className="w-full h-auto max-w-[256px]" />
) : (
<div className="w-64 h-64 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[var(--primary)]"></div>
</div>
)}
</div>
{/* Info text */}
<p className="text-sm text-slate-400 text-center mb-6">
Отсканируйте QR код в приложении Umbrix для автоматического подключения
</p>
{/* Actions */}
<div className="grid grid-cols-2 gap-3">
<button
onClick={downloadQR}
disabled={!qrDataUrl}
className="flex items-center justify-center gap-2 px-4 py-3 bg-slate-800 hover:bg-slate-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg transition-colors text-white"
>
<Download className="h-4 w-4" />
<span>Скачать</span>
</button>
<button
onClick={shareQR}
disabled={!qrDataUrl}
className="flex items-center justify-center gap-2 px-4 py-3 bg-[var(--primary)] hover:bg-[var(--primary)]/80 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg transition-colors text-white"
>
<Share2 className="h-4 w-4" />
<span>Поделиться</span>
</button>
</div>
{/* Copy URL button */}
<button
onClick={copyUrl}
className="w-full mt-3 flex items-center justify-center gap-2 px-4 py-3 bg-slate-800/50 hover:bg-slate-700/50 rounded-lg transition-colors text-slate-300 text-sm"
>
{copied ? (
<>
<Check className="h-4 w-4 text-green-500" />
<span className="text-green-500">Скопировано!</span>
</>
) : (
<>
<Copy className="h-4 w-4" />
<span>Скопировать ссылку</span>
</>
)}
</button>
</div>
</div>
);
}