diff --git a/src/components/Sponsor/Web3.tsx b/src/components/Sponsor/Web3.tsx index 6ef6d10c..ce8e1ca5 100644 --- a/src/components/Sponsor/Web3.tsx +++ b/src/components/Sponsor/Web3.tsx @@ -2,7 +2,7 @@ import Web3Donation from './Web3Donation' import config from '@config/blog.config' import { RainbowKitProvider } from '@rainbow-me/rainbowkit' import { WagmiConfig } from 'wagmi' -import { wagmiConfig, chains, theme } from '@lib/rainbowkit' +import { wagmiConfig, chains, theme } from './Web3Donation/lib/rainbowkit' import type { ReactElement } from 'react' export default function Web3(): ReactElement { diff --git a/src/components/Sponsor/Web3Donation/InputGroup.tsx b/src/components/Sponsor/Web3Donation/InputGroup.tsx deleted file mode 100644 index ad192a94..00000000 --- a/src/components/Sponsor/Web3Donation/InputGroup.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { type ReactElement } from 'react' -import Input from '@components/Input' -import Conversion from './Conversion' -import styles from './InputGroup.module.css' - -export default function InputGroup({ - amount, - isDisabled, - symbol, - setAmount -}: { - amount: string - isDisabled: boolean - symbol: string - setAmount(amount: string): void -}): ReactElement { - return ( - <> -
-
- setAmount(e.target.value)} - className={styles.inputInput} - disabled={isDisabled} - /> -
- {symbol} -
-
- -
- - - ) -} diff --git a/src/components/Sponsor/Web3Donation/api/getBalance.ts b/src/components/Sponsor/Web3Donation/api/getBalance.ts new file mode 100644 index 00000000..5e608b97 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/api/getBalance.ts @@ -0,0 +1,9 @@ +export async function getBalance(address: `0x${string}` | undefined) { + const url = `http://localhost:3000/api/balance?address=${address}` + // const url = `https://web3-api-kremalicious.vercel.app/api/balance?address=${address}` + const response = await fetch(url) + const json = await response.json() + + if (!json) console.error(response.statusText) + return json +} diff --git a/src/components/Sponsor/Web3Donation/api/getFiat.ts b/src/components/Sponsor/Web3Donation/api/getFiat.ts new file mode 100644 index 00000000..ff5987f5 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/api/getFiat.ts @@ -0,0 +1,18 @@ +export async function getFiat({ + amount, + tokenId = 'ethereum' +}: { + amount: number + tokenId?: string +}): Promise<{ [key: string]: string }> { + const url = `https://api.coingecko.com/api/v3/simple/price?ids=${tokenId}&vs_currencies=eur%2Cusd` + const response = await fetch(url) + const json = await response.json() + + if (!json) console.error(response.statusText) + const { usd, eur } = json[tokenId] + const dollar = (amount * usd).toFixed(2) + const euro = (amount * eur).toFixed(2) + + return { dollar, euro } +} diff --git a/src/components/Sponsor/Web3Donation/Alert.module.css b/src/components/Sponsor/Web3Donation/components/Alert/Alert.module.css similarity index 100% rename from src/components/Sponsor/Web3Donation/Alert.module.css rename to src/components/Sponsor/Web3Donation/components/Alert/Alert.module.css diff --git a/src/components/Sponsor/Web3Donation/Alert.test.tsx b/src/components/Sponsor/Web3Donation/components/Alert/Alert.test.tsx similarity index 100% rename from src/components/Sponsor/Web3Donation/Alert.test.tsx rename to src/components/Sponsor/Web3Donation/components/Alert/Alert.test.tsx diff --git a/src/components/Sponsor/Web3Donation/Alert.tsx b/src/components/Sponsor/Web3Donation/components/Alert/Alert.tsx similarity index 100% rename from src/components/Sponsor/Web3Donation/Alert.tsx rename to src/components/Sponsor/Web3Donation/components/Alert/Alert.tsx diff --git a/src/components/Sponsor/Web3Donation/Conversion.module.css b/src/components/Sponsor/Web3Donation/components/Conversion/Conversion.module.css similarity index 100% rename from src/components/Sponsor/Web3Donation/Conversion.module.css rename to src/components/Sponsor/Web3Donation/components/Conversion/Conversion.module.css diff --git a/src/components/Sponsor/Web3Donation/Conversion.test.tsx b/src/components/Sponsor/Web3Donation/components/Conversion/Conversion.test.tsx similarity index 84% rename from src/components/Sponsor/Web3Donation/Conversion.test.tsx rename to src/components/Sponsor/Web3Donation/components/Conversion/Conversion.test.tsx index 6f386b8d..3f2710ef 100644 --- a/src/components/Sponsor/Web3Donation/Conversion.test.tsx +++ b/src/components/Sponsor/Web3Donation/components/Conversion/Conversion.test.tsx @@ -1,6 +1,6 @@ import { render } from '@testing-library/react' import { describe, it } from 'vitest' -import Conversion from './Conversion' +import { Conversion } from './Conversion' describe('Conversion', () => { it('renders without crashing', async () => { diff --git a/src/components/Sponsor/Web3Donation/Conversion.tsx b/src/components/Sponsor/Web3Donation/components/Conversion/Conversion.tsx similarity index 62% rename from src/components/Sponsor/Web3Donation/Conversion.tsx rename to src/components/Sponsor/Web3Donation/components/Conversion/Conversion.tsx index bcabaa04..9371c1ea 100644 --- a/src/components/Sponsor/Web3Donation/Conversion.tsx +++ b/src/components/Sponsor/Web3Donation/components/Conversion/Conversion.tsx @@ -1,26 +1,8 @@ import { type ReactElement, useEffect, useState } from 'react' import styles from './Conversion.module.css' +import { getFiat } from '../../api/getFiat' -export async function getFiat({ - amount, - tokenId = 'ethereum' -}: { - amount: number - tokenId?: string -}): Promise<{ [key: string]: string }> { - const url = `https://api.coingecko.com/api/v3/simple/price?ids=${tokenId}&vs_currencies=eur%2Cusd` - const response = await fetch(url) - const json = await response.json() - - if (!json) console.error(response.statusText) - const { usd, eur } = json[tokenId] - const dollar = (amount * usd).toFixed(2) - const euro = (amount * eur).toFixed(2) - - return { dollar, euro } -} - -export default function Conversion({ +export function Conversion({ amount, symbol }: { diff --git a/src/components/Sponsor/Web3Donation/components/Conversion/index.tsx b/src/components/Sponsor/Web3Donation/components/Conversion/index.tsx new file mode 100644 index 00000000..289752d5 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Conversion/index.tsx @@ -0,0 +1 @@ +export * from './Conversion' diff --git a/src/components/Sponsor/Web3Donation/InputGroup.module.css b/src/components/Sponsor/Web3Donation/components/Input/InputGroup.module.css similarity index 63% rename from src/components/Sponsor/Web3Donation/InputGroup.module.css rename to src/components/Sponsor/Web3Donation/components/Input/InputGroup.module.css index 9e1d324c..5fa03a57 100644 --- a/src/components/Sponsor/Web3Donation/InputGroup.module.css +++ b/src/components/Sponsor/Web3Donation/components/Input/InputGroup.module.css @@ -3,48 +3,28 @@ position: relative; animation: fadeIn 0.8s ease-out backwards; margin-top: var(--spacer); + display: flex; } -@media (min-width: 40rem) { +/* @media (min-width: 40rem) { .inputGroup { display: flex; flex-wrap: wrap; } -} +} */ -.inputGroup button { - width: 100%; - border-top-left-radius: 0; - border-top-right-radius: 0; - border-color: var(--border-color); -} +/* .currency { +} */ -@media (min-width: 40rem) { - .inputGroup button { - width: 40%; - border-top-right-radius: var(--border-radius); - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: 0; - } -} - -.input { - position: relative; -} - -@media (min-width: 40rem) { - .input { - width: 60%; - } +:global([data-theme='dark']) .currency { + border-right-color: #000; } .inputInput { text-align: center; border: 1px solid var(--border-color); font-size: var(--font-size-large); - padding: calc(var(--spacer) / 3) calc(var(--spacer) / 3) - calc(var(--spacer) / 3) calc(var(--spacer) * 1.7); + padding: calc(var(--spacer) / 4); border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; @@ -68,27 +48,25 @@ border-color: var(--border-color); } -.currency { - position: absolute; - top: 1px; - bottom: 1px; - left: 1px; - font-size: var(--font-size-small); - padding: calc(var(--spacer) / 3); - background: var(--box-background-color); - border-right: 1px solid var(--text-color-dimmed); - border-top-left-radius: var(--border-radius); - border-bottom-left-radius: var(--border-radius); - display: flex; - align-items: center; +.submit { + width: 100%; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-color: var(--border-color); } -:global([data-theme='dark']) .currency { - border-right-color: #000; +@media (min-width: 40rem) { + .submit { + width: fit-content; + border-top-right-radius: var(--border-radius); + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: 0; + } } .message { - composes: message from './index.module.css'; + composes: message from '../../index.module.css'; } @keyframes fadeIn { diff --git a/src/components/Sponsor/Web3Donation/InputGroup.test.tsx b/src/components/Sponsor/Web3Donation/components/Input/InputGroup.test.tsx similarity index 96% rename from src/components/Sponsor/Web3Donation/InputGroup.test.tsx rename to src/components/Sponsor/Web3Donation/components/Input/InputGroup.test.tsx index bf735179..67734af0 100644 --- a/src/components/Sponsor/Web3Donation/InputGroup.test.tsx +++ b/src/components/Sponsor/Web3Donation/components/Input/InputGroup.test.tsx @@ -1,6 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' -import InputGroup from './InputGroup' +import { InputGroup } from '.' const setAmount = vi.fn() diff --git a/src/components/Sponsor/Web3Donation/components/Input/InputGroup.tsx b/src/components/Sponsor/Web3Donation/components/Input/InputGroup.tsx new file mode 100644 index 00000000..0dc0e909 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Input/InputGroup.tsx @@ -0,0 +1,43 @@ +import { type ReactElement } from 'react' +import Input from '@components/Input' +import { Conversion } from '../Conversion' +import styles from './InputGroup.module.css' +import { TokenSelect } from '../Tokens' + +export function InputGroup({ + amount, + isDisabled, + symbol, + setAmount +}: { + amount: string + isDisabled: boolean + symbol: string + setAmount(amount: string): void +}): ReactElement { + return ( + <> +
+
+ +
+ setAmount(e.target.value)} + className={styles.inputInput} + disabled={isDisabled} + /> + +
+ + + ) +} diff --git a/src/components/Sponsor/Web3Donation/components/Input/index.tsx b/src/components/Sponsor/Web3Donation/components/Input/index.tsx new file mode 100644 index 00000000..c5658db9 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Input/index.tsx @@ -0,0 +1 @@ +export * from './InputGroup' diff --git a/src/components/Sponsor/Web3Donation/components/Tokens/Select.css b/src/components/Sponsor/Web3Donation/components/Tokens/Select.css new file mode 100644 index 00000000..78487447 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Tokens/Select.css @@ -0,0 +1,67 @@ +/* reset */ +button { + all: unset; +} + +.SelectTrigger { + display: inline-flex; + align-items: center; + justify-content: center; + width: 70px; + height: 100%; + font-size: var(--font-size-small); + line-height: 1; + padding: 0 calc(var(--spacer) / 4); + background: var(--box-background-color); + border-right: 1px solid var(--text-color-dimmed); + border-top-left-radius: var(--border-radius); + border-bottom-left-radius: var(--border-radius); +} + +.SelectTrigger:hover { + background-color: whitesmoke; +} + +.SelectTrigger:focus { + box-shadow: 0 0 0 2px blue; +} + +.SelectTrigger[data-disabled] { + opacity: 0.5; + pointer-events: none; +} + +.SelectContent { + overflow: hidden; + background-color: var(--body-background-color); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); +} + +.SelectViewport { + padding: 0; +} + +.SelectLabel { + padding: 0 25px; + font-size: var(--font-size-small); + color: var(--text-color-light); + text-transform: capitalize; + border-bottom: 1px solid var(--border-color); +} + +.SelectSeparator { + height: 1px; + background-color: var(--border-color); + margin: 5px; +} + +.SelectScrollButton { + display: flex; + align-items: center; + justify-content: center; + height: 25px; + background-color: var(--body-background-color); + color: var(--text-color); + cursor: default; +} diff --git a/src/components/Sponsor/Web3Donation/components/Tokens/Select.tsx b/src/components/Sponsor/Web3Donation/components/Tokens/Select.tsx new file mode 100644 index 00000000..27a86b53 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Tokens/Select.tsx @@ -0,0 +1,70 @@ +import * as Select from '@radix-ui/react-select' +import './Select.css' +import { SelectItem } from './SelectItem' +import { ChevronDown, ChevronsDown, ChevronsUp } from '@images/components/react' +import { useEffect, useState } from 'react' +import { getBalance } from '../../api/getBalance' +import { useAccount, useNetwork } from 'wagmi' + +export function TokenSelect() { + const { address } = useAccount() + const { chain } = useNetwork() + + const [tokens, setTokens] = useState() + + useEffect(() => { + if (!address || !chain) return + + async function getTokens() { + try { + const response = await getBalance(address) + const tokens = response.filter( + (token: any) => parseInt(token.chainId) === chain?.id + ) + setTokens(tokens) + } catch (error) { + console.error((error as Error).message) + } + } + getTokens() + }, [address, chain]) + + return ( + + + + + + + + + + + + + + + + + In Your Wallet + + + {tokens?.map((token: any) => ( + + {token.name} + + ))} + + + + + + + + + ) +} diff --git a/src/components/Sponsor/Web3Donation/components/Tokens/SelectItem.css b/src/components/Sponsor/Web3Donation/components/Tokens/SelectItem.css new file mode 100644 index 00000000..319cc838 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Tokens/SelectItem.css @@ -0,0 +1,52 @@ +.SelectItem { + font-size: var(--font-size-small); + line-height: 1; + color: var(--text-color); + display: flex; + align-items: center; + padding: calc(var(--spacer) / 4) calc(var(--spacer) / 4) + calc(var(--spacer) / 4) 25px; + position: relative; + user-select: none; +} + +.SelectItem[data-disabled] { + opacity: 0.5; + pointer-events: none; +} + +.SelectItem[data-highlighted] { + outline: none; + background-color: var(--text-color); + color: var(--body-background-color); +} + +.SelectItem, +.Token { + display: flex; + align-items: center; + position: relative; +} + +.Token { + min-width: 200px; +} + +.Token img { + width: 32px; + height: 32px; + margin: 0; + margin-right: calc(var(--spacer) / 4); + border-radius: 50%; + border: 1px solid var(--border-color); + background: var(--border-color); +} + +.SelectItemIndicator { + position: absolute; + left: 0; + width: 25px; + display: inline-flex; + align-items: center; + justify-content: center; +} diff --git a/src/components/Sponsor/Web3Donation/components/Tokens/SelectItem.tsx b/src/components/Sponsor/Web3Donation/components/Tokens/SelectItem.tsx new file mode 100644 index 00000000..4828e099 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Tokens/SelectItem.tsx @@ -0,0 +1,33 @@ +import { forwardRef, type HTMLAttributes } from 'react' +import * as Select from '@radix-ui/react-select' +import classnames from 'classnames' +import './SelectItem.css' +import { Check } from '@images/components/react' + +interface SelectItemProps extends HTMLAttributes { + value: string + icon: string +} + +export const SelectItem = forwardRef( + ({ children, className, value, icon, ...props }, forwardedRef) => { + return ( + +
+ + + + {children} +
+ + + +
+ ) + } +) diff --git a/src/components/Sponsor/Web3Donation/components/Tokens/index.tsx b/src/components/Sponsor/Web3Donation/components/Tokens/index.tsx new file mode 100644 index 00000000..3e383f07 --- /dev/null +++ b/src/components/Sponsor/Web3Donation/components/Tokens/index.tsx @@ -0,0 +1 @@ +export * from './Select' diff --git a/src/components/Sponsor/Web3Donation/index.tsx b/src/components/Sponsor/Web3Donation/index.tsx index 01f5a8f0..d3188f46 100644 --- a/src/components/Sponsor/Web3Donation/index.tsx +++ b/src/components/Sponsor/Web3Donation/index.tsx @@ -9,8 +9,8 @@ import { } from 'wagmi' import { ConnectButton } from '@rainbow-me/rainbowkit' -import Alert, { getTransactionMessage } from './Alert' -import InputGroup from './InputGroup' +import Alert, { getTransactionMessage } from './components/Alert/Alert' +import { InputGroup } from './components/Input' import styles from './index.module.css' export default function Web3Donation({ diff --git a/src/lib/rainbowkit.ts b/src/components/Sponsor/Web3Donation/lib/rainbowkit.ts similarity index 96% rename from src/lib/rainbowkit.ts rename to src/components/Sponsor/Web3Donation/lib/rainbowkit.ts index 3eb488de..9a4b2be1 100644 --- a/src/lib/rainbowkit.ts +++ b/src/components/Sponsor/Web3Donation/lib/rainbowkit.ts @@ -1,6 +1,6 @@ import { type Theme, getDefaultWallets } from '@rainbow-me/rainbowkit' import { configureChains, createConfig } from 'wagmi' -import { mainnet, polygon } from 'wagmi/chains' +import { mainnet, polygon, base, bsc } from 'wagmi/chains' import { infuraProvider } from 'wagmi/providers/infura' import { publicProvider } from 'wagmi/providers/public' @@ -13,7 +13,7 @@ if (isProduction && (!PUBLIC_INFURA_ID || !PUBLIC_WALLETCONNECT_ID)) { } export const { chains, publicClient } = configureChains( - [mainnet, polygon], + [mainnet, polygon, base, bsc], [infuraProvider({ apiKey: PUBLIC_INFURA_ID }), publicProvider()] ) diff --git a/src/pages/thanks.astro b/src/pages/thanks.astro index 44322a0b..3bad5f00 100644 --- a/src/pages/thanks.astro +++ b/src/pages/thanks.astro @@ -33,7 +33,7 @@ const coins = Object.entries(config.author).filter( } - +