Skip to content

Commit 8d1dc73

Browse files
committed
add: switcher for toggle between imperial and metric units
1 parent bc82c38 commit 8d1dc73

File tree

20 files changed

+282
-93
lines changed

20 files changed

+282
-93
lines changed

src/app.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { WeatherPage } from '@/modules/weather';
22
import { PrimaryPage } from '@/pages';
33
import { ApiProvider } from '@/services/api/provider';
4+
import { AppProvider } from '@/services/store/provider';
45
import './styles/main.scss';
56

67
export const App = () => {
78
return (
8-
<ApiProvider>
9-
<PrimaryPage>
10-
<WeatherPage />
11-
</PrimaryPage>
12-
</ApiProvider>
9+
<AppProvider>
10+
<ApiProvider>
11+
<PrimaryPage>
12+
<WeatherPage />
13+
</PrimaryPage>
14+
</ApiProvider>
15+
</AppProvider>
1316
);
1417
};

src/components/header/header.module.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
.wrapper {
99
display: flex;
10+
justify-content: space-between;
1011
width: 94%;
1112
max-width: toRem($maxWidthApp);
1213
height: toRem(64);
@@ -15,6 +16,10 @@
1516
margin: 0 auto;
1617
}
1718

19+
.brand {
20+
display: flex;
21+
}
22+
1823
.logo {
1924
display: inline-block;
2025
line-height: toRem(81.5);

src/components/header/header.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1+
import { useAppContext } from '@/services/store/provider';
12
import { DaySunnyIcon } from '@/ui-kit/icons';
3+
import { Switcher } from '@/ui-kit/switcher/switcher';
24
import styles from './header.module.scss';
35

4-
export const Header = () => (
5-
<div className={styles.header}>
6-
<div className={styles.wrapper}>
7-
<a href='/' className={styles.logo}>
8-
<DaySunnyIcon className={styles.logoIcon} />
9-
</a>
10-
<h1 className={styles.brand}>forecast</h1>
6+
export const Header = () => {
7+
const { toggleUnits } = useAppContext();
8+
9+
return (
10+
<div className={styles.header}>
11+
<div className={styles.wrapper}>
12+
<div className={styles.brand}>
13+
<a href='/' className={styles.logo}>
14+
<DaySunnyIcon className={styles.logoIcon} />
15+
</a>
16+
<h1 className={styles.brand}>forecast</h1>
17+
</div>
18+
<Switcher label1='C' label2='F' onChange={toggleUnits} />
19+
</div>
1120
</div>
12-
</div>
13-
);
21+
);
22+
};

src/main.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { StrictMode } from 'react';
21
import { createRoot } from 'react-dom/client';
32
import { App } from './app';
43

@@ -7,8 +6,4 @@ if (!rootElement) {
76
throw new Error('Root element not found');
87
}
98

10-
createRoot(rootElement).render(
11-
<StrictMode>
12-
<App />
13-
</StrictMode>
14-
);
9+
createRoot(rootElement).render(<App />);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { apiConfig } from '../config';
22

3-
export const getForecastByCity = (city: string) =>
3+
export const getForecastByCity = (city: string, tempUnit: Units) =>
44
fetch(
5-
`${apiConfig.host}/forecast/daily?q=${city}&appid=${apiConfig.appId}&units=metric&cnt=14`
5+
`${apiConfig.host}/forecast/daily?q=${city}&appid=${apiConfig.appId}&units=${tempUnit}&cnt=14`
66
).then((response) => response.json());
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { apiConfig } from '../config';
22

3-
export const getCurrentWeatherByCity = (city: string) =>
3+
export const getCurrentWeatherByCity = (city: string, tempUnit: Units) =>
44
fetch(
5-
`${apiConfig.host}/weather?appid=${apiConfig.appId}&q=${city}&units=metric`
5+
`${apiConfig.host}/weather?appid=${apiConfig.appId}&q=${city}&units=${tempUnit}`
66
).then((response) => response.json());

src/modules/weather/api/adapters.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { getForecastByCity } from './actions/forecast';
44
import { getCurrentWeatherByCity } from './actions/weather';
55
import { prepareCurrentWeatherData, prepareForecastData } from './utils';
66

7-
export const useFetchCurrentWeather = (city: string) => {
7+
export const useFetchCurrentWeather = (city: string, tempUnit: Units) => {
88
const { data, isError, refetch, isFetching } = useQuery(
99
'current-weather',
10-
() => getCurrentWeatherByCity(city),
10+
() => getCurrentWeatherByCity(city, tempUnit),
1111
{ enabled: false }
1212
);
1313

@@ -21,10 +21,10 @@ export const useFetchCurrentWeather = (city: string) => {
2121
};
2222
};
2323

24-
export const useFetchForecast = (city: string) => {
24+
export const useFetchForecast = (city: string, tempUnit: Units) => {
2525
const { data, isError, refetch, isFetching } = useQuery(
2626
'forecast',
27-
() => getForecastByCity(city),
27+
() => getForecastByCity(city, tempUnit),
2828
{ enabled: false }
2929
);
3030

src/modules/weather/components/city-card/city-card.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Clock } from '@/components';
2+
import { getTempLabel } from '@/modules/weather/components/city-card/utils/get-temp-label';
23
import {
34
Forecast as ForecastType,
45
Weather,
@@ -14,12 +15,14 @@ export type Props = {
1415
currentWeather: Weather;
1516
forecast: Array<ForecastType>;
1617
lastDataUpdate: Date;
18+
units: Units;
1719
};
1820

1921
export const CityCard = ({
2022
currentWeather,
2123
forecast,
2224
lastDataUpdate,
25+
units,
2326
}: Props) => {
2427
const {
2528
city,
@@ -47,7 +50,7 @@ export const CityCard = ({
4750
{getIconByWeatherCode(iconCode, sunrise)}
4851
</div>
4952
<div className={styles.temperature}>
50-
<span data-deg='°' data-unit='C'>
53+
<span data-deg='°' data-unit={getTempLabel(units)}>
5154
{Math.round(temperature)}
5255
</span>
5356
</div>
@@ -65,10 +68,17 @@ export const CityCard = ({
6568
</div>
6669
<div className={styles.divider}></div>
6770
<InfoList
68-
{...{ windSpeed: wind.speed, humidity, sunset, sunrise, pressure }}
71+
{...{
72+
windSpeed: wind.speed,
73+
humidity,
74+
sunset,
75+
sunrise,
76+
pressure,
77+
units,
78+
}}
6979
/>
7080
<div className={styles.divider}></div>
71-
{forecast.length > 0 && <Forecast forecast={forecast} />}
81+
{forecast.length > 0 && <Forecast forecast={forecast} units={units} />}
7282
</div>
7383
</div>
7484
);

src/modules/weather/components/city-card/forecast/forecast.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState } from 'react';
2+
import { getTempLabel } from '@/modules/weather/components/city-card/utils/get-temp-label';
23
import { Forecast as ForecastType } from '@/modules/weather/types/weather';
34
import { Button } from '@/ui-kit/button';
45
import { getIconByWeatherCode } from '@/ui-kit/icons/weather-icons/adapters';
@@ -7,9 +8,10 @@ import styles from './forecast.module.scss';
78

89
export type ForecastProps = {
910
forecast: Array<ForecastType>;
11+
units: Units;
1012
};
1113

12-
export const Forecast = ({ forecast }: ForecastProps) => {
14+
export const Forecast = ({ forecast, units }: ForecastProps) => {
1315
const [showMore, setShowMore] = useState(false);
1416

1517
const foreCastData = showMore ? forecast : [...forecast].splice(0, 7);
@@ -38,7 +40,7 @@ export const Forecast = ({ forecast }: ForecastProps) => {
3840
<span
3941
className={styles.listItemTemperatureData}
4042
data-deg='°'
41-
data-unit='C'
43+
data-unit={getTempLabel(units)}
4244
>
4345
{Math.round(dayTemperature)}
4446
</span>
@@ -49,7 +51,7 @@ export const Forecast = ({ forecast }: ForecastProps) => {
4951
<span
5052
className={styles.listItemTemperatureData}
5153
data-deg='°'
52-
data-unit='C'
54+
data-unit={getTempLabel(units)}
5355
>
5456
{Math.round(nightTemperature)}
5557
</span>

src/modules/weather/components/city-card/info-list/info-list.module.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
color: $colorNeutral850;
99
font-size: toRem(13);
1010
opacity: .8;
11-
gap: toRem(100);
11+
gap: toRem(190);
1212

1313
@include respond-below(xs) {
1414
flex-flow: wrap;
@@ -19,7 +19,7 @@
1919
}
2020

2121
.infoList {
22-
margin: 0 9%;
22+
width: toRem(150);
2323

2424
@include respond-below(xs) {
2525
flex: 0 0 50%;

src/modules/weather/components/city-card/info-list/info-list.tsx

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type TInfoListProps = {
1010
pressure: Weather['pressure'];
1111
sunrise: Weather['sunrise'];
1212
sunset: Weather['sunset'];
13+
units: Units;
1314
};
1415

1516
export const InfoList = ({
@@ -18,35 +19,45 @@ export const InfoList = ({
1819
sunset,
1920
pressure,
2021
humidity,
21-
}: TInfoListProps) => (
22-
<div className={styles.info}>
23-
<div className={styles.infoList}>
24-
<div className={styles.infoListItem}>
25-
<strong>Wind:</strong>
26-
<span>{getWindBeaufortScaleByMeterInSecond(windSpeed)}</span>
27-
</div>
28-
<div className={styles.infoListItem}>
29-
<strong>Wind speed:</strong>
30-
<span>{windSpeed} m/s</span>
31-
</div>
32-
<div className={styles.infoListItem}>
33-
<strong>Humidity:</strong>
34-
<span>{humidity}%</span>
35-
</div>
36-
</div>
37-
<div className={styles.infoList}>
38-
<div className={styles.infoListItem}>
39-
<strong>Pressure:</strong>
40-
<span>{getPressureInMmHg(pressure)} mm/Hg</span>
41-
</div>
42-
<div className={styles.infoListItem}>
43-
<strong>Sunrise:</strong>
44-
<span>{getTime(sunrise)}</span>
22+
units,
23+
}: TInfoListProps) => {
24+
const windUnits = units === 'metric' ? 'm/s' : 'mil/h';
25+
const pressureUnits = units === 'metric' ? 'mm/Hg' : 'hPa';
26+
27+
return (
28+
<div className={styles.info}>
29+
<div className={styles.infoList}>
30+
<div className={styles.infoListItem}>
31+
<strong>Wind:</strong>
32+
<span>{getWindBeaufortScaleByMeterInSecond(windSpeed)}</span>
33+
</div>
34+
<div className={styles.infoListItem}>
35+
<strong>Wind speed:</strong>
36+
<span>
37+
{windSpeed} {windUnits}
38+
</span>
39+
</div>
40+
<div className={styles.infoListItem}>
41+
<strong>Humidity:</strong>
42+
<span>{humidity}%</span>
43+
</div>
4544
</div>
46-
<div className={styles.infoListItem}>
47-
<strong>Sunset:</strong>
48-
<span>{getTime(sunset)}</span>
45+
<div className={styles.infoList}>
46+
<div className={styles.infoListItem}>
47+
<strong>Pressure:</strong>
48+
<span>
49+
{getPressureInMmHg(pressure)} {pressureUnits}
50+
</span>
51+
</div>
52+
<div className={styles.infoListItem}>
53+
<strong>Sunrise:</strong>
54+
<span>{getTime(sunrise)}</span>
55+
</div>
56+
<div className={styles.infoListItem}>
57+
<strong>Sunset:</strong>
58+
<span>{getTime(sunset)}</span>
59+
</div>
4960
</div>
5061
</div>
51-
</div>
52-
);
62+
);
63+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const getTempLabel = (units: Units) => (units === 'metric' ? 'C' : 'F');
Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,54 @@
1-
import { useEffect } from 'react';
1+
import { useCallback, useEffect, useState } from 'react';
22
import { Weather } from '@/modules/weather/types/weather';
3+
import { useAppContext } from '@/services/store/provider';
34
import { useFetchCurrentWeather } from './api/adapters';
45

6+
const DATA_UPDATE_INTERVAL = 5 * 60 * 1000;
7+
58
export type TUseCurrentWeather = (city: string) => {
69
currentWeather: Weather | null;
710
isLoading: boolean;
8-
getData: () => void;
11+
lastDataUpdate: Date;
912
};
1013

1114
export const useCurrentWeather: TUseCurrentWeather = (city) => {
12-
const { data, getData, isLoading } = useFetchCurrentWeather(city);
15+
const { units } = useAppContext();
16+
const { data, getData, isLoading } = useFetchCurrentWeather(city, units);
17+
const [lastDataUpdate, setLastDataUpdate] = useState<Date>(new Date());
1318

14-
useEffect(() => {
19+
const getCurrentData = useCallback(() => {
1520
if (city) {
1621
getData().catch(console.log); // eslint-disable-line no-console
1722
}
1823
}, [city, getData]);
1924

25+
const updateLastDataUpdate = useCallback(() => {
26+
setLastDataUpdate(new Date());
27+
}, []);
28+
29+
useEffect(() => {
30+
getCurrentData();
31+
updateLastDataUpdate();
32+
}, [getCurrentData, units, updateLastDataUpdate]);
33+
34+
useEffect(() => {
35+
getCurrentData();
36+
37+
const intervalId = setInterval(() => {
38+
getCurrentData();
39+
40+
updateLastDataUpdate();
41+
}, DATA_UPDATE_INTERVAL);
42+
43+
return () => {
44+
clearInterval(intervalId);
45+
};
46+
}, [getData, getCurrentData]);
47+
2048
return {
2149
currentWeather: data,
2250
isLoading,
2351
getData,
52+
lastDataUpdate,
2453
};
2554
};

0 commit comments

Comments
 (0)