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 `
-`
-
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)
+---
+
+`
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
+}
+`
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 (
-
- )
-}
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
---
-