Inject the Umami tracking script into the root <head> via Next.js's
Script component with the afterInteractive strategy so it loads after
the page is interactive and never blocks first paint.
- src: http://umami-vmyngbmelll45wokwr270wbx.169.239.180.130.sslip.io/script.js
- website-id: 7655a10d-78f7-4391-a0b5-1f8449d4598a
The script is self-hosted at the Umami instance URL and does not set
any cookies of its own; it reads the visitor IP / User-Agent directly
from the request for the standard, privacy-friendly page-view events.
The previous .dockerignore excluded the scripts/ directory to keep
the runtime image lean, but build-og-image.mjs is invoked as part
of 'npm run build' (which is 'npm run og-image && next build'). Without
the script in the build context, the Docker build fails with:
Error: Cannot find module '/app/scripts/build-og-image.mjs'
The scripts/ directory is needed at build time only; the final
runner image doesn't execute any of these files, so including them
in the build context is harmless. The script is small (~4.6 KB)
and only runs during 'npm run build' in the builder stage.
Two small cleanup changes:
1. Drop the 'CSV uploads' card at the bottom of the Fuel page.
The Template / Upload CSV buttons in the topbar still let users
ingest readings, but the recent-uploads mini-list was redundant —
the Uploads page already shows every upload. The fuelCsvTemplate
import is still in use by the topbar download button.
Also clean up the now-unused uploads/setUploads state, the
FileSpreadsheet icon, and the unused CardDescription / Badge /
FileSpreadsheet references in the bottom block.
2. Plant a guaranteed critical anomaly on the most recent working
day so the Anomaly log's first page and the dashboard's 'Needs
attention' rail show one for demo purposes. The previous random
spike day often landed too far back to be visible without
paginating. Now MI-TT-07 (one of the tipper trucks) always has
a 2.4x-2.8x theft spike on the latest day, while the other
eight vehicles still get a random earlier spike for variety.
3. Sort the Anomaly log by date desc, then by severity desc
(critical → alert → watch → ok) so actionable entries surface
first within each day. The dashboard's 'Needs attention' rail
also benefits from the more recent critical sitting at the top.
4. Restore the Uploads nav item in the sidebar that was removed
in the previous commit by mistake.
When someone shares a link to the dashboard in WhatsApp, Facebook,
LinkedIn, Discord, etc., the platform now shows a custom preview
instead of a plain blank card.
- public/og-image.png 1200x630 PNG with the brand mark, the
MEGA INFRASTRUCTURE wordmark, and a
tagline over a dark gradient with the
brand accent bars.
- public/fonts/ Inter ExtraBold (woff) used by the OG image
generator so the rendered text is identical
on every machine.
- scripts/build-og-image.mjs
Generates the image from an SVG template
using sharp. The font is loaded from the
local file (or Google Fonts CDN as fallback)
and embedded as base64 inside the SVG so
the renderer doesn't depend on system
fonts.
- src/app/layout.tsx Adds the openGraph + twitter metadata
pointing at /og-image.png so the preview
appears in link-unfurl contexts.
- package.json 'npm run og-image' runs the generator;
'npm run build' chains it via prebuild so
the image is regenerated on every CI build.
New /dashboard/uploads page with a 2x2 grid of drop zones, one per
ingest source:
- Orders (orange) — CSV / XLS / XLSX, max 10 MB
- Delivery Notes (teal) — PDF / images, max 20 MB
- Fuel Logs (yellow-green) — CSV / XLS / XLSX, max 5 MB
- Time Sheets (green) — CSV / XLS / XLSX, max 5 MB
Each block has:
- A coloured left stripe + icon disc in the accent colour
- Title, format-count badge, and a long description of what to upload
- File-format chips and a max-size hint
- A drag-and-drop drop zone that also accepts files via click
- A primary 'Upload X' button in the accent colour
- A 'Recent' list showing the last 4 uploads with status badges
When files are dropped or picked, the client POSTs to
/api/upload with a 'type' field. The endpoint validates MIME and size
against the kind-specific rules in src/lib/uploads.ts and returns a
JSON descriptor with accepted/rejected entries. New entries are
prepended to the page's recent-uploads list in real time, and a
bottom-right toast shows accepted/rejected counts.
The sidebar gets a new 'Uploads' nav item with the upload icon.
Files:
- src/lib/types.ts type definitions
- src/lib/uploads.ts acceptance rules + mock history
- src/app/api/upload/route.ts type-aware upload handler
- src/app/dashboard/uploads/page.tsx the new page
- src/components/layout/sidebar.tsx Uploads nav entry
The card showed hardcoded '5 projects · 3 flagged · 2 on schedule'
stats that were never updated from the mock data and duplicated
information in the Sites module. Removing it gives the sidebar a
cleaner Logo → Nav → Lock structure and means the Lock screen button
is visible without scrolling on standard heights.
Also align the mobile drawer's close-button breakpoint from lg to xl
to match the rest of the sidebar's breakpoint logic.
The postinstall hook runs scripts/patch-next-dev-blocker.cjs, but at
that point in the Docker build only package.json and package-lock.json
have been copied — the scripts/ directory isn't there yet, so the
hook fails with MODULE_NOT_FOUND.
The patch is dev-only (it mutates Next 16's dev-only cross-site
guard, which is never loaded in production), so it's safe to skip
during the Docker build. Two changes:
1. Dockerfile: use 'npm ci --ignore-scripts' in the deps stage so
the postinstall hook doesn't run before the scripts/ directory
is available.
2. scripts/patch-next-dev-blocker.cjs: bail out cleanly with exit 0
when node_modules/next is missing, so the script is also safe to
run in any intermediate state.