Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.

Commit 6d27d26

Browse files
authored
Merge pull request #385 from 0xProject/feat/live-vote-badge
Adding governance header badge for live and upcoming votes
2 parents 55c54f6 + 4f833b8 commit 6d27d26

File tree

1 file changed

+92
-4
lines changed

1 file changed

+92
-4
lines changed

ts/components/staking/header/header.tsx

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { gql, request } from 'graphql-request';
2+
import moment from 'moment';
13
import React, { useCallback, useEffect, useState } from 'react';
24
import Headroom from 'react-headroom';
5+
import { useQuery } from 'react-query';
36
import { useDispatch, useSelector } from 'react-redux';
47
import MediaQuery from 'react-responsive';
58
import styled, { css } from 'styled-components';
@@ -10,15 +13,51 @@ import { Hamburger } from 'ts/components/hamburger';
1013
import { Logo } from 'ts/components/logo';
1114
import { FlexWrap } from 'ts/components/newLayout';
1215
import { SubMenu } from 'ts/components/staking/header/sub_menu';
16+
import { Proposal, proposals as prodProposals, stagingProposals } from 'ts/pages/governance/data';
1317
import { Dispatcher } from 'ts/redux/dispatcher';
1418
import { State } from 'ts/redux/reducer';
1519
import { ThemeValuesInterface } from 'ts/style/theme';
1620
import { zIndex } from 'ts/style/z_index';
17-
import { AccountState, WebsitePaths } from 'ts/types';
21+
import { AccountState, OnChainProposal, WebsitePaths } from 'ts/types';
1822

1923
import { useWeb3React } from '@web3-react/core';
2024
import { useWallet } from 'ts/hooks/use_wallet';
2125
import { colors } from 'ts/style/colors';
26+
import { GOVERNANCE_THEGRAPH_ENDPOINT } from 'ts/utils/configs';
27+
import { environments } from 'ts/utils/environments';
28+
29+
const FETCH_PROPOSALS = gql`
30+
query proposals {
31+
proposals(orderDirection: desc) {
32+
id
33+
proposer
34+
description
35+
votesFor
36+
votesAgainst
37+
createdTimestamp
38+
voteEpoch {
39+
id
40+
startTimestamp
41+
endTimestamp
42+
}
43+
executionEpoch {
44+
startTimestamp
45+
endTimestamp
46+
}
47+
executionTimestamp
48+
}
49+
}
50+
`;
51+
52+
type ProposalWithOrder = Proposal & {
53+
order?: number;
54+
};
55+
56+
const PROPOSALS = environments.isProduction() ? prodProposals : stagingProposals;
57+
const ZEIP_IDS = Object.keys(PROPOSALS).map((idString) => parseInt(idString, 10));
58+
const ZEIP_PROPOSALS: ProposalWithOrder[] = ZEIP_IDS.map((id) => PROPOSALS[id]).sort(
59+
(a, b) => b.voteStartDate.unix() - a.voteStartDate.unix(),
60+
);
2261

2362
interface HeaderProps {
2463
location?: Location;
@@ -62,11 +101,34 @@ export const Header: React.FC<HeaderProps> = ({ isNavToggled, toggleMobileNav })
62101

63102
const dispatch = useDispatch();
64103
const [dispatcher, setDispatcher] = useState<Dispatcher | undefined>(undefined);
104+
const [hasLiveOrUpcomingVotes, setHasLiveOrUpcomingVotes] = useState(false);
105+
106+
const { data } = useQuery('proposals', async () => {
107+
const { proposals: treasuryProposals } = await request(GOVERNANCE_THEGRAPH_ENDPOINT, FETCH_PROPOSALS);
108+
return treasuryProposals;
109+
});
65110

66111
useEffect(() => {
67112
setDispatcher(new Dispatcher(dispatch));
68113
}, [dispatch]);
69114

115+
const checkHasLiveOrUpcomingVotes = useCallback((treasuryData) => {
116+
const hasZEIPS = ZEIP_PROPOSALS.filter((zeip) => {
117+
return zeip.voteEndDate.isSameOrAfter(moment());
118+
});
119+
120+
const hasTreasuryProposals = treasuryData.filter((proposal: OnChainProposal) => {
121+
return moment.unix((proposal.voteEpoch.endTimestamp as unknown) as number).isSameOrAfter(moment());
122+
});
123+
setHasLiveOrUpcomingVotes(hasZEIPS.length || hasTreasuryProposals.length);
124+
}, []);
125+
126+
useEffect(() => {
127+
if (data) {
128+
checkHasLiveOrUpcomingVotes(data);
129+
}
130+
}, [data, checkHasLiveOrUpcomingVotes]);
131+
70132
const onUnpin = useCallback(() => {
71133
if (isNavToggled) {
72134
toggleMobileNav();
@@ -115,9 +177,16 @@ export const Header: React.FC<HeaderProps> = ({ isNavToggled, toggleMobileNav })
115177

116178
<MediaQuery minWidth={1200}>
117179
<NavLinks>
118-
{navItems.map((link, index) => (
119-
<NavItem key={`navlink-${index}`} link={link} />
120-
))}
180+
{navItems.map((link, index) => {
181+
return (
182+
<div key={index} style={{ display: 'flex' }}>
183+
<NavItem key={`navlink-${index}`} link={link} />
184+
{link.id === 'governance' && (
185+
<GovernanceActiveIndicator hasProposals={hasLiveOrUpcomingVotes} />
186+
)}
187+
</div>
188+
);
189+
})}
121190
</NavLinks>
122191
{subMenu}
123192
</MediaQuery>
@@ -182,6 +251,11 @@ interface WalletConnectedIndicatorProps {
182251
isConnected: boolean;
183252
isNavToggled: boolean;
184253
}
254+
255+
interface GovernanceActiveIndicatorProps {
256+
hasProposals: boolean;
257+
}
258+
185259
const WalletConnectedIndicator = styled.div<WalletConnectedIndicatorProps>`
186260
width: 12px;
187261
height: 12px;
@@ -196,6 +270,20 @@ const WalletConnectedIndicator = styled.div<WalletConnectedIndicatorProps>`
196270
z-index: ${zIndex.header + 1};
197271
`;
198272

273+
const GovernanceActiveIndicator = styled.div<GovernanceActiveIndicatorProps>`
274+
width: 10px;
275+
height: 10px;
276+
border-radius: 50%;
277+
border: 1px solid #ffffff;
278+
background-color: #e71d36;
279+
transition: opacity 0.25s ease-in;
280+
opacity: ${(props) => (props.hasProposals ? 1 : 0)};
281+
position: relative;
282+
right: 1.8rem;
283+
top: 0.6rem;
284+
z-index: ${zIndex.header + 1};
285+
`;
286+
199287
const DocsLogoWrap = styled.div`
200288
position: relative;
201289
display: flex;

0 commit comments

Comments
 (0)