Skip to content

Commit 3514b5c

Browse files
committed
Update eslint-config
1 parent 65a3e8b commit 3514b5c

12 files changed

Lines changed: 525 additions & 2157 deletions

‎frontend/eslint.config.js‎

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
import eslintReactConfig from "eslint-config/react";
1+
import reactConfig from "eslint-config/react";
22

3-
export default [
4-
...eslintReactConfig,
5-
{
6-
files: ["**/*.ts", "**/*.tsx"],
7-
rules: {
8-
"react/no-unknown-property": ["error", { ignore: ["css"] }],
9-
"react/prop-types": "off",
10-
},
11-
},
12-
];
3+
export default reactConfig;

‎frontend/package.json‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,11 @@
3535
"@vitejs/plugin-react": "^6.0.1",
3636
"@vitest/browser-playwright": "^4.1.5",
3737
"babel-plugin-react-compiler": "^1.0.0",
38-
"eslint": "^9.39.4",
39-
"eslint-config": "github:eetu/eslint-config#1.0.0",
38+
"eslint": "^10.3.0",
39+
"eslint-config": "github:eetu/eslint-config#v2.0.1",
4040
"playwright": "^1.59.1",
4141
"prettier": "^3.8.3",
4242
"typescript": "^6.0.3",
43-
"typescript-eslint": "^8.59.0",
4443
"unplugin-auto-import": "^21.0.0",
4544
"vite": "^8.0.10",
4645
"vite-tsconfig-paths": "^6.1.1",

‎frontend/src/components/Box.tsx‎

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useTheme } from "@emotion/react";
2-
import React, { useEffect, useRef, useState } from "react";
2+
import React, { useRef, useState } from "react";
33

