Skip to content

Commit

Permalink
feat: Added a feature which shows you open weather warnings and updat…
Browse files Browse the repository at this point in the history
…ed the docs
  • Loading branch information
FleetAdmiralJakob committed Jan 12, 2024
1 parent 126bcd4 commit 5ce22a6
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 5 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion apps/web/public/locales/de/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
6 changes: 5 additions & 1 deletion apps/web/public/locales/en/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
79 changes: 76 additions & 3 deletions apps/web/src/pages/home/index.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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 */ },
);
Expand Down Expand Up @@ -342,6 +349,72 @@ const InternalHome = observer(() => {
</CollapsibleContent>
</Collapsible>
) : null}
{weatherData.data?.warnings ? (
<div className="flex w-full justify-center">
{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 (
<div
className={cn(
"mt-3 flex w-11/12 flex-col items-center rounded-md p-2 xl:w-9/12",
{
"bg-red-300":
warning.severity === "Severe" ||
warning.severity === "Extreme" ||
warning.severity === "Major",
"bg-yellow-300":
warning.severity === "Moderate" ||
warning.severity === "Minor",
"bg-green-300": warning.severity === "Standard",
"bg-blue-300":
warning.severity === "Unknown" ||
warning.severity === "None" ||
warning.severity === "Cancel",
},
)}
key={index}
>
<div className="flex flex-col text-center text-xl">
<div className="font-bold">
{ReactHtmlParser(warning.title)}
</div>
<div className="flex w-full flex-col items-center">
<span>{ReactHtmlParser(warningText)}</span>
{textToLong && (
<button
className="w-44 rounded-md bg-gray-500"
onClick={() =>
setIsMoreWarningsCollapsibleOpen(
(prevState) => !prevState,
)
}
>
{isMoreWarningsCollapsibleOpen
? translationHome("less information")
: translationHome("more information")}
</button>
)}
</div>
<div className="text-base">
{translationHome("from")}: {ReactHtmlParser(warning.sender)}
</div>
</div>
</div>
);
})}
</div>
) : null}
<div className="mt-12 flex flex-col items-center">
{weatherData.data?.hourlyForecast ? (
<ScrollArea className="mb-5 w-11/12 rounded-md xl:w-9/12">
Expand Down
48 changes: 48 additions & 0 deletions packages/api/src/routers/weather.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ const MoonPhaseSchema = z.object({

type MoonPhase = z.infer<typeof MoonPhaseSchema> | 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<typeof WarningSchema> | undefined;

/**
* Calculates the Air Quality Index (AQI) based on the given PM10 and PM2.5 values.
*
Expand Down Expand Up @@ -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 }) => {
Expand All @@ -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
Expand All @@ -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<HourlyAndDailyWeather>(urlHourlyAndDailyForecast),
axios.get<PresentWeather>(urlWeather),
axios.get<PresentAirQuality>(urlAirQuality),
axios.get<MoonPhase>(urlMoonPhase),
axios.get<Warning>(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 {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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,
};
}),
});

1 comment on commit 5ce22a6

@vercel
Copy link

@vercel vercel bot commented on 5ce22a6 Jan 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.