From 2fc88f86a70cd27ffe006364608855dccc01c922 Mon Sep 17 00:00:00 2001 From: 1marcghannam <1marc.ghannam@gmail.com> Date: Tue, 23 May 2023 17:51:22 -0400 Subject: [PATCH 01/11] feat: FAQ and fixes --- README.md | 5 +- client/packages/ui/package.json | 2 +- client/packages/ui/src/App.tsx | 4 +- client/packages/ui/src/Routes.ts | 3 +- .../ui/src/api/contracts/readFunctions.ts | 24 +++++++- .../packages/ui/src/components/faq/index.tsx | 61 +++++++++++++++++++ client/packages/ui/src/components/index.ts | 1 + .../ui/src/components/navigationBar/index.tsx | 13 ++++ .../raffleCreate/components/create.tsx | 8 ++- .../raffleDetail/components/detail.tsx | 15 ++--- .../raffleDetail/components/stepManager.tsx | 5 +- .../packages/ui/src/hooks/useAsyncManager.ts | 3 +- client/packages/ui/tsconfig.json | 16 +---- 13 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 client/packages/ui/src/components/faq/index.tsx diff --git a/README.md b/README.md index 6feb37f..ddfab7a 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ export ANVIL_PRIVATE_KEY="" # Get from anvil after running for the first time, s # UI export UI_RAFFLE_MANAGER_CONTRACT_ADDRESS= # Get from anvil after deploying contract -export UI_LINK_TOKEN_CONTRACT_ADDRESS= +export UI_LINK_TOKEN_CONTRACT_ADDRESS= +export UI_KEEPER_REGISTRY_CONTRACT_ADDRESS= ``` ### 4. Setup Wallet @@ -62,7 +63,7 @@ $ docker compose up ### 1. Setup Foundry -([Installation instructions](https://book.getfoundry.sh/getting-started/installation) +[Installation instructions](https://book.getfoundry.sh/getting-started/installation) ```bash # Download foundry diff --git a/client/packages/ui/package.json b/client/packages/ui/package.json index 4362b0e..ccf8e8b 100644 --- a/client/packages/ui/package.json +++ b/client/packages/ui/package.json @@ -65,7 +65,7 @@ "react-test-renderer": "^18.2.0", "string.prototype.replaceall": "^1.0.7", "ts-jest": "^29.0.5", - "typescript": "4.9.5", + "typescript": "^5.0.4", "vite": "^2.5.4", "vite-plugin-react-md": "^1.0.1" } diff --git a/client/packages/ui/src/App.tsx b/client/packages/ui/src/App.tsx index d59b1ea..8fd2690 100644 --- a/client/packages/ui/src/App.tsx +++ b/client/packages/ui/src/App.tsx @@ -5,7 +5,7 @@ import { Routes } from '@ui/Routes' import { RaffleList } from '@ui/features/raffleList' import { RaffleDetail } from '@ui/features/raffleDetail' import { RaffleCreate } from '@ui/features/raffleCreate' -import { AuthenticatedRoute, Hero } from '@ui/components' +import { AuthenticatedRoute, Hero, FAQ } from '@ui/components' export const App = () => ( <> @@ -26,6 +26,8 @@ export const App = () => ( render={({ match }) => } /> + + diff --git a/client/packages/ui/src/Routes.ts b/client/packages/ui/src/Routes.ts index 0d18050..82c4edc 100644 --- a/client/packages/ui/src/Routes.ts +++ b/client/packages/ui/src/Routes.ts @@ -1,7 +1,8 @@ export const Routes = { RaffleList: '/', RaffleDetail: '/raffle/:id', - RaffleCreate: '/create' + RaffleCreate: '/create', + FAQ: '/faq' } export const createRoute = ({ route, id }) => route.replace(':id', id) diff --git a/client/packages/ui/src/api/contracts/readFunctions.ts b/client/packages/ui/src/api/contracts/readFunctions.ts index d32adae..5939786 100644 --- a/client/packages/ui/src/api/contracts/readFunctions.ts +++ b/client/packages/ui/src/api/contracts/readFunctions.ts @@ -1,5 +1,6 @@ import { readContract } from '@wagmi/core' - +import { useEffect } from 'react' +import { useContractRead } from 'wagmi' import { env } from '@ui/config' import { transformRaffleItem, @@ -42,6 +43,27 @@ export const getRaffle = async (id: number): Promise => { } } +export const getRaffleHook = (store, id) => { + const { data, isError } = useContractRead({ + ...defaultOptions, + address: raffleManagerContractAddress, + functionName: 'getRaffle', + args: [id] + }) + + useEffect(() => { + const transformedData = transformRaffleItem(data) + if ( + data && + JSON.stringify(store.state.raffle) !== JSON.stringify(transformedData) + ) { + store.update({ raffle: transformedData }) + } + }, [data, store]) + + return { data, isError } +} + export const getClaimableLink = async (id: number): Promise => { try { const data = await readContract({ diff --git a/client/packages/ui/src/components/faq/index.tsx b/client/packages/ui/src/components/faq/index.tsx new file mode 100644 index 0000000..69fb122 --- /dev/null +++ b/client/packages/ui/src/components/faq/index.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import { + Container, + Heading, + Text, + Divider, + Box, + VStack +} from '@chakra-ui/react' + +const faqList = [ + { + question: 'What is a Chainlink Raffle?', + answer: + "A Chainlink Raffle is a lottery system built on the blockchain. It uses Chainlink's Verifiable Random Function (VRF) to ensure that the selection of winners is provably fair and transparent." + }, + { + question: 'How do I participate in a Chainlink Raffle?', + answer: + "To participate in a Chainlink Raffle, you first need to register or 'sign up' during the sign up period. Once the raffle event is committed on-chain, you become a participant and stand a chance to win prizes when the raffle is settled." + }, + { + question: 'How are the winners selected?', + answer: + "Winners are selected using Chainlink's Verifiable Random Function (VRF). This is a trusted and secure source of randomness on the blockchain. It ensures that the winner selection process is fair, transparent, and tamper-proof." + }, + { + question: 'What is the role of the raffle owner?', + answer: + 'The raffle owner is responsible for creating and managing the raffle. They commit the rules of the raffle on-chain, such as the number of winners, the prizes, and the sign up period.' + }, + { + question: 'What is the difference between Dynamic and Static Raffles?', + answer: + 'Dynamic Raffles have a level of flexibility as they allow participants to enter at any point before the raffle ends, provided they meet the necessary conditions. This feature provides the potential for a larger participant pool over time. Static Raffles, however, have a predetermined set of participants at the outset of the raffle event. Once the raffle starts, no new entrants can participate. This creates a fixed and unchanging number of entries over time.' + } +] + +export const FAQ = () => { + return ( + + + What are Chainlink Raffles? + + + Frequently Asked Questions + + + + {faqList.map((faq, index) => ( + + + {faq.question} + + {faq.answer} + + ))} + + + ) +} diff --git a/client/packages/ui/src/components/index.ts b/client/packages/ui/src/components/index.ts index 52ba721..973fabf 100644 --- a/client/packages/ui/src/components/index.ts +++ b/client/packages/ui/src/components/index.ts @@ -10,3 +10,4 @@ export * from './pending' export * from './success' export * from './form' export * from './icons' +export * from './faq' diff --git a/client/packages/ui/src/components/navigationBar/index.tsx b/client/packages/ui/src/components/navigationBar/index.tsx index 0acb5bd..90be524 100644 --- a/client/packages/ui/src/components/navigationBar/index.tsx +++ b/client/packages/ui/src/components/navigationBar/index.tsx @@ -49,6 +49,19 @@ export const NavigationBar = () => { href={Routes.RaffleList}> Home + + FAQ + {address && ( diff --git a/client/packages/ui/src/features/raffleCreate/components/create.tsx b/client/packages/ui/src/features/raffleCreate/components/create.tsx index d20f953..6c18f6b 100644 --- a/client/packages/ui/src/features/raffleCreate/components/create.tsx +++ b/client/packages/ui/src/features/raffleCreate/components/create.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react' import { useHistory } from 'react-router-dom' import { useAccount } from 'wagmi' +import { Link as RouterLink } from 'react-router-dom' import { Container, Heading, @@ -10,9 +11,9 @@ import { Input, Grid, GridItem, - Button + Button, + Link } from '@chakra-ui/react' - import { Routes } from '@ui/Routes' import { Error, Control } from '@ui/components' import { useAsyncManager, useStore } from '@ui/hooks' @@ -132,6 +133,9 @@ export const RaffleCreate = () => { Create dynamic or static raffle + + Need help? Check out our FAQ page. + diff --git a/client/packages/ui/src/features/raffleDetail/components/detail.tsx b/client/packages/ui/src/features/raffleDetail/components/detail.tsx index 286edd6..e25986a 100644 --- a/client/packages/ui/src/features/raffleDetail/components/detail.tsx +++ b/client/packages/ui/src/features/raffleDetail/components/detail.tsx @@ -30,7 +30,6 @@ import { } from '@ui/models' import { StepManager, - getRaffle, JoinButton, CheckStatusButton, PickWinnersButton, @@ -41,6 +40,7 @@ import { } from '@ui/features/raffleDetail' import { formatUnixTs, formatFinishDate, shortenAddress } from '@ui/utils' import { UploadWinners } from '@ui/features/raffleDetail' +import { getRaffleHook } from '@ui/api/contracts' export const initialState = { raffle: null, @@ -76,16 +76,16 @@ export const RaffleDetail = ({ id }) => { const asyncManager = useAsyncManager() const { raffle } = store.state - const componentDidMount = () => { - if (id) getRaffle({ id, update: store.update, asyncManager }) - } - useEffect(componentDidMount, []) + getRaffleHook(store, id) + + console.log(raffle) - const addressOrRafleDidChange = () => { + const addressOrRaffleDidChange = () => { if (raffle?.type == RaffleType.DYNAMIC) store.update({ identifier: address }) } - useEffect(addressOrRafleDidChange, [address, raffle]) + useEffect(addressOrRaffleDidChange, [address, raffle]) + return ( raffle?.id && ( { + { const asyncManager = useAsyncManager() const { step } = store.state - const reset = (_store) => _store.update({ step: null }) + const reset = (_store) => { + _store.update({ step: null }) + asyncManager.reset() + } return ( reset(store)} isOpen={!!step}> diff --git a/client/packages/ui/src/hooks/useAsyncManager.ts b/client/packages/ui/src/hooks/useAsyncManager.ts index f05707d..72e4130 100644 --- a/client/packages/ui/src/hooks/useAsyncManager.ts +++ b/client/packages/ui/src/hooks/useAsyncManager.ts @@ -48,6 +48,7 @@ export const useAsyncManager = () => { start, waiting, success, - fail + fail, + reset } } diff --git a/client/packages/ui/tsconfig.json b/client/packages/ui/tsconfig.json index df0ab6e..7237159 100644 --- a/client/packages/ui/tsconfig.json +++ b/client/packages/ui/tsconfig.json @@ -3,11 +3,7 @@ "compilerOptions": { "target": "es2018", "lib": ["es2018", "dom", "esnext.asynciterable"], - "types": [ - "@types/jest", - "jest", - "node" - ], + "types": ["@types/jest", "jest", "node"], "typeRoots": ["node_modules/@types"], "allowSyntheticDefaultImports": true, "experimentalDecorators": true, @@ -27,12 +23,6 @@ "@ui/*": ["./src/*"] } }, - "include": [ - "./src/**/*.ts", - "./src/**/*.tsx", - ], - "exclude": [ - "babel.config.js", - "node_modules" - ] + "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "exclude": ["babel.config.js", "node_modules"] } From 067caee9adbafd56775a13e9481e2d39a3929a48 Mon Sep 17 00:00:00 2001 From: 1marcghannam <1marc.ghannam@gmail.com> Date: Tue, 23 May 2023 19:49:47 -0400 Subject: [PATCH 02/11] feat: Fix getRaffleHook --- client/packages/ui/src/api/contracts/readFunctions.ts | 8 ++++---- .../src/features/raffleDetail/components/detail.tsx | 11 ++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/client/packages/ui/src/api/contracts/readFunctions.ts b/client/packages/ui/src/api/contracts/readFunctions.ts index 5939786..689eb23 100644 --- a/client/packages/ui/src/api/contracts/readFunctions.ts +++ b/client/packages/ui/src/api/contracts/readFunctions.ts @@ -44,14 +44,14 @@ export const getRaffle = async (id: number): Promise => { } export const getRaffleHook = (store, id) => { - const { data, isError } = useContractRead({ + const { data, isError, isLoading, isSuccess } = useContractRead({ ...defaultOptions, address: raffleManagerContractAddress, functionName: 'getRaffle', args: [id] }) - useEffect(() => { + if (!id || !isSuccess) return const transformedData = transformRaffleItem(data) if ( data && @@ -59,9 +59,9 @@ export const getRaffleHook = (store, id) => { ) { store.update({ raffle: transformedData }) } - }, [data, store]) + }, [data, store, id, isSuccess]) - return { data, isError } + return { data, isError, isLoading, isSuccess } } export const getClaimableLink = async (id: number): Promise => { diff --git a/client/packages/ui/src/features/raffleDetail/components/detail.tsx b/client/packages/ui/src/features/raffleDetail/components/detail.tsx index e25986a..cc80ede 100644 --- a/client/packages/ui/src/features/raffleDetail/components/detail.tsx +++ b/client/packages/ui/src/features/raffleDetail/components/detail.tsx @@ -40,7 +40,7 @@ import { } from '@ui/features/raffleDetail' import { formatUnixTs, formatFinishDate, shortenAddress } from '@ui/utils' import { UploadWinners } from '@ui/features/raffleDetail' -import { getRaffleHook } from '@ui/api/contracts' +import { getRaffleHook } from '@ui/api/contracts/readFunctions' export const initialState = { raffle: null, @@ -78,14 +78,11 @@ export const RaffleDetail = ({ id }) => { const { raffle } = store.state getRaffleHook(store, id) - console.log(raffle) - - const addressOrRaffleDidChange = () => { + const addressOrRafleDidChange = () => { if (raffle?.type == RaffleType.DYNAMIC) store.update({ identifier: address }) } - useEffect(addressOrRaffleDidChange, [address, raffle]) - + useEffect(addressOrRafleDidChange, [address, raffle]) return ( raffle?.id && ( { - + Date: Tue, 23 May 2023 20:01:40 -0400 Subject: [PATCH 03/11] feat: Fix csv example --- .../raffleCreate/components/formStatic.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/client/packages/ui/src/features/raffleCreate/components/formStatic.tsx b/client/packages/ui/src/features/raffleCreate/components/formStatic.tsx index 37bcfc9..59855cd 100644 --- a/client/packages/ui/src/features/raffleCreate/components/formStatic.tsx +++ b/client/packages/ui/src/features/raffleCreate/components/formStatic.tsx @@ -1,7 +1,6 @@ import React from 'react' import { ethers } from 'ethers' import { GridItem } from '@chakra-ui/react' - import { CSVUpload, Control } from '@ui/components' export const initialStaticState = { @@ -18,6 +17,18 @@ export const FormStatic = ({ update, validation }) => { update({ participants }) } + const downloadCSV = () => { + const exampleCSV = 'john\nsteve\nbrad' + const blob = new Blob([exampleCSV], { type: 'text/csv' }) + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = 'example.csv' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + } + return ( { errorMessage={validation['participants']} helper={ <> - CSV file example + CSV + file example }> Date: Thu, 25 May 2023 08:24:07 -0600 Subject: [PATCH 04/11] feat: Fixed pickwinners rerender --- .../raffleDetail/components/detail.tsx | 108 ++++++++---------- .../features/raffleDetail/methods/contract.ts | 2 - 2 files changed, 50 insertions(+), 60 deletions(-) diff --git a/client/packages/ui/src/features/raffleDetail/components/detail.tsx b/client/packages/ui/src/features/raffleDetail/components/detail.tsx index cc80ede..6cc75eb 100644 --- a/client/packages/ui/src/features/raffleDetail/components/detail.tsx +++ b/client/packages/ui/src/features/raffleDetail/components/detail.tsx @@ -70,19 +70,13 @@ export const RaffleDetail = ({ id }) => { const [isLargerThanMd] = useMediaQuery('(min-width: 48em)') const { address } = useAccount() const store = useStore({ - ...initialState, - identifier: address ? address : initialState.identifier + ...initialState }) const asyncManager = useAsyncManager() const { raffle } = store.state getRaffleHook(store, id) - const addressOrRafleDidChange = () => { - if (raffle?.type == RaffleType.DYNAMIC) - store.update({ identifier: address }) - } - useEffect(addressOrRafleDidChange, [address, raffle]) return ( raffle?.id && ( { value={isLargerThanMd ? raffle.owner : shortenAddress(raffle.owner)} /> - {raffle.status !== RaffleStatus.RESOLVING && ( -
- - - - +
+ + + + - - - - - - -
- )} + + + + + +
+
{ asyncManager.success() - await getRaffle({ id, asyncManager, update }) - success(true) return true } catch (error) { From ebd20c791cd55e2be18fa175368715a07c96d311 Mon Sep 17 00:00:00 2001 From: 1marcghannam <1marc.ghannam@gmail.com> Date: Thu, 25 May 2023 09:35:43 -0600 Subject: [PATCH 05/11] feat: Add tx link --- .../raffleCreate/components/create.tsx | 6 ++++- .../raffleDetail/components/detail.tsx | 2 +- .../raffleDetail/components/pickWinners.tsx | 23 +++++++++++++++---- .../features/raffleDetail/methods/contract.ts | 6 +++-- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/client/packages/ui/src/features/raffleCreate/components/create.tsx b/client/packages/ui/src/features/raffleCreate/components/create.tsx index 6c18f6b..6ddfe12 100644 --- a/client/packages/ui/src/features/raffleCreate/components/create.tsx +++ b/client/packages/ui/src/features/raffleCreate/components/create.tsx @@ -133,7 +133,11 @@ export const RaffleCreate = () => { Create dynamic or static raffle - + Need help? Check out our FAQ page.
diff --git a/client/packages/ui/src/features/raffleDetail/components/detail.tsx b/client/packages/ui/src/features/raffleDetail/components/detail.tsx index 6cc75eb..2597a1b 100644 --- a/client/packages/ui/src/features/raffleDetail/components/detail.tsx +++ b/client/packages/ui/src/features/raffleDetail/components/detail.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React from 'react' import { useAccount } from 'wagmi' import { Container, diff --git a/client/packages/ui/src/features/raffleDetail/components/pickWinners.tsx b/client/packages/ui/src/features/raffleDetail/components/pickWinners.tsx index 6f25a81..45922b2 100644 --- a/client/packages/ui/src/features/raffleDetail/components/pickWinners.tsx +++ b/client/packages/ui/src/features/raffleDetail/components/pickWinners.tsx @@ -1,20 +1,22 @@ import React, { useState, useEffect } from 'react' -import { Button, Text, Flex, Heading } from '@chakra-ui/react' +import { Button, Text, Flex, Heading, Link } from '@chakra-ui/react' +import { useNetwork } from 'wagmi' import { pickWinners } from '@ui/features/raffleDetail' export const PickWinners = ({ id, reset, asyncManager, store }) => { const [success, setSuccess] = useState(false) + const [txHash, setTxHash] = useState(null) + const { chain } = useNetwork() - const componentDidMount = () => { + useEffect(() => { pickWinners({ id, asyncManager, success: setSuccess, - update: store.update + txHash: setTxHash }) - } - useEffect(componentDidMount, []) + }, []) return ( success && ( @@ -23,6 +25,17 @@ export const PickWinners = ({ id, reset, asyncManager, store }) => { Pick Winners Successfully picked winners for raffle id `{id}`. + {txHash && ( + + + View VRF transaction + + + )} From 265ea8c11a8780cbc09ca81ebff89dee65e23acb Mon Sep 17 00:00:00 2001 From: 1marcghannam <1marc.ghannam@gmail.com> Date: Thu, 25 May 2023 10:32:06 -0600 Subject: [PATCH 08/11] feat: Wrap buttons --- client/packages/ui/src/components/navigationBar/index.tsx | 1 + .../ui/src/features/raffleDetail/components/detail.tsx | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/packages/ui/src/components/navigationBar/index.tsx b/client/packages/ui/src/components/navigationBar/index.tsx index 90be524..29ffc36 100644 --- a/client/packages/ui/src/components/navigationBar/index.tsx +++ b/client/packages/ui/src/components/navigationBar/index.tsx @@ -46,6 +46,7 @@ export const NavigationBar = () => { textTransform: 'none', color: 'brand.primary' }} + display={{ base: 'none', md: 'inline-flex' }} href={Routes.RaffleList}> Home diff --git a/client/packages/ui/src/features/raffleDetail/components/detail.tsx b/client/packages/ui/src/features/raffleDetail/components/detail.tsx index 2597a1b..39aafaa 100644 --- a/client/packages/ui/src/features/raffleDetail/components/detail.tsx +++ b/client/packages/ui/src/features/raffleDetail/components/detail.tsx @@ -9,7 +9,8 @@ import { Flex, HStack, Divider, - useMediaQuery + useMediaQuery, + Wrap } from '@chakra-ui/react' import { @@ -178,7 +179,7 @@ export const RaffleDetail = ({ id }) => { />
- + { address={address} raffle={raffle} /> - +
Date: Thu, 25 May 2023 10:39:48 -0600 Subject: [PATCH 09/11] feat: Add tooltip to export winners button --- .../raffleDetail/components/checkWinners.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/client/packages/ui/src/features/raffleDetail/components/checkWinners.tsx b/client/packages/ui/src/features/raffleDetail/components/checkWinners.tsx index ac5923e..70f80d4 100644 --- a/client/packages/ui/src/features/raffleDetail/components/checkWinners.tsx +++ b/client/packages/ui/src/features/raffleDetail/components/checkWinners.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react' -import { Button, Text, Stack, Flex, Heading } from '@chakra-ui/react' +import { Button, Text, Stack, Flex, Heading, Tooltip } from '@chakra-ui/react' export const CheckWinners = ({ store, reset }) => { const [winners, setWinners] = useState([]) @@ -46,9 +46,16 @@ export const CheckWinners = ({ store, reset }) => { ))} - + + + From 57d1f9239ff6cd8212e62fa9df65f40bfcd740f0 Mon Sep 17 00:00:00 2001 From: jongregis Date: Thu, 25 May 2023 16:24:18 -0500 Subject: [PATCH 10/11] update error handling on pick winners --- .../raffleDetail/components/pickWinners.tsx | 14 +++++++------- .../src/features/raffleDetail/methods/contract.ts | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/client/packages/ui/src/features/raffleDetail/components/pickWinners.tsx b/client/packages/ui/src/features/raffleDetail/components/pickWinners.tsx index 238cb9b..9a61c61 100644 --- a/client/packages/ui/src/features/raffleDetail/components/pickWinners.tsx +++ b/client/packages/ui/src/features/raffleDetail/components/pickWinners.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react' import { Button, Text, Flex, Heading, Link } from '@chakra-ui/react' +import { ExternalLinkIcon } from '@chakra-ui/icons' import { useNetwork } from 'wagmi' import { pickWinners } from '@ui/features/raffleDetail' @@ -26,13 +27,12 @@ export const PickWinners = ({ id, reset, asyncManager, store }) => { Successfully picked winners for raffle id `{id}`. {txHash && ( - - - View VRF Request - - + + View VRF Request + )}