44
import Icon from "./Icon";
55
import Spinner from "./Spinner";
@@ -98,41 +98,32 @@ type BoxDrawerProps = {
9898

9999
const BoxDrawer: React.FC<BoxDrawerProps> = ({ children, collapsed }) => {
100100
const theme = useTheme();
101-
const contentRef = useRef<HTMLDivElement>(null);
102101
const wrapperRef = useRef<HTMLDivElement>(null);
103-
const [height, setHeight] = useState(0);
104-
105-
useEffect(() => {
106-
if (!collapsed && contentRef.current) {
107-
setHeight(contentRef.current.scrollHeight);
108-
} else {
109-
setHeight(0);
110-
}
111-
}, [collapsed]);
112102

113103
return (
114104
<div
115105
ref={wrapperRef}
116106
css={{
117-
overflow: "hidden",
118-
height,
119-
transition: "height 0.15s ease",
107+
display: "grid",
108+
gridTemplateRows: collapsed ? "0fr" : "1fr",
109+
transition: "grid-template-rows 0.15s ease",
120110
}}
121111
onTransitionEnd={() => {
122112
if (!collapsed) {
123113
wrapperRef.current?.scrollIntoView({ behavior: "smooth" });
124114
}
125115
}}
126116
>
127-
<div
128-
ref={contentRef}
129-
css={{
130-
overflowY: "scroll",
131-
borderLeft: `1px ${theme.colors.border} solid`,
132-
borderRight: `1px ${theme.colors.border} solid`,
133-
}}
134-
>
135-
{children}
117+
<div css={{ minHeight: 0, overflow: "hidden" }}>
118+
<div
119+
css={{
120+
overflowY: "scroll",
121+
borderLeft: `1px ${theme.colors.border} solid`,
122+
borderRight: `1px ${theme.colors.border} solid`,
123+
}}
124+
>
125+
{children}
126+
</div>
136127
</div>
137128
</div>
138129
);

‎frontend/src/components/FmiWeatherBox.tsx‎

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { useTheme } from "@emotion/react";
22
import { format } from "date-fns";
33
import { fi } from "date-fns/locale/fi";
4-
import { memo } from "react";
4+
import { type LucideProps } from "lucide-react";
5+
import { createElement, memo, useState } from "react";
56
import useSWR from "swr";
67

78
import { api, fetcher } from "../api";
@@ -20,13 +21,25 @@ import LocationForm from "./LocationForm";
2021
import RaindropIcon from "./RaindropIcon";
2122
import WeatherChart from "./WeatherChart";
2223

24+
type WeatherIconProps = {
25+
weatherSymbol: number;
26+
isNight: boolean;
27+
} & LucideProps;
28+
29+
const WeatherIcon: React.FC<WeatherIconProps> = ({
30+
weatherSymbol,
31+
isNight,
32+
...props
33+
}) => createElement(getFmiWeatherIcon(weatherSymbol, isNight), props);
34+
2335
type WeatherBoxProps = {
2436
className?: string;
2537
};
2638

2739
const WeatherBox: React.FC<WeatherBoxProps> = ({ className }) => {
2840
const theme = useTheme();
2941
const { location, isLoading: locationLoading } = useLocationSettings();
42+
const [now] = useState(() => new Date());
3043

3144
const weatherUrl = location
3245
? api(`/api/weather/fmi?lat=${location.lat}&lon=${location.lon}`)
@@ -95,9 +108,8 @@ const WeatherBox: React.FC<WeatherBoxProps> = ({ className }) => {
95108
});
96109
}
97110

98-
const nowDate = new Date();
99-
const todayKey = format(nowDate, "yyyy-MM-dd");
100-
const hideToday = nowDate.getHours() >= HIDE_TODAY_PAST_HOUR;
111+
const todayKey = format(now, "yyyy-MM-dd");
112+
const hideToday = now.getHours() >= HIDE_TODAY_PAST_HOUR;
101113

102114
const chartData = daily
103115
.filter((d) => !(d.date.slice(0, 10) === todayKey && hideToday))
@@ -117,7 +129,6 @@ const WeatherBox: React.FC<WeatherBoxProps> = ({ className }) => {
117129

118130
const segments = getFmiTemperatureSegments(hourly);
119131

120-
const now = new Date();
121132
const isNight =
122133
now < new Date(current.sunrise) || now > new Date(current.sunset);
123134
const title = getFmiWeatherDescription(current.weatherSymbol);
@@ -194,21 +205,15 @@ const WeatherBox: React.FC<WeatherBoxProps> = ({ className }) => {
194205
}}
195206
>{`${Math.round(current.temperatureApparent)}°`}</div>
196207
</div>
197-
{(() => {
198-
const IconComponent = getFmiWeatherIcon(
199-
current.weatherSymbol,
200-
isNight,
201-
);
202-
return (
203-
<IconComponent
204-
css={{
205-
marginLeft: "25px",
206-
alignSelf: "center",
207-
}}
208-
size={52}
209-
/>
210-
);
211-
})()}
208+
<WeatherIcon
209+
weatherSymbol={current.weatherSymbol}
210+
isNight={isNight}
211+
css={{
212+
marginLeft: "25px",
213+
alignSelf: "center",
214+
}}
215+
size={52}
216+
/>
212217
<div
213218
css={{
214219
fontSize: "13px",

‎frontend/src/components/History.tsx‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,28 @@ type HistoryProps = {
8181

8282
const History: React.FC<HistoryProps> = ({ className }) => {
8383
const theme = useTheme();
84-
const [readings, setReadings] = useState<SensorReading[]>([]);
8584
const [range, setRange] = useState<(typeof RANGES)[number]>(RANGES[1]);
86-
const [loading, setLoading] = useState(true);
85+
const [response, setResponse] = useState<{
86+
range: (typeof RANGES)[number];
87+
readings: SensorReading[];
88+
}>();
89+
const loading = !response || response.range !== range;
90+
const readings = response?.readings ?? [];
8791

8892
useEffect(() => {
89-
setLoading(true);
93+
const controller = new AbortController();
9094
const params = new URLSearchParams({ hours: String(range.hours) });
9195
if (range.maxPoints !== undefined)
9296
params.set("max_points", String(range.maxPoints));
93-
fetch(api(`/api/history/sensors?${params}`))
97+
fetch(api(`/api/history/sensors?${params}`), { signal: controller.signal })
9498
.then((r) => r.json())
9599
.then((data: SensorReading[]) => {
96-
setReadings(data);
97-
setLoading(false);
100+
setResponse({ range, readings: data });
98101
})
99-
.catch(() => setLoading(false));
102+
.catch((err) => {
103+
if (err.name !== "AbortError") setResponse({ range, readings: [] });
104+
});
105+
return () => controller.abort();
100106
}, [range]);
101107

102108
// Group readings by sensor

‎frontend/src/components/LightGroups.tsx‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useTheme } from "@emotion/react";
2-
import { FC, memo, useCallback, useEffect, useState } from "react";
2+
import { FC, memo, useCallback, useState } from "react";
33

44
import { api } from "../api";
55
import { mq } from "../mq";
@@ -45,10 +45,12 @@ type LightGroupProps = {
4545

4646
const LightGroup: FC<LightGroupProps> = ({ group }) => {
4747
const [isOn, setIsOn] = useState(group.state.on);
48+
const [prevOn, setPrevOn] = useState(group.state.on);
4849

49-
useEffect(() => {
50+
if (prevOn !== group.state.on) {
51+
setPrevOn(group.state.on);
5052
setIsOn(group.state.on);
51-
}, [group.state.on]);
53+
}
5254

5355
const handleClick = useCallback(
5456
(id: string, isOn: boolean) => () => {

‎frontend/src/components/LocationForm.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ const LocationForm: React.FC<LocationFormProps> = ({ className }) => {
164164
>
165165
{results.map((r, i) => (
166166
<button
167-
key={i}
167+
key={`${r.lat},${r.lon}`}
168168
onClick={() => selectResult(r)}
169169
css={{
170170
padding: "0.6em 0.75em",

‎frontend/src/components/Motion.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const Motion: FC<MotionProps> = ({ className, sensors = [], error }) => {
1919
const theme = useTheme();
2020
const isMobile = useMediaQuery("(max-width: 600px)");
2121

22-
const [pending, setPending] = useState<Set<string>>(new Set());
22+
const [pending, setPending] = useState<Set<string>>(() => new Set());
2323
const motionSensors = sensors.filter(
2424
(s) => s.motion !== undefined || s.motionEnabled !== undefined,
2525
);

‎frontend/src/components/TomorrowWeatherBox.tsx‎

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useTheme } from "@emotion/react";
22
import { format } from "date-fns";
33
import { fi } from "date-fns/locale/fi";
4+
import { type LucideProps } from "lucide-react";
5+
import { createElement } from "react";
46
import useSWR from "swr";
57

68
import { api, fetcher } from "../api";
@@ -40,6 +42,13 @@ const weatherCode: Record<number, string> = {
4042
8000: "Ukkosmyrsky",
4143
};
4244

45+
type WeatherIconProps = {
46+
weatherCode: number;
47+
} & LucideProps;
48+
49+
const WeatherIcon: React.FC<WeatherIconProps> = ({ weatherCode, ...props }) =>
50+
createElement(getWeatherIcon(weatherCode), props);
51+
4352
type TomorrowWeatherBoxProps = {
4453
className?: string;
4554
};
@@ -164,18 +173,14 @@ const TomorrowWeatherBox: React.FC<TomorrowWeatherBoxProps> = ({
164173
}}
165174
>{`${Math.round(weather.values.temperatureApparent)}°`}</div>
166175
</div>
167-
{(() => {
168-
const IconComponent = getWeatherIcon(weather.values.weatherCode);
169-
return (
170-
<IconComponent
171-
css={{
172-
marginLeft: "25px",
173-
alignSelf: "center",
174-
}}
175-
size={52}
176-
/>
177-
);
178-
})()}
176+
<WeatherIcon
177+
weatherCode={weather.values.weatherCode}
178+
css={{
179+
marginLeft: "25px",
180+
alignSelf: "center",
181+
}}
182+
size={52}
183+
/>
179184
<div
180185
css={{
181186
fontSize: "13px",

‎frontend/src/hooks/useCurrentTime.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useState } from "react";
22

33
const useCurrentTime = () => {
4-
const [currenTime, setCurrentTime] = useState(new Date());
4+
const [currentTime, setCurrentTime] = useState<Date>(() => new Date());
55

66
useEffect(() => {
77
const timer = setInterval(() => {
@@ -12,7 +12,7 @@ const useCurrentTime = () => {
1212
};
1313
}, []);
1414

15-
return currenTime;
15+
return currentTime;
1616
};
1717

1818
export default useCurrentTime;

0 commit comments

Comments
 (0)