diff --git a/.config/blog.config.ts b/.config/blog.config.ts index 96e577a7..f8d0f026 100644 --- a/.config/blog.config.ts +++ b/.config/blog.config.ts @@ -9,7 +9,10 @@ export default { mastodon: 'https://mas.to/@krema', github: 'https://github.com/kremalicious', bitcoin: '171qDmKEXm9YBgBLXyGjjPvopP5o9htQ1V', - ether: '0xf50F267b5689b005FE107cfdb34619f24c014457' + ether: { + ens: 'krema.eth', + address: '0xf50F267b5689b005FE107cfdb34619f24c014457' + } }, rss: '/feed.xml', jsonfeed: '/feed.json', diff --git a/.config/husky/pre-commit b/.config/husky/pre-commit index f0bc84ea..d24fdfc6 100755 --- a/.config/husky/pre-commit +++ b/.config/husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -npm run validate +npx lint-staged diff --git a/.env.sample b/.env.sample index ea9f4bcb..67094b9a 100644 --- a/.env.sample +++ b/.env.sample @@ -4,4 +4,5 @@ PUBLIC_TYPEKIT_ID=xxx PUBLIC_UMAMI_SCRIPT_URL=xxx PUBLIC_UMAMI_WEBSITE_ID=xxx PUBLIC_INFURA_ID=xxx -PUBLIC_WALLETCONNECT_ID="xxx" \ No newline at end of file +PUBLIC_WALLETCONNECT_ID="xxx" +PUBLIC_WEB3_API_URL="xxx" \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..ad61eae9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: kremalicious +patreon: kremalicious +custom: ['https://kremalicious.com/thanks'] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2089807..20854df6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ env: PUBLIC_UMAMI_WEBSITE_ID: ${{ secrets.GATSBY_UMAMI_WEBSITE_ID }} PUBLIC_INFURA_ID: ${{ secrets.GATSBY_INFURA_ID }} PUBLIC_WALLETCONNECT_ID: ${{ secrets.GATSBY_WALLETCONNECT_ID }} + PUBLIC_WEB3_API_URL: ${{ secrets.PUBLIC_WEB3_API_URL }} jobs: lint: diff --git a/README.md b/README.md index 59bbfe53..ad010f6f 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ - [🎉 Features](#-features) - [🌅 Image handling](#-image-handling) - [🎆 EXIF extraction](#-exif-extraction) - - [💰 Cryptocurrency donation via Web3/MetaMask](#-cryptocurrency-donation-via-web3metamask) + - [💰 Cryptocurrency donation via Web3 browser wallets](#-cryptocurrency-donation-via-web3-browser-wallets) - [🔍 Search](#-search) - [🕸 Related Posts](#-related-posts) - - [📝 GitHub changelog rendering](#-github-changelog-rendering) + - [📝 GitHub Changelog Rendering](#-github-changelog-rendering) - [🌗 Theme Switcher](#-theme-switcher) - [💎 SVG assets as components](#-svg-assets-as-components) - [astro-redirect-from](#astro-redirect-from) @@ -46,7 +46,7 @@ The whole [blog](https://kremalicious.com) is a statically exported site built w Styling happens through a combination of basic global styles and on components level either through CSS modules or CSS in ` -{title ? ({title}) : ''}${innerSVG}` - export const toInnerSvg = (input: string) => optimizeSVGNative(input, { plugins: [ diff --git a/scripts/create-icons/toAstroComponent.ts b/scripts/create-icons/toAstroComponent.ts new file mode 100644 index 00000000..863f5623 --- /dev/null +++ b/scripts/create-icons/toAstroComponent.ts @@ -0,0 +1,39 @@ +export const toAstroComponent = (innerSVG: string, title: string) => `--- +import type { Props } from './Props.d.ts'; +export type { Props }; + +let { + size = '24px', + title, + width = size, + height = size, + ...props +}: Props = { + 'fill': 'none', + 'title': '${title}', + 'viewBox': '0 0 24 24', + ...Astro.props +} + +const toAttributeSize = (size: number | string) => + String(size).replace(/(?<=[0-9])x$/, 'em') + +size = toAttributeSize(size) +width = toAttributeSize(width) +height = toAttributeSize(height) +--- + +{title ? ({title}) : ''}${innerSVG}` diff --git a/scripts/create-icons/toReactComponent.ts b/scripts/create-icons/toReactComponent.ts new file mode 100644 index 00000000..6f9bcb71 --- /dev/null +++ b/scripts/create-icons/toReactComponent.ts @@ -0,0 +1,35 @@ +export const toReactComponent = (innerSVG: string, title: string) => ` +import type { Props } from './Props.d.ts'; + +export function Icon(props: Props) { + let { + size = '24px', + title, + width = size, + height = size + }: Props = { + 'title': '${title}', + ...props + } + + const toAttributeSize = (size: number | string) => + String(size).replace(/(?<=[0-9])x$/, 'em') + + size = toAttributeSize(size) + width = toAttributeSize(width) + height = toAttributeSize(height) + + const style = { + width: '1em', + height: '1em', + stroke: 'currentcolor', + strokeWidth: 'var(--border-width)', + strokeLinecap: 'round', + strokeLinejoin: 'round', + fill: 'none', + verticalAlign: 'baseline' + } + + return {title ? ({title}) : ''}${innerSVG} +} +` diff --git a/src/components/CopyCode.astro b/src/components/CopyCode.astro new file mode 100644 index 00000000..fea16aa0 --- /dev/null +++ b/src/components/CopyCode.astro @@ -0,0 +1,29 @@ +--- +import Copy from '@components/Copy.astro' + +type Props = { + address: string +} + +const { address }: Props = Astro.props +--- + + + +
+ {address} + +
diff --git a/src/components/Donation/Coin.astro b/src/components/Donation/Coin.astro deleted file mode 100644 index 882b506f..00000000 --- a/src/components/Donation/Coin.astro +++ /dev/null @@ -1,42 +0,0 @@ ---- -import Copy from '@components/Copy.astro' - -type Props = { - address: string - title: string -} - -const { address, title }: Props = Astro.props ---- - - - -
-

{title}

-
- {address} - -
-
diff --git a/src/components/Donation/Web3.tsx b/src/components/Donation/Web3.tsx deleted file mode 100644 index f92c971b..00000000 --- a/src/components/Donation/Web3.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import Web3Donation from '@components/Donation/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 type { ReactElement } from 'react' - -export default function Web3(): ReactElement { - return ( - - - - - - ) -} diff --git a/src/components/Donation/Web3Donation/Alert.module.css b/src/components/Donation/Web3Donation/Alert.module.css deleted file mode 100644 index 39f4c6ee..00000000 --- a/src/components/Donation/Web3Donation/Alert.module.css +++ /dev/null @@ -1,41 +0,0 @@ -.alert { - font-size: var(--font-size-small); - display: inline-block; -} - -.alert:empty { - display: none; -} - -.alert::after { - overflow: hidden; - display: inline-block; - vertical-align: bottom; - animation: ellipsis steps(4, end) 1s infinite; - - /* ascii code for the ellipsis character */ - content: '\2026'; - width: 0; - position: absolute; -} - -.success { - composes: alert; - color: green; -} - -.error { - composes: alert; - color: red; -} - -.error::after, -.success::after { - display: none; -} - -@keyframes ellipsis { - to { - width: 0.75rem; - } -} diff --git a/src/components/Donation/Web3Donation/Alert.test.tsx b/src/components/Donation/Web3Donation/Alert.test.tsx deleted file mode 100644 index a3204dee..00000000 --- a/src/components/Donation/Web3Donation/Alert.test.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { render } from '@testing-library/react' -import { describe, it } from 'vitest' -import Alert from './Alert' - -describe('Alert', () => { - it('renders without crashing', async () => { - render( - - ) - }) -}) diff --git a/src/components/Donation/Web3Donation/Alert.tsx b/src/components/Donation/Web3Donation/Alert.tsx deleted file mode 100644 index b387d4b2..00000000 --- a/src/components/Donation/Web3Donation/Alert.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { type ReactElement } from 'react' -import styles from './Alert.module.css' - -export function getTransactionMessage(transactionHash?: string): { - [key: string]: string -} { - return { - transaction: `See your transaction on etherscan.io.`, - waitingForUser: 'Waiting for your confirmation', - waitingConfirmation: 'Waiting for network confirmation, hang on', - success: 'Confirmed. You are awesome, thanks!' - } -} - -function constructMessage( - transactionHash: string, - message?: { text?: string } -): string | undefined { - return transactionHash - ? message?.text + - '

' + - getTransactionMessage(transactionHash).transaction - : message && message.text -} - -const classes = (status: string) => - status === 'success' - ? styles.success - : status === 'error' - ? styles.error - : styles.alert - -export default function Alert({ - transactionHash, - message -}: { - transactionHash?: string - message: { text?: string; status?: string } -}): ReactElement { - return ( -
- ) -} diff --git a/src/components/Donation/Web3Donation/Conversion.module.css b/src/components/Donation/Web3Donation/Conversion.module.css deleted file mode 100644 index cf0ca5a0..00000000 --- a/src/components/Donation/Web3Donation/Conversion.module.css +++ /dev/null @@ -1,12 +0,0 @@ -.conversion { - font-size: var(--font-size-mini); - color: var(--text-color-light); - text-align: left; - margin-top: 0; - margin-left: calc(var(--spacer) * 2.4); - animation: fadeIn 0.5s 0.8s ease-out backwards; -} - -.conversion span { - margin-left: calc(var(--spacer) / 2); -} diff --git a/src/components/Donation/Web3Donation/Conversion.test.tsx b/src/components/Donation/Web3Donation/Conversion.test.tsx deleted file mode 100644 index 6f386b8d..00000000 --- a/src/components/Donation/Web3Donation/Conversion.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { render } from '@testing-library/react' -import { describe, it } from 'vitest' -import Conversion from './Conversion' - -describe('Conversion', () => { - it('renders without crashing', async () => { - render() - }) -}) diff --git a/src/components/Donation/Web3Donation/Conversion.tsx b/src/components/Donation/Web3Donation/Conversion.tsx deleted file mode 100644 index bcabaa04..00000000 --- a/src/components/Donation/Web3Donation/Conversion.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { type ReactElement, useEffect, useState } from 'react' -import styles from './Conversion.module.css' - -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({ - amount, - symbol -}: { - amount: string - symbol: string -}): ReactElement { - const [conversion, setConversion] = useState({ - euro: '0.00', - dollar: '0.00' - }) - const { dollar, euro } = conversion - - useEffect(() => { - async function getFiatResponse() { - try { - const tokenId = symbol === 'MATIC' ? 'matic-network' : 'ethereum' - const { dollar, euro } = await getFiat({ - amount: Number(amount), - tokenId - }) - setConversion({ euro, dollar }) - } catch (error) { - console.error((error as Error).message) - } - } - - getFiatResponse() - }, [amount, symbol]) - - return ( -
- {dollar !== '0.00' && `= $ ${dollar}`} - {euro !== '0.00' && `= € ${euro}`} -
- ) -} diff --git a/src/components/Donation/Web3Donation/InputGroup.module.css b/src/components/Donation/Web3Donation/InputGroup.module.css deleted file mode 100644 index 9e1d324c..00000000 --- a/src/components/Donation/Web3Donation/InputGroup.module.css +++ /dev/null @@ -1,102 +0,0 @@ -.inputGroup { - margin: auto; - position: relative; - animation: fadeIn 0.8s ease-out backwards; - margin-top: var(--spacer); -} - -@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); -} - -@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%; - } -} - -.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); - border-bottom: 0; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -@media (min-width: 40rem) { - .inputInput { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-bottom-left-radius: var(--border-radius); - border-bottom: 1px solid var(--border-color); - border-right: 0; - } -} - -.inputInput::-webkit-inner-spin-button { - margin-left: -1rem; -} - -:global([data-theme='dark']) .inputInput { - 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; -} - -:global([data-theme='dark']) .currency { - border-right-color: #000; -} - -.message { - composes: message from './index.module.css'; -} - -@keyframes fadeIn { - from { - opacity: 0.01; - } - - to { - opacity: 1; - } -} diff --git a/src/components/Donation/Web3Donation/InputGroup.tsx b/src/components/Donation/Web3Donation/InputGroup.tsx deleted file mode 100644 index ad192a94..00000000 --- a/src/components/Donation/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/Donation/Web3Donation/index.module.css b/src/components/Donation/Web3Donation/index.module.css deleted file mode 100644 index 0258b31f..00000000 --- a/src/components/Donation/Web3Donation/index.module.css +++ /dev/null @@ -1,54 +0,0 @@ -.web3 { - margin-left: auto; - margin-right: auto; - max-width: 25rem; - width: 100%; - text-align: center; -} - -.web3 > div:first-child { - display: flex; - justify-content: space-between; - font-size: var(--font-size-small); - margin-bottom: var(--spacer); -} - -/* connect button */ -.web3 > div:first-child > button:only-child { - margin-left: auto; - margin-right: auto; -} - -.message { - font-size: var(--font-size-small); - position: relative; -} - -.message::after { - overflow: hidden; - display: inline-block; - vertical-align: bottom; - animation: ellipsis steps(4, end) 1s infinite; - - /* ascii code for the ellipsis character */ - content: '\2026'; - width: 0; - position: absolute; - left: 100%; - bottom: 0; -} - -.success { - composes: message; - color: green; -} - -.success::after { - display: none; -} - -@keyframes ellipsis { - to { - width: 0.75rem; - } -} diff --git a/src/components/Donation/Web3Donation/index.tsx b/src/components/Donation/Web3Donation/index.tsx deleted file mode 100644 index 01f5a8f0..00000000 --- a/src/components/Donation/Web3Donation/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { type ReactElement, useState } from 'react' -import { useDebounce } from 'use-debounce' -import { parseEther } from 'viem' -import { - useAccount, - useNetwork, - usePrepareSendTransaction, - useSendTransaction -} from 'wagmi' -import { ConnectButton } from '@rainbow-me/rainbowkit' - -import Alert, { getTransactionMessage } from './Alert' -import InputGroup from './InputGroup' -import styles from './index.module.css' - -export default function Web3Donation({ - address -}: { - address: string -}): ReactElement { - const { address: account } = useAccount() - const { chain } = useNetwork() - - const [amount, setAmount] = useState('0.005') - const [debouncedAmount] = useDebounce(amount, 500) - - const { config } = usePrepareSendTransaction({ - to: address, - value: debouncedAmount ? parseEther(debouncedAmount) : undefined - }) - const { sendTransactionAsync, isError, isSuccess } = - useSendTransaction(config) - - const [message, setMessage] = useState<{ status: string; text: string }>() - const [transactionHash, setTransactionHash] = useState() - - async function handleSendTransaction() { - setMessage({ - status: 'loading', - text: getTransactionMessage().waitingForUser - }) - - try { - const result = sendTransactionAsync && (await sendTransactionAsync()) - - if (isError) { - throw new Error(undefined) - } - - setTransactionHash(result?.hash) - setMessage({ - status: 'loading', - text: getTransactionMessage().waitingConfirmation - }) - - if (isSuccess) { - setMessage({ - status: 'success', - text: getTransactionMessage().success - }) - } - } catch (error) { - setMessage(undefined) - } - } - - const isDisabled = !account - - return ( -
{ - e.preventDefault() - handleSendTransaction() - }} - > - - - {message ? ( - - ) : ( - - )} - - ) -} diff --git a/src/components/Footer/index.astro b/src/components/Footer/index.astro index 60230ba0..e7ad26ea 100644 --- a/src/components/Footer/index.astro +++ b/src/components/Footer/index.astro @@ -8,7 +8,7 @@ const year = new Date().getFullYear() const { name, url, github } = config.author --- -