Skip to content

Commit

Permalink
feat: Added the ability to view the current moon phase
Browse files Browse the repository at this point in the history
  • Loading branch information
FleetAdmiralJakob committed Dec 28, 2023
1 parent 02d700d commit d54a752
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 22 deletions.
14 changes: 13 additions & 1 deletion public/locales/de/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"air quality": "Luftqualität",
"pressure": "Luftdruck",
"speed": "Geschwindigkeit",
"moon phase": "Mondphase",

"early morning": "Früh",
"morning": "Morgen",
Expand Down Expand Up @@ -53,5 +54,16 @@

"wind pressure card title": "Derzeitiger Wind",

"not available": "Nicht verfügbar"
"not available": "Nicht verfügbar",

"moon phase card title": "Derzeitige Mondphase",
"moon phase card content": "Die Mondphase ist die Form des Mondes, die wir von der Erde aus sehen können.",

"moon phase new moon": "Neumond",
"moon phase waxing crescent": "Zunehmende Sichel",
"moon phase first quarter": "Erstes Viertel",
"moon phase waxing gibbous": "Zunehmender Mond",
"moon phase full moon": "Vollmond",
"moon phase waning gibbous": "Abnehmender Mond",
"moon phase last quarter": "Letztes Viertel"
}
14 changes: 13 additions & 1 deletion public/locales/en/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"air quality": "Air Quality",
"pressure": "Pressure",
"speed": "Speed",
"moon phase": "Moon Phase",

"early morning": "Early Morning",
"morning": "Morning",
Expand Down Expand Up @@ -53,5 +54,16 @@

"wind pressure card title": "Current Wind & Pressure",

"not available": "Not available"
"not available": "Not available",

"moon phase card title": "Current Moon Phase",
"moon phase card content": "The moon phase is the shape of the directly sunlit portion of the Moon as viewed from Earth.",

"moon phase new moon": "New Moon",
"moon phase waxing crescent": "Waxing Crescent",
"moon phase first quarter": "First Quarter",
"moon phase waxing gibbous": "Waxing Gibbous",
"moon phase full moon": "Full Moon",
"moon phase waning gibbous": "Waning Gibbous",
"moon phase last quarter": "Last Quarter"
}
8 changes: 5 additions & 3 deletions src/env.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { z } from 'zod'
import {z} from 'zod'

