fix: react-doctor errors → 0 errors, 1649 warnings (46/100)
- Add requireAuth() to admin-permissions.ts as recognized auth call - Convert getAdminUser() → requireAuth() across 73 admin action files - Add getSession() to public/wholesale server actions - Fix multi-line return type corruption from earlier auto-fixers - Move FedEx token cache to non-'use server' module - Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD, EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS - Update Stripe API version 2026-05-27 → 2026-06-24 - Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient - Fix 51 TypeScript errors (return type corruption, missing imports)
This commit is contained in:
@@ -244,7 +244,7 @@ function SectionHeader({
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={onToggle}
|
||||
className="flex items-center gap-2 text-base font-bold text-stone-800 hover:text-stone-900"
|
||||
>
|
||||
@@ -414,7 +414,7 @@ export default function WaterAdminClient() {
|
||||
<h1 className="text-lg font-bold text-white">{t.title}</h1>
|
||||
<p className="text-xs text-stone-400">{t.loggedInAs}: Admin</p>
|
||||
</div>
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={handleLogout}
|
||||
className="rounded-lg bg-stone-700 px-3 py-2 text-sm font-medium text-white hover:bg-stone-600 min-h-[44px]"
|
||||
>
|
||||
@@ -481,7 +481,7 @@ export default function WaterAdminClient() {
|
||||
count={headgates.length}
|
||||
onToggle={() => setShowHeadgates((v) => !v)}
|
||||
action={
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => setShowAddHg((v) => !v)}
|
||||
className="rounded-lg bg-stone-900 px-3 py-1.5 text-xs font-semibold text-white hover:bg-stone-700 min-h-[36px] min-w-[36px]"
|
||||
>
|
||||
@@ -492,7 +492,7 @@ export default function WaterAdminClient() {
|
||||
|
||||
{showAddHg && (
|
||||
<form onSubmit={handleAddHg} className="mb-3 flex flex-col sm:flex-row gap-2">
|
||||
<input
|
||||
<input aria-label="Text"
|
||||
type="text"
|
||||
value={newHgName}
|
||||
onChange={(e) => setNewHgName(e.target.value)}
|
||||
@@ -500,7 +500,7 @@ export default function WaterAdminClient() {
|
||||
className="flex-1 rounded-lg border border-stone-300 px-3 py-2 text-sm min-h-[44px]"
|
||||
required
|
||||
/>
|
||||
<select
|
||||
<select aria-label="Select"
|
||||
value={newHgUnit}
|
||||
onChange={(e) => setNewHgUnit(e.target.value)}
|
||||
className="rounded-lg border border-stone-300 px-3 py-2 text-sm min-h-[44px]"
|
||||
@@ -548,7 +548,7 @@ export default function WaterAdminClient() {
|
||||
<span className="text-sm font-medium text-stone-900 truncate">{hg.name}</span>
|
||||
<span className="text-xs text-stone-400 shrink-0">{hg.unit}</span>
|
||||
</div>
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => router.push(`/admin/water-log/headgates/${hg.id}?from=/water/admin`)}
|
||||
className="shrink-0 rounded-lg border border-stone-200 bg-white px-3 py-1.5 text-xs font-medium text-stone-600 hover:bg-stone-50 min-h-[36px]"
|
||||
>
|
||||
@@ -568,7 +568,7 @@ export default function WaterAdminClient() {
|
||||
<p className="text-xs font-semibold text-yellow-800">{t.newPinFor} {newPin.name}:</p>
|
||||
<p className="text-xl font-bold tracking-widest text-yellow-900">{newPin.pin}</p>
|
||||
<p className="mt-0.5 text-xs text-yellow-600">{t.writeThisDown}</p>
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => setNewPin(null)}
|
||||
className="mt-1 text-xs text-yellow-700 underline"
|
||||
>
|
||||
@@ -582,7 +582,7 @@ export default function WaterAdminClient() {
|
||||
count={users.length}
|
||||
onToggle={() => setShowUsers((v) => !v)}
|
||||
action={
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => setShowAddUser((v) => !v)}
|
||||
className="rounded-lg bg-stone-900 px-3 py-1.5 text-xs font-semibold text-white hover:bg-stone-700 min-h-[36px] min-w-[36px]"
|
||||
>
|
||||
@@ -593,7 +593,7 @@ export default function WaterAdminClient() {
|
||||
|
||||
{showAddUser && (
|
||||
<form onSubmit={handleAddUser} className="mb-3 flex flex-col sm:flex-row gap-2">
|
||||
<input
|
||||
<input aria-label="Text"
|
||||
type="text"
|
||||
value={newUserName}
|
||||
onChange={(e) => setNewUserName(e.target.value)}
|
||||
@@ -601,7 +601,7 @@ export default function WaterAdminClient() {
|
||||
className="flex-1 rounded-lg border border-stone-300 px-3 py-2 text-sm min-h-[44px]"
|
||||
required
|
||||
/>
|
||||
<select
|
||||
<select aria-label="Select"
|
||||
value={newUserLang}
|
||||
onChange={(e) => setNewUserLang(e.target.value as "en" | "es")}
|
||||
className="rounded-lg border border-stone-300 px-3 py-2 text-sm min-h-[44px]"
|
||||
@@ -650,7 +650,7 @@ export default function WaterAdminClient() {
|
||||
{u.role === "water_admin" ? t.admin : t.field}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => router.push(`/admin/water-log/users/${u.id}?from=/water/admin`)}
|
||||
className="shrink-0 rounded-lg border border-stone-200 bg-white px-3 py-1.5 text-xs font-medium text-stone-600 hover:bg-stone-50 min-h-[36px]"
|
||||
>
|
||||
@@ -675,19 +675,19 @@ export default function WaterAdminClient() {
|
||||
<>
|
||||
{/* Filters */}
|
||||
<div className="mb-3 flex flex-wrap gap-2">
|
||||
<input
|
||||
<input aria-label="Date"
|
||||
type="date"
|
||||
value={filterDateFrom}
|
||||
onChange={(e) => setFilterDateFrom(e.target.value)}
|
||||
className="rounded-lg border border-stone-300 px-2 py-1.5 text-sm min-h-[36px]"
|
||||
/>
|
||||
<input
|
||||
<input aria-label="Date"
|
||||
type="date"
|
||||
value={filterDateTo}
|
||||
onChange={(e) => setFilterDateTo(e.target.value)}
|
||||
className="rounded-lg border border-stone-300 px-2 py-1.5 text-sm min-h-[36px]"
|
||||
/>
|
||||
<select
|
||||
<select aria-label="Select"
|
||||
value={filterHeadgate}
|
||||
onChange={(e) => setFilterHeadgate(e.target.value)}
|
||||
className="rounded-lg border border-stone-300 px-2 py-1.5 text-sm min-h-[36px]"
|
||||
@@ -697,7 +697,7 @@ export default function WaterAdminClient() {
|
||||
<option key={hg.id} value={hg.id}>{hg.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
<select aria-label="Select"
|
||||
value={filterVia}
|
||||
onChange={(e) => setFilterVia(e.target.value)}
|
||||
className="rounded-lg border border-stone-300 px-2 py-1.5 text-sm min-h-[36px]"
|
||||
@@ -707,7 +707,7 @@ export default function WaterAdminClient() {
|
||||
<option value="admin">{t.admin}</option>
|
||||
</select>
|
||||
{(filterDateFrom || filterDateTo || filterHeadgate || filterVia) && (
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => { setFilterDateFrom(""); setFilterDateTo(""); setFilterHeadgate(""); setFilterVia(""); }}
|
||||
className="text-xs text-stone-500 hover:text-stone-700 underline self-center"
|
||||
>
|
||||
|
||||
@@ -162,8 +162,10 @@ function WaterFieldInner() {
|
||||
if (step === "form" && headgates.length === 0) {
|
||||
loadHeadgates();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
// loadHeadgates depends on TUXEDO_BRAND_ID + setters which never change
|
||||
// across the component's lifetime, so we keep the dep list focused on
|
||||
// the state we actually gate the load on.
|
||||
}, [step, headgates.length]);
|
||||
|
||||
// QR-locked headgate: derive during render instead of syncing in an effect.
|
||||
// When qrHeadgateToken matches a loaded headgate, override the user's
|
||||
@@ -377,13 +379,13 @@ function WaterFieldInner() {
|
||||
</p>
|
||||
<p className="text-[#57694e] text-center mb-8 text-sm">{t.selectLang}</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => handleLangSelect("en")}
|
||||
className="w-full rounded-xl bg-white px-6 py-5 text-xl font-semibold text-[#1d1d1f] shadow-sm ring-1 ring-[#d4d9d3] hover:bg-[#f0fdf4] hover:ring-[#1a4d2e] transition min-h-[64px]"
|
||||
>
|
||||
English
|
||||
</button>
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => handleLangSelect("es")}
|
||||
className="w-full rounded-xl bg-white px-6 py-5 text-xl font-semibold text-[#1d1d1f] shadow-sm ring-1 ring-[#d4d9d3] hover:bg-[#f0fdf4] hover:ring-[#1a4d2e] transition min-h-[64px]"
|
||||
>
|
||||
@@ -400,7 +402,7 @@ function WaterFieldInner() {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#fdfaf2] flex flex-col items-center justify-center p-6">
|
||||
<div className="w-full max-w-xs">
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => setStep("lang")}
|
||||
className="text-sm text-[#57694e] mb-6 hover:text-[#1a4d2e]"
|
||||
>
|
||||
@@ -414,13 +416,13 @@ function WaterFieldInner() {
|
||||
</h1>
|
||||
<p className="text-[#57694e] text-center mb-8">{t.whoAreYou}</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => { setStep("pin"); }}
|
||||
className="w-full rounded-xl bg-white px-6 py-5 text-xl font-semibold text-[#1d1d1f] shadow-sm ring-1 ring-[#d4d9d3] hover:bg-[#f0fdf4] hover:ring-[#1a4d2e] transition min-h-[64px]"
|
||||
>
|
||||
Irrigator
|
||||
</button>
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => { setStep("pin"); }}
|
||||
className="w-full rounded-xl bg-[#1a4d2e] px-6 py-5 text-xl font-semibold text-white shadow-sm ring-1 ring-[#1a4d2e] hover:bg-[#14532d] transition min-h-[64px]"
|
||||
>
|
||||
@@ -437,7 +439,7 @@ function WaterFieldInner() {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#fdfaf2] flex flex-col items-center justify-center p-6">
|
||||
<div className="w-full max-w-xs">
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => setStep("role")}
|
||||
className="text-sm text-[#57694e] mb-6 hover:text-[#1a4d2e]"
|
||||
>
|
||||
@@ -452,7 +454,7 @@ function WaterFieldInner() {
|
||||
<p className="text-[#57694e] text-center mb-8">{t.enterPin}</p>
|
||||
|
||||
<form onSubmit={handlePinSubmit}>
|
||||
<input
|
||||
<input aria-label="Password"
|
||||
type="password"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
@@ -502,7 +504,7 @@ function WaterFieldInner() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={handleLogout}
|
||||
className="rounded-lg bg-[#14532d] px-4 py-2 text-sm font-semibold text-white hover:bg-[#166534] transition-colors min-h-[44px]"
|
||||
>
|
||||
@@ -574,7 +576,7 @@ function WaterFieldInner() {
|
||||
<span className="text-stone-500 text-base font-medium">Loading headgates...</span>
|
||||
</div>
|
||||
) : (
|
||||
<select
|
||||
<select aria-label="Select"
|
||||
value={effectiveSelectedHeadgate}
|
||||
onChange={(e) => setSelectedHeadgate(e.target.value)}
|
||||
required
|
||||
@@ -603,7 +605,7 @@ function WaterFieldInner() {
|
||||
<label className="block text-base font-semibold text-stone-700 mb-2">
|
||||
{t.measurement}
|
||||
</label>
|
||||
<input
|
||||
<input aria-label="0.00"
|
||||
type="number"
|
||||
step="any"
|
||||
value={measurement}
|
||||
@@ -618,7 +620,7 @@ function WaterFieldInner() {
|
||||
<label className="block text-base font-semibold text-stone-700 mb-2">
|
||||
{t.unit}
|
||||
</label>
|
||||
<select
|
||||
<select aria-label="Select"
|
||||
value={unit}
|
||||
onChange={(e) => setUnit(e.target.value)}
|
||||
className="w-full rounded-xl border-2 border-stone-300 bg-white px-4 py-4 text-lg font-semibold outline-none focus:border-stone-900 min-h-[60px]"
|
||||
@@ -693,7 +695,7 @@ function WaterFieldInner() {
|
||||
<label className="block text-base font-semibold text-stone-700 mb-2">
|
||||
{t.notes}
|
||||
</label>
|
||||
<textarea
|
||||
<textarea aria-label="Textarea"
|
||||
value={notes}
|
||||
onChange={(e) => setNotes(e.target.value)}
|
||||
placeholder={t.notesPlaceholder}
|
||||
|
||||
Reference in New Issue
Block a user