diff --git a/README.md b/README.md index 55fcfd6d..8ebecc9a 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,11 @@ If you are not familiar with the different technologies used in this project, pl - [tRPC](https://trpc.io) - [TypeScript](https://www.typescriptlang.org) - [Turborepo](https://turbo.build/repo) +#### APIs - [OpenWeatherMap API](https://openweathermap.org/api) - [Open Meteo API](https://open-meteo.com) +- [QWeather API](https://dev.qweather.com/en/) +- [API Ninjas API](https://api-ninjas.com/) (For the Reverse Geocoding) ## Learn More diff --git a/apps/web/public/locales/de/home.json b/apps/web/public/locales/de/home.json index 4d2f140e..36649131 100644 --- a/apps/web/public/locales/de/home.json +++ b/apps/web/public/locales/de/home.json @@ -69,5 +69,9 @@ "moon phase waning crescent": "Abnehmende Sichel", "more information": "Mehr Informationen", - "sun hours": "Sonnenstunden" + "less information": "Weniger Informationen", + + "sun hours": "Sonnenstunden", + + "from": "Von" } diff --git a/apps/web/public/locales/en/home.json b/apps/web/public/locales/en/home.json index 722bff3c..40bd4494 100644 --- a/apps/web/public/locales/en/home.json +++ b/apps/web/public/locales/en/home.json @@ -69,5 +69,9 @@ "moon phase waning crescent": "Waning Crescent", "more information": "More Information", - "sun hours": "Sun Hours" + "less information": "Less Information", + + "sun hours": "Sun Hours", + + "from": "From" } diff --git a/apps/web/src/pages/home/index.tsx b/apps/web/src/pages/home/index.tsx index afa296b0..c48900c8 100644 --- a/apps/web/src/pages/home/index.tsx +++ b/apps/web/src/pages/home/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import dynamic from "next/dynamic"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -109,10 +109,17 @@ function convertWindSpeed( const InternalHome = observer(() => { const [isMoreInfoCollapsibleOpen, setIsMoreInfoCollapsibleOpen] = - React.useState(false); + useState(false); + const [isMoreWarningsCollapsibleOpen, setIsMoreWarningsCollapsibleOpen] = + useState(false); + const { locale } = useRouter(); const weatherData = api.weather.getWeather.useQuery( - { coordinates: activeCity$.coord.get(), timezone: dayjs.tz.guess() }, + { + coordinates: activeCity$.coord.get(), + timezone: dayjs.tz.guess(), + lang: locale, + }, // TODO: The cache (stale time) is not yet working if you refresh the page { refetchOnWindowFocus: false, staleTime: 1000 * 60 * 60 /* 1 hour */ }, ); @@ -342,6 +349,72 @@ const InternalHome = observer(() => { ) : null} + {weatherData.data?.warnings ? ( +
+ {weatherData.data.warnings.map((warning, index: number) => { + if (index > 0 || warning.status !== "active") return null; + const maxWarningTextLength = 150; + const textToLong = warning.text.length > maxWarningTextLength; + let warningText = warning.text; + + if ( + warning.text.length > maxWarningTextLength && + !isMoreWarningsCollapsibleOpen + ) { + warningText = warning.text.slice(0, maxWarningTextLength) + "..."; + } + + return ( +
+
+
+ {ReactHtmlParser(warning.title)} +
+
+ {ReactHtmlParser(warningText)} + {textToLong && ( + + )} +
+
+ {translationHome("from")}: {ReactHtmlParser(warning.sender)} +
+
+
+ ); + })} +
+ ) : null}
{weatherData.data?.hourlyForecast ? ( diff --git a/packages/api/src/routers/weather.ts b/packages/api/src/routers/weather.ts index 7a45f2cc..2ca8f65c 100644 --- a/packages/api/src/routers/weather.ts +++ b/packages/api/src/routers/weather.ts @@ -129,6 +129,26 @@ const MoonPhaseSchema = z.object({ type MoonPhase = z.infer | undefined; +const WarningSchema = z.object({ + warning: z.array( + z.object({ + sender: z.string(), + pubTime: z.string(), + title: z.string(), + startTime: z.string(), + endTime: z.string(), + status: z.string(), + level: z.string(), + severity: z.string(), + urgency: z.string(), + certainty: z.string(), + text: z.string(), + }), + ), +}); + +type Warning = z.infer | undefined; + /** * Calculates the Air Quality Index (AQI) based on the given PM10 and PM2.5 values. * @@ -172,6 +192,7 @@ export const weatherRouter = createTRPCRouter({ lon: z.number().min(-180).max(180), }), timezone: z.string(), + lang: z.union([z.string(), z.undefined()] as const), }), ) .query(async ({ input, ctx }) => { @@ -180,6 +201,7 @@ export const weatherRouter = createTRPCRouter({ timezone: input.timezone, user: ctx.ip, }); + const lang = input.lang ? input.lang : "en"; // OpenWeatherMap API const urlWeather = `https://api.openweathermap.org/data/2.5/weather?lat=${input.coordinates.lat}&lon=${input.coordinates.lon}&appid=${env.OPEN_WEATHER_API_KEY}`; // Open Meteo @@ -191,23 +213,31 @@ export const weatherRouter = createTRPCRouter({ )},${input.coordinates.lat.toFixed(2)}&key=${ env.QWEATHER_API_KEY }&date=${dayjs().tz("Asia/Shanghai").format("YYYYMMDD")}`; + const urlWarning = `https://devapi.qweather.com/v7/warning/now?location=${input.coordinates.lon.toFixed( + 2, + )},${input.coordinates.lat.toFixed(2)}&key=${ + env.QWEATHER_API_KEY + }&lang=${lang}`; const [ hourlyResult, presentWeatherResult, presentAirQualityResult, moonPhaseResult, + warningResult, ] = await Promise.allSettled([ axios.get(urlHourlyAndDailyForecast), axios.get(urlWeather), axios.get(urlAirQuality), axios.get(urlMoonPhase), + axios.get(urlWarning), ]); let hourlyAndDailyData: HourlyAndDailyWeather = undefined; let presentWeather: PresentWeather = undefined; let presentAirQuality: PresentAirQuality = undefined; let moonPhase: MoonPhase = undefined; + let warning: Warning = undefined; if (hourlyResult.status === "fulfilled") { try { @@ -330,6 +360,23 @@ export const weatherRouter = createTRPCRouter({ : "The reason is not a string", }); } + if (warningResult.status === "fulfilled") { + try { + // console.debug(warningResult.value.data); + // console.debug(urlWarning); + warning = WarningSchema.parse(warningResult.value.data); + } catch (error) { + if (error instanceof z.ZodError) { + log.error("Zod Errors in the warning", error.issues); + console.debug("Warning data without the filter", { + data: warningResult.value.data, + }); + console.debug("Warning data url", urlWarning); + } else { + log.error("Else Error in the warning", { error }); + } + } + } let presentAirQualityIndex: number | undefined = undefined; @@ -653,6 +700,7 @@ export const weatherRouter = createTRPCRouter({ ? (hourlyAndDailyData.daily.sunshine_duration[0] / 3600).toFixed(0) : undefined, maxUVIndex: hourlyAndDailyData?.daily.uv_index_max[0], + warnings: warning?.warning, }; }), });