2026-09-12 07:35:35 +00:00
|
|
|
import PocketBase from "pocketbase";
|
|
|
|
|
|
Round 4: regressions fixed + mobile standard
Part A — Round 3 regressions:
- A.1: verify-cost-reconciliation.mjs now loads .env.local, wraps in try/catch, exits 1 on any failure
- A.2: Vehicle Costs restored Santam — back to R 14,996.31 (matches row-by-row sum)
- A.3: incl<excl / VAT math / VAT>20% now FATAL — seed exits 2 with full report (4 anomalies on Charl-CCS + Buildsmart)
- A.4: Reconciliation target R 694,962.93 with both deltas itemised (Personnel +R19,800, Santam -R5,322.44, unexplained R 0.00)
- A.5: 'Lines' view uses actual lines (recharts LineChart), no fills; fills only in 'Cost stack'
Part B — Mobile standard:
- B.1: header wraps to 2 rows on mobile; YTD badge cards stack; page-wide overflow eliminated on /income, /charts, /overheads, /running, /expenses
- B.2: IncomeView card row layout stacked — name gets its own line on mobile, never truncated to single char
- B.3: Plant type filter is a <select> dropdown below lg (was 18 pills wrapping to 13 rows); same for sort on /income and /running
- B.4: Plant table first column sticky; -webkit-overflow-scrolling: touch; tables scroll inside their own container
- B.5: All buttons/links use min-h-[44px] tap targets; text below 12px is bumped where it matters
- B.6: Charts legend wraps below title; chart min-height h-44 → h-52 on mobile
- B.7: paddingTop env(safe-area-inset-top) on sticky header for notch/home indicator clearance
AGENTS.md created with the standing mobile checklist rule.
2026-09-12 10:05:43 +00:00
|
|
|
// No fallback on purpose. NEXT_PUBLIC_PB_URL is inlined at build time, so a
|
|
|
|
|
// default here would silently bake the wrong database into a production image
|
|
|
|
|
// whenever the build arg is missing. Better to fail at startup than to serve
|
|
|
|
|
// someone else's data. The Dockerfile checks for it before building too.
|
|
|
|
|
export const PB_URL = process.env.NEXT_PUBLIC_PB_URL ?? "";
|
|
|
|
|
|
|
|
|
|
if (!PB_URL) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
"NEXT_PUBLIC_PB_URL is not set. Set it in .env.local for local dev, and as " +
|
|
|
|
|
"a Coolify Build Argument (not just an env var) for deploys — see COOLIFY.md.",
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-09-12 07:35:35 +00:00
|
|
|
|
|
|
|
|
// Lazy singleton — the browser SDK auto-cancels in-flight requests when the
|
|
|
|
|
// client is recreated, so we keep one instance for the whole browser session.
|
|
|
|
|
let _pb: PocketBase | null = null;
|
|
|
|
|
export function getPB(): PocketBase {
|
|
|
|
|
if (!_pb) {
|
|
|
|
|
_pb = new PocketBase(PB_URL);
|
|
|
|
|
_pb.autoCancellation(false); // critical for SSR + parallel fetches
|
|
|
|
|
}
|
|
|
|
|
return _pb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function isSuperuserLoggedIn(): Promise<boolean> {
|
|
|
|
|
const pb = getPB();
|
|
|
|
|
return pb.authStore.isValid && pb.authStore.record?.collectionName === "_superusers";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Server-only auth helper used by server components / route handlers.
|
|
|
|
|
// Part 5.3: PocketBase 0.23+ renamed the admin collection to `_superusers`.
|
|
|
|
|
// `pb.admins.authWithPassword()` is deprecated — use the collection endpoint instead.
|
|
|
|
|
export async function serverAuth() {
|
|
|
|
|
const pb = new PocketBase(PB_URL);
|
|
|
|
|
pb.autoCancellation(false);
|
|
|
|
|
const email = process.env.PB_ADMIN;
|
|
|
|
|
const pass = process.env.PB_PASS;
|
|
|
|
|
if (!email || !pass) return pb;
|
|
|
|
|
if (pb.authStore.isValid) return pb;
|
|
|
|
|
try {
|
|
|
|
|
await pb.collection("_superusers").authWithPassword(email, pass);
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore — read endpoints still work with superuser token-less for open rules
|
|
|
|
|
}
|
|
|
|
|
return pb;
|
|
|
|
|
}
|