"use client"; /** * /admin/water-log/settings * * Site-admin-facing settings page for the Water Log module. Controls: * - Whether `/water/admin` is enabled * - 4-digit admin PIN (regenerate on demand) * - Session duration (hours) * - Per-coarse-permission flags (edit / delete / export) * - High/low alert phone + enable flag * * Visual language: Field Almanac. Same cream + forest palette as * the main WaterLogAdminPanel, but trimmed to a single column for * a focused "form" feel. */ import { useReducer, useEffect, useCallback } from "react"; import { getWaterAdminSettings, saveWaterAdminSettings, regenerateAdminPin, type AdminSettings, } from "@/actions/water-log/settings"; import SmartsheetHub from "@/components/admin/water-log/SmartsheetHub"; import SmartsheetIntegrationCard from "@/components/admin/water-log/SmartsheetIntegrationCard"; import TimeTrackingSmartsheetCard from "@/components/admin/time-tracking/TimeTrackingSmartsheetCard"; const TUXEDO_BRAND_ID = "64294306-5f42-463d-a5e8-2ad6c81a96de"; type Message = { type: "success" | "error"; text: string } | null; type State = { settings: AdminSettings | null; loading: boolean; saving: boolean; regenerating: boolean; revealedPin: string | null; message: Message; enabled: boolean; sessionDuration: number; canEdit: boolean; canDelete: boolean; canExport: boolean; alertPhone: string; alertsEnabled: boolean; }; type Action = | { type: "LOAD_START" } | { type: "LOAD_SUCCESS"; data: AdminSettings; } | { type: "LOAD_DONE" } | { type: "SAVE_START" } | { type: "SAVE_SUCCESS"; settings: AdminSettings | undefined } | { type: "SAVE_FAIL"; message: Message } | { type: "SAVE_DONE" } | { type: "REGEN_START" } | { type: "REGEN_SUCCESS"; pin: string; message: Message } | { type: "REGEN_FAIL"; message: Message } | { type: "REGEN_DONE" } | { type: "DISMISS_PIN" } | { type: "CLEAR_MESSAGE" } | { type: "SET_ENABLED"; value: boolean } | { type: "SET_SESSION_DURATION"; value: number } | { type: "SET_CAN_EDIT"; value: boolean } | { type: "SET_CAN_DELETE"; value: boolean } | { type: "SET_CAN_EXPORT"; value: boolean } | { type: "SET_ALERT_PHONE"; value: string } | { type: "SET_ALERTS_ENABLED"; value: boolean }; const initialState: State = { settings: null, loading: true, saving: false, regenerating: false, revealedPin: null, message: null, enabled: false, sessionDuration: 12, canEdit: true, canDelete: true, canExport: true, alertPhone: "", alertsEnabled: false, }; function reducer(state: State, action: Action): State { switch (action.type) { case "LOAD_START": return { ...state, loading: true }; case "LOAD_SUCCESS": return { ...state, settings: action.data, enabled: action.data.enabled, sessionDuration: action.data.sessionDurationHours, canEdit: action.data.canEditEntries, canDelete: action.data.canDeleteEntries, canExport: action.data.canExportCsv, alertPhone: action.data.alertPhone ?? "", alertsEnabled: action.data.alertsEnabled, }; case "LOAD_DONE": return { ...state, loading: false }; case "SAVE_START": return { ...state, saving: true, message: null }; case "SAVE_SUCCESS": return { ...state, message: { type: "success", text: "Settings saved" }, settings: action.settings ?? state.settings, }; case "SAVE_FAIL": return { ...state, message: action.message }; case "SAVE_DONE": return { ...state, saving: false }; case "REGEN_START": return { ...state, regenerating: true, message: null }; case "REGEN_SUCCESS": return { ...state, revealedPin: action.pin, message: action.message, }; case "REGEN_FAIL": return { ...state, message: action.message }; case "REGEN_DONE": return { ...state, regenerating: false }; case "DISMISS_PIN": return { ...state, revealedPin: null }; case "CLEAR_MESSAGE": return { ...state, message: null }; case "SET_ENABLED": return { ...state, enabled: action.value }; case "SET_SESSION_DURATION": return { ...state, sessionDuration: action.value }; case "SET_CAN_EDIT": return { ...state, canEdit: action.value }; case "SET_CAN_DELETE": return { ...state, canDelete: action.value }; case "SET_CAN_EXPORT": return { ...state, canExport: action.value }; case "SET_ALERT_PHONE": return { ...state, alertPhone: action.value }; case "SET_ALERTS_ENABLED": return { ...state, alertsEnabled: action.value }; default: return state; } } export default function WaterLogSettingsPage() { const [state, dispatch] = useReducer(reducer, initialState); const { settings, loading, saving, regenerating, revealedPin, message, enabled, sessionDuration, canEdit, canDelete, canExport, alertPhone, alertsEnabled, } = state; const loadSettings = useCallback(async () => { dispatch({ type: "LOAD_START" }); const data = await getWaterAdminSettings(TUXEDO_BRAND_ID); if (data) { dispatch({ type: "LOAD_SUCCESS", data }); } dispatch({ type: "LOAD_DONE" }); }, []); useEffect(() => { // Load on mount — setState-in-effect is intentional here // because we're hydrating from server data. void loadSettings(); }, [loadSettings]); async function handleSave(e: React.FormEvent) { e.preventDefault(); dispatch({ type: "SAVE_START" }); const result = await saveWaterAdminSettings(TUXEDO_BRAND_ID, { enabled, sessionDurationHours: sessionDuration, canEditEntries: canEdit, canDeleteEntries: canDelete, canExportCsv: canExport, alertPhone: alertPhone || null, alertsEnabled, }); if (result.success) { dispatch({ type: "SAVE_SUCCESS", settings: result.settings }); } else { dispatch({ type: "SAVE_FAIL", message: { type: "error", text: result.error ?? "Save failed" }, }); } dispatch({ type: "SAVE_DONE" }); } async function handleRegenerate() { if (!confirm("Regenerate the admin PIN? All current admin sessions will be signed out.")) { return; } dispatch({ type: "REGEN_START" }); const result = await regenerateAdminPin(TUXEDO_BRAND_ID); if (result.success && result.pin) { dispatch({ type: "REGEN_SUCCESS", pin: result.pin, message: { type: "success", text: "New PIN generated. Save it now — it will not be shown again.", }, }); } else { dispatch({ type: "REGEN_FAIL", message: { type: "error", text: result.error ?? "Failed to regenerate PIN" }, }); } dispatch({ type: "REGEN_DONE" }); } if (loading) { return (
§ 04 — Settings
Configure PIN access for the field admin portal at /water/admin.
This is separate from the platform admin login.
§ 05 — Integrations
Connect one Smartsheet workbook and pick which sheet each feature writes to. The token is encrypted at rest; per-feature cards below manage only their sheet mapping.
{subtitle}
)}{label}
{description &&{description}
}