Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created @vocdoni/react-providers #81

Merged
merged 3 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ vite.config.ts.timestamp-*.mjs
.vscode
.yarn
.yarnrc*

package.json.backup
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ Included packages
For now, there's only a single package published, but we may split it in the
future, or create new ones for other frameworks:

- [`@vocdoni/chakra-components`] React components and hooks built with chakra
for easily integrating Vocdoni services.
- [`@vocdoni/chakra-components`] React components built with chakra for easily
integrating Vocdoni services.
- [`@vocdoni/rainbowkit-wallets`] A set of wallets for rainbowkit, reducing the
technical gap for users, not requiring them to have a Web3 wallet.
- [`@vocdoni/react-providers`] React providers and hooks, where most of the magic
of react apps happens (and for other packages like [`@vocdoni/chakra-components`])

Development
-----------
Expand Down Expand Up @@ -78,3 +80,4 @@ with them.
[`templates`]: ./templates
[`@vocdoni/chakra-components`]: ./packages/chakra-components/README.md
[`@vocdoni/rainbowkit-wallets`]: ./packages/rainbowkit-wallets/README.md
[`@vocdoni/react-providers`]: ./packages/react-providers/README.md
15 changes: 15 additions & 0 deletions clean-package.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"replace": {
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./package.json": "./package.json"
}
}
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@
"lint:fix": "prettier -c . --write",
"lint": "prettier -c ."
},
"dependencies": {
"tsup": "^7.2.0"
},
"packageManager": "[email protected]"
}
4 changes: 4 additions & 0 deletions packages/chakra-components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const App = () => {
}
~~~

> Beware that you'll also see a `ClientProvider` also from
> `@vocdoni/react-providers`. You should always be using the one included with
> chakra components in order to have all the features they provide.

Note `env` can be any of the [SDK available environments][sdk environments],
either in string format, or using the SDK `EnvOptions` enum.

Expand Down
58 changes: 18 additions & 40 deletions packages/chakra-components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vocdoni/chakra-components",
"version": "0.4.1",
"version": "0.5.0",
"license": "GPL-3.0-or-later",
"homepage": "https://github.com/vocdoni/ui-components/tree/main/packages/chakra-components#readme",
"bugs": "https://github.com/vocdoni/ui-components/issues",
Expand All @@ -17,30 +17,13 @@
"dist"
],
"scripts": {
"dev": "NODE_ENV=development yarn build --watch",
"lint": "tsc",
"build": "vite build",
"size": "size-limit",
"preview": "vite preview",
"prepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies\""
},
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"exports": {
"import": "./dist/index.es.js",
"require": "./dist/index.cjs.js"
"build": "tsup src --dts",
"prepack": "clean-package"
},
"main": "src/index.ts",
"dependencies": {
"@vocdoni/sdk": "^0.0.18",
"date-fns": "^2.29.3",
"latinize": "^0.5.0",
"react-hook-form": "^7.43.8",
"react-icons": "^4.8.0",
"react-markdown": "^8.0.6",
"react-string-replace": "^1.1.0",
"remark-gfm": "^3.0.1",
"ts-deepmerge": "^6.0.3"
"@vocdoni/react-providers": "0.1.0"
},
"peerDependencies": {
"@chakra-ui/alert": "^2.0.18",
Expand Down Expand Up @@ -68,6 +51,7 @@
"@chakra-ui/form-control": "^2.0.18",
"@chakra-ui/image": "^2.0.13",
"@chakra-ui/layout": "^2.1.17",
"@chakra-ui/progress": "^2.1.6",
"@chakra-ui/radio": "^2.0.22",
"@chakra-ui/system": "2.5.5",
"@chakra-ui/table": "^2.0.17",
Expand All @@ -76,32 +60,26 @@
"@chakra-ui/toast": "^6.1.1",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/wallet": "^5.7.0",
"@size-limit/file": "^8.2.4",
"@types/latinize": "^0.2.15",
"@types/react": "^18.0.30",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^3.0.0",
"@vocdoni/react-providers": "*",
"@vocdoni/sdk": "^0.0.18",
"clean-package": "^2.2.0",
"date-fns": "^2.29.3",
"eslint": "^8.42.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^4.2.1",
"json": "^11.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"size-limit": "^8.2.4",
"react-hook-form": "^7.43.8",
"react-icons": "^4.8.0",
"react-markdown": "^8.0.6",
"react-string-replace": "^1.1.0",
"remark-gfm": "^3.0.1",
"ts-deepmerge": "^6.0.3",
"tsconfig": "*",
"typescript": "^4.9.5",
"vite": "^4.2.1",
"vite-plugin-dts": "^2.1.0"
"typescript": "^4.9.5"
},
"size-limit": [
{
"path": "dist/index.es.js",
"limit": "50 KB"
},
{
"path": "dist/index.cjs.js",
"limit": "50 KB"
}
]
"clean-package": "../../clean-package.config.json"
}
208 changes: 7 additions & 201 deletions packages/chakra-components/src/client.tsx
Original file line number Diff line number Diff line change
@@ -1,210 +1,16 @@
import { ToastProvider } from '@chakra-ui/toast'
import { Signer } from '@ethersproject/abstract-signer'
import { Wallet } from '@ethersproject/wallet'
import { Account, EnvOptions, VocdoniSDKClient } from '@vocdoni/sdk'
import { PropsWithChildren, createContext, useContext, useEffect } from 'react'
import { ClientProviderComponentProps, ClientProvider as RPClientProvider } from '@vocdoni/react-providers'
import merge from 'ts-deepmerge'
import { locales } from './i18n/locales'
import { LocaleProvider, LocaleProviderProps, useLocalize } from './i18n/localize'
import { ClientEnvSetPayload, useClientReducer } from './use-client-reducer'

