Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/js/common/components/CardForListBody.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function CardForListBody (props) {
ballotItemDisplayName,
candidateWeVoteId, classes, districtName, finalElectionDateInPast, hideCardMargins,
hideItemActionBar, isClaimedProfile, limitCardWidth, linkedCampaignXWeVoteId, officeName,
photoLargeUrl, politicalParty, politicianBasePath,
onDisplayNameClick, photoLargeUrl, politicalParty, politicianBasePath,
politicianDescription, politicianWeVoteId, profileImageBackgroundColor,
searchText, showPoliticianOpenInNewWindow, stateCode,
supportPolitician, tagIdBaseName,
Expand Down Expand Up @@ -129,7 +129,16 @@ function CardForListBody (props) {
className={isCordova() ? 'u-link-color u-link-underline' : ''}
id={`${tagIdBaseName}DisplayName`}
to={politicianBasePath}
onClick={() => (isCordova() ? AppObservableStore.setShowOrganizationModal(false) : null)}
onClick={(e) => {
if (onDisplayNameClick) {
e.preventDefault();
onDisplayNameClick();
return;
}
if (isCordova()) {
AppObservableStore.setShowOrganizationModal(false);
}
}}
>
{highlightSearchText(ballotItemDisplayName || nameFromUrl, searchText)}
</Link>
Expand Down Expand Up @@ -421,7 +430,13 @@ function CardForListBody (props) {
hideCardMargins={hideCardMargins}
id={`${tagIdBaseName}PhotoDesktop`}
limitCardWidth={limitCardWidth}
onClick={hideCardMargins ? null : () => historyPush(politicianBasePath)}
onClick={hideCardMargins ? null : () => {
if (onDisplayNameClick) {
onDisplayNameClick();
return;
}
historyPush(politicianBasePath);
}}
profileImageBackgroundColor={profileImageBackgroundColor}
useVerticalCard={useVerticalCard}
>
Expand Down Expand Up @@ -504,6 +519,7 @@ CardForListBody.propTypes = {
limitCardWidth: PropTypes.bool,
linkedCampaignXWeVoteId: PropTypes.string,
officeName: PropTypes.string,
onDisplayNameClick: PropTypes.func,
photoLargeUrl: PropTypes.string,
politicalParty: PropTypes.string,
politicianBasePath: PropTypes.string.isRequired,
Expand Down
17 changes: 16 additions & 1 deletion src/js/common/stores/PoliticianStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class PoliticianStore extends ReduceStore {
politicianEmailQueuedToSaveSet: false,
politicianNameQueuedToSave: '',
politicianNameQueuedToSaveSet: false,
politicianQueryResultsWeVoteIds: [], // politician_we_vote_id's returned by the most recent politiciansQuery/politiciansRetrieve, as opposed to ones individually cached via politicianRetrieve
politicianPhotoQueuedToSave: '',
politicianPhotoQueuedToSaveSet: false,
politicianPhotoTooBig: false,
Expand Down Expand Up @@ -90,6 +91,16 @@ class PoliticianStore extends ReduceStore {
return politicianList || [];
}

// Unlike getPoliticianList (which returns every politician ever cached, including ones pulled in one-at-a-time by
// politicianRetrieve, e.g. when a candidate's side drawer is opened), this only returns the politicians returned by
// the most recent politiciansQuery/politiciansRetrieve call (e.g. "top politicians for this state").
getPoliticianQueryResultsList () {
const { allCachedPoliticians, politicianQueryResultsWeVoteIds } = this.getState();
return politicianQueryResultsWeVoteIds
.map((politicianWeVoteId) => allCachedPoliticians[politicianWeVoteId])
.filter((politician) => !!politician);
}

getPoliticianName (politicianWeVoteId) {
const politician = this.getState().allCachedPoliticians[politicianWeVoteId] || {};
if (politician) {
Expand Down Expand Up @@ -404,7 +415,7 @@ class PoliticianStore extends ReduceStore {
} = state;
let {
allCachedPoliticians, allCachedPoliticianOwners, allCachedCandidateListsByPolitician, allCachedPoliticianOwnerPhotos,
politicianListsByOfficeWeVoteId,
politicianListsByOfficeWeVoteId, politicianQueryResultsWeVoteIds,
voterCanSendUpdatesPoliticianWeVoteIds, voterCanVoteForPoliticianWeVoteIds, voterOwnedPoliticianWeVoteIds,
// voterStartedPoliticianWeVoteIds,
} = state;
Expand Down Expand Up @@ -635,6 +646,7 @@ class PoliticianStore extends ReduceStore {
politicianListsByOfficeWeVoteId = {};
}
localPoliticianList = [];
politicianQueryResultsWeVoteIds = [];
politicianList.forEach((one) => {
if (action.type === 'politiciansQuery') {
// Since the politiciansQuery doesn't return all of the
Expand All @@ -643,8 +655,10 @@ class PoliticianStore extends ReduceStore {
if (!(one.politician_we_vote_id in allCachedPoliticians)) {
allCachedPoliticians[one.politician_we_vote_id] = one;
}
politicianQueryResultsWeVoteIds.push(one.politician_we_vote_id);
} else {
allCachedPoliticians[one.we_vote_id] = one;
politicianQueryResultsWeVoteIds.push(one.we_vote_id);
}
localPoliticianList.push(one);
});
Expand All @@ -653,6 +667,7 @@ class PoliticianStore extends ReduceStore {
...state,
allCachedPoliticians,
politicianListsByOfficeWeVoteId,
politicianQueryResultsWeVoteIds,
};

case 'voterCanEditPolitician':
Expand Down
15 changes: 15 additions & 0 deletions src/js/components/CandidateListRoot/CandidateCardForList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { Component, Suspense } from 'react';
import CardForListBodyPlaceholder from '../../common/components/CardForListBodyPlaceholder';
import { getTodayAsInteger } from '../../common/utils/dateFormat';
import { renderLog } from '../../common/utils/logging';
import AppObservableStore from '../../common/stores/AppObservableStore';
import CampaignSupporterStore from '../../common/stores/CampaignSupporterStore';
import CandidateStore from '../../stores/CandidateStore';
import keepHelpingDestination from '../../common/utils/keepHelpingDestination';
Expand All @@ -21,6 +22,7 @@ class CandidateCardForList extends Component {
this.getCampaignXBasePath = this.getCampaignXBasePath.bind(this);
this.getPathToUseToKeepHelping = this.getPathToUseToKeepHelping.bind(this);
this.getPoliticianBasePath = this.getPoliticianBasePath.bind(this);
this.onCandidateNameClick = this.onCandidateNameClick.bind(this);
// this.pullCampaignXSupporterVoterEntry = this.pullCampaignXSupporterVoterEntry.bind(this);
}

Expand Down Expand Up @@ -80,6 +82,18 @@ class CandidateCardForList extends Component {
});
}

onCandidateNameClick () {
const { candidate } = this.state;
const candidateWeVoteId = candidate && candidate.we_vote_id;
if (!candidateWeVoteId) {
return;
}
AppObservableStore.setOrganizationModalBallotItemWeVoteId(candidateWeVoteId);
AppObservableStore.setHideOrganizationModalBallotItemInfo(false);
AppObservableStore.setHideOrganizationModalPositions(false);
AppObservableStore.setShowOrganizationModal(true);
}

getCampaignXBasePath () {
const { candidate } = this.state;
// console.log('candidate:', candidate);
Expand Down Expand Up @@ -232,6 +246,7 @@ class CandidateCardForList extends Component {
limitCardWidth={limitCardWidth}
linkedCampaignXWeVoteId={linkedCampaignXWeVoteId}
officeName={contestOfficeName}
onDisplayNameClick={candidateWeVoteId ? this.onCandidateNameClick : undefined}
pathToUseToKeepHelping={pathToUseToKeepHelping}
photoLargeUrl={candidatePhotoLargeUrl}
politicalParty={politicalParty.length ? politicalParty : ''}
Expand Down
25 changes: 25 additions & 0 deletions src/js/components/PoliticianListRoot/PoliticianCardForList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import React, { Component, Suspense } from 'react';
import CardForListBodySkeleton from '../../common/components/CardForListBodySkeleton';
import { getTodayAsInteger } from '../../common/utils/dateFormat';
import { renderLog } from '../../common/utils/logging';
import AppObservableStore from '../../common/stores/AppObservableStore';
import CampaignSupporterStore from '../../common/stores/CampaignSupporterStore';
import CandidateStore from '../../stores/CandidateStore';
import PoliticianActions from '../../common/actions/PoliticianActions';
import PoliticianStore from '../../common/stores/PoliticianStore';
import keepHelpingDestination from '../../common/utils/keepHelpingDestination';
import { mostLikelyCandidateDictFromList } from '../../utils/candidateFunctions';
Expand All @@ -24,6 +26,7 @@ class PoliticianCardForList extends Component {
this.getCampaignXBasePath = this.getCampaignXBasePath.bind(this);
this.getPathToUseToKeepHelping = this.getPathToUseToKeepHelping.bind(this);
this.getPoliticianBasePath = this.getPoliticianBasePath.bind(this);
this.onPoliticianNameClick = this.onPoliticianNameClick.bind(this);
// this.pullCampaignXSupporterVoterEntry = this.pullCampaignXSupporterVoterEntry.bind(this);
}

Expand Down Expand Up @@ -90,6 +93,12 @@ class PoliticianCardForList extends Component {
const {
linked_campaignx_we_vote_id: linkedCampaignXWeVoteId,
} = politician;
// The politician list-view (politiciansQuery/politiciansRetrieve) doesn't include candidate_list, so we need a
// one-time full retrieve to learn which candidate record (if any) to open in the side drawer on name click.
if (!('candidate_list' in politician) && !this.fullPoliticianRetrieveTriggered) {
this.fullPoliticianRetrieveTriggered = true;
PoliticianActions.politicianRetrieve(politicianWeVoteId);
}
if (politician.candidate_list && politician.candidate_list.length > 0) {
const mostLikelyCandidate = mostLikelyCandidateDictFromList(politician.candidate_list);
// console.log('mostLikelyCandidate: ', mostLikelyCandidate);
Expand All @@ -106,6 +115,21 @@ class PoliticianCardForList extends Component {
});
}

onPoliticianNameClick () {
const { politicianWeVoteId } = this.props;
const { candidateWeVoteId } = this.state;
// Prefer the politician's current candidate record when known; otherwise fall back to the politician
// we_vote_id itself so the drawer always opens instead of navigating to the full page.
const ballotItemWeVoteId = candidateWeVoteId || politicianWeVoteId;
if (!ballotItemWeVoteId) {
return;
}
AppObservableStore.setOrganizationModalBallotItemWeVoteId(ballotItemWeVoteId);
AppObservableStore.setHideOrganizationModalBallotItemInfo(false);
AppObservableStore.setHideOrganizationModalPositions(false);
AppObservableStore.setShowOrganizationModal(true);
}

getCampaignXBasePath () {
const { politician } = this.state;
// console.log('politician:', politician);
Expand Down Expand Up @@ -262,6 +286,7 @@ class PoliticianCardForList extends Component {
limitCardWidth={limitCardWidth}
linkedCampaignXWeVoteId={linkedCampaignXWeVoteId}
officeName={contestOfficeName}
onDisplayNameClick={this.onPoliticianNameClick}
pathToUseToKeepHelping={pathToUseToKeepHelping}
photoLargeUrl={politicianPhotoLargeUrl}
politicalParty={politicalParty}
Expand Down
95 changes: 71 additions & 24 deletions src/js/components/VoterGuide/OrganizationModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import MeasureStore from '../../stores/MeasureStore';
import VoterGuideStore from '../../stores/VoterGuideStore';
import VoterStore from '../../stores/VoterStore';
import CardForListBodySkeleton from '../../common/components/CardForListBodySkeleton';
import PoliticianStore from '../../common/stores/PoliticianStore';
import VoterPositionEntryAndDisplay from '../PositionItem/VoterPositionEntryAndDisplay';
import { Candidate, CandidateNameAndPartyWrapper, CandidateNameH4, CandidateParty, CandidateTopRow } from '../Style/BallotStyles';
import { DrawerHeaderAnimateDownInnerContainer, DrawerHeaderAnimateDownOuterContainer } from '../Style/drawerLayoutStyles';
Expand Down Expand Up @@ -79,13 +80,17 @@ class OrganizationModal extends Component {
this.appStateSubscription = messageService.getMessage().subscribe(() => this.onAppObservableStoreChange());
this.candidateStoreListener = CandidateStore.addListener(this.onCandidateStoreChange.bind(this));
this.measureStoreListener = MeasureStore.addListener(this.onMeasureStoreChange.bind(this));
this.politicianStoreListener = PoliticianStore.addListener(this.onPoliticianStoreChange.bind(this));
const { ballotItemWeVoteId } = this.props;
// console.log('ballotItemWeVoteId:', ballotItemWeVoteId);
const isMeasure = stringContains('meas', ballotItemWeVoteId);
const isCandidate = stringContains('cand', ballotItemWeVoteId);
// A politician we_vote_id can be passed directly when a politician has no linked candidate record to show instead
const isPolitician = !isCandidate && !isMeasure;
this.setState({
isCandidate,
isMeasure,
isPolitician,
});
setTimeout(() => {
const drawer = document.querySelector('.MuiDrawer-paper');
Expand Down Expand Up @@ -172,6 +177,23 @@ class OrganizationModal extends Component {
});
AnalyticsActions.saveActionMeasure(VoterStore.electionId(), ballotItemWeVoteId);
}
if (isPolitician) {
PoliticianActions.politicianRetrieve(ballotItemWeVoteId);
const politician = PoliticianStore.getPoliticianByWeVoteId(ballotItemWeVoteId);
const {
linked_campaignx_we_vote_id: linkedCampaignXWeVoteId,
political_party: politicalParty,
politician_name: ballotItemDisplayName,
we_vote_hosted_profile_image_url_large: politicianImageUrlLarge,
} = politician;
this.setState({
ballotItemDisplayName,
linkedCampaignXWeVoteId,
politicalParty,
politicianImageUrlLarge,
politicianWeVoteId: ballotItemWeVoteId,
});
}
if (apiCalming('organizationsFollowedRetrieve', 60000)) {
OrganizationActions.organizationsFollowedRetrieve();
}
Expand Down Expand Up @@ -205,6 +227,7 @@ class OrganizationModal extends Component {
componentWillUnmount () {
this.candidateStoreListener.remove();
this.measureStoreListener.remove();
this.politicianStoreListener.remove();
this.appStateSubscription.unsubscribe();
AppObservableStore.setScrolledDownDrawer(false);
}
Expand Down Expand Up @@ -293,6 +316,26 @@ class OrganizationModal extends Component {
}
}

onPoliticianStoreChange () {
const { ballotItemWeVoteId } = this.props;
const { isPolitician } = this.state;
if (isPolitician) {
const politician = PoliticianStore.getPoliticianByWeVoteId(ballotItemWeVoteId);
const {
linked_campaignx_we_vote_id: linkedCampaignXWeVoteId,
political_party: politicalParty,
politician_name: ballotItemDisplayName,
we_vote_hosted_profile_image_url_large: politicianImageUrlLarge,
} = politician;
this.setState({
ballotItemDisplayName,
linkedCampaignXWeVoteId,
politicalParty,
politicianImageUrlLarge,
});
}
}

// handleResizeLocal () {
// if (handleResize('Footer')) {
// // console.log('Footer handleResizeEntry update');
Expand Down Expand Up @@ -343,7 +386,7 @@ class OrganizationModal extends Component {
const { ballotItemWeVoteId, classes, hideBallotItemInfo, hidePositions, params } = this.props;
const {
allCachedPositionsForThisBallotItem, ballotItemDisplayName,
isCandidate, isMeasure, linkedCampaignXWeVoteId, modalOpen,
isCandidate, isMeasure, isPolitician, linkedCampaignXWeVoteId, modalOpen,
politicianWeVoteId, scrolledDown, unFurlPositions, politicalParty, politicianImageUrlLarge,
} = this.state;
const avatarBackgroundImage = normalizedImagePath('../img/global/svg-icons/avatar-generic.svg');
Expand Down Expand Up @@ -433,7 +476,7 @@ class OrganizationModal extends Component {
</HeartToggleAndThermometerWrapper>
</DrawerHeaderAnimateDownInnerContainer>
</DrawerHeaderAnimateDownOuterContainer>
{(isCandidate && !hideBallotItemInfo) && (
{((isCandidate || isPolitician) && !hideBallotItemInfo) && (
<PoliticianCardForListWrapper>
<Suspense fallback={<OrganizationModalPoliticianCardSkeleton />}>
<PoliticianCardForList
Expand All @@ -445,20 +488,22 @@ class OrganizationModal extends Component {
useVerticalCard
/>
</Suspense>
<Suspense fallback={(
<Box display="flex" gap={1} flexWrap="wrap" sx={{ mb: 2 }}>
<Skeleton variant="rounded" width={80} height={24} sx={{ borderRadius: 3 }} />
<Skeleton variant="rounded" width={100} height={24} sx={{ borderRadius: 3 }} />
<Skeleton variant="rounded" width={90} height={24} sx={{ borderRadius: 3 }} />
<Skeleton variant="rounded" width={70} height={24} sx={{ borderRadius: 3 }} />
</Box>
)}> {/* CORDOVA_TOKEN_AT_CLOSE_OF_A_MULTI_LINE_FALLBACK_DO_NOT_REMOVE */}
<IssuesByBallotItemDisplayList
ballotItemDisplayName={ballotItemDisplayName}
ballotItemWeVoteId={ballotItemWeVoteId}
externalUniqueId={`candidateItem-${ballotItemWeVoteId}`}
/>
</Suspense>
{isCandidate && (
<Suspense fallback={(
<Box display="flex" gap={1} flexWrap="wrap" sx={{ mb: 2 }}>
<Skeleton variant="rounded" width={80} height={24} sx={{ borderRadius: 3 }} />
<Skeleton variant="rounded" width={100} height={24} sx={{ borderRadius: 3 }} />
<Skeleton variant="rounded" width={90} height={24} sx={{ borderRadius: 3 }} />
<Skeleton variant="rounded" width={70} height={24} sx={{ borderRadius: 3 }} />
</Box>
)}> {/* CORDOVA_TOKEN_AT_CLOSE_OF_A_MULTI_LINE_FALLBACK_DO_NOT_REMOVE */}
<IssuesByBallotItemDisplayList
ballotItemDisplayName={ballotItemDisplayName}
ballotItemWeVoteId={ballotItemWeVoteId}
externalUniqueId={`candidateItem-${ballotItemWeVoteId}`}
/>
</Suspense>
)}
<BallotItemBottomSpacer />
</PoliticianCardForListWrapper>
)}
Expand All @@ -470,7 +515,7 @@ class OrganizationModal extends Component {
</>
</Suspense>
)}
{ (!hidePositions || unFurlPositions) && (
{ !isPolitician && (!hidePositions || unFurlPositions) && (
<>
<Suspense fallback={(
<Box sx={{ mb: 3 }}>
Expand All @@ -487,13 +532,15 @@ class OrganizationModal extends Component {
<ScoreSummaryListControllerBottomSpacer />
</>
)}
<VoterPositionEntryAndDisplayWrapper>
<VoterPositionEntryAndDisplay
ballotItemWeVoteId={ballotItemWeVoteId}
politicianWeVoteId={politicianWeVoteId}
/>
</VoterPositionEntryAndDisplayWrapper>
{ !!(allCachedPositionsForThisBallotItem.length) && (
{!isPolitician && (
<VoterPositionEntryAndDisplayWrapper>
<VoterPositionEntryAndDisplay
ballotItemWeVoteId={ballotItemWeVoteId}
politicianWeVoteId={politicianWeVoteId}
/>
</VoterPositionEntryAndDisplayWrapper>
)}
{ !isPolitician && !!(allCachedPositionsForThisBallotItem.length) && (
<>
{ !hidePositions || unFurlPositions ? (
<Suspense fallback={(
Expand Down
Loading
Loading