diff --git a/.github/workflows/azure-devops.yaml b/.github/workflows/azure-devops.yaml
deleted file mode 100644
index 8f8f0666ed..0000000000
--- a/.github/workflows/azure-devops.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-# https://github.com/marketplace/actions/github-issues-to-azure-devops
-# name: Sync issues to Azure DevOps
-
-# on:
-# issues:
-# types:
-# [opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
-
-# jobs:
-# alert:
-# runs-on: ubuntu-latest
-# steps:
-# - uses: danhellem/github-actions-issue-to-work-item@master
-# env:
-# ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
-# github_token: "${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}"
-# ado_organization: "contentsmaydiffer"
-# ado_project: "Democracy Sausage"
-# ado_area_path: ""
-# ado_iteration_path: ""
-# ado_wit: "Issue"
-# ado_new_state: "New"
-# ado_active_state: "Active"
-# ado_close_state: "Closed"
-# ado_bypassrules: true
-# log_level: 100
diff --git a/public-redesign/src/app/ui/socialSharingTagsHelpers.tsx b/public-redesign/src/app/ui/socialSharingTagsHelpers.tsx
new file mode 100644
index 0000000000..a4c6848de2
--- /dev/null
+++ b/public-redesign/src/app/ui/socialSharingTagsHelpers.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+export const getDefaultOGMetaTags = () => (
+
+
+
+
+);
diff --git a/public-redesign/src/features/aboutPage/AboutPage.tsx b/public-redesign/src/features/aboutPage/AboutPage.tsx
index a6cd43650b..f14baae77d 100644
--- a/public-redesign/src/features/aboutPage/AboutPage.tsx
+++ b/public-redesign/src/features/aboutPage/AboutPage.tsx
@@ -2,6 +2,7 @@ import { List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
import { grey } from '@mui/material/colors';
import { styled } from '@mui/material/styles';
import { Helmet } from 'react-helmet-async';
+import { getDefaultOGMetaTags } from '../../app/ui/socialSharingTagsHelpers';
import { StyledInteractableBoxFullHeight } from '../../app/ui/styledInteractableBoxFullHeight';
import { mapaThemePrimaryGrey } from '../../app/ui/theme';
import { getBaseURL } from '../../app/utils';
@@ -40,6 +41,7 @@ export default function AboutPage() {
FAQs and About Us | Democracy Sausage
{/* Open Graph: Facebook / Twitter */}
+ {getDefaultOGMetaTags()}
diff --git a/public-redesign/src/features/app/appSlice.ts b/public-redesign/src/features/app/appSlice.ts
index ecfb263055..9d300cfc8f 100644
--- a/public-redesign/src/features/app/appSlice.ts
+++ b/public-redesign/src/features/app/appSlice.ts
@@ -6,7 +6,9 @@ import { IMapPollingPlaceGeoJSONFeatureCollection } from '../map/mapHelpers';
import { IMapFilterSettings, IPollingPlace } from '../pollingPlaces/pollingPlacesInterfaces';
export interface AppState {
- mapFilterSettings: IMapFilterSettings;
+ mapFilterSettings: {
+ [key: number]: IMapFilterSettings;
+ };
searchBarFilterControlState: boolean;
pollingPlaces: IMapPollingPlaceGeoJSONFeatureCollection | undefined;
mapFeatures: IPollingPlace | null | undefined;
@@ -76,8 +78,11 @@ export const appSlice = createSlice({
// setActiveElectionId: (state, action: PayloadAction) => {
// state.electionId = action.payload;
// },
- setMapFilterSettings: (state, action: PayloadAction) => {
- state.mapFilterSettings = action.payload;
+ setMapFilterSettings: (
+ state,
+ action: PayloadAction<{ mapFilterSettings: IMapFilterSettings; electionId: number }>,
+ ) => {
+ state.mapFilterSettings[action.payload.electionId] = action.payload.mapFilterSettings;
},
setSearchBarFilterControlState: (state, action: PayloadAction) => {
state.searchBarFilterControlState = action.payload;
@@ -138,16 +143,32 @@ export const { setMapFilterSettings, setSearchBarFilterControlState, setPollingP
export const selectMapFeatures = (state: RootState) => state.app.mapFeatures;
-export const selectMapFilterSettings = (state: RootState) => state.app.mapFilterSettings;
+// These selectors for the map filter settings accept electionId into the selector as an external parameter
+// Ref: https://stackoverflow.com/a/61220891/7368493
+export const selectMapFilterSettings = createSelector(
+ [
+ // First input selector extracts filter settings from the state
+ (state: RootState) => state.app.mapFilterSettings,
+ // Second input selector forwards the electionId argument
+ (mapFilterSettings, electionId: number) => electionId,
+ ],
+ (mapFilterSettings, electionId) => mapFilterSettings[electionId] || {},
+);
export const selectIsMapFiltered = createSelector(
- selectMapFilterSettings,
- (mapFilterSettings) => values(mapFilterSettings).find((option) => option === true) || false,
+ [
+ // First input selector extracts forwards the electionId argument to selectMapFilterSettings
+ (state: RootState, electionId: number) => selectMapFilterSettings(state, electionId),
+ ],
+ (mapFilterSettingsForElection) => values(mapFilterSettingsForElection).find((option) => option === true) || false,
);
export const selectNumberOfMapFilterSettingsApplied = createSelector(
- selectMapFilterSettings,
- (mapFilterSettings) => values(mapFilterSettings).filter((option) => option === true).length,
+ [
+ // First input selector extracts forwards the electionId argument to selectMapFilterSettings
+ (state: RootState, electionId: number) => selectMapFilterSettings(state, electionId),
+ ],
+ (mapFilterSettingsForElection) => values(mapFilterSettingsForElection).filter((option) => option === true).length,
);
export const selectSearchBarFilterControlState = (state: RootState) => state.app.searchBarFilterControlState;
diff --git a/public-redesign/src/features/map/layersSelector/layersSelector.tsx b/public-redesign/src/features/map/layersSelector/layersSelector.tsx
index daf6b65951..fd723cd97e 100644
--- a/public-redesign/src/features/map/layersSelector/layersSelector.tsx
+++ b/public-redesign/src/features/map/layersSelector/layersSelector.tsx
@@ -99,8 +99,18 @@ export default function LayersSelector(props: Props) {
selectMapFilterSettings(state));
+ const mapFilterSettings = useAppSelector((state) => selectMapFilterSettings(state, election.id));
// ######################
// Map Loading
@@ -228,6 +229,7 @@ function Map(props: Props) {
{election.name} | Democracy Sausage
{/* Open Graph: Facebook / Twitter */}
+ {getDefaultOGMetaTags()}
@@ -252,19 +254,21 @@ function Map(props: Props) {
-
-
-
-
-
+ {isEmbedModeActive() === false && }
+
+ {isEmbedModeActive() === false && (
+
+
+
+ )}
{isEmbedModeActive() === false && }
diff --git a/public-redesign/src/features/map/mapFilterHelpers.ts b/public-redesign/src/features/map/mapFilterHelpers.ts
index 872c46fe11..8039f72d45 100644
--- a/public-redesign/src/features/map/mapFilterHelpers.ts
+++ b/public-redesign/src/features/map/mapFilterHelpers.ts
@@ -1,11 +1,11 @@
import { IMapFilterSettings, IPollingPlace } from '../pollingPlaces/pollingPlacesInterfaces';
import { NomsReader } from './nomsReader';
-export const hasFilterOptions = (mapFilterSettings: IMapFilterSettings) =>
+export const hasFilterOptions = (mapFilterSettings: IMapFilterSettings, electionId: number) =>
Object.values(mapFilterSettings).filter((enabled) => enabled === true).length > 0;
-export const satisfiesMapFilter = (noms: NomsReader, mapFilterSettings: IMapFilterSettings) => {
- if (hasFilterOptions(mapFilterSettings) && noms.hasAnyNoms() === true) {
+export const satisfiesMapFilter = (noms: NomsReader, mapFilterSettings: IMapFilterSettings, electionId: number) => {
+ if (hasFilterOptions(mapFilterSettings, electionId) && noms.hasAnyNoms() === true) {
for (const [option, enabled] of Object.entries(mapFilterSettings)) {
if (enabled === true && noms.hasNomsOption(option) === false) {
return false;
@@ -20,8 +20,9 @@ export const satisfiesMapFilter = (noms: NomsReader, mapFilterSettings: IMapFilt
export const doesPollingPlaceSatisifyFilterCriteria = (
pollingPlace: IPollingPlace,
mapFilterSettings: IMapFilterSettings,
+ electionId: number,
) => {
- if (hasFilterOptions(mapFilterSettings) === false) {
+ if (hasFilterOptions(mapFilterSettings, electionId) === false) {
return true;
}
@@ -34,7 +35,7 @@ export const doesPollingPlaceSatisifyFilterCriteria = (
return false;
}
- if (satisfiesMapFilter(nomsReader, mapFilterSettings) === false) {
+ if (satisfiesMapFilter(nomsReader, mapFilterSettings, electionId) === false) {
return false;
}
diff --git a/public-redesign/src/features/map/mapStyleHelpers.ts b/public-redesign/src/features/map/mapStyleHelpers.ts
index 2d83d076ac..82da52f60c 100644
--- a/public-redesign/src/features/map/mapStyleHelpers.ts
+++ b/public-redesign/src/features/map/mapStyleHelpers.ts
@@ -8,6 +8,7 @@ export const olStyleFunction = (
feature: IMapPollingPlaceFeature,
resolution: number,
mapFilterSettings: IMapFilterSettings,
+ electionId: number,
) => {
const noms = getObjectOrUndefinedFromFeature(feature, 'noms');
@@ -18,12 +19,15 @@ export const olStyleFunction = (
const nomsReader = new NomsReader(noms as IMapPollingGeoJSONNoms);
if (nomsReader.hasAnyNoms() === true) {
- if (hasFilterOptions(mapFilterSettings) === true && satisfiesMapFilter(nomsReader, mapFilterSettings) === false) {
+ if (
+ hasFilterOptions(mapFilterSettings, electionId) === true &&
+ satisfiesMapFilter(nomsReader, mapFilterSettings, electionId) === false
+ ) {
return null;
}
return resolution >= 7 ? nomsReader.getIconForNoms() : nomsReader.getDetailedIconsForNoms(feature /*, resolution*/);
}
- return hasFilterOptions(mapFilterSettings) === false ? supportingIcons.grey_question.icon.ol : null;
+ return hasFilterOptions(mapFilterSettings, electionId) === false ? supportingIcons.grey_question.icon.ol : null;
};
diff --git a/public-redesign/src/features/map/openLayersMap/OpenLayersMap.tsx b/public-redesign/src/features/map/openLayersMap/OpenLayersMap.tsx
index 49c217b3ca..af2ca89311 100644
--- a/public-redesign/src/features/map/openLayersMap/OpenLayersMap.tsx
+++ b/public-redesign/src/features/map/openLayersMap/OpenLayersMap.tsx
@@ -150,7 +150,7 @@ class OpenLayersMap extends React.PureComponent {
if (sausageLayer !== null) {
const styleFunction = (feature: IMapPollingPlaceFeature, resolution: number) =>
- olStyleFunction(feature, resolution, this.props.mapFilterSettings);
+ olStyleFunction(feature, resolution, this.props.mapFilterSettings, this.props.election.id);
sausageLayer.setStyle(styleFunction as StyleFunction);
}
}
@@ -391,7 +391,7 @@ class OpenLayersMap extends React.PureComponent {
this.vectorSourceChangedEventKey = vectorSource.once('change', this.onVectorSourceChanged.bind(this));
const styleFunction = (feature: IMapPollingPlaceFeature, resolution: number) =>
- olStyleFunction(feature, resolution, mapFilterSettings);
+ olStyleFunction(feature, resolution, mapFilterSettings, this.props.election.id);
const vectorLayer = new VectorLayer({
// renderMode: 'image',
diff --git a/public-redesign/src/features/search/searchByAddressOrGPS/searchBar/searchBar.tsx b/public-redesign/src/features/search/searchByAddressOrGPS/searchBar/searchBar.tsx
index bae92238fc..1501e5b1b1 100644
--- a/public-redesign/src/features/search/searchByAddressOrGPS/searchBar/searchBar.tsx
+++ b/public-redesign/src/features/search/searchByAddressOrGPS/searchBar/searchBar.tsx
@@ -27,6 +27,7 @@ import { useLocation, useParams } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from '../../../../app/hooks/store';
import { useUnmount } from '../../../../app/hooks/useUnmount';
import { getStringParamOrEmptyString } from '../../../../app/routing/routingHelpers';
+import { Election } from '../../../../app/services/elections';
import { mapaThemePrimaryPurple } from '../../../../app/ui/theme';
import {
selectIsMapFiltered,
@@ -39,6 +40,7 @@ import SearchFilterComponent from '../../shared/searchFilterComponent';
import './searchBar.css';
interface Props {
+ election: Election;
autoFocusSearchField?: boolean;
enableFiltering?: boolean;
isFetching: boolean;
@@ -50,6 +52,7 @@ interface Props {
export default function SearchBar(props: Props) {
const {
+ election,
autoFocusSearchField,
enableFiltering,
isFetching,
@@ -84,8 +87,11 @@ export default function SearchBar(props: Props) {
}
const searchBarFilterControlOpen = useAppSelector((state) => selectSearchBarFilterControlState(state));
- const isMapFiltered = useAppSelector(selectIsMapFiltered);
- const numberOfMapFilterSettingsApplied = useAppSelector(selectNumberOfMapFilterSettingsApplied);
+ const isMapFiltered = useAppSelector((state) => selectIsMapFiltered(state, election.id));
+
+ const numberOfMapFilterSettingsApplied = useAppSelector((state) =>
+ selectNumberOfMapFilterSettingsApplied(state, election.id),
+ );
// ######################
// Search Field
@@ -403,7 +409,7 @@ export default function SearchBar(props: Props) {
{(isWaitingForGPSLocation === true || isFetching === true) && }
- {enableFiltering === true && searchBarFilterControlOpen === true && }
+ {enableFiltering === true && searchBarFilterControlOpen === true && }
{isGeolocationErrored === true && (
diff --git a/public-redesign/src/features/search/searchByAddressOrGPS/searchBar/searchBarCosmeticNonFunctional.tsx b/public-redesign/src/features/search/searchByAddressOrGPS/searchBar/searchBarCosmeticNonFunctional.tsx
index 7f30543dd3..7a3b5e1814 100644
--- a/public-redesign/src/features/search/searchByAddressOrGPS/searchBar/searchBarCosmeticNonFunctional.tsx
+++ b/public-redesign/src/features/search/searchByAddressOrGPS/searchBar/searchBarCosmeticNonFunctional.tsx
@@ -12,6 +12,7 @@ import {
navigateToSearchDrawerRootFromExternalToSearchBar,
} from '../../../../app/routing/navigationHelpers/navigationHelpersSearch';
import { getStringParamOrEmptyString } from '../../../../app/routing/routingHelpers';
+import { Election } from '../../../../app/services/elections';
import { mapaThemePrimaryPurple } from '../../../../app/ui/theme';
import {
selectIsMapFiltered,
@@ -20,7 +21,13 @@ import {
} from '../../../app/appSlice';
import './searchBar.css';
-export default function SearchBarCosmeticNonFunctional() {
+interface Props {
+ election: Election;
+}
+
+export default function SearchBarCosmeticNonFunctional(props: Props) {
+ const { election } = props;
+
const dispatch = useAppDispatch();
const params = useParams();
@@ -30,8 +37,10 @@ export default function SearchBarCosmeticNonFunctional() {
const searchBarSearchText = getStringParamOrEmptyString(params, 'search_term');
- const isMapFiltered = useAppSelector(selectIsMapFiltered);
- const numberOfMapFilterSettingsApplied = useAppSelector(selectNumberOfMapFilterSettingsApplied);
+ const isMapFiltered = useAppSelector((state) => selectIsMapFiltered(state, election.id));
+ const numberOfMapFilterSettingsApplied = useAppSelector((state) =>
+ selectNumberOfMapFilterSettingsApplied(state, election.id),
+ );
// ######################
// Search Field
diff --git a/public-redesign/src/features/search/searchByAddressOrGPS/searchComponent.tsx b/public-redesign/src/features/search/searchByAddressOrGPS/searchComponent.tsx
index 5e3bb46d38..39c3e87484 100644
--- a/public-redesign/src/features/search/searchByAddressOrGPS/searchComponent.tsx
+++ b/public-redesign/src/features/search/searchByAddressOrGPS/searchComponent.tsx
@@ -78,7 +78,7 @@ export default function SearchComponent(props: Props) {
const urlLonLatFromGPS = getStringParamOrEmptyString(params, 'gps_lon_lat');
const urlLonLat = urlLonLatFromGPS !== '' ? urlLonLatFromGPS : urlLonLatFromSearch;
- const mapFilterSettings = useAppSelector((state) => selectMapFilterSettings(state));
+ const mapFilterSettings = useAppSelector((state) => selectMapFilterSettings(state, election.id));
// ######################
// Mapbox Search Query
@@ -123,7 +123,8 @@ export default function SearchComponent(props: Props) {
isSuccessFetchingNearbyPollingPlaces === true &&
pollingPlaceNearbyResults !== undefined
? pollingPlaceNearbyResults.filter(
- (pollingPlace) => doesPollingPlaceSatisifyFilterCriteria(pollingPlace, mapFilterSettings) === true,
+ (pollingPlace) =>
+ doesPollingPlaceSatisifyFilterCriteria(pollingPlace, mapFilterSettings, election.id) === true,
)
: undefined;
@@ -138,6 +139,7 @@ export default function SearchComponent(props: Props) {
return (
selectMapFilterSettings(state));
+ const mapFilterSettings = useAppSelector((state) => selectMapFilterSettings(state, election.id));
// ######################
// Polling Place By Ids Query
@@ -46,7 +46,8 @@ export default function SearchByIdsStackComponent(props: Props) {
isSuccessFetchingPollingPlacesByIds === true &&
pollingPlaceByIdsResult !== undefined
? pollingPlaceByIdsResult.filter(
- (pollingPlace) => doesPollingPlaceSatisifyFilterCriteria(pollingPlace, mapFilterSettings) === true,
+ (pollingPlace) =>
+ doesPollingPlaceSatisifyFilterCriteria(pollingPlace, mapFilterSettings, election.id) === true,
)
: undefined;
@@ -70,8 +71,9 @@ export default function SearchByIdsStackComponent(props: Props) {
{pollingPlaceNearbyResultsFiltered !== undefined && (
diff --git a/public-redesign/src/features/search/searchByIds/searchResultsContainer/searchByIdsResultsContainer.tsx b/public-redesign/src/features/search/searchByIds/searchResultsContainer/searchByIdsResultsContainer.tsx
index 110fbf5529..506215877b 100644
--- a/public-redesign/src/features/search/searchByIds/searchResultsContainer/searchByIdsResultsContainer.tsx
+++ b/public-redesign/src/features/search/searchByIds/searchResultsContainer/searchByIdsResultsContainer.tsx
@@ -10,6 +10,7 @@ import React, { useCallback, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useAppSelector } from '../../../../app/hooks';
import { navigateToMapUsingURLParamsWithoutUpdatingTheView } from '../../../../app/routing/navigationHelpers/navigationHelpersMap';
+import { Election } from '../../../../app/services/elections';
import { mapaThemePrimaryPurple } from '../../../../app/ui/theme';
import { selectIsMapFiltered, selectNumberOfMapFilterSettingsApplied } from '../../../app/appSlice';
import SearchFilterComponent from '../../shared/searchFilterComponent';
@@ -23,6 +24,7 @@ const IconButtonOutlined = styled(Button)({
});
interface Props {
+ election: Election;
numberOfResults: number;
isFiltered: boolean;
pollingPlacesLoaded: boolean;
@@ -31,7 +33,7 @@ interface Props {
}
export default function SearchByIdsResultsContainer(props: Props) {
- const { numberOfResults, isFiltered, pollingPlacesLoaded, onViewOnMap, children } = props;
+ const { election, numberOfResults, isFiltered, pollingPlacesLoaded, onViewOnMap, children } = props;
const params = useParams();
const navigate = useNavigate();
@@ -42,8 +44,10 @@ export default function SearchByIdsResultsContainer(props: Props) {
// Filter Control
// ######################
const [isSearchBarFilterControlOpen, setIsSearchBarFilterControlOpen] = useState(false);
- const isMapFiltered = useAppSelector(selectIsMapFiltered);
- const numberOfMapFilterSettingsApplied = useAppSelector(selectNumberOfMapFilterSettingsApplied);
+ const isMapFiltered = useAppSelector((state) => selectIsMapFiltered(state, election.id));
+ const numberOfMapFilterSettingsApplied = useAppSelector((state) =>
+ selectNumberOfMapFilterSettingsApplied(state, election.id),
+ );
const onClickFilterControl = useCallback(() => {
setIsSearchBarFilterControlOpen(!isSearchBarFilterControlOpen);
@@ -145,7 +149,7 @@ export default function SearchByIdsResultsContainer(props: Props) {
)}
- {isSearchBarFilterControlOpen === true && }
+ {isSearchBarFilterControlOpen === true && }
{pollingPlacesLoaded === false && (
selectMapFilterSettings(state));
+ const mapFilterSettings = useAppSelector((state) => selectMapFilterSettings(state, election.id));
const onClickFilterOptionListItemButton = useCallback(
(value: keyof IMapFilterSettings) => () => {
dispatch(
setMapFilterSettings({
- ...mapFilterSettings,
- [value]: !mapFilterSettings[value],
+ mapFilterSettings: {
+ ...mapFilterSettings,
+ [value]: !mapFilterSettings[value],
+ },
+ electionId: election.id,
}),
);
},
- [dispatch, mapFilterSettings],
+ [dispatch, election.id, mapFilterSettings],
);
const onChangeFilterOption = useCallback(
(value: keyof IMapFilterSettings) => (_event: React.ChangeEvent, checked: boolean) => {
dispatch(
setMapFilterSettings({
- ...mapFilterSettings,
- [value]: checked,
+ mapFilterSettings: {
+ ...mapFilterSettings,
+ [value]: checked,
+ },
+ electionId: election.id,
}),
);
},
- [dispatch, mapFilterSettings],
+ [dispatch, election.id, mapFilterSettings],
);
return (