/**
* Specify your server-side environment variables schema here. This way you can ensure the app isn't
Expand All @@ -11,7 +11,8 @@ const server = z.object({
UPSTASH_REDIS_REST_TOKEN: z.string().min(1),
UPSTASH_RATELIMITER_TOKENS_PER_TIME: z.string().min(1),
UPSTASH_RATELIMITER_TIME_INTERVAL: z.string().min(1),
RESEND_API_KEY: z.string().min(1)
RESEND_API_KEY: z.string().min(1),
QWEATHER_API_KEY: z.string().min(1)
})

/**
Expand All @@ -37,7 +38,8 @@ const processEnv = {
process.env.UPSTASH_RATELIMITER_TOKENS_PER_TIME,
UPSTASH_RATELIMITER_TIME_INTERVAL:
process.env.UPSTASH_RATELIMITER_TIME_INTERVAL,
RESEND_API_KEY: process.env.RESEND_API_KEY
RESEND_API_KEY: process.env.RESEND_API_KEY,
QWEATHER_API_KEY: process.env.QWEATHER_API_KEY
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
}

Expand Down
113 changes: 103 additions & 10 deletions src/pages/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@ import {
FaSun,
FaWind,
} from "react-icons/fa6";
import { WiRaindrop } from "react-icons/wi";
import {
WiMoonAltFull,
WiMoonAltNew,
WiMoonAltWaningCrescent2,
WiMoonAltWaningGibbous2,
WiMoonAltWaxingCrescent2,
WiMoonAltWaxingGibbous2,
WiRaindrop,
} from "react-icons/wi";
import cn from "classnames";
import { PiSunglasses } from "react-icons/pi";
import { BsWind } from "react-icons/bs";
Expand Down Expand Up @@ -689,7 +697,7 @@ const InternalHome = observer(() => {
)}

{weatherData.data?.air_quality ? (
<div className="col-span-5 row-span-2 row-start-3 rounded-md bg-gray-400 md:col-span-2 md:col-start-4 xl:col-span-1 xl:col-start-4">
<div className="col-span-5 row-span-2 row-start-3 rounded-md bg-gray-400 md:col-span-2 md:col-start-4 md:row-span-1 xl:col-span-1 xl:col-start-4">
<div className="ml-2 mt-1.5 hyphens-auto break-words text-xl">
{translationHome("air quality")}
</div>
Expand Down Expand Up @@ -726,7 +734,7 @@ const InternalHome = observer(() => {
</div>
</div>
) : (
<Skeleton className="col-span-5 row-span-2 row-start-3 md:col-span-2 md:col-start-4 xl:col-span-1 xl:col-start-4" />
<Skeleton className="col-span-5 row-span-2 row-start-3 md:col-span-2 md:col-start-4 md:row-span-1 xl:col-span-1 xl:col-start-4" />
)}

{weatherData.data?.visibility ? (
Expand All @@ -752,7 +760,7 @@ const InternalHome = observer(() => {
</HoverCard>
</div>
<div className="flex w-full items-center">
<PiSunglasses className="ml-4 mt-2 h-full w-full" />
<PiSunglasses className="ml-4 mt-2 hidden h-auto w-5/12 sm:block md:w-8/12 xl:h-32 xl:w-32" />
<div className="ml-4 mt-2 text-3xl xl:text-5xl">
{weatherData.data?.visibility}%
</div>
Expand All @@ -764,7 +772,7 @@ const InternalHome = observer(() => {

{weatherData.data?.wind_speed !== undefined &&
weatherData.data?.wind_pressure !== undefined ? (
<div className="col-span-4 col-start-6 row-span-2 row-start-3 rounded-md bg-gray-400 md:col-span-2 md:col-start-6 xl:col-span-3 xl:col-start-5">
<div className="col-span-4 col-start-6 row-span-1 row-start-3 rounded-md bg-gray-400 md:col-span-2 md:col-start-6 xl:col-span-3 xl:col-start-5">
<div className="mt-1.5 flex w-full justify-between pl-4 pr-3 text-xl">
{translationHome("wind pressure")}{" "}
<HoverCard>
Expand Down Expand Up @@ -800,9 +808,9 @@ const InternalHome = observer(() => {
</HoverCardContent>
</HoverCard>
</div>
<div className="ml-3 flex h-full w-full flex-col xl:ml-4">
<BsWind className="mt-5 h-auto w-10/12 xl:h-32 xl:w-32" />
<div className="mb-9 mt-0 flex flex-col gap-5 text-xs xl:mb-0 xl:mt-9 xl:flex-row xl:gap-10 xl:text-xl">
<div className="ml-3 flex h-3/5 w-full flex-row gap-3 md:flex-col xl:ml-4 xl:h-full">
<BsWind className="mt-5 hidden h-auto w-3/12 sm:block md:w-7/12 xl:h-28 xl:w-28" />
<div className="mb-3 mt-0 flex flex-col gap-5 text-xs xl:mt-3 xl:flex-row xl:gap-10 xl:text-xl">
<div>
{translationHome("pressure")}
<div className="mt-2">
Expand All @@ -823,9 +831,94 @@ const InternalHome = observer(() => {
</div>
</div>
) : (
<Skeleton className="col-span-4 col-start-6 row-span-2 row-start-3 w-full md:col-span-2 md:col-start-6 xl:col-span-3 xl:col-start-5" />
<Skeleton className="col-span-4 col-start-6 row-span-1 row-start-3 w-full md:col-span-2 md:col-start-6 xl:col-span-3 xl:col-start-5" />
)}
<div className="z-0 col-span-2 col-start-8 row-span-4 hidden rounded-md bg-gray-400 md:block">

{weatherData.data?.moonPhaseCode ? (
<div className="col-span-4 col-start-6 row-span-1 row-start-4 rounded-md bg-gray-400 md:col-span-4 md:col-start-4">
<div className="mt-1.5 flex justify-between pl-4 pr-3 text-xl">
<span className="hyphens-auto break-words">
{translationHome("moon phase")}{" "}
</span>
<HoverCard>
<HoverCardTrigger asChild>
<Button
className="w-10 rounded-full p-1.5"
aria-label="Infos over moon phase card"
>
<InfoIcon className="h-full w-full" />
</Button>
</HoverCardTrigger>
<HoverCardContent className="w-80">
<span className="font-semibold underline">
{translationHome("moon phase card title")}:
</span>{" "}
<br /> <br />
{ReactHtmlParser(
translationHome("moon phase card content"),
)}
</HoverCardContent>
</HoverCard>
</div>
<div className="ml-4 mt-2 flex flex-col items-center justify-center hyphens-auto break-words text-center">
{weatherData.data.moonPhaseCode === "800" ? (
<>
<WiMoonAltNew className="h-24 w-24" />
{translationHome("moon phase new moon")}
</>
) : (weatherData.data.moonPhaseCode === "801" &&
activeCity$.coord.lat.get() > 0) ||
(weatherData.data.moonPhaseCode === "807" &&
activeCity$.coord.lat.get() < 0) ? (
<>
<WiMoonAltNew className="h-24 w-24" />
{translationHome("moon phase waxing crescent")}
</>
) : (weatherData.data.moonPhaseCode === "802" &&
activeCity$.coord.lat.get() > 0) ||
(weatherData.data.moonPhaseCode === "806" &&
activeCity$.coord.lat.get() < 0) ? (
<>
<WiMoonAltWaxingCrescent2 className="h-24 w-24" />
{translationHome("moon phase first quarter")}
</>
) : (weatherData.data.moonPhaseCode === "803" &&
activeCity$.coord.lat.get() > 0) ||
(weatherData.data.moonPhaseCode === "805" &&
activeCity$.coord.lat.get() < 0) ? (
<>
<WiMoonAltWaxingGibbous2 className="h-24 w-24" />
{translationHome("moon phase waxing gibbous")}
</>
) : weatherData.data.moonPhaseCode === "804" ? (
<>
<WiMoonAltFull className="h-24 w-24" />
{translationHome("moon phase full moon")}
</>
) : (weatherData.data.moonPhaseCode === "803" &&
activeCity$.coord.lat.get() < 0) ||
(weatherData.data.moonPhaseCode === "805" &&
activeCity$.coord.lat.get() > 0) ? (
<>
<WiMoonAltWaningGibbous2 className="h-24 w-24" />
{translationHome("moon phase waning gibbous")}
</>
) : (weatherData.data.moonPhaseCode === "802" &&
activeCity$.coord.lat.get() < 0) ||
(weatherData.data.moonPhaseCode === "806" &&
activeCity$.coord.lat.get() > 0) ? (
<>
<WiMoonAltWaningCrescent2 className="h-24 w-24" />
{translationHome("moon phase last quarter")}
</>
) : null}
</div>
</div>
) : (
<Skeleton className="col-span-4 col-start-6 row-span-1 row-start-4 w-full md:col-span-4 md:col-start-4" />
)}

<div className="z-0 col-span-2 col-start-8 row-span-4 row-start-1 hidden rounded-md bg-gray-400 md:block">
<div className="h-full w-full">
<Map
position={mapPosition}
Expand Down
55 changes: 48 additions & 7 deletions src/server/api/routers/weather.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ const PresentAirQualitySchema = z.object({

type PresentAirQuality = z.infer<typeof PresentAirQualitySchema> | undefined;

const MoonPhaseSchema = z.object({
moonPhase: z.array(z.object({ icon: z.string() })),
});

type MoonPhase = z.infer<typeof MoonPhaseSchema> | undefined;

/**
* Calculates the Air Quality Index (AQI) based on the given PM10 and PM2.5 values.
*
Expand Down Expand Up @@ -171,17 +177,29 @@ export const weatherRouter = createTRPCRouter({
// Open Meteo
const urlHourlyForecast = `https://api.open-meteo.com/v1/forecast?latitude=${input.coordinates.lat}&longitude=${input.coordinates.lon}&hourly=temperature_2m,rain,showers,snowfall,precipitation_probability,cloudcover,windspeed_10m,apparent_temperature&forecast_days=9&timezone=${input.timezone}`;
const urlAirQuality = `https://air-quality-api.open-meteo.com/v1/air-quality?latitude=${input.coordinates.lat}&longitude=${input.coordinates.lon}&hourly=pm10,pm2_5,nitrogen_dioxide`;

const [hourlyResult, presentWeatherResult, presentAirQualityResult] =
await Promise.allSettled([
axios.get<HourlyWeather>(urlHourlyForecast),
axios.get<PresentWeather>(urlWeather),
axios.get<PresentAirQuality>(urlAirQuality),
]);
// QWeather
const urlMoonPhase = `https://devapi.qweather.com/v7/astronomy/moon?location=${
input.coordinates.lon
},${input.coordinates.lat}&key=${env.QWEATHER_API_KEY}&date=${dayjs()
.tz(input.timezone)
.format("YYYYMMDD")}`;

const [
hourlyResult,
presentWeatherResult,
presentAirQualityResult,
moonPhaseResult,
] = await Promise.allSettled([
axios.get<HourlyWeather>(urlHourlyForecast),
axios.get<PresentWeather>(urlWeather),
axios.get<PresentAirQuality>(urlAirQuality),
axios.get<MoonPhase>(urlMoonPhase),
]);

let hourlyData: HourlyWeather = undefined;
let presentWeather: PresentWeather = undefined;
let presentAirQuality: PresentAirQuality = undefined;
let moonPhase: MoonPhase = undefined;

if (hourlyResult.status === "fulfilled") {
try {
Expand Down Expand Up @@ -276,6 +294,28 @@ export const weatherRouter = createTRPCRouter({
});
}

if (moonPhaseResult.status === "fulfilled") {
try {
// console.debug(moonPhaseResult.value.data);
// console.debug(urlMoonPhase);
moonPhase = MoonPhaseSchema.parse(moonPhaseResult.value.data);
} catch (error) {
if (error instanceof z.ZodError) {
log.error("Zod Errors in the moon phase", error.issues);
} else {
log.error("Else Error in the moon phase", { error });
}
}
} else {
log.error("Moon phase data request failed", {
status: moonPhaseResult.status,
reason:
typeof moonPhaseResult.reason === "string"
? moonPhaseResult.reason
: "The reason is not a string",
});
}

let presentAirQualityIndex: number | undefined = undefined;

if (
Expand Down Expand Up @@ -560,6 +600,7 @@ export const weatherRouter = createTRPCRouter({
.unix(presentWeather?.sys.sunset ?? 0)
.tz(input.timezone)
.format(),
moonPhaseCode: moonPhase?.moonPhase[0]?.icon,
};
}),
});

1 comment on commit d54a752

@vercel
Copy link

@vercel vercel bot commented on d54a752 Dec 28, 2023

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.