From 0af682f4177147a92386459b26e796bf8490bf6e Mon Sep 17 00:00:00 2001 From: Dhruvil Mehta <68022411+dhruvilmehta@users.noreply.github.com> Date: Mon, 10 Jul 2023 00:50:39 +0530 Subject: [PATCH 1/7] feat: community events to map (#7522) * resolved merge conflicts * Update components/map/Clusters.js * Update components/map/EventMarker.js * Update components/map/EventMarker.js * Update models/Profile.js * Suggestions implemented * Update components/map/EventMarker.js --------- Co-authored-by: Eddie Jaoude --- components/map/Clusters.js | 13 +++--- components/map/EventMarker.js | 58 ++++++++++++++++++++++++++ components/map/Map.js | 4 +- models/Profile/Event.js | 8 ++++ pages/api/events.js | 18 +++++++- pages/api/system/reload.js | 76 ++++++++++++++++++++++++++-------- pages/map.js | 55 +++++++++++++++++++----- public/placard.png | Bin 0 -> 23774 bytes 8 files changed, 196 insertions(+), 36 deletions(-) create mode 100644 components/map/EventMarker.js create mode 100644 public/placard.png diff --git a/components/map/Clusters.js b/components/map/Clusters.js index 1d64cfc2fa8..4f02bf347ca 100644 --- a/components/map/Clusters.js +++ b/components/map/Clusters.js @@ -4,8 +4,9 @@ import { Marker, useMap } from "react-leaflet"; import useSupercluster from "use-supercluster"; import UserMarker from "./UserMarker"; import styles from "./Clusters.module.css"; +import EventMarker from "./EventMarker"; -export default function Clusters({users}) { +export default function Clusters({points}) { const map = useMap(); const mapB = map.getBounds(); const [bounds, setBounds] = useState([ @@ -32,7 +33,7 @@ export default function Clusters({users}) { }) const { clusters, supercluster } = useSupercluster({ - points: users, + points: points, bounds, zoom, options: { @@ -40,7 +41,6 @@ export default function Clusters({users}) { maxZoom: 18 } }); - const icons = {}; const fetchIcon = (count) => { const size = @@ -65,7 +65,8 @@ export default function Clusters({users}) { const { cluster: isCluster, point_count: pointCount, - username + username, + name } = cluster.properties; // we have a cluster to render @@ -91,7 +92,9 @@ export default function Clusters({users}) { } // we have a single point to render - return ( + return cluster.properties.isEvent ? ( + + ) : ( ); })} diff --git a/components/map/EventMarker.js b/components/map/EventMarker.js new file mode 100644 index 00000000000..84cdc935383 --- /dev/null +++ b/components/map/EventMarker.js @@ -0,0 +1,58 @@ +import L from "leaflet"; +import { Marker, Popup } from "react-leaflet"; +import { ReactMarkdown } from "react-markdown/lib/react-markdown"; +import Link from "@components/Link"; + +export default function EventMarker({event}) { + // Custom component for rendering links within ReactMarkdown + const LinkRenderer = ({ href, children }) => ( + + {children} + + ); + + return ( + + + + + `, + popupAnchor: [0, -10], + iconSize: [40, 40], + iconAnchor: [20, 20], + })} + position={[event.geometry.coordinates[1], event.geometry.coordinates[0]]} + > + +
+

+ + {event.properties.name} + +

+ + {[ + event.properties.location.city, + event.properties.location.state, + event.properties.location.country, + ] + .filter((x) => x) + .join(", ")} + + + + {event.properties.description} + + + + {`${new Date(event.properties.date.start).toLocaleDateString()} - + ${new Date(event.properties.date.end).toLocaleDateString()}`} + +
+
+
+ ) +} diff --git a/components/map/Map.js b/components/map/Map.js index 34b05ae0121..5e7c5885211 100644 --- a/components/map/Map.js +++ b/components/map/Map.js @@ -2,7 +2,7 @@ import { MapContainer, TileLayer } from "react-leaflet"; import Clusters from "./Clusters"; import "leaflet/dist/leaflet.css"; -export default function Map({ users }) { +export default function Map({ points }) { const boundsMap = [ [-90, -180], // Southwest coordinates [90, 180], // Northeast coordinates @@ -23,7 +23,7 @@ export default function Map({ users }) { attribution='© OpenStreetMap contributors' url="https://b.tile.openstreetmap.org/{z}/{x}/{y}.png" /> - + ); } diff --git a/models/Profile/Event.js b/models/Profile/Event.js index ced1e30f612..4b62ea0ccf1 100644 --- a/models/Profile/Event.js +++ b/models/Profile/Event.js @@ -34,6 +34,14 @@ const EventSchema = new Schema({ price: { startingFrom: Number, }, + location: { + road: String, + city: String, + state: String, + country: String, + lat: Number, + lon: Number, + }, }); EventSchema.pre("save", () => { diff --git a/pages/api/events.js b/pages/api/events.js index 6927b4aac51..dd1b4316b79 100644 --- a/pages/api/events.js +++ b/pages/api/events.js @@ -12,7 +12,7 @@ export default async function handler(req, res) { return res.status(200).json(events); } -export async function getEvents() { +export async function getEvents(withLocation = false) { let events = []; try { events = await Profile.aggregate([ @@ -20,6 +20,21 @@ export async function getEvents() { { $match: { "events.date.start": { $gt: new Date() }, isEnabled: true } }, { $unwind: "$events" }, { $match: { "events.date.end": { $gt: new Date() } } }, + ...(withLocation + ? [ + { + $match: { + $and: [ + { "events.location": { $exists: true } }, + { "events.location.lat": { $exists: true } }, + { "events.location.lon": { $exists: true } }, + { "events.location.lat": { $ne: null } }, + { "events.location.lon": { $ne: null } }, + ], + }, + }, + ] + : []), { $sort: { "events.date.start": 1 } }, { $group: { @@ -31,6 +46,7 @@ export async function getEvents() { url: { $first: "$events.url" }, name: { $first: "$events.name" }, description: { $first: "$events.description" }, + location: { $first: "$events.location" }, isEnabled: { $first: "$isEnabled" }, }, }, diff --git a/pages/api/system/reload.js b/pages/api/system/reload.js index 9d1ecad91d6..6a2c48eca92 100644 --- a/pages/api/system/reload.js +++ b/pages/api/system/reload.js @@ -246,28 +246,68 @@ export default async function handler(req, res) { } // - events - try { - if (profile.events) { + async function getCoordinates(city, state, country) { + let locationDb = {}; + const provided = [city, state, country].filter((x) => x).join(","); + if (locationDb[provided]) { + return locationDb[provided]; + } + try { + const location = await fetch( + `https://nominatim.openstreetmap.org/?addressdetails=1&q= + ${encodeURIComponent(provided)}&format=json&limit=1` + ); + const coordinates = await location.json(); + if (coordinates) { + const point = { + lat: coordinates[0].lat, + lon: coordinates[0].lon, + }; + locationDb[provided] = point; + return point; + } + } catch (e) { + return null; + } + return null; + } + + if (profile.events) { + try { + const events = await Promise.all( + profile.events.map(async (event, position) => { + let location = {}; + if (event.location) { + location = { + location: { ...event.location }, + }; + if (new Date(event.date.start) > Date.now() || new Date(event.date.end) > Date.now()) { + const coordinates = await getCoordinates( + event.location.city, + event.location.state, + event.location.country + ); + if (coordinates) { + location.location.lat = coordinates.lat; + location.location.lon = coordinates.lon; + } + } + } + return { + order: position, + ...event, + ...location, + }; + }) + ); + await Profile.findOneAndUpdate( { username: profile.username }, - { - events: profile.events.map((event) => ({ - isVirtual: event.isVirtual, - color: event.color, - name: event.name, - description: event.description, - date: { - start: event.date.start, - end: event.date.end, - }, - url: event.url, - price: event.price, - })), - } + { events } ); + } catch (e) { + logger.error(e,`failed to update events for ${profile.username}`); } - } catch (e) { - logger.error(e, `failed to update events for ${profile.username}`); } }) ); diff --git a/pages/map.js b/pages/map.js index 70618bcd019..7117218271a 100644 --- a/pages/map.js +++ b/pages/map.js @@ -9,6 +9,7 @@ import Page from "@components/Page"; import Badge from "@components/Badge"; import { getTags } from "./api/discover/tags"; import { getUsers } from "./api/profiles"; +import { getEvents } from "./api/events"; import config from "@config/app.json"; //this is required as leaflet is not compatible with SSR @@ -21,6 +22,8 @@ export async function getStaticProps() { let data = { users: [], tags: [], + events:[], + points: [] }; try { data.users = await getUsers(); @@ -85,6 +88,38 @@ export async function getStaticProps() { logger.error(e, "ERROR loading tags"); } + try { + data.events = await getEvents(true); + } catch (e) { + logger.error(e, "ERROR loading Events"); + } + + data.events = data.events.map((event, index) => { + const offset = Math.random() * 0.02; // ~2.2km + const offset2 = Math.random() * 0.02; // ~2.2km + return { + type: "Feature", + properties: { + cluster: false, + isEvent: true, + description: event.description, + name: event.name, + location: event.location, + date: event.date, + url: event.url || '' + }, + geometry: { + type: "Point", + coordinates: adjustCoords( + [parseFloat(event.location.lon), parseFloat(event.location.lat)], + offset, + offset2, + index + ), + }, + }; + }); + data.points=[...data.users,...data.events] return { props: { data }, revalidate: pageConfig.revalidateSeconds, @@ -92,8 +127,8 @@ export async function getStaticProps() { } export default function Map({ data }) { - let { users, tags } = data; - const [filteredUsers, setFilteredUsers] = useState([]); + let { tags, points } = data; + const [filteredPoints, setFilteredPoints] = useState([]); const [selectedTags, setSelectedTags] = useState(new Set()); let results = []; @@ -115,12 +150,12 @@ export default function Map({ data }) { const valueLower = value.toLowerCase(); const terms = [...updateSelectedTagsFilter(value)]; - results = users.filter((user) => { - if (user.properties.name.toLowerCase().includes(valueLower)) { + results = points.filter((point) => { + if (point.properties.name.toLowerCase().includes(valueLower)) { return true; } - let userTags = user.properties.tags?.map((tag) => tag.toLowerCase()); + let userTags = point.properties.tags?.map((tag) => tag.toLowerCase()); if (terms.every((keyword) => userTags?.includes(keyword.toLowerCase()))) { return true; @@ -129,11 +164,11 @@ export default function Map({ data }) { return false; }); - setFilteredUsers(results); + setFilteredPoints(results); }; const resetFilter = () => { - setFilteredUsers([]); + setFilteredPoints([]); setSelectedTags(new Set()); }; @@ -172,9 +207,9 @@ export default function Map({ data }) { 0 ? filteredUsers.length : users.length + filteredPoints.length > 0 ? filteredPoints.length : points.length } - > + > + > + Clear/Reset Filters + {tags && tags From 8adcac39bf076011968c65afbedb3fa3bde9c743 Mon Sep 17 00:00:00 2001 From: Eddie Jaoude Date: Sun, 9 Jul 2023 20:45:12 +0100 Subject: [PATCH 4/7] fix: removed event image file --- public/placard.png | Bin 23774 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 public/placard.png diff --git a/public/placard.png b/public/placard.png deleted file mode 100644 index fea3c78a9e6795a4650f54115326b4876ea34300..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23774 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 Date: Sun, 23 Jul 2023 10:26:44 +0100 Subject: [PATCH 5/7] fix: event for with location --- models/Profile/Event.js | 24 +++++- pages/account/manage/event/[[...data]].js | 91 ++++++++++++++++++----- 2 files changed, 91 insertions(+), 24 deletions(-) diff --git a/models/Profile/Event.js b/models/Profile/Event.js index 13951669a49..08a06cc7dc4 100644 --- a/models/Profile/Event.js +++ b/models/Profile/Event.js @@ -35,10 +35,26 @@ const EventSchema = new Schema({ startingFrom: Number, }, location: { - road: String, - city: String, - state: String, - country: String, + road: { + type: String, + min: 2, + max: 128, + }, + city: { + type: String, + min: 2, + max: 128, + }, + state: { + type: String, + min: 2, + max: 128, + }, + country: { + type: String, + min: 2, + max: 128, + }, lat: Number, lon: Number, }, diff --git a/pages/account/manage/event/[[...data]].js b/pages/account/manage/event/[[...data]].js index 59bb6e66382..9304c7d0c8e 100644 --- a/pages/account/manage/event/[[...data]].js +++ b/pages/account/manage/event/[[...data]].js @@ -70,7 +70,11 @@ export default function ManageEvent({ BASE_URL, event }) { event.date?.end && formatDate(event.date?.end) ); const [price, setPrice] = useState(event.price?.startingFrom || 0); - const [color, setColor] = useState(event.color || "" ); + const [color, setColor] = useState(event.color || ""); + const [road, setRoad] = useState(event.location.road || ""); + const [city, setCity] = useState(event.location.city || ""); + const [state, setState] = useState(event.location.state || ""); + const [country, setCountry] = useState(event.location.country || ""); const handleSubmit = async (e) => { e.preventDefault(); @@ -83,6 +87,7 @@ export default function ManageEvent({ BASE_URL, event }) { isVirtual, price: { startingFrom: price }, color, + location: { road, city, state, country }, }; let apiUrl = `${BASE_URL}/api/account/manage/event/`; if (event._id) { @@ -156,11 +161,11 @@ export default function ManageEvent({ BASE_URL, event }) { additionalMessage={showNotification.additionalMessage} /> -

-
+ +
@@ -278,21 +283,67 @@ export default function ManageEvent({ BASE_URL, event }) {
- -
- + +
+ +
+

+ Does the event have a location? +

+
+
+ setRoad(e.target.value)} + value={road} + minLength="2" + maxLength="64" + /> +
+
+ setCity(e.target.value)} + value={city} + minLength="2" + maxLength="64" + /> +
+
+ setState(e.target.value)} + value={state} + minLength="2" + maxLength="64" + /> +
+
+ setCountry(e.target.value)} + value={country} + minLength="2" + maxLength="64" + /> +
+
-
+ Date: Sun, 23 Jul 2023 13:55:37 +0100 Subject: [PATCH 6/7] fix: in person events --- components/event/EventCard.js | 16 ++++++-------- components/user/UserEvents.js | 2 +- pages/api/events.js | 2 +- pages/docs/how-to-guides/events-json.mdx | 27 ++++++++++++------------ pages/events.js | 2 +- 5 files changed, 22 insertions(+), 27 deletions(-) diff --git a/components/event/EventCard.js b/components/event/EventCard.js index 5b9eabb1b6c..33a64469d1f 100644 --- a/components/event/EventCard.js +++ b/components/event/EventCard.js @@ -52,7 +52,9 @@ export default function EventCard({ manage, event, usernames }) { {event.isVirtual && ( )} - {event.isInPerson && } + {event.location?.country && ( + + )} {event.date.cfpOpen && } {event.price?.startingFrom > 0 && } {event.price?.startingFrom === 0 && } @@ -97,17 +99,11 @@ export default function EventCard({ manage, event, usernames }) { {event.description}

- {(event.isVirtual || (event.isInPerson && event.location)) && ( - - )} + {(event.isVirtual || event.location?.country) && } {event.isVirtual && "Remote"} - {event.isVirtual && - event.isInPerson && - event.location && - " AND in "} - {event.isInPerson && - event.location && + {event.isVirtual && event.location?.country && " AND in "} + {event.location?.country && Object.values(event.location).join(", ")}

diff --git a/components/user/UserEvents.js b/components/user/UserEvents.js index bac34959069..2da1ad6d5f3 100644 --- a/components/user/UserEvents.js +++ b/components/user/UserEvents.js @@ -31,7 +31,7 @@ export default function UserEvents({ manage = false, events }) { case "virtual": return event.date.future && event.isVirtual; case "inPerson": - return event.date.future && event.isInPerson; + return event.date.future && event.location?.country; case "cfpOpen": return event.date.cfpOpen; case "free": diff --git a/pages/api/events.js b/pages/api/events.js index cf6219b12bd..21db480f589 100644 --- a/pages/api/events.js +++ b/pages/api/events.js @@ -48,7 +48,7 @@ export async function getEvents(withLocation = false) { url: { $first: "$events.url" }, name: { $first: "$events.name" }, description: { $first: "$events.description" }, - location: { $first: "$events.location" }, + location: { $mergeObjects: "$events.location" }, isEnabled: { $first: "$isEnabled" }, }, }, diff --git a/pages/docs/how-to-guides/events-json.mdx b/pages/docs/how-to-guides/events-json.mdx index ff486500399..237c06937e5 100644 --- a/pages/docs/how-to-guides/events-json.mdx +++ b/pages/docs/how-to-guides/events-json.mdx @@ -24,14 +24,13 @@ All future events also appear on the Events page for the app. _If you need help on how to edit this file, please see the Editing Guide_ -3. This `json` file will contain one object for the event and must have these six fields: `isVirtual` and/or `isInPerson`, `name`, `description`, `date`, `url`, and `location` (this field will be required only when `isInPerson` is `true`). +3. This `json` file will contain one object for the event and must have these six fields: `isVirtual`, `name`, `description`, `date`, `url`, and `location`. It can also optionally include other fields. For instance: `userStatus` & `speakingTopic`. It will look like this: ```js { "isVirtual": true, - "isInPerson": true, "name": "Open Source GitHub reviews", "description": "In this livestream I will be going reviewing your **Open Source projects** and profiles! I will be joined by **Amanda**, a Developer Advocate.", "date": { @@ -55,18 +54,18 @@ _If you need help on how to edit this file, please see the - + );