export type ClientProviderProps = {
client?: VocdoniSDKClient
env?: ClientEnvSetPayload
signer?: Wallet | Signer
}

export const useClientProvider = ({ client: c, env: e, signer: s }: ClientProviderProps) => {
const localize = useLocalize()
const { actions, state } = useClientReducer({
client: c,
env: e,
signer: s,
})

// update env on updates
useEffect(() => {
if (!e) return
actions.setEnv(e)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [e])

// fetch account (only with signer connected)
useEffect(() => {
if (!state.connected || (state.connected && state.account)) return

fetchAccount()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.account, state.connected, state.env, state.signer])

// fetch balance (only with signer connected)
useEffect(() => {
if (!state.connected || !state.account) return

fetchBalance()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.account, state.connected])

// update signer on updates
useEffect(() => {
if (!s) return
actions.setSigner(s)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [s])

// switch account behavior handler
useEffect(() => {
if (!('ethereum' in window)) return

const accChanged = async () => {
actions.setClient(
new VocdoniSDKClient({
env: state.env as EnvOptions,
wallet: state.signer,
})
)
// undefine so other effects do their job
actions.setAccount(undefined)
}

;(window as any).ethereum.on('accountsChanged', accChanged)

return () => {
;(window as any).ethereum.removeListener('accountsChanged', accChanged)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

/**
* Fetches currently connected account information.
*
* @returns {Promise<AccountData>}
*/
const fetchAccount = () => {
if (state.loading.account) return

actions.fetchAccount()
return state.client.fetchAccountInfo().then(actions.setAccount).catch(actions.errorAccount)
}

/**
* Fetches and sets to state current account balance.
*
* @returns {Promise<number>}
*/
const fetchBalance = async () => {
if (state.loading.balance) return

try {
if (!state.account) {
throw new Error('Account not available')
}
// tell state machine we're fetching balance
actions.fetchBalance()

if (state.account.balance <= 10 && state.env !== 'prod') {
await state.client.collectFaucetTokens()
const acc = await state.client.fetchAccountInfo()
actions.setBalance(acc.balance)

return acc.balance
}

actions.setBalance(state.account.balance)
return state.account.balance
} catch (e: any) {
actions.errorBalance(e)
}
}

/**
* Creates an account.
*
* @returns {Promise<AccountData>}
*/
const createAccount = (account?: Account, faucetPackage?: string) => {
if (state.loading.account) return

actions.fetchAccount()
return state.client.createAccount({ account, faucetPackage }).then(actions.setAccount).catch(actions.errorAccount)
}

/**
* Generates a signer so the implementer can use it to sign transactions.
* @param seed
* @returns
*/
const generateSigner = (seed?: string | string[]) => {
if (!state.client) {
throw new Error('No client initialized')
}

let signer: Wallet
if (!seed) {
state.client.generateRandomWallet()
signer = state.client.wallet as Wallet
} else {
signer = VocdoniSDKClient.generateWalletFromData(seed)
}

return signer
}

return {
...state,
clear: actions.clear,
createAccount,
fetchAccount,
fetchBalance,
setClient: actions.setClient,
localize,
setSigner: actions.setSigner,
generateSigner,
}
}

export type ClientState = ReturnType<typeof useClientProvider>

export const ClientContext = createContext<ClientState | undefined>(undefined)

export const useClient = <T extends VocdoniSDKClient>() => {
const ctxt = useContext(ClientContext)

if (!ctxt) {
throw new Error('useClient returned `undefined`, maybe you forgot to wrap the component within <ClientProvider />?')
}

return {
...ctxt,
// Allow client extensions
client: ctxt.client as T,
}
}

export type InternalClientProviderComponentProps = PropsWithChildren<ClientProviderProps>
export type ClientProviderComponentProps = InternalClientProviderComponentProps & LocaleProviderProps

/**
* Required internal client provider so we can use useLocalize in useClientProvider.
*/
const InternalClientProvider = ({ env, client, signer, ...rest }: InternalClientProviderComponentProps) => {
const value = useClientProvider({ env, client, signer })

return <ClientContext.Provider value={value} {...rest} />
}

export const ClientProvider = ({ locale, datesLocale, ...rest }: ClientProviderComponentProps) => {
export const ClientProvider = (props: ClientProviderComponentProps) => {
const loc = {
locale: merge(locales, locale || {}),
datesLocale,
locale: merge(locales, props.locale || {}),
}

return (
<LocaleProvider {...loc}>
<>
<ToastProvider />
<InternalClientProvider {...rest} />
</>
</LocaleProvider>
<>
<ToastProvider />
<RPClientProvider {...props} {...loc} />
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Tag, TagProps } from '@chakra-ui/tag'
import { useClient } from '../../client'
import { useClient } from '@vocdoni/react-providers'

export const Balance = (props: TagProps) => {
const { balance } = useClient()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { ButtonGroup, IconButton } from '@chakra-ui/button'
import { ChakraProps, chakra, useMultiStyleConfig } from '@chakra-ui/system'
import { ToastId, useToast } from '@chakra-ui/toast'
import { useClient, useElection } from '@vocdoni/react-providers'
import { ElectionStatus, areEqualHexStrings } from '@vocdoni/sdk'
import { useRef, useState } from 'react'
import { FaPause, FaPlay, FaStop } from 'react-icons/fa'
import { ImCross } from 'react-icons/im'
import { useClient } from '../../client'
import { useElection } from './Election'

const PlayIcon = chakra(FaPlay)
const PauseIcon = chakra(FaPause)
Expand Down
Loading