Skip to content
This repository was archived by the owner on Aug 7, 2024. It is now read-only.

Commit 0af682f

Browse files
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 <eddie@jaoudestudios.com>
1 parent efc8ff7 commit 0af682f

8 files changed

Lines changed: 196 additions & 36 deletions

File tree

components/map/Clusters.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { Marker, useMap } from "react-leaflet";
44
import useSupercluster from "use-supercluster";
55
import UserMarker from "./UserMarker";
66
import styles from "./Clusters.module.css";
7+
import EventMarker from "./EventMarker";
78

8-
export default function Clusters({users}) {
9+
export default function Clusters({points}) {
910
const map = useMap();
1011
const mapB = map.getBounds();
1112
const [bounds, setBounds] = useState([
@@ -32,15 +33,14 @@ export default function Clusters({users}) {
3233
})
3334

3435
const { clusters, supercluster } = useSupercluster({
35-
points: users,
36+
points: points,
3637
bounds,
3738
zoom,
3839
options: {
3940
radius: zoom < 17 ? 75 : 50,
4041
maxZoom: 18
4142
}
4243
});
43-
4444
const icons = {};
4545
const fetchIcon = (count) => {
4646
const size =
@@ -65,7 +65,8 @@ export default function Clusters({users}) {
6565
const {
6666
cluster: isCluster,
6767
point_count: pointCount,
68-
username
68+
username,
69+
name
6970
} = cluster.properties;
7071

7172
// we have a cluster to render
@@ -91,7 +92,9 @@ export default function Clusters({users}) {
9192
}
9293

9394
// we have a single point to render
94-
return (
95+
return cluster.properties.isEvent ? (
96+
<EventMarker event={cluster} key={name} />
97+
) : (
9598
<UserMarker user={cluster} key={username} />
9699
);
97100
})}

components/map/EventMarker.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import L from "leaflet";
2+
import { Marker, Popup } from "react-leaflet";
3+
import { ReactMarkdown } from "react-markdown/lib/react-markdown";
4+
import Link from "@components/Link";
5+
6+
export default function EventMarker({event}) {
7+
// Custom component for rendering links within ReactMarkdown
8+
const LinkRenderer = ({ href, children }) => (
9+
<Link href={href}>
10+
{children}
11+
</Link>
12+
);
13+
14+
return (
15+
<Marker
16+
icon={L.divIcon({
17+
className: "rounded-full",
18+
html: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
19+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
20+
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" />
21+
</svg>
22+
`,
23+
popupAnchor: [0, -10],
24+
iconSize: [40, 40],
25+
iconAnchor: [20, 20],
26+
})}
27+
position={[event.geometry.coordinates[1], event.geometry.coordinates[0]]}
28+
>
29+
<Popup>
30+
<div className="flex flex-col gap-[5px]">
31+
<h1 className="font-[600]">
32+
<Link href={event.properties.url}>
33+
{event.properties.name}
34+
</Link>
35+
</h1>
36+
<span>
37+
{[
38+
event.properties.location.city,
39+
event.properties.location.state,
40+
event.properties.location.country,
41+
]
42+
.filter((x) => x)
43+
.join(", ")}
44+
</span>
45+
<span>
46+
<ReactMarkdown components={{ a: LinkRenderer }}>
47+
{event.properties.description}
48+
</ReactMarkdown>
49+
</span>
50+
<span>
51+
{`${new Date(event.properties.date.start).toLocaleDateString()} -
52+
${new Date(event.properties.date.end).toLocaleDateString()}`}
53+
</span>
54+
</div>
55+
</Popup>
56+
</Marker>
57+
)
58+
}

components/map/Map.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { MapContainer, TileLayer } from "react-leaflet";
22
import Clusters from "./Clusters";
33
import "leaflet/dist/leaflet.css";
44

5-
export default function Map({ users }) {
5+
export default function Map({ points }) {
66
const boundsMap = [
77
[-90, -180], // Southwest coordinates
88
[90, 180], // Northeast coordinates
@@ -23,7 +23,7 @@ export default function Map({ users }) {
2323
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
2424
url="https://b.tile.openstreetmap.org/{z}/{x}/{y}.png"
2525
/>
26-
<Clusters users={users} />
26+
<Clusters points={points} />
2727
</MapContainer>
2828
);
2929
}

models/Profile/Event.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ const EventSchema = new Schema({
3434
price: {
3535
startingFrom: Number,
3636
},
37+
location: {
38+
road: String,
39+
city: String,
40+
state: String,
41+
country: String,
42+
lat: Number,
43+
lon: Number,
44+
},
3745
});
3846

3947
EventSchema.pre("save", () => {

pages/api/events.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,29 @@ export default async function handler(req, res) {
1212
return res.status(200).json(events);
1313
}
1414

15-
export async function getEvents() {
15+
export async function getEvents(withLocation = false) {
1616
let events = [];
1717
try {
1818
events = await Profile.aggregate([
1919
{ $project: { username: 1, events: 1, isEnabled: 1 } },
2020
{ $match: { "events.date.start": { $gt: new Date() }, isEnabled: true } },
2121
{ $unwind: "$events" },
2222
{ $match: { "events.date.end": { $gt: new Date() } } },
23+
...(withLocation
24+
? [
25+
{
26+
$match: {
27+
$and: [
28+
{ "events.location": { $exists: true } },
29+
{ "events.location.lat": { $exists: true } },
30+
{ "events.location.lon": { $exists: true } },
31+
{ "events.location.lat": { $ne: null } },
32+
{ "events.location.lon": { $ne: null } },
33+
],
34+
},
35+
},
36+
]
37+
: []),
2338
{ $sort: { "events.date.start": 1 } },
2439
{
2540
$group: {
@@ -31,6 +46,7 @@ export async function getEvents() {
3146
url: { $first: "$events.url" },
3247
name: { $first: "$events.name" },
3348
description: { $first: "$events.description" },
49+
location: { $first: "$events.location" },
3450
isEnabled: { $first: "$isEnabled" },
3551
},
3652
},

pages/api/system/reload.js

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -246,28 +246,68 @@ export default async function handler(req, res) {
246246
}
247247

248248
// - events
249-
try {
250-
if (profile.events) {
249+
async function getCoordinates(city, state, country) {
250+
let locationDb = {};
251+
const provided = [city, state, country].filter((x) => x).join(",");
252+
if (locationDb[provided]) {
253+
return locationDb[provided];
254+
}
255+
try {
256+
const location = await fetch(
257+
`https://nominatim.openstreetmap.org/?addressdetails=1&q=
258+
${encodeURIComponent(provided)}&format=json&limit=1`
259+
);
260+
const coordinates = await location.json();
261+
if (coordinates) {
262+
const point = {
263+
lat: coordinates[0].lat,
264+
lon: coordinates[0].lon,
265+
};
266+
locationDb[provided] = point;
267+
return point;
268+
}
269+
} catch (e) {
270+
return null;
271+
}
272+
return null;
273+
}
274+
275+
if (profile.events) {
276+
try {
277+
const events = await Promise.all(
278+
profile.events.map(async (event, position) => {
279+
let location = {};
280+
if (event.location) {
281+
location = {
282+
location: { ...event.location },
283+
};
284+
if (new Date(event.date.start) > Date.now() || new Date(event.date.end) > Date.now()) {
285+
const coordinates = await getCoordinates(
286+
event.location.city,
287+
event.location.state,
288+
event.location.country
289+
);
290+
if (coordinates) {
291+
location.location.lat = coordinates.lat;
292+
location.location.lon = coordinates.lon;
293+
}
294+
}
295+
}
296+
return {
297+
order: position,
298+
...event,
299+
...location,
300+
};
301+
})
302+
);
303+
251304
await Profile.findOneAndUpdate(
252305
{ username: profile.username },
253-
{
254-
events: profile.events.map((event) => ({
255-
isVirtual: event.isVirtual,
256-
color: event.color,
257-
name: event.name,
258-
description: event.description,
259-
date: {
260-
start: event.date.start,
261-
end: event.date.end,
262-
},
263-
url: event.url,
264-
price: event.price,
265-
})),
266-
}
306+
{ events }
267307
);
308+
} catch (e) {
309+
logger.error(e,`failed to update events for ${profile.username}`);
268310
}
269-
} catch (e) {
270-
logger.error(e, `failed to update events for ${profile.username}`);
271311
}
272312
})
273313
);

pages/map.js

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Page from "@components/Page";
99
import Badge from "@components/Badge";
1010
import { getTags } from "./api/discover/tags";
1111
import { getUsers } from "./api/profiles";
12+
import { getEvents } from "./api/events";
1213
import config from "@config/app.json";
1314

1415
//this is required as leaflet is not compatible with SSR
@@ -21,6 +22,8 @@ export async function getStaticProps() {
2122
let data = {
2223
users: [],
2324
tags: [],
25+
events:[],
26+
points: []
2427
};
2528
try {
2629
data.users = await getUsers();
@@ -85,15 +88,47 @@ export async function getStaticProps() {
8588
logger.error(e, "ERROR loading tags");
8689
}
8790

91+
try {
92+
data.events = await getEvents(true);
93+
} catch (e) {
94+
logger.error(e, "ERROR loading Events");
95+
}
96+
97+
data.events = data.events.map((event, index) => {
98+
const offset = Math.random() * 0.02; // ~2.2km
99+
const offset2 = Math.random() * 0.02; // ~2.2km
100+
return {
101+
type: "Feature",
102+
properties: {
103+
cluster: false,
104+
isEvent: true,
105+
description: event.description,
106+
name: event.name,
107+
location: event.location,
108+
date: event.date,
109+
url: event.url || ''
110+
},
111+
geometry: {
112+
type: "Point",
113+
coordinates: adjustCoords(
114+
[parseFloat(event.location.lon), parseFloat(event.location.lat)],
115+
offset,
116+
offset2,
117+
index
118+
),
119+
},
120+
};
121+
});
122+
data.points=[...data.users,...data.events]
88123
return {
89124
props: { data },
90125
revalidate: pageConfig.revalidateSeconds,
91126
};
92127
}
93128

94129
export default function Map({ data }) {
95-
let { users, tags } = data;
96-
const [filteredUsers, setFilteredUsers] = useState([]);
130+
let { tags, points } = data;
131+
const [filteredPoints, setFilteredPoints] = useState([]);
97132
const [selectedTags, setSelectedTags] = useState(new Set());
98133

99134
let results = [];
@@ -115,12 +150,12 @@ export default function Map({ data }) {
115150
const valueLower = value.toLowerCase();
116151
const terms = [...updateSelectedTagsFilter(value)];
117152

118-
results = users.filter((user) => {
119-
if (user.properties.name.toLowerCase().includes(valueLower)) {
153+
results = points.filter((point) => {
154+
if (point.properties.name.toLowerCase().includes(valueLower)) {
120155
return true;
121156
}
122157

123-
let userTags = user.properties.tags?.map((tag) => tag.toLowerCase());
158+
let userTags = point.properties.tags?.map((tag) => tag.toLowerCase());
124159

125160
if (terms.every((keyword) => userTags?.includes(keyword.toLowerCase()))) {
126161
return true;
@@ -129,11 +164,11 @@ export default function Map({ data }) {
129164
return false;
130165
});
131166

132-
setFilteredUsers(results);
167+
setFilteredPoints(results);
133168
};
134169

135170
const resetFilter = () => {
136-
setFilteredUsers([]);
171+
setFilteredPoints([]);
137172
setSelectedTags(new Set());
138173
};
139174

@@ -172,9 +207,9 @@ export default function Map({ data }) {
172207
<Badge
173208
disable={selectedTags.size == 0 ? true : false}
174209
content={
175-
filteredUsers.length > 0 ? filteredUsers.length : users.length
210+
filteredPoints.length > 0 ? filteredPoints.length : points.length
176211
}
177-
>
212+
>
178213
<Button
179214
onClick={resetFilter}
180215
primary={true}
@@ -196,7 +231,7 @@ export default function Map({ data }) {
196231
</div>
197232
<div className="h-screen">
198233
<DynamicMap
199-
users={filteredUsers.length > 0 ? filteredUsers : users}
234+
points={filteredPoints.length > 0 ? filteredPoints : points}
200235
/>
201236
</div>
202237
</Page>

public/placard.png

23.2 KB
Loading

0 commit comments

Comments
 (0)