diff --git a/docker-compose.yml b/docker-compose.yml
index 5e25558..4cb908d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -22,11 +22,13 @@ services:
depends_on:
db:
condition: service_healthy
+ volumes:
+ - ./backend/data:/app/data
environment:
DATABASE_URL: postgresql://luna:${DB_PASSWORD:-luna-recipes-secret-2026}@db:5432/luna_recipes
- JWT_SECRET: ${JWT_SECRET:-luna-jwt-change-in-prod}
- JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET:-luna-refresh-change-in-prod}
- COOKIE_SECRET: ${COOKIE_SECRET:-luna-cookie-change-in-prod}
+ JWT_SECRET: ${JWT_SECRET:-luna-recipes-jwt-secret-change-in-prod-2026}
+ JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET:-luna-recipes-refresh-secret-change-in-prod-2026}
+ COOKIE_SECRET: ${COOKIE_SECRET:-luna-recipes-cookie-secret-change-in-prod-2026}
PORT: "6001"
NODE_ENV: production
ports:
diff --git a/frontend/nginx.conf b/frontend/nginx.conf
index 7284860..6926b82 100644
--- a/frontend/nginx.conf
+++ b/frontend/nginx.conf
@@ -4,6 +4,14 @@ server {
root /usr/share/nginx/html;
index index.html;
+ # Image proxy to backend
+ location /images/ {
+ proxy_pass http://backend:6001;
+ proxy_set_header Host $host;
+ expires 30d;
+ add_header Cache-Control "public, immutable";
+ }
+
# API proxy to backend
location /api/ {
proxy_pass http://backend:6001;
diff --git a/frontend/src/components/profile/HouseholdCard.tsx b/frontend/src/components/profile/HouseholdCard.tsx
index 762829a..231a845 100644
--- a/frontend/src/components/profile/HouseholdCard.tsx
+++ b/frontend/src/components/profile/HouseholdCard.tsx
@@ -1,6 +1,5 @@
import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
-import { motion, AnimatePresence } from 'framer-motion'
import { Home, Users, Copy, RefreshCw, LogOut, X, Check } from 'lucide-react'
import {
getMyHousehold,
@@ -23,13 +22,20 @@ export function HouseholdCard() {
const [code, setCode] = useState('')
const [copied, setCopied] = useState(false)
- const { data, isLoading } = useQuery({
+ const { data: household, isLoading } = useQuery({
queryKey: ['household'],
- queryFn: getMyHousehold,
+ queryFn: async () => {
+ try {
+ const res = await getMyHousehold()
+ return res.data as Household
+ } catch {
+ // 404 = no household, that's fine
+ return null
+ }
+ },
retry: false,
})
- const household: Household | null = data?.data ?? null
const myRole = household?.members.find((m) => m.user_id === user?.id)?.role
const createMut = useMutation({
@@ -73,14 +79,29 @@ export function HouseholdCard() {
onError: (err: Error) => showToast.error(err.message),
})
- const copyCode = async () => {
+ // Clipboard fallback for HTTP (no navigator.clipboard)
+ const copyCode = () => {
if (!household?.invite_code) return
try {
- await navigator.clipboard.writeText(household.invite_code)
+ if (navigator.clipboard && window.isSecureContext) {
+ navigator.clipboard.writeText(household.invite_code)
+ } else {
+ // Fallback: textarea trick
+ const ta = document.createElement('textarea')
+ ta.value = household.invite_code
+ ta.style.position = 'fixed'
+ ta.style.left = '-9999px'
+ document.body.appendChild(ta)
+ ta.select()
+ document.execCommand('copy')
+ document.body.removeChild(ta)
+ }
setCopied(true)
+ showToast.success('Code kopiert!')
setTimeout(() => setCopied(false), 2000)
} catch {
- showToast.error('Kopieren fehlgeschlagen')
+ // Last resort: show code in prompt
+ showToast.error('Kopiere den Code manuell: ' + household.invite_code)
}
}
@@ -88,48 +109,6 @@ export function HouseholdCard() {
return
}
- // Mini modal component
- const MiniModal = ({
- open,
- onClose,
- title,
- children,
- }: {
- open: boolean
- onClose: () => void
- title: string
- children: React.ReactNode
- }) => (
-
- {open && (
-
- e.stopPropagation()}
- >
-
-
{title}
-
-
- {children}
-
-
- )}
-
- )
-
// No household
if (!household) {
return (
@@ -158,39 +137,80 @@ export function HouseholdCard() {
- setShowCreate(false)} title="Haushalt erstellen">
- setName(e.target.value)}
- placeholder="z.B. Luna & Marc"
- className="w-full bg-surface border border-sand rounded-xl px-4 py-3 text-espresso placeholder:text-warm-grey/50 focus:outline-none focus:ring-2 focus:ring-primary/30 min-h-[44px] mb-4"
- />
-