feat: add weather unit preferences
Some checks failed
CI / Lint, test, typecheck and build (push) Has been cancelled

This commit is contained in:
zv
2026-07-04 20:16:11 +02:00
parent ab6b7b414f
commit 91acdb39b8
27 changed files with 623 additions and 76 deletions

View File

@@ -6,10 +6,13 @@ import {
BellRing,
ChevronDown,
Clock3,
CloudRain,
Gauge,
Languages,
LayoutDashboard,
MapPinned,
Palette,
Ruler,
Settings,
Smartphone,
Thermometer,
@@ -36,8 +39,14 @@ import {
} from "@/lib/notification-api";
import { formatProvinceName, getProvinceForSelection, PROVINCES } from "@/lib/provinces";
import { useWeatherStore } from "@/lib/store";
import { TEMPERATURE_UNITS, WIND_SPEED_UNITS } from "@/lib/weather-utils";
import type { TemperatureUnit, WindSpeedUnit } from "@/types/units";
import {
DISTANCE_UNITS,
PRECIPITATION_UNITS,
PRESSURE_UNITS,
TEMPERATURE_UNITS,
WIND_SPEED_UNITS,
} from "@/lib/weather-utils";
import type { DistanceUnit, PrecipitationUnit, PressureUnit, TemperatureUnit, WindSpeedUnit } from "@/types/units";
type NotificationSupportStatus =
| "checking"
@@ -154,11 +163,17 @@ function NotificationSwitchLabel({
const windSpeedUnitKeys: Record<
WindSpeedUnit,
"settings.units.wind.ms" | "settings.units.wind.kmh" | "settings.units.wind.mph"
| "settings.units.wind.ms"
| "settings.units.wind.kmh"
| "settings.units.wind.mph"
| "settings.units.wind.kt"
| "settings.units.wind.bft"
> = {
ms: "settings.units.wind.ms",
kmh: "settings.units.wind.kmh",
mph: "settings.units.wind.mph",
kt: "settings.units.wind.kt",
bft: "settings.units.wind.bft",
};
const temperatureUnitKeys: Record<TemperatureUnit, "settings.units.temperature.c" | "settings.units.temperature.f"> = {
@@ -166,6 +181,33 @@ const temperatureUnitKeys: Record<TemperatureUnit, "settings.units.temperature.c
f: "settings.units.temperature.f",
};
const precipitationUnitKeys: Record<
PrecipitationUnit,
"settings.units.precipitation.mm" | "settings.units.precipitation.cm" | "settings.units.precipitation.in"
> = {
mm: "settings.units.precipitation.mm",
cm: "settings.units.precipitation.cm",
in: "settings.units.precipitation.in",
};
const pressureUnitKeys: Record<
PressureUnit,
| "settings.units.pressure.hpa"
| "settings.units.pressure.kpa"
| "settings.units.pressure.inhg"
| "settings.units.pressure.mmhg"
> = {
hpa: "settings.units.pressure.hpa",
kpa: "settings.units.pressure.kpa",
inhg: "settings.units.pressure.inhg",
mmhg: "settings.units.pressure.mmhg",
};
const distanceUnitKeys: Record<DistanceUnit, "settings.units.distance.km" | "settings.units.distance.mi"> = {
km: "settings.units.distance.km",
mi: "settings.units.distance.mi",
};
export function SettingsPage() {
const { language, t } = useI18n();
const [notificationStatus, setNotificationStatus] = useState<NotificationSupportStatus>("checking");
@@ -185,6 +227,9 @@ export function SettingsPage() {
const manualProvince = useWeatherStore((state) => state.warningNotificationProvince);
const temperatureUnit = useWeatherStore((state) => state.temperatureUnit);
const windSpeedUnit = useWeatherStore((state) => state.windSpeedUnit);
const precipitationUnit = useWeatherStore((state) => state.precipitationUnit);
const pressureUnit = useWeatherStore((state) => state.pressureUnit);
const distanceUnit = useWeatherStore((state) => state.distanceUnit);
const dashboardSections = useWeatherStore((state) => state.dashboardSections);
const appSections = useWeatherStore((state) => state.appSections);
const setNotificationsEnabled = useWeatherStore((state) => state.setWarningNotificationsEnabled);
@@ -194,6 +239,9 @@ export function SettingsPage() {
const setManualProvince = useWeatherStore((state) => state.setWarningNotificationProvince);
const setTemperatureUnit = useWeatherStore((state) => state.setTemperatureUnit);
const setWindSpeedUnit = useWeatherStore((state) => state.setWindSpeedUnit);
const setPrecipitationUnit = useWeatherStore((state) => state.setPrecipitationUnit);
const setPressureUnit = useWeatherStore((state) => state.setPressureUnit);
const setDistanceUnit = useWeatherStore((state) => state.setDistanceUnit);
const setDashboardSectionVisible = useWeatherStore((state) => state.setDashboardSectionVisible);
const setAppSectionVisible = useWeatherStore((state) => state.setAppSectionVisible);
@@ -268,6 +316,9 @@ export function SettingsPage() {
countyTeryt: notificationCountyTeryt,
temperatureUnit,
windSpeedUnit,
precipitationUnit,
pressureUnit,
distanceUnit,
}).catch(() => undefined);
}
@@ -289,6 +340,9 @@ export function SettingsPage() {
notificationTimezone,
selectedRegion,
temperatureUnit,
precipitationUnit,
pressureUnit,
distanceUnit,
tomorrowBriefEnabled,
vapidPublicKey,
windSpeedUnit,
@@ -376,6 +430,9 @@ export function SettingsPage() {
countyTeryt: notificationCountyTeryt,
temperatureUnit,
windSpeedUnit,
precipitationUnit,
pressureUnit,
distanceUnit,
});
setNotificationsEnabled(true);
setNotificationMessage(t("settings.notifications.saveSuccess"));
@@ -444,6 +501,9 @@ export function SettingsPage() {
countyTeryt: notificationCountyTeryt,
temperatureUnit,
windSpeedUnit,
precipitationUnit,
pressureUnit,
distanceUnit,
});
} catch {
setNotificationMessage(t("settings.notifications.saveError"));
@@ -470,6 +530,9 @@ export function SettingsPage() {
countyTeryt: notificationCountyTeryt,
temperatureUnit,
windSpeedUnit,
precipitationUnit,
pressureUnit,
distanceUnit,
});
} catch {
setNotificationMessage(t("settings.notifications.saveError"));
@@ -554,6 +617,90 @@ export function SettingsPage() {
))}
</div>
</SettingsRow>
<SettingsRow
icon={CloudRain}
title={t("settings.units.precipitation.title")}
description={t("settings.units.precipitation.description")}
>
<div
className="surface-control inline-flex rounded-full p-1"
role="radiogroup"
aria-label={t("settings.units.precipitation.label")}
>
{PRECIPITATION_UNITS.map((unit) => (
<button
key={unit}
type="button"
role="radio"
aria-checked={precipitationUnit === unit}
className={`min-w-14 rounded-full border px-3.5 py-1.5 text-sm font-medium transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${
precipitationUnit === unit
? "border-accent/45 bg-accent/10 text-accent shadow-soft"
: "border-transparent text-muted hover:bg-surface-muted/70 hover:text-foreground"
}`}
onClick={() => setPrecipitationUnit(unit)}
>
{t(precipitationUnitKeys[unit])}
</button>
))}
</div>
</SettingsRow>
<SettingsRow
icon={Gauge}
title={t("settings.units.pressure.title")}
description={t("settings.units.pressure.description")}
>
<div
className="surface-control inline-flex rounded-full p-1"
role="radiogroup"
aria-label={t("settings.units.pressure.label")}
>
{PRESSURE_UNITS.map((unit) => (
<button
key={unit}
type="button"
role="radio"
aria-checked={pressureUnit === unit}
className={`min-w-14 rounded-full border px-3.5 py-1.5 text-sm font-medium transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${
pressureUnit === unit
? "border-accent/45 bg-accent/10 text-accent shadow-soft"
: "border-transparent text-muted hover:bg-surface-muted/70 hover:text-foreground"
}`}
onClick={() => setPressureUnit(unit)}
>
{t(pressureUnitKeys[unit])}
</button>
))}
</div>
</SettingsRow>
<SettingsRow
icon={Ruler}
title={t("settings.units.distance.title")}
description={t("settings.units.distance.description")}
>
<div
className="surface-control inline-flex rounded-full p-1"
role="radiogroup"
aria-label={t("settings.units.distance.label")}
>
{DISTANCE_UNITS.map((unit) => (
<button
key={unit}
type="button"
role="radio"
aria-checked={distanceUnit === unit}
className={`min-w-14 rounded-full border px-3.5 py-1.5 text-sm font-medium transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${
distanceUnit === unit
? "border-accent/45 bg-accent/10 text-accent shadow-soft"
: "border-transparent text-muted hover:bg-surface-muted/70 hover:text-foreground"
}`}
onClick={() => setDistanceUnit(unit)}
>
{t(distanceUnitKeys[unit])}
</button>
))}
</div>
</SettingsRow>
</Card>
<Card className="p-4 sm:p-5">