Code for the Nearby Workshop Wrapper
import React, { ReactNode, useEffect, useState } from "react";
import { Builder, withChildren, StateProvider } from "@builder.io/react";
import { Domain, Venue, Vertical, Workshop } from "@/functions/alfred";
import { uniqBy } from "lodash";
type GeocodeResponse = {
results: { geometry: { location: { lat: number; lng: number } } }[];
};
const stateCountry = [
{
country: "United States",
countryAbbr: "US",
statesLong: [
"Alabama",
"Alaska",
"Arizona",
"Arkansas",
"California",
"Colorado",
"Connecticut",
"Delaware",
"District of Columbia",
"Florida",
"Georgia",
"Hawaii",
"Idaho",
"Illinois",
"Indiana",
"Iowa",
"Kansas",
"Kentucky",
"Louisiana",
"Maine",
"Maryland",
"Massachusetts",
"Michigan",
"Minnesota",
"Mississippi",
"Missouri",
"Montana",
"Nebraska",
"Nevada",
"New Hampshire",
"New Jersey",
"New Mexico",
"New York",
"North Carolina",
"North Dakota",
"Ohio",
"Oklahoma",
"Oregon",
"Pennsylvania",
"Rhode Island",
"South Carolina",
"South Dakota",
"Tennessee",
"Texas",
"Utah",
"Vermont",
"Virginia",
"Washington",
"West Virginia",
"Wisconsin",
"Wyoming",
],
statesAbbr: [
"AL",
"AK",
"AZ",
"AR",
"CA",
"CO",
"CT",
"DE",
"FL",
"GA",
"HI",
"ID",
"IL",
"IN",
"IA",
"KS",
"KY",
"LA",
"ME",
"MD",
"MA",
"MI",
"MN",
"MS",
"MO",
"MT",
"NE",
"NV",
"NH",
"NJ",
"NM",
"NY",
"NC",
"ND",
"OH",
"OK",
"OR",
"PA",
"RI",
"SC",
"SD",
"TN",
"TX",
"UT",
"VT",
"VA",
"WA",
"WV",
"WI",
"WY",
],
},
{
country: "Canada",
countryAbbr: "CA",
statesLong: [
"Alberta",
"British Columbia",
"Manitoba",
"New Brunswick",
"Newfoundland and Labrador",
"Northwest Territories",
"Nova Scotia",
"Nunavut",
"Ontario",
"Prince Edward Island",
"Quebec",
"Saskatchewan",
"Yukon Territory",
],
statesAbbr: [
"AB",
"BC",
"MB",
"NB",
"NL",
"NS",
"ON",
"PE",
"QC",
"SK",
"NT",
"NU",
"YT",
],
},
];
class NearbyVisual {
firstRun: Boolean = true;
venue: Venue;
vertical: Vertical;
workshop: Workshop;
domain: Domain;
numberOfWorkshops: number;
constructor({
venue,
vertical,
workshop,
numberOfWorkshops,
domain,
}: {
venue: Venue;
vertical: Vertical;
workshop: Workshop;
numberOfWorkshops: number;
domain: Domain;
}) {
this.venue = venue;
this.vertical = vertical;
this.workshop = workshop;
this.numberOfWorkshops = numberOfWorkshops;
this.domain = domain;
}
getCountryForState(venueState: string) {
const countryFound = stateCountry.filter((country) =>
country.statesAbbr.includes(venueState)
);
return countryFound;
}
getLocationForState(country: string, venueState: string) {
const uri = `https://maps.googleapis.com/maps/api/geocode/json?address=${venueState}&components=country:${country}&sensor=false&key=AIzaSyDybLJkmeSefOVacWnRAtDEFWr6Env-YhA`;
return this.sendGet<GeocodeResponse>(uri);
}
async getStateGeoLocation(venueState: string) {
const countryFound = this.getCountryForState(venueState);
if (Array.isArray(countryFound) && countryFound.length > 0) {
const country = countryFound[0].countryAbbr;
const googleMapReturn = await this.getLocationForState(
country,
venueState
);
if ("status" in googleMapReturn && googleMapReturn.status == "OK") {
const result = googleMapReturn.results[0];
const latitude = result.geometry.location.lat;
const longitude = result.geometry.location.lng;
const geo = {
coordinates: [longitude, latitude],
type: "Point",
};
return geo;
}
}
}
async getNearbyWorkshopsWithGeo(
geo: Venue["geo"],
venueState: string,
radius = 25,
stateFilter = false
) {
const results = await this.getNearByEvents(this.vertical?.id, geo, radius);
const workshops = results.filter(
(record: { distance: any; workshop: any }) => {
const { distance, workshop = {} } = record;
if (workshop._id && workshop._id.value == this.workshop?.id) {
return false;
}
if (
!workshop.page ||
!workshop.title ||
!workshop.status ||
!workshop.type ||
workshop.status.toUpperCase() != "ACTIVE"
) {
return false;
}
if ("is_full" in workshop && workshop.is_full == true) {
return false;
}
if (stateFilter && "state" in workshop.venue_details) {
const workshopState = workshop.venue_details.state.toUpperCase();
if (workshopState != venueState) {
return false;
}
}
const events = workshop.events;
if (events && Array.isArray(events) && events.length > 0) {
const filteredEvents = events
.filter((event_record) => {
const { cur_registrants = 0, max_registrants = 0 } = event_record;
return Number(cur_registrants) < Number(max_registrants);
})
.filter((event_record) => {
const { is_full = false } = event_record;
return !is_full;
})
.filter((event_record) => {
const { event_status = "ACTIVE" } = event_record;
return event_status.toUpperCase() === "ACTIVE";
})
.filter((event_record) => {
const { date = 0 } = event_record;
return date > new Date().getTime();
});
return Array.isArray(filteredEvents) && filteredEvents.length > 0;
}
return true;
}
);
return workshops;
}
// Entry point
async getNearbyWorkshops() {
const { geo = null, ...venue } = this.venue || {};
const numberOfWorkshops = this.numberOfWorkshops;
if (!geo) {
console.log("no geo");
return [];
}
const venueState = this.venue.state?.toUpperCase() || "";
const nearWorkshops = await this.getNearbyWorkshopsWithGeo(
geo,
venueState,
25,
false
);
const stateWorkshops = await (async function (c) {
if (!(nearWorkshops.length < numberOfWorkshops)) {
if (venueState.length == 2) {
const stateGeo = await c.getStateGeoLocation(venueState);
return await c.getNearbyWorkshopsWithGeo(
stateGeo,
venueState,
600,
true
);
}
}
return [];
})(this);
const workshops = uniqBy(
[...nearWorkshops, ...stateWorkshops],
"workshop.title"
)
.sort((a: { distance: number }, b: { distance: number }) => {
return a.distance - b.distance;
})
.slice(0, this.numberOfWorkshops);
if (workshops.length > 0) return workshops;
return [];
}
getVenueLocation(id: string) {
const uri = `${this.domain?.baseUrl}/api/v1/venues/${id}`;
return this.sendGet(uri);
}
getNearByEvents(verticalId: string, geo: Venue["geo"], radius = 25) {
const uri = `${this.domain?.baseUrl}/api/v1/workshops:nearby`;
const body = {
verticals: [
{
value: verticalId,
},
],
radius: radius,
isOnlyAvailable: true,
center: geo,
status: "ACTIVE",
};
return this.sendPost<{ distance: number; workshop: { title: string } }[]>(
uri,
body
);
}
sendGet<T = {}>(uri: string): Promise<T>;
sendGet(uri: string) {
return fetch(uri, {
method: "GET",
}).then((response) => {
if (response.ok) {
return response.json();
}
return {};
});
}
sendPost<T = {}>(uri: string, body: object): Promise<T>;
sendPost(uri: string, body: object) {
return fetch(uri, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
mode: "cors",
}).then((response) => {
return response.json();
});
}
}
export { NearbyVisual };
type NearbyWorkshopsWrapperComponentProps = {
builderState: any;
children: ReactNode;
numberOfWorkshops: number;
};
export const NearbyWorkshopsWrapperComponent = ({
children,
builderState,
numberOfWorkshops,
}: NearbyWorkshopsWrapperComponentProps) => {
const [nearbyWorkshops, setNearbyWorkshops] = useState<any[]>([]);
useEffect(() => {
const nearbyWorkshopsService = new NearbyVisual({
venue: builderState.rootState.venue,
vertical: builderState.rootState.vertical,
workshop: builderState.rootState.workshop,
domain: builderState.rootState.domain,
numberOfWorkshops,
});
nearbyWorkshopsService
.getNearbyWorkshops()
.then((nearbyWorkshops) => setNearbyWorkshops(nearbyWorkshops));
}, [
numberOfWorkshops,
builderState.rootState.venue,
builderState.rootState.vertical,
builderState.rootState.workshop,
builderState.rootState.domain,
]);
return <StateProvider state={{ nearbyWorkshops }}>{children}</StateProvider>;
};
export const NearbyWorkshopsWrapper = Builder.registerComponent(
withChildren(NearbyWorkshopsWrapperComponent),
{
name: "Nearby Workshops Wrapper",
inputs: [
{
name: "numberOfWorkshops",
type: "number",
defaultValue: 5,
required: true,
},
],
}
);