import React, { useState, useEffect, useRef } from 'react';
import { initializeApp } from 'firebase/app';
import {
getAuth,
signInAnonymously,
onAuthStateChanged,
signInWithCustomToken
} from 'firebase/auth';
import {
getFirestore,
collection,
addDoc,
onSnapshot,
query,
orderBy,
serverTimestamp
} from 'firebase/firestore';
import {
QrCode,
ScanLine,
CheckCircle2,
XCircle,
Loader2,
Copy,
History,
Trash2,
Smartphone
} from 'lucide-react';
// --- Firebase Configuration ---
const firebaseConfig = JSON.parse(__firebase_config);
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
// --- Helper: Load External Scripts (for QR Scanner) ---
const loadScript = (src) => {
return new Promise((resolve, reject) => {
if (document.querySelector(`script[src="${src}"]`)) {
resolve();
return;
}
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
});
};
export default function App() {
const [user, setUser] = useState(null);
const [activeTab, setActiveTab] = useState('generate'); // 'generate' | 'scan' | 'history'
const [inputText, setInputText] = useState('');
const [generatedCode, setGeneratedCode] = useState(null);
const [registeredCodes, setRegisteredCodes] = useState([]);
const [scanResult, setScanResult] = useState(null);
const [scannerReady, setScannerReady] = useState(false);
const [loading, setLoading] = useState(false);
const [notification, setNotification] = useState(null);
// Scanner refs
const scannerRef = useRef(null);
const scannerContainerId = "reader";
// --- 1. Authentication ---
useEffect(() => {
const initAuth = async () => {
try {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
} catch (error) {
console.error("Auth Error:", error);
}
};
initAuth();
const unsubscribe = onAuthStateChanged(auth, setUser);
return () => unsubscribe();
}, []);
// --- 2. Data Sync (Get Registered Codes) ---
useEffect(() => {
if (!user) return;
// We store codes in a public collection so we can verify them easily across devices
// In a real app, you might split this logic
const q = query(collection(db, 'artifacts', appId, 'public', 'data', 'qrcodes'));
const unsubscribe = onSnapshot(q, (snapshot) => {
const codes = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
// Sort in memory (Rule 2: avoid complex query errors)
codes.sort((a, b) => (b.createdAt?.seconds || 0) - (a.createdAt?.seconds || 0));
setRegisteredCodes(codes);
}, (error) => {
console.error("Firestore Error:", error);
});
return () => unsubscribe();
}, [user]);
// --- 3. Scanner Logic ---
useEffect(() => {
if (activeTab === 'scan' && !scannerReady) {
loadScript("https://unpkg.com/html5-qrcode")
.then(() => {
setScannerReady(true);
})
.catch(err => console.error("Failed to load scanner", err));
}
// Cleanup scanner when leaving tab
return () => {
if (scannerRef.current) {
scannerRef.current.clear().catch(console.error);
scannerRef.current = null;
}
};
}, [activeTab, scannerReady]);
useEffect(() => {
if (activeTab === 'scan' && scannerReady && !scannerRef.current) {
startScanner();
}
}, [activeTab, scannerReady]);
const startScanner = () => {
if (!window.Html5QrcodeScanner) return;
// Use Html5Qrcode for more control, or Scanner for UI
const html5QrCode = new window.Html5Qrcode(scannerContainerId);
scannerRef.current = html5QrCode;
const config = { fps: 10, qrbox: { width: 250, height: 250 } };
html5QrCode.start(
{ facingMode: "environment" },
config,
(decodedText) => {
handleScan(decodedText);
// Optional: Pause scanning after success to let user read result
html5QrCode.pause();
},
(errorMessage) => {
// Parse errors are common, ignore them to keep console clean
}
).catch(err => {
console.error("Error starting scanner", err);
setNotification({ type: 'error', text: 'Erro ao iniciar câmera. Verifique as permissões.' });
});
};
const handleScan = (text) => {
// Check against registered codes
const found = registeredCodes.find(code => code.content === text);
if (found) {
setScanResult({ status: 'valid', text: text, data: found });
} else {
setScanResult({ status: 'invalid', text: text });
}
};
const resetScanner = () => {
setScanResult(null);
if (scannerRef.current) {
scannerRef.current.resume();
}
};
// --- 4. Actions ---
const handleGenerate = async () => {
if (!inputText.trim() || !user) return;
setLoading(true);
try {
// Check if already exists locally to avoid duplicates
const exists = registeredCodes.find(c => c.content === inputText);
if (!exists) {
await addDoc(collection(db, 'artifacts', appId, 'public', 'data', 'qrcodes'), {
content: inputText,
createdAt: serverTimestamp(),
createdBy: user.uid
});
setNotification({ type: 'success', text: 'QR Code cadastrado com sucesso!' });
} else {
setNotification({ type: 'info', text: 'Este código já estava cadastrado.' });
}
setGeneratedCode(inputText);
setInputText('');
} catch (error) {
console.error("Error saving:", error);
setNotification({ type: 'error', text: 'Erro ao salvar.' });
} finally {
setLoading(false);
setTimeout(() => setNotification(null), 3000);
}
};
const copyToClipboard = (text) => {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
setNotification({ type: 'success', text: 'Copiado!' });
} catch (err) {
setNotification({ type: 'error', text: 'Erro ao copiar.' });
}
document.body.removeChild(textArea);
setTimeout(() => setNotification(null), 2000);
};
// --- Render ---
return (
{/* Header */}
{/* Main Content */}
{/* Navigation */}
setActiveTab('generate')}
className={`flex-1 py-3 text-sm font-medium rounded-lg flex items-center justify-center gap-2 transition-all ${
activeTab === 'generate' ? 'bg-blue-100 text-blue-700 shadow-sm' : 'text-slate-500'
}`}
>
Gerar
{
setActiveTab('scan');
setScanResult(null);
}}
className={`flex-1 py-3 text-sm font-medium rounded-lg flex items-center justify-center gap-2 transition-all ${
activeTab === 'scan' ? 'bg-blue-100 text-blue-700 shadow-sm' : 'text-slate-500'
}`}
>
Ler / Validar
{/* Notifications */}
{notification && (
{notification.text}
)}
{/* --- GENERATE TAB --- */}
{activeTab === 'generate' && (
Conteúdo do Novo QR Code
{generatedCode && (
{/* Using API for simpler rendering than canvas manipulation */}
)}
{/* List of Registered Codes (Mini History) */}
Últimos Cadastrados
{registeredCodes.length === 0 ? (
Nenhum código cadastrado ainda.
) : (
registeredCodes.slice(0, 5).map((code) => (
{code.content}
copyToClipboard(code.content)} className="text-slate-400 hover:text-blue-600 p-1">
))
)}
)}
{/* --- SCAN TAB --- */}
{activeTab === 'scan' && (
{!scanResult ? (
{!scannerReady && (
)}
{/* Visual Overlay guide */}
) : (
{scanResult.status === 'valid' ? (
<>
QR Code Válido!
Este código está cadastrado no sistema.
Conteúdo
{scanResult.text}
>
) : (
<>
Não Encontrado
Este QR Code não consta no banco de dados.
Conteúdo Lido
{scanResult.text}
>
)}
Escanear Novamente
)}
Para melhor funcionamento da câmera, use este aplicativo em um dispositivo móvel (celular) ou garanta que seu navegador tenha permissão para acessar a webcam.
)}
);
}