From 818d156927cc37aa3e5a78969a8df35be03783de Mon Sep 17 00:00:00 2001 From: matthew-millard <123032297+matthew-millard@users.noreply.github.com> Date: Tue, 22 Jul 2025 11:56:14 -0400 Subject: [PATCH 1/5] feat: show member count in navigation on large viewports --- scripts/fetchMeetupData.js | 212 +++++++++++++++++------------- src/components/MembersCount.astro | 17 +++ src/components/Navigation.astro | 53 ++++---- src/data/meetupStats.json | 6 + 4 files changed, 171 insertions(+), 117 deletions(-) create mode 100644 src/components/MembersCount.astro create mode 100644 src/data/meetupStats.json diff --git a/scripts/fetchMeetupData.js b/scripts/fetchMeetupData.js index f9781fc..ac1fe40 100644 --- a/scripts/fetchMeetupData.js +++ b/scripts/fetchMeetupData.js @@ -1,101 +1,135 @@ import { writeFile } from "node:fs/promises"; +const MEETUP_URL = "https://www.meetup.com/ottawa-forwardjs-meetup"; +const MEETUP_ID = "28909672"; + async function writeJsonToFile(filename, jsonData) { - const jsonString = JSON.stringify(jsonData, null, 2); + const jsonString = JSON.stringify(jsonData, null, 2); + + await writeFile(filename, jsonString, "utf8"); +} + +async function getNextDataJsonFromPage(url) { + // fetch the page and get the HTML content + const response = await fetch(url); + const htmlContent = await response.text(); - await writeFile(filename, jsonString + "\n", "utf8"); + // get the NEXT_DATA script tag + const regex = + /\s*(.*?)(?=<\/script>|\s*<\/body>)/s; + const match = htmlContent.match(regex); + + const data = match[0] + .replace('", ""); + + return data; } -async function fetchEvents(url) { - // fetch the page and get the HTML content - const response = await fetch(url); - const htmlContent = await response.text(); - - // get the NEXT_DATA script tag - const regex = - /\s*(.*?)(?=<\/script>|\s*<\/body>)/s; - const match = htmlContent.match(regex); - - const data = match[0] - .replace('", ""); - - // parse the data to JSON in the hackiest way possible - const parsedData = JSON.parse(JSON.parse(JSON.stringify(data))); - - // get the APOLLO_STATE from the parsed json - const state = parsedData.props.pageProps.__APOLLO_STATE__; - - // extract event data from the apollo state - const events = []; - for (const key of Object.keys(state)) { - if (key.includes("Event")) { - const { id, title, eventUrl, dateTime, going, venue, rsvpSettings } = - state[key]; - - // get meetup photo if it exists - let photo = null; - if (state[key].featuredEventPhoto) { - const photoRef = state[key].featuredEventPhoto.__ref; - photo = state[photoRef].highResUrl; - } - - // get member photos for the first 5 RSVPs - const memberPhotos = []; - const firstFiveRSVPs = state[key]['rsvps({"first":5})'].edges; - for (const rsvp of firstFiveRSVPs) { - const memberRef = state[rsvp.node.__ref].member.__ref; - - if (memberRef) { - const photoRef = state[memberRef]?.memberPhoto?.__ref; - if (photoRef) memberPhotos.push(state[photoRef].highResUrl); - } - } - - // get venue details - const { - name, - address, - city, - state: province, - country, - } = state[venue.__ref]; - - events.push({ - id, - title, - eventUrl, - dateTime, - rsvpsOpen: !rsvpSettings.rsvpsClosed, - rsvpCount: going.totalCount, - photo, - memberPhotos, - location: { - name, - address: `${address} ${city} ${province} ${country}`, - }, - }); - } - } - - return events; +function getApolloState(parsedData) { + // get the APOLLO_STATE from the parsed json + const state = parsedData.props.pageProps.__APOLLO_STATE__; + + return state; +} + +function doubleParseJson(data) { + // parse the data to JSON in the hackiest way possible + const parsedData = JSON.parse(JSON.parse(JSON.stringify(data))); + return parsedData; +} + +async function fetchMeetupEvents(url) { + const data = await getNextDataJsonFromPage(url); + const parsedData = doubleParseJson(data); + const state = getApolloState(parsedData); + + // extract event data from the apollo state + const events = []; + for (const key of Object.keys(state)) { + if (key.includes("Event")) { + const { id, title, eventUrl, dateTime, going, venue, rsvpSettings } = + state[key]; + + // get meetup photo if it exists + let photo = null; + if (state[key].featuredEventPhoto) { + const photoRef = state[key].featuredEventPhoto.__ref; + photo = state[photoRef].highResUrl; + } + + // get member photos for the first 5 RSVPs + const memberPhotos = []; + const firstFiveRSVPs = state[key]['rsvps({"first":5})'].edges; + for (const rsvp of firstFiveRSVPs) { + const memberRef = state[rsvp.node.__ref].member.__ref; + + if (memberRef) { + const photoRef = state[memberRef]?.memberPhoto?.__ref; + if (photoRef) memberPhotos.push(state[photoRef].highResUrl); + } + } + + // get venue details + const { + name, + address, + city, + state: province, + country, + } = state[venue.__ref]; + + events.push({ + id, + title, + eventUrl, + dateTime, + rsvpsOpen: !rsvpSettings.rsvpsClosed, + rsvpCount: going.totalCount, + photo, + memberPhotos, + location: { + name, + address: `${address} ${city} ${province} ${country}`, + }, + }); + } + } + + return events; +} + +async function fetchMeetupStats(url) { + const data = await getNextDataJsonFromPage(url); + const parsedData = doubleParseJson(data); + const state = getApolloState(parsedData); + + const { memberCounts, eventRatings } = state[`Group:${MEETUP_ID}`].stats; + + return { + memberCount: memberCounts.all, + organizerCount: memberCounts.leadership, + averageEventRating: eventRatings.average, + totalEventRatings: eventRatings.total, + }; } async function main() { - try { - const upcoming = await fetchEvents( - "https://www.meetup.com/ottawa-forwardjs-meetup/events/?type=upcoming", - ); - await writeJsonToFile("./src/data/upcomingEvents.json", upcoming); - - const past = await fetchEvents( - "https://www.meetup.com/ottawa-forwardjs-meetup/events/?type=past", - ); - // write the last 5 past events to the JSON files - await writeJsonToFile("./src/data/pastEvents.json", past.slice(0, 5)); - } catch (error) { - console.error("Error fetching events", error); - } + try { + const meetupStats = await fetchMeetupStats(MEETUP_URL); + await writeJsonToFile("./src/data/meetupStats.json", meetupStats); + + const upcoming = await fetchMeetupEvents( + `${MEETUP_URL}/events/?type=upcoming`, + ); + await writeJsonToFile("./src/data/upcomingEvents.json", upcoming); + + const past = await fetchMeetupEvents(`${MEETUP_URL}/events/?type=past`); + // write the last 5 past events to the JSON files + await writeJsonToFile("./src/data/pastEvents.json", past.slice(0, 5)); + } catch (error) { + console.error("Error fetching events", error); + } } main(); diff --git a/src/components/MembersCount.astro b/src/components/MembersCount.astro new file mode 100644 index 0000000..8f0f2ed --- /dev/null +++ b/src/components/MembersCount.astro @@ -0,0 +1,17 @@ +--- +import { memberCount } from "../data/meetupStats.json"; +--- + +

{memberCount.toLocaleString()} Members

+ + diff --git a/src/components/Navigation.astro b/src/components/Navigation.astro index 3e366d0..1c4d9a3 100644 --- a/src/components/Navigation.astro +++ b/src/components/Navigation.astro @@ -1,5 +1,6 @@ --- import logo from "../assets/forward.svg"; +import MembersCount from "./MembersCount.astro"; ---
@@ -23,11 +24,10 @@ import logo from "../assets/forward.svg"; +
@@ -185,52 +186,48 @@ import logo from "../assets/forward.svg"; ", ""); + const data = match[0] + .replace('", ""); - return data; + return data; } function getApolloState(parsedData) { - // get the APOLLO_STATE from the parsed json - const state = parsedData.props.pageProps.__APOLLO_STATE__; + // get the APOLLO_STATE from the parsed json + const state = parsedData.props.pageProps.__APOLLO_STATE__; - return state; + return state; } function doubleParseJson(data) { - // parse the data to JSON in the hackiest way possible - const parsedData = JSON.parse(JSON.parse(JSON.stringify(data))); - return parsedData; + // parse the data to JSON in the hackiest way possible + const parsedData = JSON.parse(JSON.parse(JSON.stringify(data))); + return parsedData; } async function fetchMeetupEvents(url) { - const data = await getNextDataJsonFromPage(url); - const parsedData = doubleParseJson(data); - const state = getApolloState(parsedData); - - // extract event data from the apollo state - const events = []; - for (const key of Object.keys(state)) { - if (key.includes("Event")) { - const { id, title, eventUrl, dateTime, going, venue, rsvpSettings } = - state[key]; - - // get meetup photo if it exists - let photo = null; - if (state[key].featuredEventPhoto) { - const photoRef = state[key].featuredEventPhoto.__ref; - photo = state[photoRef].highResUrl; - } - - // get member photos for the first 5 RSVPs - const memberPhotos = []; - const firstFiveRSVPs = state[key]['rsvps({"first":5})'].edges; - for (const rsvp of firstFiveRSVPs) { - const memberRef = state[rsvp.node.__ref].member.__ref; - - if (memberRef) { - const photoRef = state[memberRef]?.memberPhoto?.__ref; - if (photoRef) memberPhotos.push(state[photoRef].highResUrl); - } - } - - // get venue details - const { - name, - address, - city, - state: province, - country, - } = state[venue.__ref]; - - events.push({ - id, - title, - eventUrl, - dateTime, - rsvpsOpen: !rsvpSettings.rsvpsClosed, - rsvpCount: going.totalCount, - photo, - memberPhotos, - location: { - name, - address: `${address} ${city} ${province} ${country}`, - }, - }); - } - } - - return events; + const data = await getNextDataJsonFromPage(url); + const parsedData = doubleParseJson(data); + const state = getApolloState(parsedData); + + // extract event data from the apollo state + const events = []; + for (const key of Object.keys(state)) { + if (key.includes("Event")) { + const { id, title, eventUrl, dateTime, going, venue, rsvpSettings } = + state[key]; + + // get meetup photo if it exists + let photo = null; + if (state[key].featuredEventPhoto) { + const photoRef = state[key].featuredEventPhoto.__ref; + photo = state[photoRef].highResUrl; + } + + // get member photos for the first 5 RSVPs + const memberPhotos = []; + const firstFiveRSVPs = state[key]['rsvps({"first":5})'].edges; + for (const rsvp of firstFiveRSVPs) { + const memberRef = state[rsvp.node.__ref].member.__ref; + + if (memberRef) { + const photoRef = state[memberRef]?.memberPhoto?.__ref; + if (photoRef) memberPhotos.push(state[photoRef].highResUrl); + } + } + + // get venue details + const { + name, + address, + city, + state: province, + country, + } = state[venue.__ref]; + + events.push({ + id, + title, + eventUrl, + dateTime, + rsvpsOpen: !rsvpSettings.rsvpsClosed, + rsvpCount: going.totalCount, + photo, + memberPhotos, + location: { + name, + address: `${address} ${city} ${province} ${country}`, + }, + }); + } + } + + return events; } async function fetchMeetupStats(url) { - const data = await getNextDataJsonFromPage(url); - const parsedData = doubleParseJson(data); - const state = getApolloState(parsedData); - - const { memberCounts, eventRatings } = state[`Group:${MEETUP_ID}`].stats; - - return { - memberCount: memberCounts.all, - organizerCount: memberCounts.leadership, - averageEventRating: eventRatings.average, - totalEventRatings: eventRatings.total, - }; + const data = await getNextDataJsonFromPage(url); + const parsedData = doubleParseJson(data); + const state = getApolloState(parsedData); + + const { memberCounts, eventRatings } = state[`Group:${MEETUP_ID}`].stats; + + return { + memberCount: memberCounts.all, + organizerCount: memberCounts.leadership, + averageEventRating: eventRatings.average, + totalEventRatings: eventRatings.total, + }; } async function main() { - try { - const meetupStats = await fetchMeetupStats(MEETUP_URL); - await writeJsonToFile("./src/data/meetupStats.json", meetupStats); - - const upcoming = await fetchMeetupEvents( - `${MEETUP_URL}/events/?type=upcoming`, - ); - await writeJsonToFile("./src/data/upcomingEvents.json", upcoming); - - const past = await fetchMeetupEvents(`${MEETUP_URL}/events/?type=past`); - // write the last 5 past events to the JSON files - await writeJsonToFile("./src/data/pastEvents.json", past.slice(0, 5)); - } catch (error) { - console.error("Error fetching events", error); - } + try { + const meetupStats = await fetchMeetupStats(MEETUP_URL); + await writeJsonToFile("./src/data/meetupStats.json", meetupStats); + + const upcoming = await fetchMeetupEvents( + `${MEETUP_URL}/events/?type=upcoming`, + ); + await writeJsonToFile("./src/data/upcomingEvents.json", upcoming); + + const past = await fetchMeetupEvents(`${MEETUP_URL}/events/?type=past`); + // write the last 5 past events to the JSON files + await writeJsonToFile("./src/data/pastEvents.json", past.slice(0, 5)); + } catch (error) { + console.error("Error fetching events", error); + } } main(); diff --git a/src/components/Navigation.astro b/src/components/Navigation.astro index 1c4d9a3..a0ae91f 100644 --- a/src/components/Navigation.astro +++ b/src/components/Navigation.astro @@ -24,10 +24,11 @@ import MembersCount from "./MembersCount.astro";