ac88584b8b
Deploy to route.crispygoat.com / deploy (push) Successful in 4m13s
Replaces the legacy 1000+ line WaterFieldClient with a polished
three-screen state machine tuned for phones and tablets in the field:
PIN -> Headgates -> Log -> Success
Design (Apple Human Interface Guidelines):
- System font stack (-apple-system, BlinkMacSystemFont, 'SF Pro Display')
instead of the admin's Fraunces/Manrope for native iOS feel
- iOS systemGreen (#34C759) primary success, forest-900 brand accent
- iOS systemGroupedBackground (#F2F2F7) surface, white cards,
rgba(60,60,67,0.x) text greys
- 44pt minimum touch targets, 58pt primary buttons, 68pt PIN boxes
- env(safe-area-inset-top/bottom) respected on every screen
- 100dvh shell so iOS Safari chrome collapse doesn't reflow
Screens:
- PinScreen: 4 iOS-style digit boxes with blinking caret, big
'Unlock' button (disabled until 4 digits entered), branded
'TUXEDO WATER LOG' header, error shake on bad PIN
- HeadgateList: grouped list of large tappable cards showing
name + geocode (notes) + unit + last-logged relative time,
pull-to-refresh, empty state with refresh CTA
- LogForm: sticky headgate card + 44pt numeric input +
SegmentedControl for units (CFS / GPM / gal / ac-in / ac-ft)
+ live 'Logged now' timestamp + optional 500-char notes +
green Submit Log button with inline measurement/unit preview
- SuccessScreen: animated ring + checkmark draw-on, recap card,
'Log Another Entry' (green) and 'Back to Headgates' (secondary)
Reusable primitives (mobile/):
- SegmentedControl: iOS-style sliding pill with spring timing
- PullToRefresh: callback-based (no router.refresh) with resistance
- icons.tsx: inline SVG icon set (Lock, Droplet, Gauge, Refresh, etc.)
Backend:
- getWaterHeadgates extended to include notes (the 'geocode') and
last_used_at for richer card display
- No new routes, no DB migration - reuses verifyWaterPin,
submitWaterEntry, logoutWater server actions unchanged
Quality gates:
- tsc --noEmit clean
- eslint clean on all new files
- npm run build succeeds (/water rendered as static)
- prefers-reduced-motion respected (existing global guard)
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* WaterFieldClient — entrypoint for the public `/water` route.
|
|
*
|
|
* As of 2026-07, this delegates to the new mobile-first
|
|
* `MobileWaterApp` state machine. The legacy implementation that
|
|
* lived here supported language selection and role-based admin
|
|
* routing; both are reachable from the new flow via dedicated
|
|
* secondary routes (`/water/admin` for the admin side, the QR
|
|
* picker for Spanish signage in the future) but the primary
|
|
* `/water` flow is now a clean three-screen Apple-HIG experience:
|
|
* PIN → Headgates → Log
|
|
*
|
|
* The wrapping div uses `100dvh` so the iOS Safari address bar
|
|
* collapse doesn't trigger layout jumps, and `100vh` as a
|
|
* fallback for older browsers.
|
|
*/
|
|
import { Suspense } from "react";
|
|
import { MobileWaterApp } from "./mobile/MobileWaterApp";
|
|
|
|
export default function WaterFieldClient() {
|
|
return (
|
|
<div
|
|
className="fixed inset-0 bg-[#F2F2F7]"
|
|
style={{
|
|
// 100dvh = dynamic viewport height (excludes collapsing
|
|
// iOS Safari chrome). 100svh excludes the bottom safe-area
|
|
// inset, which is what we want when the soft keyboard is
|
|
// up. Fallback to 100vh for older browsers.
|
|
height: "100dvh",
|
|
minHeight: "100vh",
|
|
// Disable rubber-band scrolling on the whole shell — each
|
|
// screen manages its own internal scroll.
|
|
overscrollBehavior: "none",
|
|
}}
|
|
>
|
|
<Suspense
|
|
fallback={
|
|
<div className="flex h-full items-center justify-center">
|
|
<div className="text-[15px] text-[rgba(60,60,67,0.55)]">
|
|
Loading…
|
|
</div>
|
|
</div>
|
|
}
|
|
>
|
|
<MobileWaterApp />
|
|
</Suspense>
|
|
</div>
|
|
);
|
|
} |