1+ import { gql , request } from 'graphql-request' ;
2+ import moment from 'moment' ;
13import React , { useCallback , useEffect , useState } from 'react' ;
24import Headroom from 'react-headroom' ;
5+ import { useQuery } from 'react-query' ;
36import { useDispatch , useSelector } from 'react-redux' ;
47import MediaQuery from 'react-responsive' ;
58import styled , { css } from 'styled-components' ;
@@ -10,15 +13,51 @@ import { Hamburger } from 'ts/components/hamburger';
1013import { Logo } from 'ts/components/logo' ;
1114import { FlexWrap } from 'ts/components/newLayout' ;
1215import { SubMenu } from 'ts/components/staking/header/sub_menu' ;
16+ import { Proposal , proposals as prodProposals , stagingProposals } from 'ts/pages/governance/data' ;
1317import { Dispatcher } from 'ts/redux/dispatcher' ;
1418import { State } from 'ts/redux/reducer' ;
1519import { ThemeValuesInterface } from 'ts/style/theme' ;
1620import { zIndex } from 'ts/style/z_index' ;
17- import { AccountState , WebsitePaths } from 'ts/types' ;
21+ import { AccountState , OnChainProposal , WebsitePaths } from 'ts/types' ;
1822
1923import { useWeb3React } from '@web3-react/core' ;
2024import { useWallet } from 'ts/hooks/use_wallet' ;
2125import { 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
2362interface 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+
185259const 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+
199287const DocsLogoWrap = styled . div `
200288 position: relative;
201289 display: flex;
0 commit comments