From e43089df39d95f7a50f6954f6b6c4558b1115f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 28 Jun 2026 18:30:21 +0200 Subject: [PATCH] fix(settings/environment): submit-on-Enter city search, zero-result feedback, validated timezone Wrap the home and travel city-search inputs in a form so Enter runs the search instead of doing nothing, and show a 'no matching places' line when a geocode returns nothing rather than leaving the user without feedback. Validate the home timezone against the runtime IANA set (a bogus zone would silently mis-key every stored environment day). Grow the result-row and travel-remove controls to the 44px touch floor. --- .../settings/environment-section.tsx | 75 +++++++++++++++---- src/lib/validations/environment.ts | 15 +++- 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/src/components/settings/environment-section.tsx b/src/components/settings/environment-section.tsx index 5f66bc2fb..1a892fd6b 100644 --- a/src/components/settings/environment-section.tsx +++ b/src/components/settings/environment-section.tsx @@ -88,8 +88,12 @@ export function EnvironmentSection() { // ── geocoding search (shared by home + travel pickers) ────────────────── const [homeQuery, setHomeQuery] = useState(""); const [homeResults, setHomeResults] = useState([]); + // Tracks whether a geocode has completed for the current query so a + // zero-result search can surface a "no matches" line instead of silence. + const [homeSearched, setHomeSearched] = useState(false); const [travelQuery, setTravelQuery] = useState(""); const [travelResults, setTravelResults] = useState([]); + const [travelSearched, setTravelSearched] = useState(false); const [travelLoc, setTravelLoc] = useState(null); const [travelStart, setTravelStart] = useState(todayKey()); const [travelEnd, setTravelEnd] = useState(todayKey()); @@ -104,14 +108,20 @@ export function EnvironmentSection() { const searchHome = useMutation({ mutationKey: queryKeys.environmentGeocode("home"), mutationFn: () => geocode(homeQuery), - onSuccess: (data) => setHomeResults(data.results), + onSuccess: (data) => { + setHomeResults(data.results); + setHomeSearched(true); + }, onError: () => toast.error(t("settings.sections.environment.searchError")), }); const searchTravel = useMutation({ mutationKey: queryKeys.environmentGeocode("travel"), mutationFn: () => geocode(travelQuery), - onSuccess: (data) => setTravelResults(data.results), + onSuccess: (data) => { + setTravelResults(data.results); + setTravelSearched(true); + }, onError: () => toast.error(t("settings.sections.environment.searchError")), }); @@ -127,6 +137,7 @@ export function EnvironmentSection() { onSuccess: () => { setHomeResults([]); setHomeQuery(""); + setHomeSearched(false); invalidate(); toast.success(t("settings.sections.environment.homeSaved")); }, @@ -147,6 +158,7 @@ export function EnvironmentSection() { setTravelLoc(null); setTravelResults([]); setTravelQuery(""); + setTravelSearched(false); invalidate(); toast.success(t("settings.sections.environment.travelAdded")); }, @@ -234,22 +246,39 @@ export function EnvironmentSection() { {t("settings.sections.environment.home.none")}

)} -
+
{ + e.preventDefault(); + if (homeQuery.trim().length > 0 && !searchHome.isPending) { + searchHome.mutate(); + } + }} + > setHomeQuery(e.target.value)} + onChange={(e) => { + setHomeQuery(e.target.value); + setHomeSearched(false); + }} placeholder={t("settings.sections.environment.searchPlaceholder")} aria-label={t("settings.sections.environment.searchPlaceholder")} /> -
+ + {homeSearched && + !searchHome.isPending && + homeResults.length === 0 && ( +

+ {t("settings.sections.environment.noResults")} +

+ )} {homeResults.length > 0 && (
    {homeResults.map((r) => ( @@ -261,6 +290,7 @@ export function EnvironmentSection() { - + + {travelSearched && + !searchTravel.isPending && + travelResults.length === 0 && ( +

    + {t("settings.sections.environment.noResults")} +

    + )} {travelResults.length > 0 && (
      {travelResults.map((r) => ( @@ -344,6 +392,7 @@ export function EnvironmentSection() {