Refactor newVoteModal (#73)

This commit is contained in:
Szymon Szlachtowicz 2021-09-15 02:16:08 +02:00 committed by GitHub
parent 5acbc88487
commit d4c8a3c35e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 147 additions and 142 deletions

View File

@ -1,7 +1,7 @@
import { Waku } from 'js-waku' import { Waku } from 'js-waku'
import { WakuMessage } from 'js-waku' import { WakuMessage } from 'js-waku'
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
import { Provider } from '@ethersproject/providers' import { Web3Provider } from '@ethersproject/providers'
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { Interface } from '@ethersproject/abi' import { Interface } from '@ethersproject/abi'
import { ERC20 } from '../abi' import { ERC20 } from '../abi'
@ -26,7 +26,7 @@ export class WakuMessaging {
protected appName: string protected appName: string
protected waku: Waku | undefined protected waku: Waku | undefined
protected token: Contract protected token: Contract
protected provider: Provider protected provider: Web3Provider
protected chainId = 0 protected chainId = 0
protected wakuMessages: WakuMessageStores = {} protected wakuMessages: WakuMessageStores = {}
protected observers: { callback: (msg: WakuMessage) => void; topics: string[] }[] = [] protected observers: { callback: (msg: WakuMessage) => void; topics: string[] }[] = []
@ -36,7 +36,7 @@ export class WakuMessaging {
protected constructor( protected constructor(
appName: string, appName: string,
tokenAddress: string, tokenAddress: string,
provider: Provider, provider: Web3Provider,
chainId: number, chainId: number,
multicall: string, multicall: string,
waku?: Waku waku?: Waku

View File

@ -8,7 +8,7 @@ import { TimedPollVoteMsg } from '../models/TimedPollVoteMsg'
import { DetailedTimedPoll } from '../models/DetailedTimedPoll' import { DetailedTimedPoll } from '../models/DetailedTimedPoll'
import { createWaku } from '../utils/createWaku' import { createWaku } from '../utils/createWaku'
import { WakuMessaging } from './WakuMessaging' import { WakuMessaging } from './WakuMessaging'
import { Provider } from '@ethersproject/providers' import { Web3Provider } from '@ethersproject/providers'
export enum MESSEGAGE_SENDING_RESULT { export enum MESSEGAGE_SENDING_RESULT {
ok = 0, ok = 0,
@ -21,7 +21,7 @@ export class WakuPolling extends WakuMessaging {
protected constructor( protected constructor(
appName: string, appName: string,
tokenAddress: string, tokenAddress: string,
provider: Provider, provider: Web3Provider,
chainId: number, chainId: number,
multicall: string, multicall: string,
waku?: Waku waku?: Waku
@ -54,7 +54,7 @@ export class WakuPolling extends WakuMessaging {
public static async create( public static async create(
appName: string, appName: string,
tokenAddress: string, tokenAddress: string,
provider: Provider, provider: Web3Provider,
multicall: string, multicall: string,
waku?: Waku waku?: Waku
) { ) {

View File

@ -1,10 +1,9 @@
import { VotingContract } from '@status-waku-voting/contracts/abi' import { VotingContract } from '@status-waku-voting/contracts/abi'
import { WakuMessaging } from './WakuMessaging' import { WakuMessaging } from './WakuMessaging'
import { Contract, Wallet, BigNumber } from 'ethers' import { Contract, Wallet, BigNumber, ethers } from 'ethers'
import { Waku, WakuMessage } from 'js-waku' import { Waku, WakuMessage } from 'js-waku'
import { Provider } from '@ethersproject/abstract-provider'
import { createWaku } from '../utils/createWaku' import { createWaku } from '../utils/createWaku'
import { JsonRpcSigner } from '@ethersproject/providers' import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { VoteMsg } from '../models/VoteMsg' import { VoteMsg } from '../models/VoteMsg'
import { VotingRoom } from '../types/PollType' import { VotingRoom } from '../types/PollType'
@ -19,7 +18,7 @@ export class WakuVoting extends WakuMessaging {
appName: string, appName: string,
votingContract: Contract, votingContract: Contract,
token: string, token: string,
provider: Provider, provider: Web3Provider,
chainId: number, chainId: number,
multicallAddress: string, multicallAddress: string,
waku?: Waku waku?: Waku
@ -44,7 +43,7 @@ export class WakuVoting extends WakuMessaging {
public static async create( public static async create(
appName: string, appName: string,
contractAddress: string, contractAddress: string,
provider: Provider, provider: Web3Provider,
multicall: string, multicall: string,
waku?: Waku waku?: Waku
) { ) {
@ -54,14 +53,12 @@ export class WakuVoting extends WakuMessaging {
return new WakuVoting(appName, votingContract, tokenAddress, provider, network.chainId, multicall, waku) return new WakuVoting(appName, votingContract, tokenAddress, provider, network.chainId, multicall, waku)
} }
public async createVote( public async createVote(question: string, descripiton: string, tokenAmount: BigNumber) {
signer: JsonRpcSigner | Wallet, if (this.provider) {
question: string, const signer = this.provider.getSigner()
descripiton: string, this.votingContract = await this.votingContract.connect(signer)
tokenAmount: BigNumber await this.votingContract.initializeVotingRoom(question, descripiton, tokenAmount)
) { }
this.votingContract = await this.votingContract.connect(signer)
await this.votingContract.initializeVotingRoom(question, descripiton, tokenAmount)
} }
private lastPolls: VotingRoom[] = [] private lastPolls: VotingRoom[] = []
@ -95,12 +92,8 @@ export class WakuVoting extends WakuMessaging {
return (await this.getVotingRooms())[id] return (await this.getVotingRooms())[id]
} }
public async sendVote( public async sendVote(roomId: number, selectedAnswer: number, tokenAmount: BigNumber) {
signer: JsonRpcSigner | Wallet, const signer = this.provider.getSigner()
roomId: number,
selectedAnswer: number,
tokenAmount: BigNumber
) {
const vote = await VoteMsg._createWithSignFunction( const vote = await VoteMsg._createWithSignFunction(
signer, signer,
roomId, roomId,

View File

@ -1,36 +1,33 @@
import { useEffect, useState, useRef } from 'react' import { useEffect, useState, useRef } from 'react'
import { WakuPolling } from '@status-waku-voting/core' import { WakuPolling } from '@status-waku-voting/core'
import { useEthers, useConfig } from '@usedapp/core' import { Web3Provider } from '@ethersproject/providers'
import { Provider } from '@ethersproject/providers'
export function useWakuPolling(appName: string, tokenAddress: string) { export function useWakuPolling(
appName: string,
tokenAddress: string,
provider: Web3Provider | undefined,
multicallAddress: string | undefined
) {
const [wakuPolling, setWakuPolling] = useState<WakuPolling | undefined>(undefined) const [wakuPolling, setWakuPolling] = useState<WakuPolling | undefined>(undefined)
const queue = useRef(0) const queue = useRef(0)
const queuePos = useRef(0) const queuePos = useRef(0)
const { library, chainId } = useEthers()
const config = useConfig()
useEffect(() => { useEffect(() => {
const createNewWaku = async (queuePosition: number) => { const createNewWaku = async (queuePosition: number) => {
while (queuePosition != queuePos.current) { while (queuePosition != queuePos.current) {
await new Promise((r) => setTimeout(r, 1000)) await new Promise((r) => setTimeout(r, 1000))
} }
if (library && chainId && config.multicallAddresses && config.multicallAddresses[chainId]) { wakuPolling?.cleanUp()
wakuPolling?.cleanUp() if (provider && multicallAddress) {
const newWakuPoll = await WakuPolling.create( const newWakuPoll = await WakuPolling.create(appName, tokenAddress, provider, multicallAddress)
appName,
tokenAddress,
library as unknown as Provider,
config.multicallAddresses[chainId]
)
setWakuPolling(newWakuPoll) setWakuPolling(newWakuPoll)
queuePos.current++ queuePos.current++
} }
} }
if (library && chainId) { if (provider) {
createNewWaku(queue.current++) createNewWaku(queue.current++)
} }
return () => wakuPolling?.cleanUp() return () => wakuPolling?.cleanUp()
}, [library]) }, [provider, multicallAddress])
return wakuPolling return wakuPolling
} }

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { useEthers } from '@usedapp/core' import { useConfig, useEthers } from '@usedapp/core'
import styled from 'styled-components' import styled from 'styled-components'
import { PollList, PollCreation } from '@status-waku-voting/polling-components' import { PollList, PollCreation } from '@status-waku-voting/polling-components'
@ -15,10 +15,16 @@ type WakuPollingProps = {
} }
export function WakuPolling({ appName, signer, theme }: WakuPollingProps) { export function WakuPolling({ appName, signer, theme }: WakuPollingProps) {
const { activateBrowserWallet, account } = useEthers() const { activateBrowserWallet, account, library, chainId } = useEthers()
const config = useConfig()
const [showPollCreation, setShowPollCreation] = useState(false) const [showPollCreation, setShowPollCreation] = useState(false)
const [selectConnect, setSelectConnect] = useState(false) const [selectConnect, setSelectConnect] = useState(false)
const wakuPolling = useWakuPolling(appName, '0x80ee48b5ba5c3ea556b7ff6d850d2fb2c4bc7412') const wakuPolling = useWakuPolling(
appName,
'0x80ee48b5ba5c3ea556b7ff6d850d2fb2c4bc7412',
library,
config?.multicallAddresses?.[chainId ?? 1337]
)
return ( return (
<Wrapper> <Wrapper>
{showPollCreation && signer && ( {showPollCreation && signer && (

View File

@ -3,10 +3,9 @@ import styled from 'styled-components'
import { useEthers } from '@usedapp/core' import { useEthers } from '@usedapp/core'
import { Modal, Networks, CreateButton } from '@status-waku-voting/react-components' import { Modal, Networks, CreateButton } from '@status-waku-voting/react-components'
import { Theme } from '@status-waku-voting/react-components/dist/esm/src/style/themes' import { Theme } from '@status-waku-voting/react-components/dist/esm/src/style/themes'
import { ProposeModal } from './ProposeModal'
import { ProposeVoteModal } from './ProposeVoteModal'
import { WakuVoting } from '@status-waku-voting/core' import { WakuVoting } from '@status-waku-voting/core'
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
import { NewVoteModal } from './newVoteModal/NewVoteModal'
type ProposalHeaderProps = { type ProposalHeaderProps = {
theme: Theme theme: Theme
@ -17,55 +16,29 @@ type ProposalHeaderProps = {
export function ProposalHeader({ theme, wakuVoting, availableAmount }: ProposalHeaderProps) { export function ProposalHeader({ theme, wakuVoting, availableAmount }: ProposalHeaderProps) {
const { activateBrowserWallet, account, library } = useEthers() const { activateBrowserWallet, account, library } = useEthers()
const [selectConnect, setSelectConnect] = useState(false) const [selectConnect, setSelectConnect] = useState(false)
const [showProposeModal, setShowProposeModal] = useState(false) const [showModal, setShowModal] = useState(false)
const [showProposeVoteModal, setShowProposeVoteModal] = useState(false)
const [title, setTitle] = useState('')
const [text, setText] = useState('')
const setNext = (val: boolean) => {
setShowProposeVoteModal(val)
setShowProposeModal(false)
}
return ( return (
<Wrapper> <Wrapper>
<NewVoteModal
theme={theme}
availableAmount={availableAmount}
setShowModal={setShowModal}
showModal={showModal}
wakuVoting={wakuVoting}
/>
<Header> <Header>
<Heading>Your voice has real power</Heading> <Heading>Your voice has real power</Heading>
<HeaderText> <HeaderText>
Take part in a decentralised governance by voting on proposals provided by community or creating your own. Take part in a decentralised governance by voting on proposals provided by community or creating your own.
</HeaderText> </HeaderText>
</Header> </Header>
{showProposeModal && (
<Modal heading="Create proposal" theme={theme} setShowModal={setShowProposeModal}>
<ProposeModal
title={title}
text={text}
setText={setText}
setTitle={setTitle}
availableAmount={availableAmount}
setShowProposeVoteModal={setNext}
/>
</Modal>
)}
{showProposeVoteModal && (
<Modal heading="Create proposal" theme={theme} setShowModal={setShowProposeVoteModal}>
<ProposeVoteModal
wakuVoting={wakuVoting}
title={title}
text={text}
availableAmount={availableAmount}
setShowModal={setShowProposeVoteModal}
setText={setText}
setTitle={setTitle}
/>
</Modal>
)}
{account ? ( {account ? (
<CreateButton <CreateButton
theme={theme} theme={theme}
onClick={() => { onClick={() => {
setShowProposeModal(true) setShowModal(true)
}} }}
> >
Create proposal Create proposal

View File

@ -3,9 +3,8 @@ import { useHistory } from 'react-router'
import { useEthers } from '@usedapp/core' import { useEthers } from '@usedapp/core'
import styled from 'styled-components' import styled from 'styled-components'
import { CreateButton, Modal, Networks, Theme, useMobileVersion } from '@status-waku-voting/react-components' import { CreateButton, Modal, Networks, Theme, useMobileVersion } from '@status-waku-voting/react-components'
import { ProposeModal } from './ProposeModal'
import { ProposeVoteModal } from './ProposeVoteModal'
import { WakuVoting } from '@status-waku-voting/core' import { WakuVoting } from '@status-waku-voting/core'
import { NewVoteModal } from './newVoteModal/NewVoteModal'
type VotingEmptyProps = { type VotingEmptyProps = {
theme: Theme theme: Theme
@ -16,22 +15,21 @@ type VotingEmptyProps = {
export function VotingEmpty({ wakuVoting, theme, availableAmount }: VotingEmptyProps) { export function VotingEmpty({ wakuVoting, theme, availableAmount }: VotingEmptyProps) {
const { account, activateBrowserWallet } = useEthers() const { account, activateBrowserWallet } = useEthers()
const [selectConnect, setSelectConnect] = useState(false) const [selectConnect, setSelectConnect] = useState(false)
const [showProposeModal, setShowProposeModal] = useState(false) const [showModal, setShowModal] = useState(false)
const [showProposeVoteModal, setShowProposeVoteModal] = useState(false)
const [title, setTitle] = useState('')
const [text, setText] = useState('')
const history = useHistory() const history = useHistory()
const ref = useRef<HTMLHeadingElement>(null) const ref = useRef<HTMLHeadingElement>(null)
const mobileVersion = useMobileVersion(ref, 600) const mobileVersion = useMobileVersion(ref, 600)
const setNext = (val: boolean) => {
setShowProposeVoteModal(val)
setShowProposeModal(false)
}
return ( return (
<VotingEmptyWrap ref={ref}> <VotingEmptyWrap ref={ref}>
<NewVoteModal
theme={theme}
availableAmount={availableAmount}
setShowModal={setShowModal}
showModal={showModal}
wakuVoting={wakuVoting}
/>
<EmptyWrap> <EmptyWrap>
<EmptyHeading>There are no proposals at the moment!</EmptyHeading> <EmptyHeading>There are no proposals at the moment!</EmptyHeading>
<EmptyText> <EmptyText>
@ -39,37 +37,11 @@ export function VotingEmpty({ wakuVoting, theme, availableAmount }: VotingEmptyP
likes it! likes it!
</EmptyText> </EmptyText>
</EmptyWrap> </EmptyWrap>
{showProposeModal && (
<Modal heading="Create proposal" theme={theme} setShowModal={setShowProposeModal}>
<ProposeModal
title={title}
text={text}
setText={setText}
setTitle={setTitle}
availableAmount={availableAmount}
setShowProposeVoteModal={setNext}
/>
</Modal>
)}
{showProposeVoteModal && (
<Modal heading="Create proposal" theme={theme} setShowModal={setShowProposeVoteModal}>
<ProposeVoteModal
wakuVoting={wakuVoting}
title={title}
text={text}
availableAmount={availableAmount}
setShowModal={setShowProposeVoteModal}
setText={setText}
setTitle={setTitle}
/>
</Modal>
)}
{account ? ( {account ? (
<EmptyCreateButton <EmptyCreateButton
theme={theme} theme={theme}
onClick={() => { onClick={() => {
mobileVersion ? history.push(`/creation`) : setShowProposeModal(true) mobileVersion ? history.push(`/creation`) : setShowModal(true)
}} }}
> >
Create proposal Create proposal

View File

@ -4,7 +4,14 @@ import styled from 'styled-components'
import { useHistory } from 'react-router' import { useHistory } from 'react-router'
import { ProposingBtn } from '../Buttons' import { ProposingBtn } from '../Buttons'
import { CardHeading, CardText } from '../ProposalInfo' import { CardHeading, CardText } from '../ProposalInfo'
import { InfoText, Label, ProposingData, ProposingInfo, ProposingInput, ProposingTextInput } from '../ProposeModal' import {
InfoText,
Label,
ProposingData,
ProposingInfo,
ProposingInput,
ProposingTextInput,
} from '../newVoteModal/ProposeModal'
import { VotePropose } from '../VotePropose' import { VotePropose } from '../VotePropose'
interface ProposeVoteModalProps { interface ProposeVoteModalProps {

View File

@ -0,0 +1,49 @@
import { WakuVoting } from '@status-waku-voting/core'
import { Modal, Theme } from '@status-waku-voting/react-components'
import React, { useState } from 'react'
import { ProposeModal } from './ProposeModal'
import { ProposeVoteModal } from './ProposeVoteModal'
type NewVoteModalProps = {
theme: Theme
showModal: boolean
setShowModal: (val: boolean) => void
availableAmount: number
wakuVoting: WakuVoting
}
export function NewVoteModal({ theme, showModal, setShowModal, availableAmount, wakuVoting }: NewVoteModalProps) {
const [screen, setScreen] = useState(1)
const [title, setTitle] = useState('')
const [text, setText] = useState('')
if (!showModal) {
return null
}
return (
<Modal heading="Create proposal" theme={theme} setShowModal={setShowModal}>
{screen === 1 && (
<ProposeModal
title={title}
text={text}
setText={setText}
setTitle={setTitle}
availableAmount={availableAmount}
setShowProposeVoteModal={() => setScreen(2)}
/>
)}
{screen === 2 && (
<ProposeVoteModal
wakuVoting={wakuVoting}
title={title}
text={text}
availableAmount={availableAmount}
setShowModal={setShowModal}
setText={setText}
setTitle={setTitle}
/>
)}
</Modal>
)
}

View File

@ -1,7 +1,7 @@
import React from 'react' import React, { useMemo } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { ProposingBtn } from './Buttons' import { ProposingBtn } from '../Buttons'
import { TextArea } from './Input' import { TextArea } from '../Input'
import { blueTheme } from '@status-waku-voting/react-components/dist/esm/src/style/themes' import { blueTheme } from '@status-waku-voting/react-components/dist/esm/src/style/themes'
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
@ -22,7 +22,7 @@ export function ProposeModal({
setTitle, setTitle,
setText, setText,
}: ProposeModalProps) { }: ProposeModalProps) {
const insufficientFunds = availableAmount < 10000 const insufficientFunds = useMemo(() => availableAmount < 10000, [availableAmount])
return ( return (
<ProposingData> <ProposingData>

View File

@ -2,10 +2,10 @@ import { WakuVoting } from '@status-waku-voting/core'
import { useEthers } from '@usedapp/core' import { useEthers } from '@usedapp/core'
import React, { useState } from 'react' import React, { useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { ProposingBtn } from './Buttons' import { ProposingBtn } from '../Buttons'
import { CardHeading, CardText } from './ProposalInfo' import { CardHeading, CardText } from '../ProposalInfo'
import { ProposingData } from './ProposeModal' import { ProposingData } from './ProposeModal'
import { VotePropose } from './VotePropose' import { VotePropose } from '../VotePropose'
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
interface ProposeVoteModalProps { interface ProposeVoteModalProps {
@ -44,7 +44,7 @@ export function ProposeVoteModal({
<ProposingBtn <ProposingBtn
onClick={() => { onClick={() => {
if (library) wakuVoting.createVote(library.getSigner(), title, text, BigNumber.from(proposingAmount)) wakuVoting.createVote(title, text, BigNumber.from(proposingAmount))
setShowModal(false) setShowModal(false)
setTitle('') setTitle('')
setText('') setText('')

View File

@ -1,26 +1,27 @@
import { WakuVoting } from '@status-waku-voting/core' import { WakuVoting } from '@status-waku-voting/core'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { providers } from 'ethers' import { Web3Provider } from '@ethersproject/providers'
export function useWakuProposal() { export function useWakuProposal(
appName: string,
contractAddress: string,
provider: Web3Provider | undefined,
multicallAddress: string | undefined
) {
const [waku, setWaku] = useState<WakuVoting | undefined>(undefined) const [waku, setWaku] = useState<WakuVoting | undefined>(undefined)
useEffect(() => { useEffect(() => {
;(window as any).ethereum.on('chainChanged', () => window.location.reload()) ;(window as any).ethereum.on('chainChanged', () => window.location.reload())
const createWaku = async () => { const createWaku = async () => {
const provider = new providers.Web3Provider((window as any).ethereum) if (provider && multicallAddress) {
const wak = await WakuVoting.create( const wak = await WakuVoting.create(appName, contractAddress, provider, multicallAddress)
'test', setWaku(wak)
'0x5795A64A70cde4073DBa9EEBC5C6b675B15C815a', }
provider,
'0x53c43764255c17bd724f74c4ef150724ac50a3ed'
)
setWaku(wak)
} }
createWaku() createWaku()
return () => (window as any).ethereum.removeListener('chainChanged', () => window.location.reload()) return () => (window as any).ethereum.removeListener('chainChanged', () => window.location.reload())
}, []) }, [provider, multicallAddress, contractAddress])
return waku return waku
} }

View File

@ -5,7 +5,7 @@ import { TopBar, GlobalStyle, useMobileVersion } from '@status-waku-voting/react
import votingIcon from './assets/images/voting.svg' import votingIcon from './assets/images/voting.svg'
import styled from 'styled-components' import styled from 'styled-components'
import { blueTheme } from '@status-waku-voting/react-components/dist/esm/src/style/themes' import { blueTheme } from '@status-waku-voting/react-components/dist/esm/src/style/themes'
import { DAppProvider, ChainId, useEthers } from '@usedapp/core' import { DAppProvider, ChainId, useEthers, useConfig } from '@usedapp/core'
import { DEFAULT_CONFIG } from '@usedapp/core/dist/cjs/src/model/config/default' import { DEFAULT_CONFIG } from '@usedapp/core/dist/cjs/src/model/config/default'
const config = { const config = {
@ -14,7 +14,8 @@ const config = {
[ChainId.Ropsten]: 'https://ropsten.infura.io/v3/b4451d780cc64a078ccf2181e872cfcf', [ChainId.Ropsten]: 'https://ropsten.infura.io/v3/b4451d780cc64a078ccf2181e872cfcf',
}, },
multicallAddresses: { multicallAddresses: {
...DEFAULT_CONFIG.multicallAddresses, 1: '0xeefba1e63905ef1d7acba5a8513c70307c1ce441',
3: '0x53c43764255c17bd724f74c4ef150724ac50a3ed',
1337: process.env.GANACHE_MULTICALL_CONTRACT ?? '0x0000000000000000000000000000000000000000', 1337: process.env.GANACHE_MULTICALL_CONTRACT ?? '0x0000000000000000000000000000000000000000',
}, },
supportedChains: [...DEFAULT_CONFIG.supportedChains, 1337], supportedChains: [...DEFAULT_CONFIG.supportedChains, 1337],
@ -25,8 +26,14 @@ const config = {
} }
function Proposals() { function Proposals() {
const { account, activateBrowserWallet, deactivate } = useEthers() const { account, activateBrowserWallet, deactivate, library, chainId } = useEthers()
const waku = useWakuProposal() const config = useConfig()
const waku = useWakuProposal(
'test',
'0x5795A64A70cde4073DBa9EEBC5C6b675B15C815a',
library,
config?.multicallAddresses?.[chainId ?? 1337]
)
const ref = useRef<HTMLHeadingElement>(null) const ref = useRef<HTMLHeadingElement>(null)
const mobileVersion = useMobileVersion(ref, 600) const mobileVersion = useMobileVersion(ref, 600)