Skip to content

Commit 914060b

Browse files
feat: core map
1 parent cc6c2eb commit 914060b

30 files changed

+2632
-10
lines changed

api/src/enums/feature-flags/feature-flags-enum.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export enum FeatureFlagEnum {
2828
enableListingFiltering = 'enableListingFiltering',
2929
enableLeasingAgentAltText = 'enableLeasingAgentAltText',
3030
enableListingImageAltText = 'enableListingImageAltText',
31+
enableListingMap = 'enableListingMap',
3132
enableListingOpportunity = 'enableListingOpportunity',
3233
enableListingPagination = 'enableListingPagination',
3334
enableListingUpdatedAt = 'enableListingUpdatedAt',
@@ -195,6 +196,10 @@ export const featureFlagMap: {
195196
name: FeatureFlagEnum.enableListingImageAltText,
196197
description: 'When true, allows partners to add alt text to listing images',
197198
},
199+
{
200+
name: FeatureFlagEnum.enableListingMap,
201+
description: 'When true, a map is displayed on the listings page',
202+
},
198203
{
199204
name: FeatureFlagEnum.enableListingOpportunity,
200205
description:

shared-helpers/src/locales/general.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@
4545
"account.noClosedApplicationsSimplified": "None of the listings you've applied to have closed.",
4646
"account.noFavorites": "It looks like you haven't favorited any listings yet.",
4747
"account.noLotteryApplications": "None of the listings you've applied to have released lottery results.",
48+
"t.noVisibleListings": "No visible listings",
49+
"search.filters": "Filters",
50+
"search.totalResults": "Total results",
51+
"t.recenterMap": "Recenter",
4852
"account.noOpenApplications": "None of the listings you've applied to are still accepting applications.",
4953
"account.openApplications": "Open applications",
5054
"account.openApplicationsSubtitle": "View listings that are currently accepting submissions.",

shared-helpers/src/types/backend-swagger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10220,6 +10220,7 @@ export enum FeatureFlagEnum {
1022010220
"enableListingFiltering" = "enableListingFiltering",
1022110221
"enableLeasingAgentAltText" = "enableLeasingAgentAltText",
1022210222
"enableListingImageAltText" = "enableListingImageAltText",
10223+
"enableListingMap" = "enableListingMap",
1022310224
"enableListingOpportunity" = "enableListingOpportunity",
1022410225
"enableListingPagination" = "enableListingPagination",
1022510226
"enableListingUpdatedAt" = "enableListingUpdatedAt",

sites/public/.env.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,7 @@ MAX_BROWSE_LISTINGS=10
4141

4242
# used to control how long we hold onto a listing detail page server side render cache (in seconds)
4343
CACHE_REVALIDATE=30
44+
45+
# for listings map
46+
GOOGLE_MAPS_API_KEY=
47+
GOOGLE_MAPS_MAP_ID=

sites/public/next.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ module.exports = withBundleAnalyzer({
5858
reCaptchaKey: process.env.RECAPTCHA_KEY,
5959
maxBrowseListings: process.env.MAX_BROWSE_LISTINGS,
6060
rtlLanguages: "ar,fa",
61+
googleMapsApiKey: process.env.GOOGLE_MAPS_API_KEY,
62+
googleMapsMapId: process.env.GOOGLE_MAPS_MAP_ID,
6163
},
6264
i18n: {
6365
locales: process.env.LANGUAGES ? process.env.LANGUAGES.split(",") : ["en"],

sites/public/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@
3232
"@bloom-housing/shared-helpers": "^7.7.1",
3333
"@bloom-housing/ui-components": "13.0.6",
3434
"@bloom-housing/ui-seeds": "3.3.1",
35+
"@googlemaps/markerclusterer": "^2.5.3",
3536
"@heroicons/react": "^2.1.1",
3637
"@mapbox/mapbox-sdk": "^0.13.0",
38+
"@react-google-maps/api": "^2.20.3",
3739
"@sentry/nextjs": "^7.61.0",
40+
"@vis.gl/react-google-maps": "^1.3.0",
3841
"autoprefixer": "^10.3.4",
3942
"axios": "^1.8.3",
4043
"axios-cookiejar-support": "^5.0.5",
Lines changed: 21 additions & 0 deletions
Loading
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React, { useEffect, useContext, useState, useCallback } from "react"
2+
import { useRouter } from "next/router"
3+
import { APIProvider } from "@vis.gl/react-google-maps"
4+
import { Button, Card, Heading, LoadingState } from "@bloom-housing/ui-seeds"
5+
import {
6+
Jurisdiction,
7+
Listing,
8+
FeatureFlagEnum,
9+
MultiselectQuestion,
10+
} from "@bloom-housing/shared-helpers/src/types/backend-swagger"
11+
import {
12+
AuthContext,
13+
BloomCard,
14+
ListingList,
15+
MessageContext,
16+
pushGtmEvent,
17+
ResponseException,
18+
} from "@bloom-housing/shared-helpers"
19+
import { t } from "@bloom-housing/ui-components"
20+
import Layout from "../../../layouts/application"
21+
import MaxWidthLayout from "../../../layouts/max-width"
22+
import { UserStatus } from "../../../lib/constants"
23+
import { fetchFavoriteListingIds, isFeatureFlagOn, saveListingFavorite } from "../../../lib/helpers"
24+
import { FilterDrawer } from "../FilterDrawer"
25+
import {
26+
decodeQueryToFilterData,
27+
encodeFilterDataToQuery,
28+
FilterData,
29+
getFilterQueryFromURL,
30+
} from "../FilterDrawerHelpers"
31+
import { PageHeaderSection } from "../../../patterns/PageHeaderLayout"
32+
import { ListingCard } from "../ListingCard"
33+
import styles from "../ListingBrowse.module.scss"
34+
import { MetaTags } from "../../shared/MetaTags"
35+
import ListingsSearchCombined from "./ListingsSearchCombined"
36+
37+
export interface PaginationData {
38+
currentPage: number
39+
itemCount: number
40+
itemsPerPage: number
41+
totalItems: number
42+
totalPages: number
43+
}
44+
45+
export interface ListingBrowseProps {
46+
listings: Listing[]
47+
jurisdiction: Jurisdiction
48+
multiselectData: MultiselectQuestion[]
49+
paginationData?: PaginationData
50+
areFiltersActive?: boolean
51+
}
52+
53+
export const ListingMap = (props: ListingBrowseProps) => {
54+
const router = useRouter()
55+
const { profile, userService } = useContext(AuthContext)
56+
const { addToast } = useContext(MessageContext)
57+
const [isLoading, setIsLoading] = useState<boolean>(false)
58+
59+
useEffect(() => {
60+
pushGtmEvent<ListingList>({
61+
event: "pageView",
62+
pageTitle: "Rent Affordable Housing - Housing Portal",
63+
status: profile ? UserStatus.LoggedIn : UserStatus.NotLoggedIn,
64+
numberOfListings: props.listings?.length,
65+
listingIds: props.listings?.map((listing) => listing.id),
66+
})
67+
}, [profile, props.listings, userService])
68+
69+
const pageTitle = `${t("pageTitle.rent")} - ${t("nav.siteTitle")}`
70+
71+
let searchString =
72+
"counties:Alameda,Contra Costa,Marin,Napa,San Francisco,San Mateo,Santa Clara,Solano,Sonoma"
73+
const searchParam = Array.isArray(router.query.search)
74+
? router.query.search[0]
75+
: router.query.search
76+
77+
// override the search value if present in url
78+
if (searchParam) {
79+
searchString = searchParam
80+
}
81+
82+
return (
83+
<Layout hideFooter={true} pageTitle={pageTitle}>
84+
<Heading className={"sr-only"} priority={1}>
85+
{t("nav.listings")}
86+
</Heading>
87+
<APIProvider apiKey={process.env.googleMapsApiKey}>
88+
<ListingsSearchCombined
89+
googleMapsApiKey={process.env.googleMapsApiKey}
90+
googleMapsMapId={process.env.googleMapsMapId}
91+
searchString={searchString}
92+
bedrooms={[]}
93+
bathrooms={[]}
94+
counties={[]}
95+
/>
96+
</APIProvider>
97+
</Layout>
98+
)
99+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
.listings-combined {
2+
width: 100vw;
3+
// Map TODO: Figure out how to make this dynamic based on the viewport height and the height of the search bar and pagination components
4+
height: var(--listings-component-height);
5+
display: flex;
6+
flex-direction: column;
7+
8+
@media (max-width: $screen-md) {
9+
height: 100%;
10+
}
11+
12+
.listings-map {
13+
flex: 1;
14+
position: relative;
15+
display: flex;
16+
@media (max-width: $screen-md) {
17+
position: absolute;
18+
z-index: 0;
19+
height: 100%;
20+
width: 100%;
21+
}
22+
}
23+
24+
.listings-map-list-container {
25+
display: flex;
26+
height: 100%;
27+
@media (max-width: $screen-md) {
28+
position: relative;
29+
display: block;
30+
}
31+
}
32+
33+
.listings-map-skip-link {
34+
@apply sr-only;
35+
36+
&:focus {
37+
@apply not-sr-only;
38+
position: absolute;
39+
z-index: 20;
40+
padding: var(--bloom-s2) var(--bloom-s4);
41+
text-decoration: underline;
42+
font-size: var(--bloom-font-size-base);
43+
color: var(--bloom-color-primary);
44+
background-color: var(--bloom-color-white);
45+
}
46+
}
47+
48+
.listings-map-expanded {
49+
background-color: var(--seeds-color-white);
50+
display: none;
51+
52+
@media (max-width: $screen-md) {
53+
position: relative;
54+
display: block;
55+
z-index: 0;
56+
width: 100%;
57+
height: 100%;
58+
}
59+
}
60+
61+
.listings-list {
62+
overflow-y: auto;
63+
width: 100%;
64+
display: block;
65+
66+
@media (max-width: $screen-md) {
67+
display: block;
68+
padding-top: 0;
69+
position: absolute;
70+
z-index: 1;
71+
top: 0;
72+
width: 100%;
73+
overflow-y: auto;
74+
background-color: var(--background-color);
75+
}
76+
}
77+
78+
.listings-list-expanded {
79+
display: none;
80+
81+
@media (max-width: $screen-md) {
82+
display: block;
83+
position: static;
84+
z-index: 1;
85+
overflow-y: auto;
86+
background-color: var(--background-color);
87+
}
88+
}
89+
90+
.listings-outer-container {
91+
background-color: var(--seeds-color-white);
92+
overflow-y: auto;
93+
width: var(--listings-list-width);
94+
@media (max-width: $screen-lg) {
95+
width: 55%;
96+
}
97+
}
98+
}
99+
100+
.listings-list-wrapper {
101+
--padding: var(--seeds-s10);
102+
border-left: 1px solid var(--seeds-color-gray-450);
103+
104+
/* These attribute selectors are strange, but that's because we want to short-circuit the
105+
CSS module renaming and use these exact class names */
106+
[class*="loading-overlay"] {
107+
padding: 0;
108+
min-height: var(--seeds-s40);
109+
margin-bottom: var(--seeds-s4);
110+
z-index: 1;
111+
}
112+
113+
[class*="loading-overlay__spinner"] {
114+
top: var(--seeds-s20);
115+
color: var(--seeds-color-primary);
116+
}
117+
}
118+
119+
.cluster-icon {
120+
font-weight: var(--seeds-font-weight-semibold);
121+
font-family: var(--seeds-font-sans);
122+
font-size: var(--seeds-font-size-base);
123+
display: flex;
124+
justify-content: center;
125+
align-items: center;
126+
background-color: #205493;
127+
color: white;
128+
width: var(--seeds-s8);
129+
height: var(--seeds-s8);
130+
@media (max-width: $screen-md) {
131+
width: var(--seeds-s8);
132+
height: var(--seeds-s8);
133+
}
134+
border-radius: 50%;
135+
border: 1px solid white;
136+
box-shadow: 0 0 1px 2px rgba(0, 0, 0, 0.2);
137+
}
138+
139+
.listings-list-container {
140+
border-bottom: 1px solid var(--seeds-color-gray-450);
141+
margin-bottom: var(--seeds-s4);
142+
}

0 commit comments

Comments
 (0)