diff --git a/src/components/Calendar.tsx b/src/components/Calendar.tsx new file mode 100644 index 0000000..d8004e9 --- /dev/null +++ b/src/components/Calendar.tsx @@ -0,0 +1,121 @@ +import * as React from "react" +import { dowToString } from "../lib/utils"; +import type { Event, SecretsCalendar } from "../lib/interfaces"; +import * as styles from "../styles/containers/Calendar.module.css" + +const CALENDAR_REFRESH_INTERVAL = 15 * 60 * 1000; +const CALENDAR_TOKEN_REFRESH_INTERVAL = 45 * 60 * 1000; + +function daysDifference(date1: Date, date2: Date) { + if (date1.getTime() > date2.getTime()) { + let tmp = date2; + date2 = date1; + date1 = tmp; + } + return Math.ceil((date2.getTime() - date1.getTime()) / 1000 / 60 / 60 / 24); +} + +const Calendar = ({ secrets }: { secrets: SecretsCalendar }) => { + const [token, setToken] = React.useState("") + const [events, setEvents] = React.useState([]) + + React.useEffect(() => { + requestToken().then(pullCalendar) + const calendarInterval = setInterval(pullCalendar, CALENDAR_REFRESH_INTERVAL); + const calendarTokenInterval = setInterval(requestToken, CALENDAR_TOKEN_REFRESH_INTERVAL); + + return () => { + clearInterval(calendarInterval); + clearInterval(calendarTokenInterval); + } + }, []) + + const processEventData = (events: Event[]) => { + const eventTable = []; + let lastDate = ""; + let i = 0; + for (const event of events) { + const startDate = new Date(event.start.date ? event.start.date : event.start.dateTime) + const startDateString = `${startDate.getFullYear()}${startDate.getMonth()}${startDate.getDate()}` + if (startDateString !== lastDate) { + lastDate = startDateString; + const dayDiff = daysDifference(new Date(), startDate); + let dayDiffString: string; + switch (dayDiff) { + case 0: dayDiffString = "Heute" + break; + case 1: dayDiffString = "Morgen" + break; + default: dayDiffString = `${dayDiff} Tage`; + break; + } + eventTable.push( + + {dowToString(startDate.getDay())}, {startDate.getDate()}. {startDate.getMonth() + 1} + ({dayDiffString}) + + ); + } + + if (event.start.date) { + eventTable.push( + {event.summary} + ); + } else { + eventTable.push( + + {event.summary} + {startDate.getHours()}:{startDate.getMinutes().toString().padStart(2, "0")} + + ) + } + } + return eventTable; + } + + const requestToken = async () => { + const response = await fetch("https://accounts.google.com/o/oauth2/token", { + method: "POST", + body: JSON.stringify({ + grant_type: "refresh_token", + refresh_token: secrets.refreshToken, + client_id: secrets.clientId, + clientSecret: secrets.clientSecret + }) + }); + const token = await response.json(); + setToken(token.access_token); + return token.access_token; + } + + const pullCalendar = async (provToken: string) => { + const correctToken = provToken || token; + if (!correctToken || correctToken === "") return; + + const timeMin = new Date(); + timeMin.setHours(timeMin.getHours() - 1); + const params = new URLSearchParams({ + orderBy: "startTime", + fields: "items(creator,start,summary)", + singleEvents: "true", + maxResults: "10", + timeMin: timeMin.toISOString(), + key: (secrets.apiKey as string) + }); + + const response = await fetch(`https://www.googleapis.com/calendar/v3/calendars/${secrets.calendarId}/events?${params.toString()}`, { + headers: { Authorization: `Bearer ${correctToken}` } + }); + const events = await response.json(); + setEvents(processEventData(events.items)); + } + + return
+ + {events} +
+
+ +} + +export default Calendar; \ No newline at end of file diff --git a/src/components/WeatherAndTime.tsx b/src/components/WeatherAndTime.tsx index 35f8f2b..3b09cec 100644 --- a/src/components/WeatherAndTime.tsx +++ b/src/components/WeatherAndTime.tsx @@ -1,46 +1,16 @@ import * as React from "react" -import type { WeatherInfo } from "../lib/interfaces"; +import { dowToString, monthToString } from "../lib/utils"; +import type { SecretsWeather, WeatherInfo } from "../lib/interfaces"; import * as styles from "../styles/containers/WeatherAndTime.module.css"; const WEATHER_REFRESH_INTERVAL = 15 * 60 * 1000; -function dowToString(dow: number) { - switch (dow) { - case 0: return "Sonntag"; - case 1: return "Montag"; - case 2: return "Dienstag"; - case 3: return "Mittwoch"; - case 4: return "Donnerstag"; - case 5: return "Freitag"; - case 6: return "Samstag"; - default: return; - } -} - -function monthToString(month: number) { - switch (month) { - case 0: return "Januar"; - case 1: return "Februar"; - case 2: return "März"; - case 3: return "April"; - case 4: return "Mail"; - case 5: return "Juni"; - case 6: return "Juli"; - case 7: return "August"; - case 8: return "September"; - case 9: return "Oktober"; - case 10: return "November"; - case 11: return "Dezember"; - default: return; - } -} - function getWeatherIcon(icon: string) { const ImportedIcon = require(`../images/weather/${icon}.svg`); return } -const WeatherAndTimeContainer = ({ apiKey, coords }) => { +const WeatherAndTime = ({ secrets }: { secrets: SecretsWeather }) => { const [date, setDate] = React.useState(new Date()); const [weather, setWeather] = React.useState({ currently: { @@ -77,7 +47,7 @@ const WeatherAndTimeContainer = ({ apiKey, coords }) => { }, []) const pullWeather = () => { - fetch(`https://api.pirateweather.net/forecast/${apiKey}/${coords}?exclude=minutely,hourly&lang=de&units=ca`) + fetch(`https://api.pirateweather.net/forecast/${secrets.apiKey}/${secrets.coords}?exclude=minutely,hourly&lang=de&units=ca`) .then(resp => resp.json()) .then(setWeather); } @@ -135,4 +105,4 @@ const WeatherAndTimeContainer = ({ apiKey, coords }) => { ) } -export default WeatherAndTimeContainer; \ No newline at end of file +export default WeatherAndTime; \ No newline at end of file diff --git a/src/lib/interfaces.ts b/src/lib/interfaces.ts index 3bdf8db..37eb94f 100644 --- a/src/lib/interfaces.ts +++ b/src/lib/interfaces.ts @@ -1,3 +1,16 @@ +export interface SecretsWeather { + apiKey: string; + coords: string; +} + +export interface SecretsCalendar { + apiKey: string; + calendarId: string; + clientId: string; + clientSecret: string; + refreshToken: string; +} + export interface WeatherInfo { currently: { icon: string; @@ -12,4 +25,9 @@ export interface WeatherInfo { temperatureLow: number; }[] } +} + +export interface Event { + start: { dateTime: string; date?: string; }; + summary: string; } \ No newline at end of file diff --git a/src/lib/types.d.ts b/src/lib/types.d.ts index a3f3afb..dff4fc6 100644 --- a/src/lib/types.d.ts +++ b/src/lib/types.d.ts @@ -1,4 +1,4 @@ declare module '*.css' { - const content: {[className: string]: string}; - export = content; - } \ No newline at end of file + const content: { [className: string]: string }; + export = content; +} \ No newline at end of file diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..6d94904 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,30 @@ +export function dowToString(dow: number) { + switch (dow) { + case 0: return "Sonntag"; + case 1: return "Montag"; + case 2: return "Dienstag"; + case 3: return "Mittwoch"; + case 4: return "Donnerstag"; + case 5: return "Freitag"; + case 6: return "Samstag"; + default: return; + } +} + +export function monthToString(month: number) { + switch (month) { + case 0: return "Januar"; + case 1: return "Februar"; + case 2: return "März"; + case 3: return "April"; + case 4: return "Mail"; + case 5: return "Juni"; + case 6: return "Juli"; + case 7: return "August"; + case 8: return "September"; + case 9: return "Oktober"; + case 10: return "November"; + case 11: return "Dezember"; + default: return; + } +} \ No newline at end of file diff --git a/src/pages/index.tsx b/src/pages/index.tsx index d48bcce..fdc212c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,6 @@ import * as React from "react" import secrets from "../../secrets.json" +import Calendar from "../components/Calendar"; import WeatherAndTimeContainer from "../components/WeatherAndTime" function importAll(r) { @@ -29,7 +30,8 @@ const IndexPage = () => { } return (
- + +
) } diff --git a/src/styles/containers/Calendar.module.css b/src/styles/containers/Calendar.module.css new file mode 100644 index 0000000..797355a --- /dev/null +++ b/src/styles/containers/Calendar.module.css @@ -0,0 +1,27 @@ +.container { + grid-area: calendar; +} +.container table { + width: 100%; + border-collapse: collapse; +} +.calendarDateHeader { + font-weight: bold; + font-size: 2vh; + background: rgba(0, 0, 0, .1); +} +.calendarDateHeaderSub { + font-size: 1.5vh; + padding-top: 0.4vh; + float: right; +} +.container table td { + padding: 5px; +} +.container tr td:first-of-type { + width: 100%; +} +.container tr.calendarEntry { + border-top: 1px solid #000000; + font-size: 1.8vh; +} \ No newline at end of file