Skip to content

Commit

Permalink
⛓ Allow linking to candidate preview (#1592)
Browse files Browse the repository at this point in the history
* Display modal if candidate ID is provided in URL

* Use correct modal parameters

* Fetch cycleId from Candidate

* Remove unneeded mocks from test

* Redirect to past elections if applicable

* Allow linking to past elections too
  • Loading branch information
p-sad authored Oct 7, 2021
1 parent 78dbda1 commit ef2b0dd
Show file tree
Hide file tree
Showing 14 changed files with 81 additions and 66 deletions.
2 changes: 2 additions & 0 deletions packages/ui/src/app/pages/Council/Election.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { TextHuge } from '@/common/components/typography'
import { camelCaseToText } from '@/common/helpers'
import { AnnounceCandidacyButton } from '@/council/components/election/announcing/AnnounceCandidacyButton'
import { AnnouncingStage } from '@/council/components/election/announcing/AnnouncingStage'
import { useCandidatePreviewViaUrlParameter } from '@/council/hooks/useCandidatePreviewViaUrlParameter'
import { useCurrentElection } from '@/council/hooks/useCurrentElection'
import { useElectionRemainingPeriod } from '@/council/hooks/useElectionRemainingPeriod'
import { useElectionStage } from '@/council/hooks/useElectionStage'
Expand All @@ -21,6 +22,7 @@ export const Election = () => {
const { isLoading: isLoadingElection, election } = useCurrentElection()
const { isLoading: isLoadingElectionStage, stage: electionStage } = useElectionStage()
const remainingPeriod = useElectionRemainingPeriod(electionStage)
useCandidatePreviewViaUrlParameter()

if (isLoadingElection || isLoadingElectionStage) {
return <PageLayout header={null} main={<Loading />} />
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/src/app/pages/Council/PastElections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import React from 'react'
import { PageHeaderRow, PageHeaderWrapper, PageLayout } from '@/app/components/PageLayout'
import { MainPanel } from '@/common/components/page/PageContent'
import { PageTitle } from '@/common/components/page/PageTitle'
import { useCandidatePreviewViaUrlParameter } from '@/council/hooks/useCandidatePreviewViaUrlParameter'

import { CouncilTabs } from './components/CouncilTabs'

export const PastElections = () => {
useCandidatePreviewViaUrlParameter()

const header = (
<PageHeaderWrapper>
<PageHeaderRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { CandidateCardImage, CandidateCardImageContainer } from './CandidateCard

export interface CandidateCardProps {
id: string
cycleId: number
member: Member
image?: string
voted?: boolean
Expand All @@ -36,7 +35,6 @@ export interface CandidateCardProps {

export const CandidateCard = ({
id,
cycleId,
member,
image,
voted,
Expand All @@ -51,7 +49,7 @@ export const CandidateCard = ({
const { showModal } = useModal()
return (
<CandidateCardWrapper
onClick={() => showModal<CandidacyPreviewModalCall>({ modal: 'CandidacyPreview', data: { id, cycleId } })}
onClick={() => showModal<CandidacyPreviewModalCall>({ modal: 'CandidacyPreview', data: { id } })}
>
<CandidateCardImageWrapper>
<CandidateCardImage imageUrl={image} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export const AnnouncingStage = ({ election }: AnnouncingStageProps) => {
<CandidateCardList
candidates={candidates.map((candidate) => ({
id: candidate.id,
cycleId: election.cycleId,
member: candidate.member,
title: 'Some title',
...(candidate.stake && isMyCandidate(myMembers, candidate) ? { stake: new BN(candidate.stake) } : {}),
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/council/hooks/useCandidate.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useGetCandidateQuery } from '../queries'
import { asCandidate } from '../types'
import { asCandidateWithDetails } from '../types'

export const useCandidate = (id: string) => {
const { data, loading } = useGetCandidateQuery({ variables: { where: { id } } })
const candidate = data?.candidateByUniqueInput ? asCandidate(data?.candidateByUniqueInput) : undefined
const candidate = data?.candidateByUniqueInput ? asCandidateWithDetails(data?.candidateByUniqueInput) : undefined
return {
isLoading: loading,
candidate,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useEffect } from 'react'

import { useModal } from '@/common/hooks/useModal'
import { useRouteQuery } from '@/common/hooks/useRouteQuery'

import { CandidacyPreviewModalCall } from '../modals/CandidacyPreview/types'

export const useCandidatePreviewViaUrlParameter = () => {
const { showModal } = useModal()
const query = useRouteQuery()
const candidateId = query.get('candidate')
useEffect(() => {
if (candidateId) {
showModal<CandidacyPreviewModalCall>({ modal: 'CandidacyPreview', data: { id: candidateId } })
}
}, [candidateId])
}
2 changes: 1 addition & 1 deletion packages/ui/src/council/hooks/useElectionCandidatesIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useMemo } from 'react'

import { useGetElectionCandidatesIdsQuery } from '../queries'

export const useElectionCandidatesIds = (electionCycleId: number) => {
export const useElectionCandidatesIds = (electionCycleId = -1) => {
const { data } = useGetElectionCandidatesIdsQuery({ variables: { electionCycleId } })
return useMemo(() => data?.candidates.map((candidate) => candidate.id), [data])
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { RowGapBlock } from '@/common/components/page/PageContent'
import { SidePaneLabel, SidePaneRow, SidePaneTable, SidePaneText } from '@/common/components/SidePane'
import { StatisticItem, Statistics } from '@/common/components/statistics'
import { TextMedium, TokenValue } from '@/common/components/typography'
import { Candidate } from '@/council/types'
import { CandidateWithDetails } from '@/council/types'
import { useMemberCandidacyStats } from '@/memberships/hooks/useMemberCandidacyStats'

interface Props {
candidate: Candidate
candidate: CandidateWithDetails
}

export const CandidacyDetails = ({ candidate }: Props) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import { useHistory } from 'react-router'

import { ButtonGhost, ButtonPrimary, ButtonsGroup, CopyButtonTemplate } from '@/common/components/buttons'
import { Arrow } from '@/common/components/icons'
Expand All @@ -7,6 +8,7 @@ import { Loading } from '@/common/components/Loading'
import { SidePaneTopButtonsGroup } from '@/common/components/SidePane'
import { useModal } from '@/common/hooks/useModal'
import { isDefined } from '@/common/utils'
import { CouncilRoutes } from '@/council/constants'
import { useCandidate } from '@/council/hooks/useCandidate'
import { useElectionCandidatesIds } from '@/council/hooks/useElectionCandidatesIds'
import { MemberDetails } from '@/memberships/components/MemberProfile'
Expand All @@ -30,8 +32,14 @@ export const CandidacyPreview = React.memo(() => {
const { modalData } = useModal<CandidacyPreviewModalCall>()
const [candidateId, setCandidateId] = useState(modalData.id)
const { isLoading, candidate } = useCandidate(candidateId)
const candidates = useElectionCandidatesIds(modalData.cycleId)
const candidates = useElectionCandidatesIds(candidate?.cycleId)
const candidateIndex = candidate && candidates?.findIndex((id) => id === candidate?.id)
const history = useHistory()
useEffect(() => {
if (candidate?.cycleFinished && history.location.pathname !== CouncilRoutes.pastElections) {
history.replace(`${CouncilRoutes.pastElections}?candidate=${candidate.id}`)
}
}, [candidate?.cycleFinished])
const onClickLeft = () => candidates && isDefined(candidateIndex) && setCandidateId(candidates[candidateIndex - 1])
const onClickRight = () => candidates && isDefined(candidateIndex) && setCandidateId(candidates[candidateIndex + 1])

Expand Down Expand Up @@ -67,7 +75,9 @@ export const CandidacyPreview = React.memo(() => {
<CopyButtonTemplate
square
size="small"
textToCopy={`${window.location.host}/#/council/election?candidate=${candidate?.member.id}&cycle=${modalData.cycleId}`}
textToCopy={`${window.location.host}/#/council/${
candidate?.cycleFinished ? 'past-elections' : 'election'
}?candidate=${candidate?.id}`}
icon={<LinkIcon />}
/>
</SidePaneTopButtonsGroup>
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/council/modals/CandidacyPreview/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { ModalWithDataCall } from '../../../common/providers/modal/types'

export type CandidacyPreviewModalCall = ModalWithDataCall<'CandidacyPreview', { id: string; cycleId: number }>
export type CandidacyPreviewModalCall = ModalWithDataCall<'CandidacyPreview', { id: string }>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions packages/ui/src/council/queries/council.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fragment ElectionRoundFields on ElectionRound {
}
}

fragment CandidateFields on Candidate {
fragment CandidateDetailedFields on Candidate {
id
stakingAccountId
rewardAccountId
Expand All @@ -42,6 +42,10 @@ fragment CandidateFields on Candidate {
member {
...MemberFields
}
cycleId {
cycleId
isFinished
}
}

query GetElectedCouncils($where: ElectedCouncilWhereInput!) {
Expand All @@ -58,7 +62,7 @@ query GetCurrentElection {

query GetCandidate($where: CandidateWhereUniqueInput!) {
candidateByUniqueInput(where: $where) {
...CandidateFields
...CandidateDetailedFields
}
}

Expand Down
10 changes: 7 additions & 3 deletions packages/ui/src/council/types/Candidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import BN from 'bn.js'

import { asMember, Member } from '@/memberships/types'

import { CandidateFieldsFragment } from '../queries'
import { CandidateDetailedFieldsFragment } from '../queries'

export interface Candidate {
export interface CandidateWithDetails {
id: string
stakingAccount: string
rewardAccount: string
Expand All @@ -13,9 +13,11 @@ export interface Candidate {
summary: string
description: string[]
member: Member
cycleId: number
cycleFinished: boolean
}

export const asCandidate = (fields: CandidateFieldsFragment): Candidate => ({
export const asCandidateWithDetails = (fields: CandidateDetailedFieldsFragment): CandidateWithDetails => ({
id: fields.id,
stakingAccount: fields.stakingAccountId,
rewardAccount: fields.rewardAccountId,
Expand All @@ -24,4 +26,6 @@ export const asCandidate = (fields: CandidateFieldsFragment): Candidate => ({
title: 'Candidate title',
summary: fields.note,
description: fields.note.split(' ').slice(0, 5),
cycleId: fields.cycleId.cycleId,
cycleFinished: fields.cycleId.isFinished,
})
57 changes: 15 additions & 42 deletions packages/ui/test/council/modals/CandidacyPreview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,29 @@ describe('UI: CandidacyPreview', () => {
modal: null,
modalData: {
id: '0',
cycleId: 0,
},
}
const server = setupMockServer({ noCleanupAfterEach: true })

beforeAll(async () => {
seedMembers(server.server, 2)
seedMember({ ...MEMBER_ALICE_DATA, id: '2', handle: 'Cindy' }, server.server)
seedMember({ ...MEMBER_ALICE_DATA, id: '3', handle: 'Dave' }, server.server)
;['0', '1'].forEach((id) =>
seedElectedCouncil(
{
id,
endedAtBlock: 100,
electedAtBlock: 90,
},
server.server
)
seedElectedCouncil(
{
id: '0',
endedAtBlock: 100,
electedAtBlock: 90,
},
server.server
)
;[0, 1].forEach((cycleId) =>
seedCouncilElection(
{
id: cycleId.toString(),
cycleId,
isFinished: false,
electedCouncilId: '0',
},
server.server
)
seedCouncilElection(
{
id: '0',
cycleId: 0,
isFinished: false,
electedCouncilId: '0',
},
server.server
)
;['0', '1', '2'].forEach((id) =>
seedCouncilCandidate(
Expand All @@ -61,18 +55,6 @@ describe('UI: CandidacyPreview', () => {
server.server
)
)
seedCouncilCandidate(
{
id: '3',
memberId: '3',
cycleIdId: '1',
stake: 2000,
stakingAccountId: '5ChwAW7ASAaewhQPNK334vSHNUrPFYg2WriY2vDBfEQwkipU',
rewardAccountId: '5ChwAW7ASAaewhQPNK334vSHNUrPFYg2WriY2vDBfEQwkipU',
note: 'alias est velit ut expedita aliquam itaque eos eaque aliquid',
},
server.server
)
})

describe('Cycle candidates', () => {
Expand Down Expand Up @@ -114,15 +96,6 @@ describe('UI: CandidacyPreview', () => {
expect(await screen.findByText(/bob/i)).toBeDefined()
expect(await screen.findByText(/candidate 2 of 3/i)).toBeDefined()
})

it('Candidate from different election', async () => {
useModal.modalData.id = '3'
displayModal()
expect(await screen.findByText(/dave/i)).toBeDefined()
expect(await screen.findByText(/candidate 0 of 3/i)).toBeDefined()
expect(await screen.findByTitle('Previous candidate')).toBeDisabled()
expect(await screen.findByTitle('Next candidate')).toBeDisabled()
})
})

const displayModal = () =>
Expand Down

0 comments on commit ef2b0dd

Please sign in to comment.