chore: initial project setup with backend, frontend, and infrastructure
Some checks failed
CI / backend (push) Failing after 31s
CI / frontend (push) Successful in 40s
CI / docker (push) Has been skipped

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:
2026-06-04 10:26:38 +02:00
commit 3792ca55e7
74 changed files with 13417 additions and 0 deletions

View 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>
);
}