diff --git a/package.json b/package.json index bf13405..c57b04e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ }, "dependencies": { "@headlessui/react": "^1.7.17", + "calendar-link": "^2.6.0", "clsx": "^2.0.0", + "google-auth-library": "^9.5.0", "next": "^13.4.19", "next-sitemap": "^4.2.3", "react": "^18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95ae437..3198de3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,15 @@ dependencies: '@headlessui/react': specifier: ^1.7.17 version: 1.7.17(react-dom@18.2.0)(react@18.2.0) + calendar-link: + specifier: ^2.6.0 + version: 2.6.0 clsx: specifier: ^2.0.0 version: 2.0.0 + google-auth-library: + specifier: ^9.5.0 + version: 9.5.0 next: specifier: ^13.4.19 version: 13.4.19(react-dom@18.2.0)(react@18.2.0) @@ -407,6 +413,15 @@ packages: hasBin: true dev: true + /agent-base@7.1.0: + resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} + engines: {node: '>= 14'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -584,6 +599,10 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false + /bignumber.js@9.1.2: + resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} + dev: false + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -621,6 +640,10 @@ packages: update-browserslist-db: 1.0.11(browserslist@4.21.10) dev: true + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false + /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: @@ -635,6 +658,13 @@ packages: streamsearch: 1.1.0 dev: false + /calendar-link@2.6.0: + resolution: {integrity: sha512-ypgYoFBz2w0WkJV1m6LoO31i8F5te3/rsTdJp/ONacVmr+C4Ny7rHhvwmIZS3yrbG2abbcohn2D8DZbcFZvwfA==} + dependencies: + dayjs: 1.11.10 + query-string: 6.14.1 + dev: false + /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -747,6 +777,10 @@ packages: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true + /dayjs@1.11.10: + resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} + dev: false + /debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -768,7 +802,11 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: true + + /decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + dev: false /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} @@ -833,6 +871,12 @@ packages: esutils: 2.0.3 dev: true + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /electron-to-chromium@1.4.508: resolution: {integrity: sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==} dev: true @@ -1232,6 +1276,10 @@ packages: engines: {node: '>=6'} dev: false + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -1276,6 +1324,11 @@ packages: dependencies: to-regex-range: 5.0.1 + /filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + dev: false + /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1341,6 +1394,30 @@ packages: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true + /gaxios@6.1.1: + resolution: {integrity: sha512-bw8smrX+XlAoo9o1JAksBwX+hi/RG15J+NTSxmNPIclKC3ZVK6C2afwY8OSdRvOK0+ZLecUJYtj2MmjOt3Dm0w==} + engines: {node: '>=14'} + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.2 + is-stream: 2.0.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /gcp-metadata@6.1.0: + resolution: {integrity: sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==} + engines: {node: '>=14'} + dependencies: + gaxios: 6.1.1 + json-bigint: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} dependencies: @@ -1444,6 +1521,21 @@ packages: slash: 3.0.0 dev: true + /google-auth-library@9.5.0: + resolution: {integrity: sha512-OUbP509lWVlZxuMY+Cgomw49VzZFP9myIcVeYEpeBlbXJbPC4R+K4BmO9hd3ciYM5QIwm5W1PODcKjqxtkye9Q==} + engines: {node: '>=14'} + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 6.1.1 + gcp-metadata: 6.1.0 + gtoken: 7.0.1 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: @@ -1457,6 +1549,17 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /gtoken@7.0.1: + resolution: {integrity: sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==} + engines: {node: '>=14.0.0'} + dependencies: + gaxios: 6.1.1 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -1496,6 +1599,16 @@ packages: function-bind: 1.1.1 dev: true + /https-proxy-agent@7.0.2: + resolution: {integrity: sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false @@ -1665,6 +1778,11 @@ packages: call-bind: 1.0.2 dev: true + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: false + /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -1735,6 +1853,12 @@ packages: argparse: 2.0.1 dev: true + /json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + dependencies: + bignumber.js: 9.1.2 + dev: false + /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} dev: true @@ -1764,6 +1888,21 @@ packages: object.values: 1.1.7 dev: true + /jwa@2.0.0: + resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + + /jws@4.0.0: + resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + dependencies: + jwa: 2.0.0 + safe-buffer: 5.2.1 + dev: false + /keyv@4.5.3: resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==} dependencies: @@ -1851,7 +1990,6 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1943,6 +2081,18 @@ packages: resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} dev: false + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: true @@ -2237,6 +2387,16 @@ packages: engines: {node: '>=6'} dev: true + /query-string@6.14.1: + resolution: {integrity: sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==} + engines: {node: '>=6'} + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + dev: false + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2467,6 +2627,11 @@ packages: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + /split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + dev: false + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -2479,6 +2644,11 @@ packages: queue-tick: 1.0.1 dev: false + /strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + dev: false + /string.prototype.matchall@4.0.9: resolution: {integrity: sha512-6i5hL3MqG/K2G43mWXWgP+qizFW/QH/7kCNN13JrJS5q48FN5IKksLDscexKP3dnmB6cdm9jlNgAsWNLpSykmA==} dependencies: @@ -2683,6 +2853,10 @@ packages: dependencies: is-number: 7.0.0 + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + /ts-api-utils@1.0.2(typescript@5.2.2): resolution: {integrity: sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==} engines: {node: '>=16.13.0'} @@ -2808,6 +2982,17 @@ packages: graceful-fs: 4.2.11 dev: false + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: diff --git a/public/filled-chevron-up-black.svg b/public/filled-chevron-up-black.svg new file mode 100644 index 0000000..e72ede7 --- /dev/null +++ b/public/filled-chevron-up-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/filled-chevron-up-white.svg b/public/filled-chevron-up-white.svg new file mode 100644 index 0000000..93ae3db --- /dev/null +++ b/public/filled-chevron-up-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/filled-chevron-up.svg b/public/filled-chevron-up.svg deleted file mode 100644 index 17f1659..0000000 --- a/public/filled-chevron-up.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index b7f815b..9c324c2 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -107,6 +107,7 @@ const Footer = (props: FooterProps) => { Roles Project Governance Meetings + Calendar Design Guide
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 672272e..cd0df05 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -3,7 +3,7 @@ import Image from 'next/image'; import Link from 'next/link'; import { Disclosure, Transition, TransitionRootProps } from '@headlessui/react'; import { Fragment, useCallback, useEffect, useRef, useState } from 'react'; -import FilledChevronUp from '@/../public/filled-chevron-up.svg'; +import FilledChevronUp from '@/../public/filled-chevron-up-white.svg'; import Arrow from '@/../public/arrow-white.svg'; import X from '@/../public/x.svg'; import Hamburger from '@/../public/menu-alt-3.svg'; diff --git a/src/components/Projects.tsx b/src/components/Projects.tsx index 4c7787e..4b28dc8 100644 --- a/src/components/Projects.tsx +++ b/src/components/Projects.tsx @@ -6,7 +6,7 @@ import Planner from '@/../public/projects/planner.png'; import API from '@/../public/projects/skedge.png'; import Skedge from '@/../public/projects/skedge.png'; import Jupiter from '@/../public/projects/jupiter.png'; -import FilledChevronUp from '@/../public/filled-chevron-up.svg'; +import FilledChevronUp from '@/../public/filled-chevron-up-white.svg'; import clsx from 'clsx'; type Project = { diff --git a/src/pages/api/getCalendar.ts b/src/pages/api/getCalendar.ts new file mode 100644 index 0000000..b605642 --- /dev/null +++ b/src/pages/api/getCalendar.ts @@ -0,0 +1,58 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import { auth, JWT } from 'google-auth-library'; +import type { NextApiRequest, NextApiResponse } from 'next'; + +const CALENDAR_ID = + 'c_64bca4fdc75077d852bc5236ec20402d8514792841894b264da57d41bb0ee32e@group.calendar.google.com'; + +type Data = { + message: string; + data?: unknown; +}; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + if ( + !( + 'REACT_APP_GOOGLE_CREDENTIALS' in process.env && + typeof process.env.REACT_APP_GOOGLE_CREDENTIALS === 'string' + ) + ) { + res.status(500).json({ message: 'API key is undefined' }); + return; + } + return new Promise((resolve) => { + const client = auth.fromJSON(JSON.parse(process.env.REACT_APP_GOOGLE_CREDENTIALS as string)); + if (client instanceof JWT) { + client.scopes = ['https://www.googleapis.com/auth/calendar.readonly']; + } + const url = new URL( + 'https://www.googleapis.com/calendar/v3/calendars/' + CALENDAR_ID + '/events', + ); + url.searchParams.append('singleEvents', 'True'); + url.searchParams.append('orderBy', 'startTime'); + url.searchParams.append('maxResults', '100'); + url.searchParams.append('timeMin', new Date().toISOString()); + const date = new Date(); + date.setMonth(date.getMonth() + 1); + url.searchParams.append('timeMax', date.toISOString()); + client + .request({ + url: url.href, + method: 'GET', + }) + .then((data) => { + if (data.statusText !== 'OK') { + throw new Error(data.statusText); + } + res.status(200).json({ + message: 'success', + data: data, + }); + resolve(); + }) + .catch((error) => { + res.status(400).json({ message: error.message }); + resolve(); + }); + }); +} diff --git a/src/pages/resources/calendar.tsx b/src/pages/resources/calendar.tsx new file mode 100644 index 0000000..2facc1a --- /dev/null +++ b/src/pages/resources/calendar.tsx @@ -0,0 +1,274 @@ +import Header from '@/components/Header'; +import Footer from '@/components/Footer'; +import Head from 'next/head'; +import { useEffect, useState } from 'react'; +import Image from 'next/image'; +import { ics } from 'calendar-link'; + +import ArrowWhite from '@/../public/filled-chevron-up-white.svg'; +import ArrowBlack from '@/../public/filled-chevron-up-black.svg'; + +const timeFormat = new Intl.DateTimeFormat('en-US', { + hour: 'numeric', + minute: 'numeric', + hour12: true, +}); + +const dateFormat = new Intl.DateTimeFormat('en-US', { + weekday: 'long', + day: 'numeric', +}); + +const monthFormat = new Intl.DateTimeFormat('en-US', { + month: 'long', +}); + +const fullFormat = new Intl.DateTimeFormat('en-US'); + +interface EventReactProps { + name: string; + start: string; + end: string; + location: string; + description: string | undefined; + htmlLink: string; +} + +const Event = (props: EventReactProps) => { + const important = + props.name.includes('Kickoff') || + props.name.includes('Social') || + props.name.includes('All-Hands'); + + const start = new Date(props.start); + const end = new Date(props.end); + let startTime = timeFormat.format(start); + if (start.getHours() >= 12 === end.getHours() >= 12) { + //AMPM the same + startTime = startTime.slice(0, -3); + } + + const buttonLinkClasses = + 'hover:scale-105 active:scale-95 transition duration-300 ease-in-out px-2 py-1 rounded-lg cursor-pointer border-2 ' + + (important + ? 'hover:bg-periwinkle hover:text-haiti border-periwinkle' + : 'hover:bg-royal hover:text-white border-royal'); + + const iCalFileString = ics({ + title: props.name, + start: props.start, + end: props.end, + location: props.location, + description: props.description, + url: props.htmlLink, + }); + + return ( +
+ +
+

{props.name}

+

+ {startTime} - {timeFormat.format(end)} +

+

{props.location === 'Unknown' ? 'TBD' : props.location}

+
+
+ Drowdown arrow +
+
+ {typeof props.description !== 'undefined' &&

{props.description}

} +
+ { + fetch('/api/getCalendar') + .then((response) => { + if (!response.ok) { + throw new Error(response.statusText); + } + return response.json() as Promise; + }) + .then((data) => { + if (data.message !== 'success') { + throw new Error(data.message); + } + console.log(data.data); + setEvents(data.data.data.items); + setState('done'); + }) + .catch((error) => { + setState('error'); + console.error('Feedback', error); + }); + }, []); + + let lastYear, lastMonth, lastDay; + let firstYear = true; + const labelsAndEvents = []; + if (typeof events !== 'undefined') { + for (const event of events) { + if (event.status !== 'confirmed') { + continue; + } + + const start = new Date(event.start.dateTime); + const year = start.getFullYear(); + if (lastYear !== year && !firstYear) { + labelsAndEvents.push( +

+ {year} +

, + ); + lastYear = year; + } + if (lastYear !== year && firstYear) { + lastYear = year; + firstYear = false; + } + const month = start.getMonth(); + if (lastMonth !== month) { + labelsAndEvents.push( +

+ {monthFormat.format(start)} +

, + ); + lastMonth = month; + } + const day = start.getDay(); + if (lastDay !== day) { + labelsAndEvents.push( +

+ {dateFormat.format(start)} +

, + ); + lastDay = day; + } + + labelsAndEvents.push( + , + ); + } + } + + const buttonLinkClasses = + 'hover:scale-105 active:scale-95 transition duration-300 ease-in-out px-4 py-2 rounded-lg cursor-pointer hover:bg-royal hover:text-white border-royal border-2'; + + //error state + let result = ( +
+

Error loading calendar

+ +
+ ); + + if (state === 'loading') { + result =

Loading...

; + } else if (state === 'done') { + result = ( +
+
{...labelsAndEvents}
+
+ ); + } + + return ( + <> + + Calendar - Nebula Labs + + + +
+ + {result} +