market data refactor, add 24h price change (#8)

* new prices data structure

* market data refactor, add 24h price change

* browser language detection with middleware

* middleware tweaks
This commit is contained in:
Matthias Kretschmann 2024-04-09 13:46:15 +01:00 committed by GitHub
parent f0380351a8
commit dd44cc864e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 695 additions and 75 deletions

View File

@ -3,6 +3,7 @@ import '@/styles/globals.css'
import '@/styles/loading-ui.css'
import Script from 'next/script'
import { title, description, font, liveUrl, isProduction } from '@/constants'
import { headers } from 'next/headers'
export const metadata: Metadata = {
title,
@ -16,8 +17,11 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode
}>) {
const headersList = headers()
const locale = headersList.get('x-locale')
return (
<html lang="en">
<html lang="en" data-locale={locale}>
<head>
{isProduction ? (
<Script

View File

@ -15,13 +15,3 @@
padding: 1rem;
font-size: 0.9rem;
}
.marketData p:last-child {
border-top: 1px solid rgba(var(--foreground-rgb), 0.1);
padding-top: 0.5rem;
margin-top: 0.5rem;
}
.fiat {
color: var(--text-dimmed);
}

View File

@ -4,36 +4,23 @@ import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
import styles from './MarketData.module.css'
import { usePrices } from '@/features/prices'
import { Badge } from '@/components'
import { Price } from '../Price'
export function MarketData() {
const { prices, isValidating, isLoading } = usePrices()
const feedbackClasses = isLoading
? 'isLoading'
: isValidating
? 'isValidating'
: ''
const { prices } = usePrices()
return (
<ul className={styles.marketData}>
<li>
<p>1 ASI</p>
<p>
<span className={`${styles.fiat} ${feedbackClasses}`}>
${prices.asi}
</span>
</p>
<Price price={prices.asi.usd} priceChange={prices.asi.usd_24h_change} />
</li>
<li>
<p>
1 Fet = {ratioFetToAsi} ASI
<Badge>fixed</Badge>
</p>
<p>
<span className={`${styles.fiat} ${feedbackClasses}`}>
${prices.fet}
</span>
</p>
<Price price={prices.fet.usd} priceChange={prices.fet.usd_24h_change} />
</li>
<li>
<p>
@ -42,11 +29,10 @@ export function MarketData() {
ASI
<Badge>fixed</Badge>
</p>
<p>
<span className={`${styles.fiat} ${feedbackClasses}`}>
${prices.ocean}
</span>
</p>
<Price
price={prices.ocean.usd}
priceChange={prices.ocean.usd_24h_change}
/>
</li>
<li>
<p>
@ -55,11 +41,10 @@ export function MarketData() {
ASI
<Badge>fixed</Badge>
</p>
<p>
<span className={`${styles.fiat} ${feedbackClasses}`}>
${prices.agix}
</span>
</p>
<Price
price={prices.agix.usd}
priceChange={prices.agix.usd_24h_change}
/>
</li>
</ul>
)

View File

@ -0,0 +1,9 @@
.price {
border-top: 1px solid rgba(var(--foreground-rgb), 0.1);
padding-top: 0.5rem;
margin-top: 0.5rem;
}
.fiat {
color: var(--text-dimmed);
}

View File

@ -0,0 +1,26 @@
import styles from './Price.module.css'
import { usePrices } from '@/features/prices'
import { PriceChange } from './PriceChange'
export function Price({
price,
priceChange
}: {
price: number
priceChange?: number
}) {
const { isValidating, isLoading } = usePrices()
const feedbackClasses = isLoading
? 'isLoading'
: isValidating
? 'isValidating'
: ''
return (
<p className={styles.price}>
<span className={`${styles.fiat} ${feedbackClasses}`}>${price} </span>
{priceChange ? <PriceChange priceChange={priceChange} /> : null}
</p>
)
}

View File

@ -0,0 +1,33 @@
.change {
font-size: 0.7rem;
--color-positive: #2ecc40;
--color-negative: #ff4136;
}
/* culture-sensitive color psychology */
[data-locale|='zh'] .change,
[data-locale|='ja'] .change {
--color-positive: #ff4136;
--color-negative: #2ecc40;
}
[data-locale|='ko'] .change {
--color-positive: #0074d9;
--color-negative: #2ecc40;
}
.change svg {
position: relative;
top: 0.15rem;
width: 13px;
height: 13px;
}
.change.positive {
color: var(--color-positive);
}
.change.negative {
color: var(--color-negative);
}

View File

@ -0,0 +1,13 @@
import { TriangleUpIcon, TriangleDownIcon } from '@radix-ui/react-icons'
import styles from './PriceChange.module.css'
export function PriceChange({ priceChange }: { priceChange: number }) {
const styleClasses = priceChange > 0 ? styles.positive : styles.negative
return (
<span className={`${styles.change} ${styleClasses}`} title="24h change">
{priceChange > 0 ? <TriangleUpIcon /> : <TriangleDownIcon />}
{Math.abs(priceChange).toFixed(1)}%
</span>
)
}

View File

@ -0,0 +1 @@
export * from './Price'

View File

@ -7,19 +7,31 @@ import useSWR from 'swr'
const tokenAddresses = tokens.map((token) => token.address).toString()
export type Prices = {
ocean: number
fet: number
agix: number
asi: number
ocean: {
usd: number
usd_24h_change: number
}
fet: {
usd: number
usd_24h_change: number
}
agix: {
usd: number
usd_24h_change: number
}
asi: {
usd: number
usd_24h_change: number
}
}
export function usePrices(): {
prices: { ocean: number; fet: number; agix: number; asi: number }
prices: Prices
isValidating: boolean
isLoading: boolean
} {
const { data, isValidating, isLoading } = useSWR(
`/api/prices/?tokens=${tokenAddresses}`,
`/api/prices?tokens=${tokenAddresses}`,
fetcher
)
@ -27,16 +39,21 @@ export function usePrices(): {
const fetAddress = getTokenAddressBySymbol('FET')
const agixAddress = getTokenAddressBySymbol('AGIX')
if (!oceanAddress || !fetAddress || !agixAddress)
if (!data || !oceanAddress || !fetAddress || !agixAddress)
return {
prices: { ocean: 0, fet: 0, agix: 0, asi: 0 },
prices: {
ocean: { usd: 0, usd_24h_change: 0 },
fet: { usd: 0, usd_24h_change: 0 },
agix: { usd: 0, usd_24h_change: 0 },
asi: { usd: 0, usd_24h_change: 0 }
},
isValidating,
isLoading
}
const ocean = data?.[oceanAddress]?.usd || 0
const fet = data?.[fetAddress]?.usd || 0
const agix = data?.[agixAddress]?.usd || 0
const ocean = data[oceanAddress]
const fet = data[fetAddress]
const agix = data[agixAddress]
const asi = fet
return { prices: { ocean, fet, agix, asi }, isValidating, isLoading }

View File

@ -22,29 +22,33 @@ export function Buy() {
</h3>
<Result
token={getTokenBySymbol('OCEAN')}
amount={debouncedAmount / prices.ocean}
amountAsi={(debouncedAmount / prices.ocean) * ratioOceanToAsi}
amount={debouncedAmount / prices.ocean.usd}
amountAsi={(debouncedAmount / prices.ocean.usd) * ratioOceanToAsi}
amountFiat={
(debouncedAmount / prices.ocean) * ratioOceanToAsi * prices.asi
(debouncedAmount / prices.ocean.usd) *
ratioOceanToAsi *
prices.asi.usd
}
isValidating={isValidating}
isLoading={isLoading}
/>
<Result
token={getTokenBySymbol('AGIX')}
amount={debouncedAmount / prices.agix}
amountAsi={(debouncedAmount / prices.agix) * ratioAgixToAsi}
amount={debouncedAmount / prices.agix.usd}
amountAsi={(debouncedAmount / prices.agix.usd) * ratioAgixToAsi}
amountFiat={
(debouncedAmount / prices.agix) * ratioAgixToAsi * prices.asi
(debouncedAmount / prices.agix.usd) * ratioAgixToAsi * prices.asi.usd
}
isValidating={isValidating}
isLoading={isLoading}
/>
<Result
token={getTokenBySymbol('FET')}
amount={debouncedAmount / prices.fet}
amountAsi={(debouncedAmount / prices.fet) * ratioFetToAsi}
amountFiat={(debouncedAmount / prices.fet) * ratioFetToAsi * prices.asi}
amount={debouncedAmount / prices.fet.usd}
amountAsi={(debouncedAmount / prices.fet.usd) * ratioFetToAsi}
amountFiat={
(debouncedAmount / prices.fet.usd) * ratioFetToAsi * prices.asi.usd
}
isValidating={isValidating}
isLoading={isLoading}
/>

View File

@ -34,10 +34,11 @@ export function SwapResults({
isLoadingToOcean
} = useQuote(tokenSymbol, amount, isUniswap)
const amountInUsd = amount * prices[tokenSymbol.toLowerCase() as keyof Prices]
const amountToOcean = amountInUsd / prices.ocean
const amountToAgix = amountInUsd / prices.agix
const amountToFet = amountInUsd / prices.fet
const amountInUsd =
amount * prices[tokenSymbol.toLowerCase() as keyof Prices].usd
const amountToOcean = amountInUsd / prices.ocean.usd
const amountToAgix = amountInUsd / prices.agix.usd
const amountToFet = amountInUsd / prices.fet.usd
return (
<>
@ -46,10 +47,12 @@ export function SwapResults({
amount={amountToOceanUniswap || amountToOcean}
amountAsi={(amountToOceanUniswap || amountToOcean) * ratioOceanToAsi}
amountFiat={
(amountToOceanUniswap || amountToOcean) * ratioOceanToAsi * prices.asi
(amountToOceanUniswap || amountToOcean) *
ratioOceanToAsi *
prices.asi.usd
}
amountOriginalFiat={
(amountToOceanUniswap || amountToOcean) * prices.ocean
(amountToOceanUniswap || amountToOcean) * prices.ocean.usd
}
isValidating={isValidatingToOcean || isValidatingPrices}
isLoading={isLoadingToOcean || isLoadingPrices}
@ -60,9 +63,13 @@ export function SwapResults({
amount={amountToAgixUniswap || amountToAgix}
amountAsi={(amountToAgixUniswap || amountToAgix) * ratioAgixToAsi}
amountFiat={
(amountToAgixUniswap || amountToAgix) * ratioAgixToAsi * prices.asi
(amountToAgixUniswap || amountToAgix) *
ratioAgixToAsi *
prices.asi.usd
}
amountOriginalFiat={
(amountToAgixUniswap || amountToAgix) * prices.agix.usd
}
amountOriginalFiat={(amountToAgixUniswap || amountToAgix) * prices.agix}
isValidating={isValidatingToAgix || isValidatingPrices}
isLoading={isLoadingToAgix || isLoadingPrices}
/>
@ -71,8 +78,10 @@ export function SwapResults({
token={getTokenBySymbol('FET')}
amount={amountToFetUniswap || amountToFet}
amountAsi={(amountToFetUniswap || amountToFet) * ratioFetToAsi}
amountFiat={(amountToFetUniswap || amountToFet) * prices.asi}
amountOriginalFiat={(amountToFetUniswap || amountToFet) * prices.asi}
amountFiat={(amountToFetUniswap || amountToFet) * prices.asi.usd}
amountOriginalFiat={
(amountToFetUniswap || amountToFet) * prices.asi.usd
}
isValidating={isValidatingToFet || isValidatingPrices}
isLoading={isLoadingToFet || isLoadingPrices}
/>

11
middleware.ts Normal file
View File

@ -0,0 +1,11 @@
import { NextResponse, type NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// add x-locale header to all responses
const locale =
request.headers.get('accept-language')?.split(',')?.[0] || 'en-US'
const response = NextResponse.next()
response.headers.set('x-locale', locale)
return response
}

531
package-lock.json generated
View File

@ -14,6 +14,7 @@
"react": "^18",
"react-dom": "^18",
"react-markdown": "^9.0.1",
"sharp": "^0.33.3",
"swr": "^2.2.5",
"use-debounce": "^10.0.0"
},
@ -57,6 +58,15 @@
"node": ">=10"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.1.1.tgz",
"integrity": "sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@ -146,6 +156,437 @@
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
"dev": true
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.3.tgz",
"integrity": "sha512-FaNiGX1MrOuJ3hxuNzWgsT/mg5OHG/Izh59WW2mk1UwYHUwtfbhk5QNKYZgxf0pLOhx9ctGiGa2OykD71vOnSw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"glibc": ">=2.26",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.2"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.3.tgz",
"integrity": "sha512-2QeSl7QDK9ru//YBT4sQkoq7L0EAJZA3rtV+v9p8xTKl4U1bUqTIaCnoC7Ctx2kCjQgwFXDasOtPTCT8eCTXvw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"glibc": ">=2.26",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.2"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz",
"integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"macos": ">=11",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz",
"integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"macos": ">=10.13",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz",
"integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.28",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz",
"integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.26",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz",
"integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.28",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz",
"integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.26",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz",
"integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"musl": ">=1.2.2",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz",
"integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"musl": ">=1.2.2",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.3.tgz",
"integrity": "sha512-Q7Ee3fFSC9P7vUSqVEF0zccJsZ8GiiCJYGWDdhEjdlOeS9/jdkyJ6sUSPj+bL8VuOYFSbofrW0t/86ceVhx32w==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.28",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.2"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.3.tgz",
"integrity": "sha512-Zf+sF1jHZJKA6Gor9hoYG2ljr4wo9cY4twaxgFDvlG0Xz9V7sinsPp8pFd1XtlhTzYo0IhDbl3rK7P6MzHpnYA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.26",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.2"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.3.tgz",
"integrity": "sha512-vFk441DKRFepjhTEH20oBlFrHcLjPfI8B0pMIxGm3+yilKyYeHEVvrZhYFdqIseSclIqbQ3SnZMwEMWonY5XFA==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.28",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.2"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.3.tgz",
"integrity": "sha512-Q4I++herIJxJi+qmbySd072oDPRkCg/SClLEIDh5IL9h1zjhqjv82H0Seupd+q2m0yOfD+/fJnjSoDFtKiHu2g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.26",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.2"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.3.tgz",
"integrity": "sha512-qnDccehRDXadhM9PM5hLvcPRYqyFCBN31kq+ErBSZtZlsAc1U4Z85xf/RXv1qolkdu+ibw64fUDaRdktxTNP9A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"musl": ">=1.2.2",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.3.tgz",
"integrity": "sha512-Jhchim8kHWIU/GZ+9poHMWRcefeaxFIs9EBqf9KtcC14Ojk6qua7ghKiPs0sbeLbLj/2IGBtDcxHyjCdYWkk2w==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"musl": ">=1.2.2",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.2"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.3.tgz",
"integrity": "sha512-68zivsdJ0koE96stdUfM+gmyaK/NcoSZK5dV5CAjES0FUXS9lchYt8LAB5rTbM7nlWtxaU/2GON0HVN6/ZYJAQ==",
"cpu": [
"wasm32"
],
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.1.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.3.tgz",
"integrity": "sha512-CyimAduT2whQD8ER4Ux7exKrtfoaUiVr7HG0zZvO0XTFn2idUWljjxv58GxNTkFb8/J9Ub9AqITGkJD6ZginxQ==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.3.tgz",
"integrity": "sha512-viT4fUIDKnli3IfOephGnolMzhz5VaTvDRkYqtZxOMIoMQ4MrAziO7pT1nVnOt2FAm7qW5aa+CCc13aEY6Le0g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -1067,11 +1508,22 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@ -1082,8 +1534,16 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/comma-separated-tokens": {
"version": "2.0.3",
@ -1252,6 +1712,14 @@
"node": ">=6"
}
},
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"engines": {
"node": ">=8"
}
},
"node_modules/devlop": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
@ -2520,6 +2988,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"node_modules/is-async-function": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
@ -4455,7 +4928,6 @@
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
@ -4470,7 +4942,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@ -4510,6 +4981,45 @@
"node": ">= 0.4"
}
},
"node_modules/sharp": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.3.tgz",
"integrity": "sha512-vHUeXJU1UvlO/BNwTpT0x/r53WkLUVxrmb5JTgW92fdFCFk0ispLMAeu/jPO2vjkXM1fYUi3K7/qcLF47pwM1A==",
"hasInstallScript": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.3",
"semver": "^7.6.0"
},
"engines": {
"libvips": ">=8.15.2",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.3",
"@img/sharp-darwin-x64": "0.33.3",
"@img/sharp-libvips-darwin-arm64": "1.0.2",
"@img/sharp-libvips-darwin-x64": "1.0.2",
"@img/sharp-libvips-linux-arm": "1.0.2",
"@img/sharp-libvips-linux-arm64": "1.0.2",
"@img/sharp-libvips-linux-s390x": "1.0.2",
"@img/sharp-libvips-linux-x64": "1.0.2",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2",
"@img/sharp-libvips-linuxmusl-x64": "1.0.2",
"@img/sharp-linux-arm": "0.33.3",
"@img/sharp-linux-arm64": "0.33.3",
"@img/sharp-linux-s390x": "0.33.3",
"@img/sharp-linux-x64": "0.33.3",
"@img/sharp-linuxmusl-arm64": "0.33.3",
"@img/sharp-linuxmusl-x64": "0.33.3",
"@img/sharp-wasm32": "0.33.3",
"@img/sharp-win32-ia32": "0.33.3",
"@img/sharp-win32-x64": "0.33.3"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -4561,6 +5071,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -5411,8 +5929,7 @@
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yocto-queue": {
"version": "0.1.0",

View File

@ -17,6 +17,7 @@
"react": "^18",
"react-dom": "^18",
"react-markdown": "^9.0.1",
"sharp": "^0.33.3",
"swr": "^2.2.5",
"use-debounce": "^10.0.0"
},