diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6ecc92845..954c9ccf2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,6 +13,9 @@ jobs: - run: yarn install + - name: Set Sched's API token env + run: echo "SCHED_ACCESS_TOKEN=${{ secrets.SCHED_ACCESS_TOKEN }}" >> .env.production + # Verify it compiles - run: yarn build diff --git a/gatsby-node.ts b/gatsby-node.ts index 0833710e4..b306bef9e 100644 --- a/gatsby-node.ts +++ b/gatsby-node.ts @@ -1,10 +1,17 @@ +import { ScheduleSession } from "./src/components/Conf/Schedule/ScheduleList" +import { SchedSpeaker } from "./src/components/Conf/Speakers/Speaker" import { GatsbyNode } from "gatsby" import * as path from "path" import { glob } from "glob" +import _ from "lodash" import { updateCodeData } from "./scripts/update-code-data/update-code-data" import { organizeCodeData } from "./scripts/update-code-data/organize-code-data" import { sortCodeData } from "./scripts/update-code-data/sort-code-data" +require("dotenv").config({ + path: `.env.${process.env.NODE_ENV}`, +}) + export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] = async ({ actions }) => { const gql = String.raw @@ -110,12 +117,93 @@ export const onCreatePage: GatsbyNode["onCreatePage"] = async ({ }) } +const fetchData = async (url: string) => { + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "User-Agent": "GraphQL Conf / GraphQL Foundation", + }, + }) + const data = await response.json() + return data + } catch (error) { + throw new Error( + `Error fetching data from ${url}: ${error.message || error.toString()}` + ) + } +} + export const createPages: GatsbyNode["createPages"] = async ({ actions, graphql, }) => { const { createPage, createRedirect } = actions + try { + const schedAccessToken = process.env.SCHED_ACCESS_TOKEN + + const schedule: ScheduleSession[] = await fetchData( + `https://graphqlconf23.sched.com/api/session/list?api_key=${schedAccessToken}&format=json` + ) + + const usernames: { username: string }[] = await fetchData( + `https://graphqlconf23.sched.com/api/user/list?api_key=${schedAccessToken}&format=json&fields=username` + ) + + // Fetch full info of each speaker individually and concurrently + const speakers: SchedSpeaker[] = await Promise.all( + usernames.map(async user => { + await new Promise(resolve => setTimeout(resolve, 2000)) // 2 second delay between requests, rate limit is 30req/min + return fetchData( + `https://graphqlconf23.sched.com/api/user/get?api_key=${schedAccessToken}&by=username&term=${user.username}&format=json&fields=username,company,position,name,about,location,url,avatar,role,socialurls` + ) + }) + ) + + // Create schedule page + createPage({ + path: "/conf/schedule", + component: path.resolve("./src/templates/schedule.tsx"), + context: { schedule }, + }) + + // Create schedule events' pages + schedule.forEach(event => { + const eventSpeakers = speakers.filter(e => + event.speakers?.includes(e.name) + ) + + createPage({ + path: `/conf/schedule/${event.id}`, + component: path.resolve("./src/templates/event.tsx"), + context: { + event, + speakers: eventSpeakers, + }, + }) + }) + + // Create speakers list page + createPage({ + path: "/conf/speakers", + component: path.resolve("./src/templates/speakers.tsx"), + context: { speakers }, + }) + + // Create a page for each speaker + speakers.forEach(speaker => { + createPage({ + path: `/conf/speakers/${speaker.username}`, + component: path.resolve("./src/templates/speaker.tsx"), + context: { speaker, schedule }, + }) + }) + } catch (error) { + console.log("CATCH ME:", error) + } + createRedirect({ fromPath: "/conf/program", toPath: "/conf/schedule", diff --git a/package.json b/package.json index f97018b83..f6724c7a1 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "clsx": "1.2.1", "codemirror": "5.65.1", "codemirror-graphql": "1.3.2", + "date-fns": "^2.30.0", "gatsby": "5.10.0", "gatsby-plugin-anchor-links": "1.2.1", "gatsby-plugin-feed": "5.10.0", @@ -43,6 +44,7 @@ "prismjs": "1.29.0", "react": "18.2.0", "react-dom": "18.2.0", + "react-tooltip": "^5.18.1", "timeago.js": "4.0.2" }, "devDependencies": { @@ -50,6 +52,7 @@ "@tailwindcss/typography": "0.5.9", "@types/codemirror": "5.60.7", "@types/prismjs": "1.26.0", + "@types/react-tooltip": "^4.2.4", "@typescript-eslint/parser": "5.59.7", "autoprefixer": "10.4.14", "eslint": "8.42.0", diff --git a/src/assets/css/global.css b/src/assets/css/global.css index 43cbb2578..51c793afb 100644 --- a/src/assets/css/global.css +++ b/src/assets/css/global.css @@ -1,4 +1,4 @@ -/*@tailwind base;*/ +@tailwind base; @tailwind components; @tailwind utilities; diff --git a/src/components/Conf/Header/index.tsx b/src/components/Conf/Header/index.tsx index 6f31b4e10..96a7d8be7 100644 --- a/src/components/Conf/Header/index.tsx +++ b/src/components/Conf/Header/index.tsx @@ -1,45 +1,96 @@ -import React from "react" +import React, { useState } from "react" import ButtonConf from "../Button" +import { Cross2Icon } from "@radix-ui/react-icons" interface LinkItem { text: string href: string - noMobile?: boolean } const links: LinkItem[] = [ { text: "Attend", href: "/conf/#attend" }, + { text: "Speakers", href: "/conf/speakers/" }, { text: "Schedule", href: "/conf/schedule/" }, { text: "Sponsor", href: "/conf/sponsor/" }, { text: "Partner", href: "/conf/partner/" }, { text: "FAQ", href: "/conf/faq/" }, ] +const LinksList = () => ( + <> + {links.map(link => ( + + {link.text} + + ))} + +) + const HeaderConf = () => { + const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false) + + const handleDrawerClick = () => { + setMobileDrawerOpen(!mobileDrawerOpen) + } + return (
-
+
- {links.map(link => ( - - {link.text} - - ))} - + )} + + + +
- Buy a Ticket! - + + Buy a Ticket! + +
+ + +
+
) diff --git a/src/components/Conf/Schedule/ScheduleList.tsx b/src/components/Conf/Schedule/ScheduleList.tsx new file mode 100644 index 000000000..8e481a69f --- /dev/null +++ b/src/components/Conf/Schedule/ScheduleList.tsx @@ -0,0 +1,202 @@ +import { format, parseISO, compareAsc } from "date-fns" +import React, { FC, useEffect, useState } from "react" +import { eventsColors } from "../../../templates/schedule" +import { Tooltip } from "react-tooltip" +import EventTemplate, { EventComponent } from "../../../templates/event" + +function groupByKey(arr: T[], getKey: (entity: T) => any) { + return Array.from<[string, T[]]>( + arr.reduce( + (entryMap, e) => + entryMap.set(getKey(e) as keyof T, [ + ...(entryMap.get(getKey(e) as keyof T) || []), + e, + ]), + new Map() + ) + ) +} + +export interface ScheduleSession { + id: string + audience: string + description: string + event_end: string + event_start: string + event_subtype: string + event_type: string + name: string + speakers?: string +} + +type Date = string +type ConcurrentSessionsGroups = [Date, ScheduleSession[]][] +type SessionsGroupByDay = [Date, ConcurrentSessionsGroups][] + +interface Props { + showEventType?: boolean + scheduleData: ScheduleSession[] + filterSchedule?: (sessions: ScheduleSession[]) => ScheduleSession[] +} +const ScheduleList: FC = ({ + showEventType, + filterSchedule, + scheduleData, +}) => { + const [groupedSessionsByDay, setGroupedSessionsByDay] = + useState([]) + const [hoveredSession, setHoveredSession] = useState( + null + ) + const [isOpen, setIsOpen] = useState(false) + const [hoveredSessionId, setHoveredSessionId] = useState(null) + + useEffect(() => { + const filteredSortedSchedule = ( + filterSchedule ? filterSchedule(scheduleData) : scheduleData + ).sort((a, b) => + compareAsc(new Date(a.event_start), new Date(b.event_start)) + ) + + const groupedConcurrentSessions = groupByKey( + filteredSortedSchedule, + e => e.event_start + ) + + const groupedSessionsByDay = groupByKey( + groupedConcurrentSessions, + // e[0] is the event date `yyyy/mm/dd hh:mm` and we split it by empty space to only get the day date excluding time. + e => e[0].split(" ")[0] + ) + + setGroupedSessionsByDay(groupedSessionsByDay) + }, []) + + return ( + <> + + + {groupedSessionsByDay.map(([date, concurrentSessionsGroup]) => ( +
+

+ {format(parseISO(date), "EEEE, MMMM d")} +

+ {concurrentSessionsGroup.map(([sharedStartDate, sessions]) => ( +
+
+
+ + {format(parseISO(sharedStartDate), "hh:mmaaaa 'PDT'")} + +
+
+
+
+ + {sessions.map(session => { + const singularEventType = ( + session.event_type as string + ).substring(0, session.event_type.length - 1) + + const [backgroundColor, textColor] = getSessionColor( + session.event_type.toLowerCase() + ) + + return session.event_type === "Breaks" ? ( +
+ {showEventType ? singularEventType + " / " : ""} + {session.name} +
+ ) : ( + { + setHoveredSession(session) + setHoveredSessionId(`session-${session.id}`) + }} + > + {showEventType ? singularEventType + " / " : ""} + {session.name} + + ) + })} +
+
+
+ ))} +
+ ))} + + ) +} + +export default ScheduleList + +function getSessionColor(sessionType: string) { + return ( + eventsColors + .find(e => sessionType.includes(e[0].toLowerCase())) + ?.slice(1) || [] + ) +} diff --git a/src/components/Conf/Speakers/Avatar.tsx b/src/components/Conf/Speakers/Avatar.tsx new file mode 100644 index 000000000..eed1115e9 --- /dev/null +++ b/src/components/Conf/Speakers/Avatar.tsx @@ -0,0 +1,41 @@ +import React, { FC } from "react" + +interface Props { + avatar?: string + name: string + className: string + href?: string +} +export const Avatar: FC = ({ avatar, name, className, href }) => { + const nameInitialsAvatarFallback = name + .split(" ") + .map(e => e[0]) + .join("") + .toUpperCase() + + const Component = () => + avatar ? ( + {`${name} + ) : ( +
+ + {nameInitialsAvatarFallback} + +
+ ) + + if (href) + return ( + + + + ) + + return +} diff --git a/src/components/Conf/Speakers/Speaker.tsx b/src/components/Conf/Speakers/Speaker.tsx new file mode 100644 index 000000000..eaac56fd3 --- /dev/null +++ b/src/components/Conf/Speakers/Speaker.tsx @@ -0,0 +1,111 @@ +import React, { FC } from "react" +import { Avatar } from "./Avatar" +import { ReactComponent as TwitterIcon } from "../../../../static/img/logos/twitter.svg" +import { ReactComponent as FacebookIcon } from "../../../../static/img/logos/facebook.svg" +import { ReactComponent as InstagramIcon } from "../../../../static/img/logos/instagram.svg" +import { ReactComponent as SnapChatIcon } from "../../../../static/img/logos/snapchat.svg" +import { ReactComponent as LinkedinIcon } from "../../../../static/img/logos/linkedin.svg" + +export interface SchedSpeaker { + username: string + name: string + about: string + company?: string + position?: string + avatar?: string + url?: string + role: string + location?: string + socialurls?: { service: string; url: string }[] +} + +type SocialMediaIconServiceType = + | "twitter" + | "linkedin" + | "facebook" + | "instagram" + | "snapchat" + +const SocialMediaIcon = ({ + service, +}: { + service: SocialMediaIconServiceType +}) => { + switch (service) { + case "twitter": + return + case "linkedin": + return + case "facebook": + return + case "instagram": + return + case "snapchat": + return + default: + return null + } +} + +const Speaker: FC = ({ + name, + company, + position, + avatar, + url, + username, + socialurls, +}) => { + return ( +
+ +
+ + {name || "-"} + +

+ {company ? ( + url ? ( + + {company} + + ) : ( + company + ) + ) : ( + "-" + )} +

+

{position || "-"}

+
+ {socialurls?.map((e, index) => { + const isLastOne = socialurls.length - 1 === index + return ( + + + + ) + })} +
+
+
+ ) +} + +export default Speaker diff --git a/src/components/Conf/Speakers/index.tsx b/src/components/Conf/Speakers/index.tsx index e28141bdd..2b8946fa1 100644 --- a/src/components/Conf/Speakers/index.tsx +++ b/src/components/Conf/Speakers/index.tsx @@ -100,3 +100,5 @@ const SpeakersConf = () => { } export default SpeakersConf + +export { speakers as keynoteSpeakers } diff --git a/src/pages/conf/schedule.tsx b/src/pages/conf/sched.tsx similarity index 100% rename from src/pages/conf/schedule.tsx rename to src/pages/conf/sched.tsx diff --git a/src/templates/event.tsx b/src/templates/event.tsx new file mode 100644 index 000000000..fb8f36955 --- /dev/null +++ b/src/templates/event.tsx @@ -0,0 +1,230 @@ +import React, { FC } from "react" +import { PageProps } from "gatsby" +import FooterConf from "../components/Conf/Footer" +import HeaderConf from "../components/Conf/Header" +import LayoutConf from "../components/Conf/Layout" +import SeoConf from "../components/Conf/Seo" +import { SchedSpeaker } from "../components/Conf/Speakers/Speaker" +import { ScheduleSession } from "../components/Conf/Schedule/ScheduleList" +import { format, parseISO } from "date-fns" +import { ReactComponent as TwitterIcon } from "../../static/img/logos/twitter.svg" +import { ReactComponent as FacebookIcon } from "../../static/img/logos/facebook.svg" +import { ReactComponent as InstagramIcon } from "../../static/img/logos/instagram.svg" +import { ReactComponent as SnapChatIcon } from "../../static/img/logos/snapchat.svg" +import { ReactComponent as LinkedinIcon } from "../../static/img/logos/linkedin.svg" +import { Avatar } from "../components/Conf/Speakers/Avatar" + +type SocialMediaIconServiceType = + | "twitter" + | "linkedin" + | "facebook" + | "instagram" + | "snapchat" + +const SocialMediaIcon = ({ + service, +}: { + service: SocialMediaIconServiceType +}) => { + switch (service) { + case "twitter": + return + case "linkedin": + return + case "facebook": + return + case "instagram": + return + case "snapchat": + return + default: + return null + } +} + +const Tag = ({ text }: { text: string }) => + !text ? null : ( + + {text} + + ) + +export const EventComponent: FC<{ + event: ScheduleSession + speakers: SchedSpeaker[] + hideBackButton?: boolean +}> = ({ event, speakers, hideBackButton }) => { + return ( +
+
+
+ {!hideBackButton && ( + + + ← + + Back to Schedule + + )} +
+
+ + + {/* */} + + + + {format(parseISO(event.event_start), "EEEE, MMM d")} + + + + {/* */} + + + + {format(parseISO(event.event_start), "hh:mmaaaa 'PDT'")} + +
+

+ {/* Event name without speaker's name and company */} + {event.name} +

+
+ + + +
+

+ {event.description} +

+ +
+ {speakers?.map(speaker => ( +
+ + +
+ + {speaker.name} + + + + {renderPositionAndCompany(speaker)} + + {!!speaker.socialurls?.length && ( +
+
+ {speaker.socialurls.map(social => ( + + + + ))} +
+
+ )} +
+
+ ))} +
+
+
+
+
+ ) +} + +const EventTemplate: FC< + PageProps<{}, { event: ScheduleSession; speakers: SchedSpeaker[] }> +> = ({ pageContext }) => { + return ( + + + + + + + ) +} + +export default EventTemplate + +export function Head() { + return +} + +function renderPositionAndCompany(speaker: SchedSpeaker) { + // Reassign "-" if position or company are undefined + const position = speaker.position || "-" + const company = speaker.company || "-" + + // Only include anchor element if url is not an empty string + const companyElement = + speaker.url !== "" ? ( + + {company} + + ) : ( + company + ) + + if (position !== "-" && company !== "-") { + return ( + <> + {position} at {companyElement} + + ) + } else if (position !== "-") { + return position + } else if (company !== "-") { + return <>Works at {companyElement} + } else { + return "-" + } +} diff --git a/src/templates/schedule.tsx b/src/templates/schedule.tsx new file mode 100644 index 000000000..394030600 --- /dev/null +++ b/src/templates/schedule.tsx @@ -0,0 +1,71 @@ +import React, { FC } from "react" +import FooterConf from "../components/Conf/Footer" +import HeaderConf from "../components/Conf/Header" +import LayoutConf from "../components/Conf/Layout" +import SeoConf from "../components/Conf/Seo" +import { PageProps } from "gatsby" +import ScheduleList, { + ScheduleSession, +} from "../components/Conf/Schedule/ScheduleList" + +export const eventsColors = [ + ["Breaks", "#a7b7c4", "#171c24"], // Cool light blue with Dark Blue-Gray text + ["Keynote Sessions", "#a56be8", "#ffffff"], // Vibrant Purple with White text + ["Lightning Talks", "#16a596", "#ffffff"], // Turquoise with White text + ["Session Presentations", "#ec4646", "#ffffff"], // Vibrant Red with White text + ["Workshops", "#e6812f", "#ffffff"], // Slightly Darker Orange with White text +] + +const ScheduleTemplate: FC> = ({ + pageContext: { schedule }, +}) => { + return ( + + + +
+
+

GraphQLConf 2023 Schedule

+
+

September 19-21, 2023 I San Francisco Bay Area, CA

+

+ Please note: All session times are in Pacific Daylight Time (UTC + -7). To view the schedule in your preferred timezone, + please select from the drop-down menu to the right, above “Filter + by Date.” +

+

+ IMPORTANT NOTE: Timing of sessions and room locations are{" "} + subject to change. +

+ +
+ + Event Types: + +
+ {eventsColors.map(([event, color]) => ( +
+
+ {event} +
+ ))} +
+
+ +
+
+
+ +
+ ) +} + +export function Head() { + return +} + +export default ScheduleTemplate diff --git a/src/templates/speaker.tsx b/src/templates/speaker.tsx new file mode 100644 index 000000000..826aacbcd --- /dev/null +++ b/src/templates/speaker.tsx @@ -0,0 +1,178 @@ +import React, { FC } from "react" +import FooterConf from "../components/Conf/Footer" +import HeaderConf from "../components/Conf/Header" +import LayoutConf from "../components/Conf/Layout" +import SeoConf from "../components/Conf/Seo" +import { PageProps } from "gatsby" +import { SchedSpeaker } from "../components/Conf/Speakers/Speaker" +import ScheduleList from "../components/Conf/Schedule/ScheduleList" +import { Avatar } from "../components/Conf/Speakers/Avatar" +import { ReactComponent as TwitterIcon } from "../../static/img/logos/twitter.svg" +import { ReactComponent as FacebookIcon } from "../../static/img/logos/facebook.svg" +import { ReactComponent as InstagramIcon } from "../../static/img/logos/instagram.svg" +import { ReactComponent as SnapChatIcon } from "../../static/img/logos/snapchat.svg" +import { ReactComponent as LinkedinIcon } from "../../static/img/logos/linkedin.svg" + +type SocialMediaIconServiceType = + | "twitter" + | "linkedin" + | "facebook" + | "instagram" + | "snapchat" + +const SocialMediaIcon = ({ + service, +}: { + service: SocialMediaIconServiceType +}) => { + switch (service) { + case "twitter": + return + case "linkedin": + return + case "facebook": + return + case "instagram": + return + case "snapchat": + return + default: + return null + } +} + +const SpeakersTemplate: FC< + PageProps<{}, { speaker: SchedSpeaker; schedule: any }> +> = ({ pageContext: { schedule, speaker } }) => { + return ( + + + +
+
+ <> +
+ + + ← + + Back to Speakers + +
+ + +
+
+

+ {speaker.name} +

+ + {!!speaker.socialurls?.length && ( +
+
+ {speaker.socialurls.map(social => ( + + + + ))} +
+
+ )} +
+
+ {renderPositionAndCompany(speaker)} +
+

+

+
+
+ +
+

+ My Speakers Sessions: +

+ {speaker && ( + + sessions.filter(session => + session.speakers?.includes(speaker?.name) + ) + } + /> + )} +
+ +
+
+ + +
+ ) +} + +export default SpeakersTemplate + +export function Head() { + return +} + +function renderPositionAndCompany(speaker: SchedSpeaker) { + // Reassign "-" if position or company are undefined + const position = speaker.position || "-" + const company = speaker.company || "-" + + // Only include anchor element if url is not an empty string + const companyElement = + speaker.url !== "" ? ( + + {company} + + ) : ( + company + ) + + if (position !== "-" && company !== "-") { + return ( + <> + {position} at {companyElement} + + ) + } else if (position !== "-") { + return position + } else if (company !== "-") { + return <>Works at {companyElement} + } else { + return "-" + } +} diff --git a/src/templates/speakers.tsx b/src/templates/speakers.tsx new file mode 100644 index 000000000..ed3bd8b55 --- /dev/null +++ b/src/templates/speakers.tsx @@ -0,0 +1,78 @@ +import React, { FC, useEffect, useState } from "react" +import FooterConf from "../components/Conf/Footer" +import HeaderConf from "../components/Conf/Header" +import LayoutConf from "../components/Conf/Layout" +import SeoConf from "../components/Conf/Seo" +import { keynoteSpeakers } from "../components/Conf/Speakers" +import Speaker, { SchedSpeaker } from "../components/Conf/Speakers/Speaker" +import { PageProps } from "gatsby" + +const SpeakersTemplate: FC> = ({ + pageContext: { speakers: speakersData }, +}) => { + const [speakers, setSpeakers] = useState([]) + + useEffect(() => { + const keynoteNames = keynoteSpeakers.map(speaker => speaker.name) + + // create an array for keynote speakers in fetched data maintaining the order in keynoteSpeakers + const keynoteSpeakersData = keynoteNames + .map(name => { + return speakersData.find( + (speaker: any) => + speaker.name === name && speaker.role.includes("speaker") + ) + }) + .filter(Boolean) as SchedSpeaker[] + + const otherSpeakersData = speakersData.filter( + (speaker: any) => + speaker.role.includes("speaker") && !keynoteNames.includes(speaker.name) + ) + + // Sort other speakers by last name alphabetically + otherSpeakersData.sort((a: any, b: any) => { + const aLastName = a.name.split(" ").slice(-1)[0].toLowerCase() + const bLastName = b.name.split(" ").slice(-1)[0].toLowerCase() + + return aLastName.localeCompare(bLastName) + }) + + setSpeakers([...keynoteSpeakersData, ...otherSpeakersData]) + }, []) + + return ( + + + +
+
+

GraphQLConf 2023 Speakers

+

+ Meet the unique lineup of insightful speakers we've carefully + chosen, who are primed to share their groundbreaking ideas and + innovative practices in the realm of GraphQL at the conference. +

+
+
+ {speakers.length ? ( + speakers.map((speaker: any) => ( + + )) + ) : ( +

+ Loading Speakers... +

+ )} +
+
+ +
+ ) +} + +export function Head() { + return +} + +export default SpeakersTemplate diff --git a/static/img/logos/facebook.svg b/static/img/logos/facebook.svg new file mode 100644 index 000000000..8673e33c3 --- /dev/null +++ b/static/img/logos/facebook.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/static/img/logos/instagram.svg b/static/img/logos/instagram.svg new file mode 100644 index 000000000..d37922b6f --- /dev/null +++ b/static/img/logos/instagram.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/logos/linkedin.svg b/static/img/logos/linkedin.svg new file mode 100644 index 000000000..639ed9d9e --- /dev/null +++ b/static/img/logos/linkedin.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/static/img/logos/snapchat.svg b/static/img/logos/snapchat.svg new file mode 100644 index 000000000..a4684fb07 --- /dev/null +++ b/static/img/logos/snapchat.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/yarn.lock b/yarn.lock index 7601b038e..72e220bd0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1465,6 +1465,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.18.3": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" + integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/runtime@^7.20.7": version "7.20.13" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b" @@ -1624,6 +1631,18 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== +"@floating-ui/core@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.3.1.tgz#4d795b649cc3b1cbb760d191c80dcb4353c9a366" + integrity sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g== + +"@floating-ui/dom@^1.0.0": + version "1.4.5" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.4.5.tgz#336dfb9870c98b471ff5802002982e489b8bd1c5" + integrity sha512-96KnRWkRnuBSSFbj0sFGwwOUd8EkiecINVl0O9wiZlZ64EkpyAOG3Xc2vKKNJmru0Z7RqWNymA+6b8OZqjgyyw== + dependencies: + "@floating-ui/core" "^1.3.1" + "@gatsbyjs/parcel-namer-relative-to-cwd@^2.10.0": version "2.10.0" resolved "https://registry.yarnpkg.com/@gatsbyjs/parcel-namer-relative-to-cwd/-/parcel-namer-relative-to-cwd-2.10.0.tgz#3ad8ba465cde801282809559190893aab1f42e8e" @@ -2495,6 +2514,11 @@ "@pnpm/network.ca-file" "^1.0.1" config-chain "^1.1.11" +"@popperjs/core@^2.11.5": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@radix-ui/react-aspect-ratio@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.0.2.tgz#4d40e9d80d861ae5805e951bc319802d2d65a96e" @@ -2950,6 +2974,13 @@ dependencies: "@types/react" "*" +"@types/react-tooltip@^4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@types/react-tooltip/-/react-tooltip-4.2.4.tgz#d0acad2a3061806e10aab91dd26a95a77fd35125" + integrity sha512-UzjzmgY/VH3Str6DcAGTLMA1mVVhGOyARNTANExrirtp+JgxhaIOVDxq4TIRmpSi4voLv+w4HA9CC5GvhhCA0A== + dependencies: + react-tooltip "*" + "@types/react@*": version "16.9.55" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.55.tgz#47078587f5bfe028a23b6b46c7b94ac0d436acff" @@ -4215,6 +4246,11 @@ ci-info@2.0.0, ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +classnames@^2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" + integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== + cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" @@ -10189,6 +10225,11 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== +react-fast-compare@^3.0.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -10199,6 +10240,23 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-popper-tooltip@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/react-popper-tooltip/-/react-popper-tooltip-4.4.2.tgz#0dc4894b8e00ba731f89bd2d30584f6032ec6163" + integrity sha512-y48r0mpzysRTZAIh8m2kpZ8S1YPNqGtQPDrlXYSGvDS1c1GpG/NUXbsbIdfbhXfmSaRJuTcaT6N1q3CKuHRVbg== + dependencies: + "@babel/runtime" "^7.18.3" + "@popperjs/core" "^2.11.5" + react-popper "^2.3.0" + +react-popper@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" + integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== + dependencies: + react-fast-compare "^3.0.1" + warning "^4.0.2" + react-refresh@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" @@ -10213,6 +10271,14 @@ react-server-dom-webpack@0.0.0-experimental-c8b778b7f-20220825: loose-envify "^1.1.0" neo-async "^2.6.1" +react-tooltip@*, react-tooltip@^5.18.1: + version "5.18.1" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.18.1.tgz#72945248d1fa141d5b02e59b50a805d0098b0c01" + integrity sha512-5zUKoMoKHTYxobzhR160+kkHdLtB+yYO8p16aQRoz2jGcOdtV0o1enNynoA4YqQb3xTjl84N8KLNoscmgMNuSg== + dependencies: + "@floating-ui/dom" "^1.0.0" + classnames "^2.3.0" + react@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -12010,7 +12076,7 @@ vscode-languageserver-types@^3.17.1: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== -warning@^4.0.3: +warning@^4.0.2, warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==