import * as qs from "querystring" ; import { BigNumber , EthAddress , CharterData } from "@joincivil/typescript-types" ; import { ButtonTheme , colors , StepProcessTopNavNoButtons , StepNoButtons , WalletOnboardingV2 , AuthApplicationEnum , } from "@joincivil/components" ; import { Civil , NewsroomInstance } from "@joincivil/core" ; import * as React from "react" ; import { connect , DispatchProp } from "react-redux" ; import { debounce } from "lodash" ; import styled , { ThemeProvider } from "styled-components" ; import { addGetCmsUserDataForAddress , addPersistCharter , getEditors , getIsOwner , getIsEditor , getNewsroom , updateCharter , addConstitutionHash , addConstitutionUri , fetchConstitution , navigateStep , reachedNewStep , analyticsEvent , } from "./actionCreators" ; import { AuthWrapper } from "./AuthWrapper" ; import { DataWrapper } from "./DataWrapper" ; import { NewsroomProfile } from "./NewsroomProfile" ; import { SmartContract } from "./SmartContract" ; import { PurchaseTokens } from "./PurchaseTokens/index" ; import { RepublishCharterNotice } from "./RepublishCharterNotice" ; import { ApplyToTCRStep } from "./ApplyToTCR/index" ; import { StateWithNewsroom } from "./reducers" ; import { CmsUserData } from "./types" ; import { Wrapper , DEFAULT_THEME } from "./styledComponents" ; import { MutationFunc } from "react-apollo" ; enum SECTION { PROFILE , CONTRACT , TOKENS , APPLY } export enum STEP { PROFILE_BIO , PROFILE_ROSTER , PROFILE_CHARTER , PROFILE_SIGN , PROFILE_SO_FAR , PROFILE_GRANT , CONTRACT_GET_STARTED , CONTRACT_UNDERSTANDING_ETH , CONTRACT_CREATE , CONTRACT_ASSIGN , TOKENS , APPLY , APPLIED } const STEP_TO_SECTION = { [ STEP . PROFILE_BIO ] : SECTION . PROFILE , [ STEP . PROFILE_ROSTER ] : SECTION . PROFILE , [ STEP . PROFILE_CHARTER ] : SECTION . PROFILE , [ STEP . PROFILE_SIGN ] : SECTION . PROFILE , [ STEP . PROFILE_SO_FAR ] : SECTION . PROFILE , [ STEP . PROFILE_GRANT ] : SECTION . PROFILE , [ STEP . CONTRACT_GET_STARTED ] : SECTION . CONTRACT , [ STEP . CONTRACT_UNDERSTANDING_ETH ] : SECTION . CONTRACT , [ STEP . CONTRACT_CREATE ] : SECTION . CONTRACT , [ STEP . CONTRACT_ASSIGN ] : SECTION . CONTRACT , [ STEP . TOKENS ] : SECTION . TOKENS , [ STEP . APPLY ] : SECTION . APPLY , } ; const SECTION_STARTS = { [ SECTION . PROFILE ] : 0 , [ SECTION . CONTRACT ] : 6 , [ SECTION . TOKENS ] : 10 , [ SECTION . APPLY ] : 11 , } ; export interface NewsroomComponentState { currentStep : STEP ; furthestStep : STEP ; subscription ? : any ; charterPartOneComplete ? : boolean ; charterPartTwoComplete ? : boolean ; hasPublishedCharter ? : boolean ; } export interface IpfsObject { add ( content , options ? ) : Promise < [ { path : string ; hash : string ; size : number ; } ] > ; } export interface NewsroomExternalProps { newsroomAddress ? : string ; disabled ? : boolean ; account ? : string ; currentNetwork ? : string ; requiredNetwork ? : string ; requiredNetworkNiceName ? : string ; civil ? : Civil ; ipfs ? : IpfsObject ; theme ? : ButtonTheme ; showWelcome ? : boolean ; helpUrl ? : string ; helpUrlBase ? : string ; logoUrl ? : string ; forceStep ? : STEP ; renderUserSearch ? ( onSetAddress ) : JSX . Element ; getCmsUserDataForAddress ? ( address ) : Promise < CmsUserData > ; } export interface NewsroomReduxProps extends NewsroomExternalProps { charter : Partial < CharterData > ; name ? : string ; newsroom ? : NewsroomInstance ; userIsOwner ? : boolean ; userIsEditor ? : boolean ; userNotOnContract ? : boolean ; charterUri ? : string ; waitingOnGrant ? : boolean ; completedGrantFlow ? : boolean ; } export interface NewsroomGqlProps { newsroomAddress ? : string ; grantRequested ? : boolean ; grantApproved ? : boolean ; tcrApplyTx ? : string ; newsroomDeployTx ? : EthAddress ; profileWalletAddress ? : EthAddress ; persistedCharter ? : Partial < CharterData > ; savedStep : STEP ; furthestStep : STEP ; saveAddress : MutationFunc ; saveSteps : MutationFunc ; minDeposit : BigNumber ; applyStageLen : BigNumber ; persistCharter ( charter ) : Promise < any > ; } export type NewsroomProps = NewsroomGqlProps & NewsroomReduxProps & DispatchProp < any > ; export const NoteSection = styled . p `
  color: ${ ( props ) => ( props . disabled ? "#dcdcdc" : colors . accent . CIVIL_GRAY_3 ) };
` ; const ErrorP = styled . p `
  color: ${ colors . accent . CIVIL_RED };
` ; class NewsroomComponent extends React . Component < NewsroomProps , NewsroomComponentState > { public static defaultProps = { theme : DEFAULT_THEME , } ; public static getDerivedStateFromProps ( props , state ) { if ( state . currentStep === STEP . PROFILE_GRANT && props . completedGrantFlow ) { props . dispatch ! ( analyticsEvent ( { action : "Auto advancing past grant step" , label : props . charter && props . charter . name , value : props . grantRequested ? 1 : 0 , } ) ) ; return { ... state , currentStep : state . currentStep + 1 , } ; } return null ; } private debouncedPersistCharter = debounce ( this . props . persistCharter , 1000 , { maxWait : 5000 } ) ; private checkCharterCompletion = debounce ( ( ) => { const charterPartOneComplete = ! ! ( this . props . charter && this . props . charter . logoUrl && this . props . charter . newsroomUrl && this . props . charter . tagline && this . props . charter . roster && this . props . charter . roster . length ) ; let charterPartTwoComplete = false ; const mission = this . props . charter . mission ; if ( mission ) { charterPartTwoComplete = ! ! ( mission . purpose && mission . structure && mission . revenue && mission . encumbrances && mission . miscellaneous ) ; } this . setState ( { charterPartOneComplete , charterPartTwoComplete , } ) ; } , 1000 , { maxWait : 2000 } ) ; constructor ( props ) { super ( props ) ; this . state = { currentStep : this . determineInitialStep ( ) , furthestStep : props . furthestStep , } ; } public async componentDidMount ( ) { if ( this . props . getCmsUserDataForAddress ) { this . props . dispatch ! ( addGetCmsUserDataForAddress ( this . props . getCmsUserDataForAddress ) ) ; } this . initCharter ( ) ; this . props . dispatch ! ( addPersistCharter ( this . debouncedPersistCharter ) ) ; if ( this . props . civil ) { if ( this . props . newsroomAddress ) { await this . hydrateNewsroom ( this . props . newsroomAddress ) ; } if ( ! this . props . newsroomAddress && this . props . newsroomDeployTx ) { const newsroom = await this . props . civil . newsroomFromFactoryTxHashUntrusted ( this . props . newsroomDeployTx ) ; await this . props . saveAddress ( { variables : { input : newsroom . address } } ) ; } if ( this . props . tcrApplyTx && this . props . savedStep === STEP . APPLY ) { await this . props . civil . awaitReceipt ( this . props . tcrApplyTx ) ; this . navigate ( 1 ) ; } const tcr = await this . props . civil . tcrSingletonTrusted ( ) ; const government = await tcr . getGovernment ( ) ; const hash = await government . getConstitutionHash ( ) ; const uri = await government . getConstitutionURI ( ) ; this . props . dispatch ! ( addConstitutionHash ( hash ) ) ; this . props . dispatch ! ( addConstitutionUri ( uri ) ) ; this . props . dispatch ! ( fetchConstitution ( uri ) ) ; } this . saveStep ( this . props . savedStep , true ) ; } public async componentDidUpdate ( prevProps ) { if ( this . props . newsroomAddress && ! prevProps . newsroomAddress ) { await this . hydrateNewsroom ( this . props . newsroomAddress ) ; } if ( prevProps . newsroom && this . props . account !== prevProps . account ) { this . setRoles ( this . props . newsroomAddress || prevProps . newsroomAddress ! ) ; } } public renderManager ( ) { return ( < > { this . props . userNotOnContract && ( < ErrorP > Your wallet address is not listed on your newsroom contract , so you are unable to make changes to it . Please contact a newsroom officer in order to be added . < /ErrorP>)} < StepProcessTopNavNoButtons activeIndex = { STEP_TO_SECTION [ this . state . currentStep ] } onActiveTabChange = { this . navigateToSection } contentPrepend = { this . renderRepublishCharter ( ) } fullyControlledIndex = { true } > { this . renderSteps ( ) } < /StepProcessTopNavNoButtons> < />); } public renderSteps ( ) { return [ < StepNoButtons title = { "Registry Profile" } complete = { this . state . charterPartOneComplete } key = "createCharterPartOne" > < NewsroomProfile profileWalletAddress = { this . props . profileWalletAddress } currentStep = { this . state . currentStep - SECTION_STARTS [ SECTION . PROFILE ] } furthestStep = { this . props . furthestStep } navigate = { this . navigate } grantRequested = { this . props . grantRequested } waitingOnGrant = { this . props . waitingOnGrant } completedGrantFlow = { this . props . completedGrantFlow } charter = { this . props . charter } updateCharter = { this . updateCharter } / > < /StepNoButtons>, < StepNoButtons title = { "Smart Contract" } disabled = { this . getDisabled ( SECTION . CONTRACT ) ( ) } key = "smartcontract" > < SmartContract currentStep = { this . state . currentStep - SECTION_STARTS [ SECTION . CONTRACT ] } navigate = { this . navigate } profileWalletAddress = { this . props . profileWalletAddress } charter = { this . props . charter } userIsOwner = { this . props . userIsOwner } newsroomAddress = { this . props . newsroomAddress } newsroomDeployTxHash = { this . props . newsroomDeployTx } updateCharter = { this . updateCharter } newsroom = { this . props . newsroom } / > < /StepNoButtons>, < StepNoButtons title = { "Civil Tokens" } disabled = { this . getDisabled ( SECTION . TOKENS ) ( ) } key = "ct" > < PurchaseTokens navigate = { this . navigate } grantApproved = { this . props . grantApproved } minDeposit = { this . props . minDeposit } / > < /StepNoButtons>, < StepNoButtons title = { "Apply to Registry" } disabled = { this . getDisabled ( SECTION . APPLY ) ( ) } key = "atr" > < ApplyToTCRStep navigate = { this . navigate } newsroom = { this . props . newsroom ! } address = { this . props . newsroomAddress } civil = { this . props . civil } minDeposit = { this . props . minDeposit } applyStageLen = { this . props . applyStageLen } / > < /StepNoButtons>, ] ; } public render ( ) { return ( < ThemeProvider theme = { this . props . theme } > < Wrapper > < WalletOnboardingV2 civil = { this . props . civil } wrongNetwork = { ! ! this . props . requiredNetwork && this . props . currentNetwork !== this . props . requiredNetwork } requiredNetworkNiceName = { this . props . requiredNetworkNiceName || this . props . requiredNetwork } metamaskWalletAddress = { this . props . account } profileWalletAddress = { this . props . profileWalletAddress } authApplicationType = { AuthApplicationEnum . NEWSROOM } > { this . renderManager ( ) } < /WalletOnboardingV2> < /Wrapper> < /ThemeProvider>); } private determineInitialStep ( ) { let currentStep = this . props . savedStep ; if ( currentStep === STEP . APPLIED ) { currentStep -- ; } if ( qs . parse ( document . location . search . substr ( 1 ) ) . purchased ) { currentStep = STEP . TOKENS ; } if ( this . props . forceStep ) { currentStep = this . props . forceStep ; } if ( this . props . waitingOnGrant ) { let grantInfo ; if ( this . props . grantApproved === true ) { grantInfo = 1 ; } else if ( this . props . grantApproved === false ) { grantInfo = 2 ; } else { grantInfo = 0 ; } this . props . dispatch ! ( analyticsEvent ( { action : "Pushed back to grant step" , label : this . props . charter && this . props . charter . name , value : grantInfo , } ) ) ; currentStep = STEP . PROFILE_GRANT ; } currentStep = this . backtrackSteps ( currentStep ) ; return currentStep ; } private backtrackSteps ( step ) { const section = STEP_TO_SECTION [ step ] ; if ( this . getDisabled ( section ) ( ) ) { return this . backtrackSteps ( step - 1 ) ; } return step ; } private renderRepublishCharter ( ) { if ( ! this . props . newsroomAddress || STEP_TO_SECTION [ this . state . currentStep ] !== SECTION . PROFILE ) { return ; } return ( < RepublishCharterNotice civil = { this . props . civil ! } charter = { this . props . charter } newsroom = { this . props . newsroom ! } / > ) ; } private getDisabled ( section ) { const functions = { [ SECTION . PROFILE ] : ( ) => { return false ; } , [ SECTION . CONTRACT ] : ( ) => { return ! this . props . completedGrantFlow ; } , [ SECTION . TOKENS ] : ( ) => { return ! this . props . newsroomAddress ; } , [ SECTION . APPLY ] : ( ) => { return ! this . props . newsroomAddress ; } , } ; return functions [ section ] ; } private navigateToSection = ( newSection ) : void => { if ( newSection === STEP_TO_SECTION [ this . state . currentStep ] ) { return ; } let newStep = SECTION_STARTS [ newSection ] ; if ( newSection === SECTION . PROFILE ) { if ( this . props . waitingOnGrant ) { newStep = STEP . PROFILE_GRANT ; } else { newStep = STEP . PROFILE_SO_FAR ; } } newStep = Math . min ( this . state . furthestStep , newStep ) ; document . documentElement . scrollTop = document . body . scrollTop = 0 ; this . saveStep ( newStep ) ; this . setState ( { currentStep : newStep } ) ; } ; private navigate = ( go ) : void => { let newStep = this . state . currentStep + go ; this . saveStep ( newStep ) ; if ( newStep === STEP . APPLIED ) { return ; } else if ( newStep === STEP . PROFILE_GRANT && this . props . completedGrantFlow ) { newStep += go ; } document . documentElement . scrollTop = document . body . scrollTop = 0 ; this . setState ( { currentStep : newStep } ) ; } ; private saveStep ( step , doNotTrack ? ) { if ( ! doNotTrack ) { this . props . dispatch ! ( navigateStep ( step ) ) ; if ( step > this . state . furthestStep ) { this . props . dispatch ! ( reachedNewStep ( step ) ) ; } } const furthestStep = Math . max ( step , this . state . furthestStep ) ; this . setState ( { furthestStep } ) ; this . props . saveSteps ( { variables : { input : { step , furthestStep , lastSeen : Math . floor ( Date . now ( ) / 1000 ) , } , } , } ) . catch ( err => { } ) ; } private initCharter ( ) { this . updateCharter ( this . props . persistedCharter || { } , true ) ; } private hydrateNewsroom = async ( address ) : Promise < void > => { await this . props . dispatch ! ( getNewsroom ( address , this . props . civil ! , this . props . persistedCharter || { } ) ) ; this . props . dispatch ! ( getEditors ( address , this . props . civil ! ) ) ; this . setRoles ( address ) ; } ; private setRoles = ( address ) : void => { this . props . dispatch ! ( getIsOwner ( address , this . props . civil ! ) ) ; this . props . dispatch ! ( getIsEditor ( address , this . props . civil ! ) ) ; } ; private updateCharter = ( charter , dontPersist ? ) : void => { this . props . dispatch ! ( updateCharter ( this . props . newsroomAddress || "" , charter , dontPersist ) ) ; this . checkCharterCompletion ( ) ; } ; } const mapStateToProps = ( state , ownProps ) : NewsroomReduxProps => { const { newsroomAddress } = ownProps ; const newsroom = state . newsrooms . get ( newsroomAddress || "" ) || { wrapper : { data : { } } } ; const waitingOnGrant = ! ! ownProps . grantRequested && typeof ownProps . grantApproved !== "boolean" ; const completedGrantFlow = typeof ownProps . grantApproved === "boolean" ; return { ... ownProps , waitingOnGrant , completedGrantFlow , newsroom : newsroom . newsroom , charter : newsroom . charter || { } , } ; } ; const NewsroomRedux = connect ( mapStateToProps ) ( NewsroomComponent ) ; export const Newsroom = props => { return ( < AuthWrapper > < DataWrapper > { ( gqlProps ) => { return < NewsroomRedux { ... props } { ... gqlProps } />; } } < /DataWrapper> < /AuthWrapper>); } ;