chore: initial project setup with backend, frontend, and infrastructure
Add complete NexaPantry application structure including: - Docker Compose configuration with PostgreSQL, Redis, FastAPI backend, worker, frontend and Caddy - Environment configuration template with database, auth, and service settings - GitHub Actions CI workflow for backend/frontend linting, testing, auditing and Docker builds - AGPL-3.0 license and comprehensive README with setup, development, and security documentation - Backend
This commit is contained in:
42
frontend/src/pages/ScannerPage.tsx
Normal file
42
frontend/src/pages/ScannerPage.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { BrowserMultiFormatReader } from '@zxing/browser';
|
||||
import { ScanLine } from 'lucide-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
import { Panel } from '../components/Forms';
|
||||
import { ProductForm } from '../components/ProductForm';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useI18n } from '../contexts/I18nContext';
|
||||
import type { Product } from '../types';
|
||||
|
||||
export function ScannerPage() {
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const { activeHome } = useAuth();
|
||||
const { t } = useI18n();
|
||||
const [barcode, setBarcode] = useState('');
|
||||
const [prefill, setPrefill] = useState<Partial<Product>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeHome || !videoRef.current) return;
|
||||
const reader = new BrowserMultiFormatReader();
|
||||
let controls: { stop: () => void } | undefined;
|
||||
let stop = false;
|
||||
reader.decodeFromVideoDevice(undefined, videoRef.current, async (result) => {
|
||||
if (result && !stop) {
|
||||
stop = true;
|
||||
const code = result.getText();
|
||||
setBarcode(code);
|
||||
const lookup = await api<{ found: boolean; product?: Partial<Product> }>(`/homes/${activeHome.id}/products/lookup/${code}`);
|
||||
setPrefill(lookup.product ?? { barcode: code });
|
||||
}
|
||||
}).then((scannerControls) => { controls = scannerControls; }).catch(() => undefined);
|
||||
return () => { stop = true; controls?.stop(); };
|
||||
}, [activeHome]);
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<h1 className="flex items-center gap-2 text-3xl font-bold"><ScanLine /> {t('scanBarcode')}</h1>
|
||||
<video ref={videoRef} className="aspect-video w-full rounded-lg bg-gray-950 object-cover" muted playsInline />
|
||||
{barcode ? <Panel title={`${t('barcode')}: ${barcode}`}><ProductForm initial={prefill} onSaved={() => setPrefill({})} /></Panel> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user