diff --git a/nginx/default.conf b/nginx/default.conf index 6823bd8..42f4050 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -41,6 +41,7 @@ server { } location /api/ { + client_max_body_size 5m; proxy_pass http://backend:8001/api/; proxy_http_version 1.1; proxy_set_header Host $host; diff --git a/src/App.css b/src/App.css index f2a3756..c43a6f4 100644 --- a/src/App.css +++ b/src/App.css @@ -19,7 +19,7 @@ display: flex; align-items: center; justify-content: center; - background: var(--color-bg-soft, #eef6ee); + background: transparent; } .page-loader__tree { @@ -2612,9 +2612,6 @@ } /* -- Page loader -- */ -[data-theme="dark"] .page-loader { - background: #0f0f0f; -} /* -- Alert messages -- */ [data-theme="dark"] .alert-msg--error { diff --git a/src/components/BgImageCropper.tsx b/src/components/BgImageCropper.tsx index 14fa523..4f00709 100644 --- a/src/components/BgImageCropper.tsx +++ b/src/components/BgImageCropper.tsx @@ -135,16 +135,28 @@ export function BgImageCropper({ imageSrc, aspectRatio, onCrop, onCancel }: Prop const srcH = cb.h / scale // Output resolution: screen size × device pixel ratio, capped at 1440px wide + // Then scale down resolution until the result is under 3MB (keeping quality at 0.92) + const MAX_BYTES = 1 * 1024 * 1024 const dpr = Math.min(window.devicePixelRatio || 1, 2) - const outW = Math.min(Math.round(window.innerWidth * dpr), 1440) - const outH = Math.round(outW / aspectRatio) + let w = Math.min(Math.round(window.innerWidth * dpr), 1440) const canvas = document.createElement('canvas') - canvas.width = outW - canvas.height = outH const ctx = canvas.getContext('2d')! - ctx.drawImage(img, srcX, srcY, srcW, srcH, 0, 0, outW, outH) - onCrop(canvas.toDataURL('image/jpeg', 0.92)) + let dataUrl: string + + do { + const h = Math.round(w / aspectRatio) + canvas.width = w + canvas.height = h + ctx.drawImage(img, srcX, srcY, srcW, srcH, 0, 0, w, h) + dataUrl = canvas.toDataURL('image/jpeg', 0.92) + // base64 → approx byte size + const bytes = (dataUrl.length - dataUrl.indexOf(',') - 1) * 0.75 + if (bytes <= MAX_BYTES) break + w = Math.round(w * 0.8) + } while (w > 200) + + onCrop(dataUrl!) }, [aspectRatio, onCrop]) return ( diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 990d1e0..2c5e808 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -16,6 +16,14 @@ import ClockTimePicker from '../components/ClockTimePicker' const MAX_PHOTO_SIZE = 200 // px — resize to 200x200 const MAX_BG_HISTORY = 3 +const MAX_BG_IMAGE_BYTES = 1 * 1024 * 1024 // 1 MB per image +const MAX_BG_PAYLOAD_BYTES = MAX_BG_HISTORY * MAX_BG_IMAGE_BYTES // 9 MB total + +/** Approximate decoded byte size of a base64 data URL */ +function dataUrlBytes(dataUrl: string): number { + const base64 = dataUrl.slice(dataUrl.indexOf(',') + 1) + return Math.round(base64.length * 0.75) +} function resizeImage(file: File): Promise { return new Promise((resolve, reject) => { @@ -196,8 +204,22 @@ export default function SettingsPage() { const handleCropDone = async (dataUrl: string) => { if (cropperSrc) URL.revokeObjectURL(cropperSrc) setCropperSrc(null) + + // Guard: individual image must be within limit (cropper already enforces this, + // but double-check in case of future code paths) + if (dataUrlBytes(dataUrl) > MAX_BG_IMAGE_BYTES) { + setMessage({ type: 'error', text: 'Image is too large. Please try a smaller photo.' }) + return + } + // Prepend to history, deduplicate, cap at MAX_BG_HISTORY - const newHistory = [dataUrl, ...bgImages.filter(i => i !== dataUrl)].slice(0, MAX_BG_HISTORY) + let newHistory = [dataUrl, ...bgImages.filter(i => i !== dataUrl)].slice(0, MAX_BG_HISTORY) + + // Guard: total payload must stay within limit — drop oldest images until it fits + while (newHistory.reduce((sum, img) => sum + dataUrlBytes(img), 0) > MAX_BG_PAYLOAD_BYTES) { + newHistory = newHistory.slice(0, -1) + } + await bgUpdate({ backgroundImage: dataUrl, backgroundImages: newHistory }) } @@ -453,35 +475,26 @@ export default function SettingsPage() {
- {/* Daily Reminder */} -
+ {/* Daily Reminder — disabled for now, logic preserved */} +
- -
+