From f91a701c44d870564ccbcc9b1386d94b76fa4a7e Mon Sep 17 00:00:00 2001 From: kingsleydon <10992364+kingsleydon@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:51:23 -0800 Subject: [PATCH] refactor: improve code reuse with shared hooks and components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create useAddTokenToWallet hook to encapsulate wallet token/network addition logic - Create MetaMaskIcon component for shared MetaMask SVG icon - Create MetaMaskButton component with consistent styling - Create GradientText component for gradient typography - Create ChainBadge component for Ethereum/Phala Mainnet badges - Update content.tsx, stake.tsx to use useAddTokenToWallet hook - Update asset-card.tsx to use MetaMaskButton component - Update claim-assets.tsx to use ChainBadge component - Replace "Phala L2" terminology with "Phala Mainnet" across codebase 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/app/app/content.tsx | 65 +++++++-- apps/app/app/khala-assets/content.tsx | 8 +- apps/app/app/providers.tsx | 55 ++++---- apps/app/components/asset-card.tsx | 81 ++++++----- apps/app/components/chain-badge.tsx | 42 ++++++ apps/app/components/claim-assets.tsx | 61 +------- apps/app/components/gradient-text.tsx | 32 +++++ apps/app/components/metamask-button.tsx | 36 +++++ apps/app/components/metamask-icon.tsx | 163 ++++++++++++++++++++++ apps/app/components/portfolio-summary.tsx | 10 +- apps/app/components/staking/stake.tsx | 46 +++++- apps/app/hooks/use-add-token-to-wallet.ts | 58 ++++++++ apps/app/package.json | 1 + bun.lock | 5 + 14 files changed, 520 insertions(+), 143 deletions(-) create mode 100644 apps/app/components/chain-badge.tsx create mode 100644 apps/app/components/gradient-text.tsx create mode 100644 apps/app/components/metamask-button.tsx create mode 100644 apps/app/components/metamask-icon.tsx create mode 100644 apps/app/hooks/use-add-token-to-wallet.ts diff --git a/apps/app/app/content.tsx b/apps/app/app/content.tsx index 1c95cd6c..b84fa98f 100644 --- a/apps/app/app/content.tsx +++ b/apps/app/app/content.tsx @@ -1,11 +1,12 @@ 'use client' import {Box, Divider, Grid, Stack, Typography} from '@mui/material' +import {toCurrency} from '@phala/lib' import phaIcon from '@phala/ui/icons/asset/pha.png' import vphaIcon from '@phala/ui/icons/asset/vpha.png' import {useAppKitAccount, useAppKitNetwork} from '@reown/appkit/react' import Decimal from 'decimal.js' -import {useMemo} from 'react' +import {useCallback, useMemo} from 'react' import {erc20Abi, formatUnits} from 'viem' import {mainnet} from 'viem/chains' import {useReadContract} from 'wagmi' @@ -20,6 +21,7 @@ import { VAULT_CONTRACT_ADDRESS, } from '@/config' import {useRewardRate, useSharePrice, useTotalAssets} from '@/hooks/staking' +import {useAddTokenToWallet} from '@/hooks/use-add-token-to-wallet' import {phalaNetwork, toAddress} from '@/lib/wagmi' export default function HomeContent() { @@ -29,6 +31,7 @@ export default function HomeContent() { const sharePrice = useSharePrice() const totalAssets = useTotalAssets() const rewardRate = useRewardRate() + const {addTokenToWallet, addNetwork} = useAddTokenToWallet() const isValidConnection = isConnected && chainId === ethChain.id @@ -79,12 +82,16 @@ export default function HomeContent() { const l1VphaInPha = useMemo(() => { if (l1VphaBalance == null || sharePrice == null) return null - return (l1VphaBalance * sharePrice) / BigInt(1e18) + return new Decimal(formatUnits(l1VphaBalance, 18)).mul( + formatUnits(sharePrice, 18), + ) }, [l1VphaBalance, sharePrice]) const l2VphaInPha = useMemo(() => { if (l2VphaBalance == null || sharePrice == null) return null - return (l2VphaBalance * sharePrice) / BigInt(1e18) + return new Decimal(formatUnits(l2VphaBalance, 18)).mul( + formatUnits(sharePrice, 18), + ) }, [l2VphaBalance, sharePrice]) const totalVphaBalance = useMemo(() => { @@ -94,16 +101,47 @@ export default function HomeContent() { const totalVphaInPha = useMemo(() => { if (l1VphaInPha == null && l2VphaInPha == null) return null - return (l1VphaInPha ?? 0n) + (l2VphaInPha ?? 0n) + return (l1VphaInPha ?? new Decimal(0)).add(l2VphaInPha ?? new Decimal(0)) }, [l1VphaInPha, l2VphaInPha]) const totalPhaValue = useMemo(() => { if (!isValidConnection) return null - const pha = l1PhaBalance ?? 0n - const vpha = totalVphaInPha ?? 0n - return pha + vpha + const pha = new Decimal(formatUnits(l1PhaBalance ?? 0n, 18)) + const vpha = totalVphaInPha ?? new Decimal(0) + return pha.add(vpha) }, [isValidConnection, l1PhaBalance, totalVphaInPha]) + const addPhaToWallet = useCallback(() => { + addTokenToWallet({ + chainId: mainnet.id, + address: PHA_CONTRACT_ADDRESS, + symbol: 'PHA', + image: 'https://app.phala.network/icons/pha.png', + }) + }, [addTokenToWallet]) + + const addL1VphaToWallet = useCallback(() => { + addTokenToWallet({ + chainId: mainnet.id, + address: VAULT_CONTRACT_ADDRESS, + symbol: 'vPHA', + image: 'https://app.phala.network/icons/vpha.png', + }) + }, [addTokenToWallet]) + + const addL2VphaToWallet = useCallback(() => { + addTokenToWallet({ + chainId: phalaNetwork.id, + address: L2_VPHA_CONTRACT_ADDRESS, + symbol: 'vPHA', + image: 'https://app.phala.network/icons/vpha.png', + }) + }, [addTokenToWallet]) + + const addPhalaNetworkToWallet = useCallback(() => { + addNetwork(phalaNetwork.id) + }, [addNetwork]) + return ( @@ -134,6 +172,7 @@ export default function HomeContent() { contractAddress={PHA_CONTRACT_ADDRESS} chainLabel="Ethereum" highlight + onAddToWallet={addPhaToWallet} actions={[ { label: 'Stake', @@ -164,13 +203,14 @@ export default function HomeContent() { chainLabel="Ethereum" subValue={ isValidConnection && l1VphaInPha != null - ? `≈ ${formatUnits(l1VphaInPha, 18).slice(0, 12)} PHA` + ? `≈ ${toCurrency(l1VphaInPha)} PHA` : undefined } + onAddToWallet={addL1VphaToWallet} actions={[ { label: 'Unstake', - href: '/staking', + href: '/staking?tab=unstake', variant: 'outlined', }, { @@ -190,12 +230,15 @@ export default function HomeContent() { balance={isValidConnection ? l2VphaBalance : null} contractAddress={L2_VPHA_CONTRACT_ADDRESS} contractExplorerUrl="https://explorer.phala.network" - chainLabel="Phala L2" + chainLabel="Phala Mainnet" subValue={ isValidConnection && l2VphaInPha != null - ? `≈ ${formatUnits(l2VphaInPha, 18).slice(0, 12)} PHA` + ? `≈ ${toCurrency(l2VphaInPha)} PHA` : undefined } + onAddToWallet={addL2VphaToWallet} + addToWalletLabel="Add vPHA" + onAddNetwork={addPhalaNetworkToWallet} actions={[ { label: 'Bridge to L1', diff --git a/apps/app/app/khala-assets/content.tsx b/apps/app/app/khala-assets/content.tsx index a304eb66..83516f7f 100644 --- a/apps/app/app/khala-assets/content.tsx +++ b/apps/app/app/khala-assets/content.tsx @@ -41,7 +41,7 @@ export default function KhalaAssetsContent() { target="_blank" rel="noopener noreferrer" > - Phala L2 + Phala Mainnet , our new Ethereum Layer 2 network. @@ -134,7 +134,7 @@ export default function KhalaAssetsContent() { - How to transfer vPHA between Ethereum and Phala L2? + How to transfer vPHA between Ethereum and Phala Mainnet? You can use{' '} @@ -145,8 +145,8 @@ export default function KhalaAssetsContent() { > Phala Bridge {' '} - to transfer vPHA tokens between Ethereum (L1) and Phala L2. The - bridge supports bidirectional transfers, allowing you to move + to transfer vPHA tokens between Ethereum (L1) and Phala Mainnet. + The bridge supports bidirectional transfers, allowing you to move assets freely between both networks. diff --git a/apps/app/app/providers.tsx b/apps/app/app/providers.tsx index 2a941067..0b318f29 100644 --- a/apps/app/app/providers.tsx +++ b/apps/app/app/providers.tsx @@ -10,6 +10,7 @@ import {QueryCache, QueryClient} from '@tanstack/react-query' import {ReactQueryDevtools} from '@tanstack/react-query-devtools' import Decimal from 'decimal.js' import {Provider as JotaiProvider} from 'jotai' +import {NuqsAdapter} from 'nuqs/adapters/next/app' import type {ReactNode} from 'react' import {useState} from 'react' import {SWRConfig} from 'swr' @@ -46,32 +47,34 @@ export default function Providers({ ) return ( - { - if (process.env.NODE_ENV === 'development') { - console.error(key, error) - } - }, - }} - > - - - - - - + + { + if (process.env.NODE_ENV === 'development') { + console.error(key, error) + } + }, + }} + > + + + + + + - - - {children} - - - - - - - - + + + {children} + + + + + + + + + ) } diff --git a/apps/app/components/asset-card.tsx b/apps/app/components/asset-card.tsx index 51694292..ed097899 100644 --- a/apps/app/components/asset-card.tsx +++ b/apps/app/components/asset-card.tsx @@ -17,6 +17,7 @@ import type {FC, ReactNode} from 'react' import {formatUnits} from 'viem' import {explorerUrl} from '@/config' +import MetaMaskButton from './metamask-button' export interface AssetAction { label: string @@ -37,6 +38,9 @@ interface AssetCardProps { subValue?: ReactNode actions?: AssetAction[] highlight?: boolean + onAddToWallet?: () => void + addToWalletLabel?: string + onAddNetwork?: () => void } const AssetCard: FC = ({ @@ -50,6 +54,9 @@ const AssetCard: FC = ({ subValue, actions = [], highlight = false, + onAddToWallet, + addToWalletLabel, + onAddNetwork, }) => { const tokenExplorerUrl = contractExplorerUrl ? `${contractExplorerUrl}/token/${contractAddress}` @@ -163,42 +170,44 @@ const AssetCard: FC = ({ - {actions.length > 0 && ( - - {actions.map((action) => ( - - ))} - - )} + + {actions.map((action) => ( + + ))} + {onAddNetwork && ( + + Add Phala Mainnet + + )} + {onAddToWallet && ( + + {addToWalletLabel ?? `Add ${symbol}`} + + )} + ) diff --git a/apps/app/components/chain-badge.tsx b/apps/app/components/chain-badge.tsx new file mode 100644 index 00000000..11886eb8 --- /dev/null +++ b/apps/app/components/chain-badge.tsx @@ -0,0 +1,42 @@ +import {Typography, type TypographyProps} from '@mui/material' +import type {FC} from 'react' + +type ChainType = 'ethereum' | 'phala' + +interface ChainBadgeProps extends Omit { + chain: ChainType +} + +const chainConfig: Record = { + ethereum: { + label: 'Ethereum', + color: 'primary.main', + }, + phala: { + label: 'Phala Mainnet', + color: 'secondary.main', + }, +} + +const ChainBadge: FC = ({chain, ...props}) => { + const config = chainConfig[chain] + + return ( + + {config.label} + + ) +} + +export default ChainBadge diff --git a/apps/app/components/claim-assets.tsx b/apps/app/components/claim-assets.tsx index 618c9cbc..dc97726b 100644 --- a/apps/app/components/claim-assets.tsx +++ b/apps/app/components/claim-assets.tsx @@ -35,6 +35,7 @@ import {useWaitForTransactionReceipt, useWriteContract} from 'wagmi' import khalaClaimerAbi from '@/assets/khala_claimer_abi' import phalaClaimerAbi from '@/assets/phala_claimer_abi' import AppKitButton from '@/components/app-kit-button' +import ChainBadge from '@/components/chain-badge' import Property from '@/components/property' import SwitchChainButton from '@/components/switch-chain-button' import { @@ -363,7 +364,7 @@ const ClaimAssets = ({chain}: {chain: ChainType}) => { color="text.secondary" component="span" > - ({toCurrency(data.staked)} vPHA on Phala L2) + ({toCurrency(data.staked)} vPHA on Phala Mainnet) ) : data ? ( @@ -492,23 +493,11 @@ const ClaimAssets = ({chain}: {chain: ChainType}) => { - - Ethereum - + )} - {/* Staked vPHA to Phala L2 */} + {/* Staked vPHA to Phala Mainnet */} {logData.stakedVPHA.gt(0) && ( { - - Phala L2 - + )} @@ -592,19 +569,7 @@ const ClaimAssets = ({chain}: {chain: ChainType}) => { - - Ethereum - + )} @@ -641,19 +606,7 @@ const ClaimAssets = ({chain}: {chain: ChainType}) => { - - Ethereum - + )} diff --git a/apps/app/components/gradient-text.tsx b/apps/app/components/gradient-text.tsx new file mode 100644 index 00000000..2e7fdd6d --- /dev/null +++ b/apps/app/components/gradient-text.tsx @@ -0,0 +1,32 @@ +import {Typography, type TypographyProps} from '@mui/material' +import type {FC, ReactNode} from 'react' + +interface GradientTextProps extends Omit { + children: ReactNode + gradient?: 'primary' | 'secondary' +} + +const GradientText: FC = ({ + children, + gradient = 'primary', + ...props +}) => { + return ( + ({ + background: + gradient === 'primary' + ? `linear-gradient(90deg, ${theme.palette.primary.main}, ${theme.palette.primary.light})` + : `linear-gradient(90deg, ${theme.palette.secondary.main}, ${theme.palette.secondary.light})`, + backgroundClip: 'text', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + })} + > + {children} + + ) +} + +export default GradientText diff --git a/apps/app/components/metamask-button.tsx b/apps/app/components/metamask-button.tsx new file mode 100644 index 00000000..f9d520c4 --- /dev/null +++ b/apps/app/components/metamask-button.tsx @@ -0,0 +1,36 @@ +import {Button, type ButtonProps} from '@mui/material' +import type {FC, ReactNode} from 'react' + +import MetaMaskIcon from './metamask-icon' + +interface MetaMaskButtonProps extends Omit { + children: ReactNode +} + +const MetaMaskButton: FC = ({children, sx, ...props}) => { + return ( + + ) +} + +export default MetaMaskButton diff --git a/apps/app/components/metamask-icon.tsx b/apps/app/components/metamask-icon.tsx new file mode 100644 index 00000000..da3f3e37 --- /dev/null +++ b/apps/app/components/metamask-icon.tsx @@ -0,0 +1,163 @@ +import type {FC} from 'react' + +interface MetaMaskIconProps { + size?: number +} + +const MetaMaskIcon: FC = ({size = 14}) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +) + +export default MetaMaskIcon diff --git a/apps/app/components/portfolio-summary.tsx b/apps/app/components/portfolio-summary.tsx index fe8a723f..962ce5b0 100644 --- a/apps/app/components/portfolio-summary.tsx +++ b/apps/app/components/portfolio-summary.tsx @@ -22,9 +22,9 @@ import {formatUnits} from 'viem' import {ethChain} from '@/config' interface PortfolioSummaryProps { - totalPhaValue: bigint | null + totalPhaValue: Decimal | null totalStakedVpha: bigint | null - totalStakedInPha: bigint | null + totalStakedInPha: Decimal | null stakingApr: Decimal | null isConnected: boolean } @@ -124,9 +124,7 @@ const PortfolioSummary: FC = ({ WebkitTextFillColor: 'transparent', }} > - {totalPhaValue != null - ? toCurrency(formatUnits(totalPhaValue, 18)) - : '-'} + {totalPhaValue != null ? toCurrency(totalPhaValue) : '-'} PHA @@ -152,7 +150,7 @@ const PortfolioSummary: FC = ({ {totalStakedInPha != null - ? `≈ ${toCurrency(formatUnits(totalStakedInPha, 18))} PHA` + ? `≈ ${toCurrency(totalStakedInPha)} PHA` : '-'} {stakingApr != null && ( diff --git a/apps/app/components/staking/stake.tsx b/apps/app/components/staking/stake.tsx index c6ce630f..2fdf67b7 100644 --- a/apps/app/components/staking/stake.tsx +++ b/apps/app/components/staking/stake.tsx @@ -5,6 +5,7 @@ import { Box, Button, Chip, + IconButton, OutlinedInput, Paper, Slider, @@ -13,6 +14,7 @@ import { Tabs, ToggleButton, ToggleButtonGroup, + Tooltip, Typography, } from '@mui/material' import {getDecimalPattern, toCurrency, trimAddress} from '@phala/lib' @@ -22,8 +24,10 @@ import {formatDuration, intervalToDuration} from 'date-fns' import Decimal from 'decimal.js' import Image from 'next/image' import {useSnackbar} from 'notistack' +import {parseAsStringLiteral, useQueryState} from 'nuqs' import {useCallback, useEffect, useMemo, useState} from 'react' import {erc20Abi, formatUnits, parseUnits} from 'viem' +import {mainnet} from 'viem/chains' import {useWaitForTransactionReceipt, useWriteContract} from 'wagmi' import vaultAbi from '@/assets/pha_vault_abi' @@ -43,14 +47,20 @@ import { useUnlockPeriod, useUnlockRequests, } from '@/hooks/staking' +import {useAddTokenToWallet} from '@/hooks/use-add-token-to-wallet' import {useValidConnection} from '@/hooks/use-valid-connection' +import MetaMaskIcon from '../metamask-icon' const oneUnit = parseUnits('1', 18) +const tabParser = parseAsStringLiteral(['stake', 'unstake']).withDefault( + 'stake', +) + const Stake = () => { - const [tab, setTab] = useState(0) - const isStake = tab === 0 - const isUnstake = tab === 1 + const [tab, setTab] = useQueryState('tab', tabParser) + const isStake = tab === 'stake' + const isUnstake = tab === 'unstake' const [useDex, setUseDex] = useState(false) const tokenContractAddress = useMemo(() => { if (isStake) { @@ -60,6 +70,18 @@ const Stake = () => { }, [isStake]) const {enqueueSnackbar} = useSnackbar() const {address, isValidConnection} = useValidConnection() + const {addTokenToWallet: addToken} = useAddTokenToWallet() + + const addTokenToWallet = useCallback(() => { + addToken({ + chainId: mainnet.id, + address: tokenContractAddress, + symbol: isStake ? 'PHA' : 'vPHA', + image: isStake + ? 'https://app.phala.network/icons/pha.png' + : 'https://app.phala.network/icons/vpha.png', + }) + }, [addToken, tokenContractAddress, isStake]) const shareRate = useSharesToAssets(oneUnit) const assetRate = useAssetsToShares(oneUnit) @@ -239,14 +261,14 @@ const Stake = () => { { + onChange={(_, value: 'stake' | 'unstake') => { setTab(value) setAmountString('') }} sx={{width: 1}} > - - + + { target="_blank" sx={{display: {xs: 'none', sm: 'inline-flex'}}} /> + + + + + {balance != null ? toCurrency(formatUnits(balance, 18)) : '-'} diff --git a/apps/app/hooks/use-add-token-to-wallet.ts b/apps/app/hooks/use-add-token-to-wallet.ts new file mode 100644 index 00000000..38d820aa --- /dev/null +++ b/apps/app/hooks/use-add-token-to-wallet.ts @@ -0,0 +1,58 @@ +import {useCallback} from 'react' +import {useSwitchChain, useWatchAsset} from 'wagmi' + +interface TokenInfo { + chainId: number + address: `0x${string}` + symbol: string + decimals?: number + image?: string +} + +export function useAddTokenToWallet() { + const {switchChainAsync} = useSwitchChain() + const {watchAsset} = useWatchAsset() + + const addTokenToWallet = useCallback( + async ({chainId, address, symbol, decimals = 18, image}: TokenInfo) => { + try { + await switchChainAsync({chainId}) + } catch (error) { + console.error('Failed to switch network:', error) + return false + } + + try { + watchAsset({ + type: 'ERC20', + options: { + address, + symbol, + decimals, + image, + }, + }) + return true + } catch (error) { + console.error('Failed to add token to wallet:', error) + return false + } + }, + [switchChainAsync, watchAsset], + ) + + const addNetwork = useCallback( + async (chainId: number) => { + try { + await switchChainAsync({chainId}) + return true + } catch (error) { + console.error('Failed to add network to wallet:', error) + return false + } + }, + [switchChainAsync], + ) + + return {addTokenToWallet, addNetwork} +} diff --git a/apps/app/package.json b/apps/app/package.json index d8bdd2d3..f6e979f0 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -34,6 +34,7 @@ "jotai-devtools": "^0.13.0", "next": "^16.0.4", "notistack": "^3.0.2", + "nuqs": "^2.8.1", "react": "^19.2.0", "react-dom": "^19.2.0", "sharp": "^0.34.5", diff --git a/bun.lock b/bun.lock index 73379a45..5104a591 100644 --- a/bun.lock +++ b/bun.lock @@ -43,6 +43,7 @@ "jotai-devtools": "^0.13.0", "next": "^16.0.4", "notistack": "^3.0.2", + "nuqs": "^2.8.1", "react": "^19.2.0", "react-dom": "^19.2.0", "sharp": "^0.34.5", @@ -627,6 +628,8 @@ "@solana/web3.js": ["@solana/web3.js@1.98.4", "", { "dependencies": { "@babel/runtime": "^7.25.0", "@noble/curves": "^1.4.2", "@noble/hashes": "^1.4.0", "@solana/buffer-layout": "^4.0.1", "@solana/codecs-numbers": "^2.1.0", "agentkeepalive": "^4.5.0", "bn.js": "^5.2.1", "borsh": "^0.7.0", "bs58": "^4.0.1", "buffer": "6.0.3", "fast-stable-stringify": "^1.0.0", "jayson": "^4.1.1", "node-fetch": "^2.7.0", "rpc-websockets": "^9.0.2", "superstruct": "^2.0.2" } }, "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@substrate/connect": ["@substrate/connect@0.8.11", "", { "dependencies": { "@substrate/connect-extension-protocol": "^2.0.0", "@substrate/connect-known-chains": "^1.1.5", "@substrate/light-client-extension-helpers": "^1.0.0", "smoldot": "2.0.26" } }, "sha512-ofLs1PAO9AtDdPbdyTYj217Pe+lBfTLltdHDs3ds8no0BseoLeAGxpz1mHfi7zB4IxI3YyAiLjH6U8cw4pj4Nw=="], "@substrate/connect-extension-protocol": ["@substrate/connect-extension-protocol@2.2.1", "", {}, "sha512-GoafTgm/Jey9E4Xlj4Z5ZBt/H4drH2CNq8VrAro80rtoznrXnFDNVivLQzZN0Xaj2g8YXSn9pC9Oc9IovYZJXw=="], @@ -1207,6 +1210,8 @@ "notistack": ["notistack@3.0.2", "", { "dependencies": { "clsx": "^1.1.0", "goober": "^2.0.33" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-0R+/arLYbK5Hh7mEfR2adt0tyXJcCC9KkA2hc56FeWik2QN6Bm/S4uW+BjzDARsJth5u06nTjelSw/VSnB1YEA=="], + "nuqs": ["nuqs@2.8.1", "", { "dependencies": { "@standard-schema/spec": "1.0.0" }, "peerDependencies": { "@remix-run/react": ">=2", "@tanstack/react-router": "^1", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router": "^5 || ^6 || ^7", "react-router-dom": "^5 || ^6 || ^7" }, "optionalPeers": ["@remix-run/react", "@tanstack/react-router", "next", "react-router", "react-router-dom"] }, "sha512-kIw8UW5KXXfVla6B9h0EKzSH/YDpee6lojniQoMyul8wq9brwV0kElE2Jzg4NSCLBo7n2E3wc1J3o7IyPYVlqQ=="], + "obj-multiplex": ["obj-multiplex@1.0.0", "", { "dependencies": { "end-of-stream": "^1.4.0", "once": "^1.4.0", "readable-stream": "^2.3.3" } }, "sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],