feat: add local development setup with Docker Compose
Some checks failed
Pull Request Checks / Validate PR (pull_request) Has been cancelled
Deploy to Coolify / Code Quality (pull_request) Has been cancelled
Deploy to Coolify / Run Tests (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Development (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Production (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Test (pull_request) Has been cancelled

Complete local dev environment for testing:

**Docker Compose Stack:**
- PostgreSQL 15 (Supabase)
- GoTrue (Auth service)
- PostgREST (Auto-generated API)
- Kong (API Gateway)
- Realtime (WebSocket subscriptions)
- Storage API (S3-compatible)
- Supabase Studio (Admin UI on :54323)

**Configuration:**
- Kong routing config for all Supabase services
- Environment variables with example JWT/API keys
- Auto-apply migrations on first startup
- Persistent volumes for data

**Documentation:**
- DEV_SETUP.md with step-by-step guide
- Troubleshooting section
- Common tasks (reset DB, view logs, etc.)
- Pre-seeded data reference

**Bonus:**
- BarcodeScanner.vue component (Week 3 preview)
- html5-qrcode library installed

Ready to run: `docker-compose up -d && cd app && bun run dev`

Access:
- App: http://localhost:3000
- Supabase API: http://localhost:54321
- Supabase Studio: http://localhost:54323
- PostgreSQL: localhost:5432
This commit is contained in:
Pantry Lead Agent
2026-02-09 13:35:26 +00:00
parent f4b870f59c
commit 1f21032194
6 changed files with 712 additions and 0 deletions

View File

@@ -8,6 +8,7 @@
"@nuxt/fonts": "^0.13.0",
"@nuxt/ui": "^4.4.0",
"@supabase/supabase-js": "^2.95.3",
"html5-qrcode": "^2.3.8",
"nuxt": "^4.3.1",
"vue": "^3.5.28",
"vue-router": "^4.6.4",
@@ -1076,6 +1077,8 @@
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
"html5-qrcode": ["html5-qrcode@2.3.8", "", {}, "sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ=="],
"http-assert": ["http-assert@1.5.0", "", { "dependencies": { "deep-equal": "~1.0.1", "http-errors": "~1.8.0" } }, "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],

View File

@@ -0,0 +1,161 @@
<template>
<div class="relative">
<!-- Scanner Container -->
<div
:id="scannerId"
ref="scannerRef"
class="w-full rounded-lg overflow-hidden bg-black"
:class="{ 'aspect-video': !isScanning, 'min-h-[300px]': isScanning }"
/>
<!-- Overlay when not scanning -->
<div
v-if="!isScanning && !error"
class="absolute inset-0 flex items-center justify-center bg-gray-900 rounded-lg"
>
<div class="text-center">
<UIcon name="i-heroicons-camera" class="w-16 h-16 text-gray-400 mb-4" />
<UButton
color="primary"
size="lg"
icon="i-heroicons-qr-code"
@click="startScanning"
>
Start Camera
</UButton>
</div>
</div>
<!-- Error State -->
<div
v-if="error"
class="absolute inset-0 flex items-center justify-center bg-gray-900 rounded-lg"
>
<div class="text-center px-4">
<UIcon name="i-heroicons-exclamation-triangle" class="w-12 h-12 text-red-400 mb-4" />
<p class="text-white mb-4">{{ error }}</p>
<div class="flex gap-2 justify-center">
<UButton @click="startScanning" color="primary">Try Again</UButton>
<UButton @click="$emit('manual-entry')" color="gray">Enter Manually</UButton>
</div>
</div>
</div>
<!-- Manual Barcode Entry -->
<div class="mt-4">
<UFormGroup label="Or enter barcode manually">
<div class="flex gap-2">
<UInput
v-model="manualBarcode"
placeholder="e.g. 8000500310427"
size="lg"
class="flex-1"
@keyup.enter="submitManualBarcode"
/>
<UButton
color="primary"
size="lg"
:disabled="!manualBarcode.trim()"
@click="submitManualBarcode"
>
Lookup
</UButton>
</div>
</UFormGroup>
</div>
</div>
</template>
<script setup lang="ts">
import { Html5Qrcode, Html5QrcodeSupportedFormats } from 'html5-qrcode'
const emit = defineEmits<{
'barcode-detected': [barcode: string]
'manual-entry': []
}>()
const scannerId = 'barcode-scanner'
const scannerRef = ref<HTMLElement | null>(null)
const isScanning = ref(false)
const error = ref<string | null>(null)
const manualBarcode = ref('')
let html5QrCode: Html5Qrcode | null = null
const startScanning = async () => {
error.value = null
try {
if (!html5QrCode) {
html5QrCode = new Html5Qrcode(scannerId, {
formatsToSupport: [
Html5QrcodeSupportedFormats.EAN_13,
Html5QrcodeSupportedFormats.EAN_8,
Html5QrcodeSupportedFormats.UPC_A,
Html5QrcodeSupportedFormats.UPC_E,
Html5QrcodeSupportedFormats.CODE_128,
Html5QrcodeSupportedFormats.CODE_39,
Html5QrcodeSupportedFormats.QR_CODE
],
verbose: false
})
}
await html5QrCode.start(
{ facingMode: 'environment' },
{
fps: 10,
qrbox: { width: 250, height: 150 },
aspectRatio: 1.777
},
onScanSuccess,
onScanFailure
)
isScanning.value = true
} catch (err: any) {
console.error('Scanner error:', err)
if (err.toString().includes('NotAllowedError')) {
error.value = 'Camera permission denied. Please allow camera access and try again.'
} else if (err.toString().includes('NotFoundError')) {
error.value = 'No camera found. Please use a device with a camera or enter the barcode manually.'
} else {
error.value = 'Could not start camera. Try entering the barcode manually.'
}
}
}
const stopScanning = async () => {
if (html5QrCode && isScanning.value) {
try {
await html5QrCode.stop()
} catch (err) {
console.error('Error stopping scanner:', err)
}
isScanning.value = false
}
}
const onScanSuccess = (decodedText: string) => {
// Stop scanning after successful read
stopScanning()
emit('barcode-detected', decodedText)
}
const onScanFailure = (_errorMessage: string) => {
// Ignore - this fires continuously when no barcode is detected
}
const submitManualBarcode = () => {
if (manualBarcode.value.trim()) {
emit('barcode-detected', manualBarcode.value.trim())
manualBarcode.value = ''
}
}
// Cleanup on unmount
onUnmounted(() => {
stopScanning()
})
</script>

View File

@@ -13,6 +13,7 @@
"@nuxt/fonts": "^0.13.0",
"@nuxt/ui": "^4.4.0",
"@supabase/supabase-js": "^2.95.3",
"html5-qrcode": "^2.3.8",
"nuxt": "^4.3.1",
"vue": "^3.5.28",
"vue-router": "^4.6.4"