mirror of
https://github.com/status-im/status-web.git
synced 2026-08-31 06:11:10 +00:00
add new wallet flow (#696)
Co-authored-by: Felicio <felicio@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@status-im/wallet': patch
|
||||
'wallet': patch
|
||||
---
|
||||
|
||||
add new wallet flow
|
||||
Vendored
+3
-1
@@ -7,6 +7,8 @@
|
||||
"mikestead.dotenv",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"vitest.explorer",
|
||||
"github.vscode-github-actions"
|
||||
"github.vscode-github-actions",
|
||||
"eamodio.gitlens",
|
||||
"github.vscode-pull-request-github"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { Navbar as NavbarBase } from '@status-im/wallet/components'
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
const Navbar = () => {
|
||||
const pathname = usePathname()
|
||||
|
||||
return <NavbarBase pathname={pathname} />
|
||||
return <NavbarBase />
|
||||
}
|
||||
|
||||
export { Navbar }
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"@cardano-sdk/core": "^0.45.4",
|
||||
"@cardano-sdk/crypto": "^0.2.3",
|
||||
"@cardano-sdk/key-management": "^0.27.5",
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@status-im/colors": "workspace:*",
|
||||
"@status-im/components": "workspace:*",
|
||||
|
||||
@@ -573,7 +573,7 @@ const apiRouter = router({
|
||||
// )
|
||||
|
||||
const { id } = await keyStore.importKey(
|
||||
Buffer.from(input.privateKey),
|
||||
new Uint8Array(Buffer.from(input.privateKey)),
|
||||
input.name,
|
||||
input.password,
|
||||
walletCore.CoinType.ethereum,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
import { useAPI } from '../providers/api-client'
|
||||
|
||||
export const useCreateWallet = () => {
|
||||
const api = useAPI()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const { mutate, mutateAsync, ...result } = useMutation({
|
||||
mutationKey: ['create-wallet'],
|
||||
@@ -15,6 +16,9 @@ export const useCreateWallet = () => {
|
||||
|
||||
return mnemonic
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['wallets'] })
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
import { useAPI } from '../providers/api-client'
|
||||
|
||||
export const useImportWallet = () => {
|
||||
const api = useAPI()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const { mutate, mutateAsync, ...result } = useMutation({
|
||||
mutationKey: ['import-wallet'],
|
||||
@@ -20,6 +21,9 @@ export const useImportWallet = () => {
|
||||
name: 'Imported Wallet',
|
||||
})
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['wallets'] })
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export const usePinExtension = () => {
|
||||
const [isPinExtension, setIsPinExtension] = useState<boolean>(false)
|
||||
|
||||
const handleClose = async () => {
|
||||
setIsPinExtension(false)
|
||||
await chrome.storage.local.set({ pinExtension: true })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function checkSettings() {
|
||||
const storage = await chrome.storage.local.get(['pinExtension'])
|
||||
if (storage.pinExtension) {
|
||||
setIsPinExtension(false)
|
||||
return
|
||||
}
|
||||
|
||||
const settings = await chrome.action.getUserSettings()
|
||||
setIsPinExtension(!settings.isOnToolbar)
|
||||
}
|
||||
|
||||
checkSettings()
|
||||
}, [])
|
||||
|
||||
return { isPinExtension, handleClose }
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { apiClient } from './api-client'
|
||||
|
||||
import type { KeyStore } from '@trustwallet/wallet-core'
|
||||
|
||||
type Wallet = KeyStore.Wallet
|
||||
|
||||
type WalletContext = {
|
||||
currentWallet: Wallet | null
|
||||
wallets: Wallet[]
|
||||
isLoading: boolean
|
||||
hasWallets: boolean
|
||||
setCurrentWallet: (id: Wallet['id']) => void
|
||||
}
|
||||
|
||||
const WalletContext = createContext<WalletContext | undefined>(undefined)
|
||||
|
||||
export function useWallet() {
|
||||
const context = useContext(WalletContext)
|
||||
if (!context) {
|
||||
throw new Error('useWallet must be used within WalletProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export function WalletProvider({ children }: { children: React.ReactNode }) {
|
||||
const [selectedWalletId, setSelectedWalletId] = useState<string | null>(null)
|
||||
|
||||
const { data: wallets = [], isLoading } = useQuery({
|
||||
queryKey: ['wallets'],
|
||||
queryFn: () => apiClient.wallet.all.query(),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
})
|
||||
|
||||
const hasWallets = wallets.length > 0
|
||||
|
||||
const currentWallet = useMemo(() => {
|
||||
if (!hasWallets) return null
|
||||
|
||||
if (selectedWalletId) {
|
||||
const selectedWallet = wallets.find(
|
||||
wallet => wallet.id === selectedWalletId,
|
||||
)
|
||||
if (selectedWallet) return selectedWallet
|
||||
}
|
||||
|
||||
return wallets[0] || null
|
||||
}, [hasWallets, selectedWalletId, wallets])
|
||||
|
||||
useEffect(() => {
|
||||
if (hasWallets && !selectedWalletId && wallets[0]) {
|
||||
setSelectedWalletId(wallets[0].id)
|
||||
}
|
||||
}, [hasWallets, selectedWalletId, wallets])
|
||||
|
||||
const setCurrentWallet = (id: string) => {
|
||||
setSelectedWalletId(id)
|
||||
}
|
||||
|
||||
const contextValue: WalletContext = {
|
||||
currentWallet,
|
||||
wallets,
|
||||
isLoading,
|
||||
hasWallets,
|
||||
setCurrentWallet,
|
||||
}
|
||||
|
||||
return (
|
||||
<WalletContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</WalletContext.Provider>
|
||||
)
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@@ -19,6 +19,7 @@ import { TanStackRouterDevtools } from '@tanstack/router-devtools'
|
||||
// import { QueryClientProvider } from '../../../portfolio/src/app/_providers/query-client-provider'
|
||||
// import { StatusProvider } from '../../../portfolio/src/app/_providers/status-provider'
|
||||
import { WagmiProvider } from '../../../portfolio/src/app/_providers/wagmi-provider'
|
||||
import { WalletProvider } from '../providers/wallet-context'
|
||||
|
||||
// import { Inter } from 'next/font/google'
|
||||
import type { QueryClient } from '@tanstack/react-query'
|
||||
@@ -69,26 +70,28 @@ function RootComponent() {
|
||||
<head>
|
||||
<HeadContent />
|
||||
</head>
|
||||
<div id="app" className="isolate">
|
||||
<div id="app" className="isolate" data-customisation="blue">
|
||||
{/* <StatusProvider> */}
|
||||
<WagmiProvider>
|
||||
{/* <QueryClientProvider> */}
|
||||
{/* <Suspense fallback={<div>Loading...</div>}> */}
|
||||
{/* <AccountsProvider> */}
|
||||
{/* <ConnectKitProvider> */}
|
||||
<div className="flex min-h-[56px] items-center px-2">
|
||||
<Navbar pathname={pathname} />
|
||||
</div>
|
||||
<div className="px-1">
|
||||
<div className="flex-1 flex-col 2md:flex xl:pb-1">
|
||||
<div className="flex h-[calc(100vh-60px)] flex-col overflow-clip rounded-[24px] bg-white-100">
|
||||
{/* <OnboardingPage /> */}
|
||||
<Outlet />
|
||||
</div>
|
||||
<WalletProvider>
|
||||
<div className="flex min-h-[56px] items-center px-2">
|
||||
<Navbar pathname={pathname} />
|
||||
</div>
|
||||
{/* <NotAllowed /> */}
|
||||
<ToastContainer />
|
||||
</div>
|
||||
<div className="px-1">
|
||||
<div className="flex-1 flex-col 2md:flex xl:pb-1">
|
||||
<div className="flex h-[calc(100vh-60px)] flex-col overflow-y-auto rounded-[24px] bg-white-100">
|
||||
{/* <OnboardingPage /> */}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
{/* <NotAllowed /> */}
|
||||
<ToastContainer />
|
||||
</div>
|
||||
</WalletProvider>
|
||||
{/* </ConnectKitProvider> */}
|
||||
{/* </AccountsProvider> */}
|
||||
{/* </Suspense> */}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { apiClient } from '../providers/api-client'
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: RouteComponent,
|
||||
@@ -9,6 +11,12 @@ export const Route = createFileRoute('/')({
|
||||
},
|
||||
],
|
||||
}),
|
||||
beforeLoad: async () => {
|
||||
const wallets = await apiClient.wallet.all.query()
|
||||
if (wallets && wallets.length > 0) {
|
||||
throw redirect({ to: '/portfolio' })
|
||||
} else throw redirect({ to: '/onboarding' })
|
||||
},
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
||||
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { apiClient } from '../../providers/api-client'
|
||||
|
||||
export const Route = createFileRoute('/onboarding')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: () => {
|
||||
// TODO: check if user is already onboarded
|
||||
// throw redirect({ to: '/' })
|
||||
beforeLoad: async () => {
|
||||
const wallets = await apiClient.wallet.all.query()
|
||||
if (wallets && wallets.length > 0) {
|
||||
throw redirect({ to: '/portfolio' })
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <Outlet />
|
||||
return (
|
||||
<div className="m-auto min-h-[650px] w-full max-w-[440px] rounded-[24px] border border-neutral-5 p-5 shadow-2">
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createFileRoute, Link } from '@tanstack/react-router'
|
||||
import { Button, Text } from '@status-im/components'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/onboarding/')({
|
||||
component: RouteComponent,
|
||||
@@ -13,19 +14,49 @@ export const Route = createFileRoute('/onboarding/')({
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<Link
|
||||
to="/onboarding/new"
|
||||
className="flex h-10 items-center rounded-12 bg-neutral-50 px-3 text-15 font-medium transition-colors active:bg-neutral-50 hover:bg-neutral-90"
|
||||
>
|
||||
Create a wallet
|
||||
</Link>
|
||||
<Link
|
||||
to="/onboarding/import"
|
||||
className="flex h-10 items-center rounded-12 bg-neutral-50 px-3 text-15 font-medium transition-colors active:bg-neutral-50 hover:bg-neutral-90"
|
||||
>
|
||||
I already have a wallet
|
||||
</Link>
|
||||
<div className="flex flex-col items-center gap-6 py-3 text-center">
|
||||
<img
|
||||
src="/images/onboarding.png"
|
||||
alt="Onboarding"
|
||||
className="size-[264px]"
|
||||
/>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Text size={40} weight="bold">
|
||||
Your Wallet.
|
||||
<br />
|
||||
Your crypto.
|
||||
</Text>
|
||||
<Text size={15} color="$neutral-50">
|
||||
Some awesome sub copy
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex w-full max-w-[270px] flex-col gap-3">
|
||||
<Button href="/onboarding/new">New wallet</Button>
|
||||
<Button href="/onboarding/import" variant="grey">
|
||||
Import wallet
|
||||
</Button>
|
||||
</div>
|
||||
<Text size={13} color="$neutral-50">
|
||||
By continuing you agree with Status
|
||||
<br />
|
||||
<a
|
||||
href="https://github.com/status-im/status-software-legal-documents/blob/master/terms-of-use.md"
|
||||
className="text-neutral-100"
|
||||
target="_blank"
|
||||
rel="noopenernoreferrer"
|
||||
>
|
||||
Terms of use
|
||||
</a>{' '}
|
||||
and{' '}
|
||||
<a
|
||||
href="https://github.com/status-im/status-software-legal-documents/blob/master/privacy-policy.md"
|
||||
className="text-neutral-100"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Privacy policy
|
||||
</a>
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,123 +1,55 @@
|
||||
import { useState } from 'react'
|
||||
import { useTransition } from 'react'
|
||||
|
||||
import { Button, Input } from '@status-im/components'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { Button } from '@status-im/components'
|
||||
import { ArrowLeftIcon } from '@status-im/icons/20'
|
||||
import { CreatePasswordForm } from '@status-im/wallet/components'
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
||||
|
||||
import { useCreateWallet } from '../../hooks/use-create-wallet'
|
||||
|
||||
import type { CreatePasswordFormValues } from '@status-im/wallet/components'
|
||||
import type { SubmitHandler } from 'react-hook-form'
|
||||
|
||||
export const Route = createFileRoute('/onboarding/new')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
type OnboardingState =
|
||||
| { type: 'create-password' }
|
||||
| { type: 'recovery-phrase'; mnemonic: string }
|
||||
|
||||
function RouteComponent() {
|
||||
const [onboardingState, setOnboardingState] = useState<OnboardingState>({
|
||||
type: 'create-password',
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{onboardingState.type === 'create-password' && (
|
||||
<CreatePassword
|
||||
onNext={mnemonic =>
|
||||
setOnboardingState({ type: 'recovery-phrase', mnemonic })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{onboardingState.type === 'recovery-phrase' && (
|
||||
<RecoveryPhrase mnemonic={onboardingState.mnemonic} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CreatePassword({ onNext }: { onNext: (wallet: string) => void }) {
|
||||
const {
|
||||
register,
|
||||
// handleSubmit,
|
||||
formState: { errors },
|
||||
watch,
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
})
|
||||
|
||||
const { createWalletAsync } = useCreateWallet()
|
||||
const navigate = useNavigate()
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
||||
// const onSubmit = handleSubmit(async data => {
|
||||
// // const wallet = await createWalletAsync(data.password)
|
||||
// // console.log(wallet.mnemonic().split(' '))
|
||||
// // onNext(wallet)
|
||||
// })
|
||||
const handleSubmit: SubmitHandler<CreatePasswordFormValues> = async data => {
|
||||
try {
|
||||
startTransition(async () => {
|
||||
await createWalletAsync(data.password)
|
||||
navigate({ to: '/portfolio' })
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* @ts-expect-error: fixme: Types of property 'onChange' are incompatible. */}
|
||||
<Input
|
||||
label="New password"
|
||||
type="password"
|
||||
placeholder="New password"
|
||||
{...register('password', {
|
||||
required: 'Password is required',
|
||||
minLength: {
|
||||
value: 8,
|
||||
message: 'Password must be at least 8 characters',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.password && (
|
||||
<p className="text-13 text-danger-50">{errors.password.message}</p>
|
||||
)}
|
||||
{/* @ts-expect-error: fixme: Types of property 'onChange' are incompatible. */}
|
||||
<Input
|
||||
label="Confirm password"
|
||||
type="password"
|
||||
placeholder="Confirm password"
|
||||
{...register('confirmPassword', {
|
||||
required: 'Please confirm your password',
|
||||
validate: value =>
|
||||
value === watch('password') || 'Passwords do not match',
|
||||
})}
|
||||
/>
|
||||
{errors.confirmPassword && (
|
||||
<p className="text-13 text-danger-50">
|
||||
{errors.confirmPassword.message}
|
||||
</p>
|
||||
)}
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={async () => {
|
||||
const wallet = await createWalletAsync('password')
|
||||
console.log(wallet)
|
||||
onNext(wallet)
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="flex h-full">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<Button
|
||||
href="/onboarding"
|
||||
variant="grey"
|
||||
icon={<ArrowLeftIcon color="$neutral-100" />}
|
||||
aria-label="Back"
|
||||
size="32"
|
||||
/>
|
||||
</div>
|
||||
<h1 className="text-27 font-600">Create password</h1>
|
||||
<div className="text-15 text-neutral-50">
|
||||
To unlock the extension and sign transactions, the password is stored
|
||||
only on your device. Status can't recover it.
|
||||
</div>
|
||||
|
||||
function RecoveryPhrase({ mnemonic }: { mnemonic: string }) {
|
||||
return (
|
||||
<div>
|
||||
<p>Write down your recovery phrase</p>
|
||||
<p>Read the following carefully before continuing</p>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4 rounded-8 bg-neutral-90 p-4">
|
||||
{mnemonic.split(' ').map((word, i) => (
|
||||
<div key={i} className="flex items-center gap-2">
|
||||
<span className="text-neutral-40">{i + 1}.</span>
|
||||
<span className="font-mono">{word}</span>
|
||||
</div>
|
||||
))}
|
||||
<CreatePasswordForm onSubmit={handleSubmit} loading={isPending} />
|
||||
</div>
|
||||
|
||||
<Button variant="danger">Continue</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
// import { Suspense } from 'react'
|
||||
|
||||
import { AssetsList } from '@status-im/wallet/components'
|
||||
import { AssetsList, PinExtension } from '@status-im/wallet/components'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { usePinExtension } from '@/hooks/use-pin-extension'
|
||||
|
||||
import { apiClient } from '../../providers/api-client'
|
||||
import { useWallet } from '../../providers/wallet-context'
|
||||
|
||||
// import { DetailDrawer } from '../../../../portfolio/src/app/[address]/@detail/_drawer'
|
||||
// import { Loading as LoadingNav } from '../../../../portfolio/src/app/[address]/@nav/loading'
|
||||
|
||||
export const Route = createFileRoute('/portfolio/')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: async () => {
|
||||
const wallets = await apiClient.wallet.all.query()
|
||||
if (!wallets || wallets.length === 0) {
|
||||
throw redirect({ to: '/onboarding' })
|
||||
}
|
||||
},
|
||||
head: () => ({
|
||||
meta: [
|
||||
{
|
||||
@@ -19,6 +30,9 @@ export const Route = createFileRoute('/portfolio/')({
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const { currentWallet, isLoading: isWalletLoading } = useWallet()
|
||||
const { isPinExtension, handleClose } = usePinExtension()
|
||||
|
||||
const handleSelect = (url: string, options?: { scroll?: boolean }) => {
|
||||
// Handle the selection of an asset
|
||||
console.log('Selected asset URL:', url)
|
||||
@@ -28,8 +42,12 @@ function RouteComponent() {
|
||||
// todo: export trpc client with api router and used instead
|
||||
// todo: cache
|
||||
const { data: assets, isLoading } = useQuery({
|
||||
queryKey: ['assets'],
|
||||
queryKey: ['assets', currentWallet?.activeAccounts[0].address],
|
||||
queryFn: async () => {
|
||||
if (!currentWallet?.activeAccounts[0].address) {
|
||||
throw new Error('No wallet address available')
|
||||
}
|
||||
|
||||
const url = new URL(
|
||||
`${import.meta.env.WXT_STATUS_API_URL}/api/trpc/assets.all`,
|
||||
)
|
||||
@@ -38,7 +56,7 @@ function RouteComponent() {
|
||||
// encodeURIComponent(
|
||||
JSON.stringify({
|
||||
json: {
|
||||
address: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
|
||||
address: currentWallet.activeAccounts[0].address,
|
||||
networks: [
|
||||
'ethereum',
|
||||
'optimism',
|
||||
@@ -52,7 +70,6 @@ function RouteComponent() {
|
||||
// ),
|
||||
)
|
||||
|
||||
// note: http://localhost:3030/api/trpc/assets.all?input={"json":{"address":"0xd8da6bf26964af9d7eed9e03e53415d37aa96045","networks":["ethereum","optimism","arbitrum","base","polygon","bsc"]}}
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@@ -61,13 +78,13 @@ function RouteComponent() {
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch.')
|
||||
throw new Error('Failed to fetch assets.')
|
||||
}
|
||||
|
||||
const body = await response.json()
|
||||
|
||||
return body.result.data.json.assets
|
||||
},
|
||||
enabled: !!currentWallet?.activeAccounts[0].address && !isWalletLoading,
|
||||
staleTime: 60 * 60 * 1000, // 1 hour
|
||||
gcTime: 60 * 60 * 1000, // 1 hour
|
||||
refetchOnMount: false,
|
||||
@@ -75,12 +92,16 @@ function RouteComponent() {
|
||||
refetchOnReconnect: false,
|
||||
})
|
||||
|
||||
if (!currentWallet) {
|
||||
return <div>No wallet selected</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full overflow-hidden px-1">
|
||||
<div className="grid flex-1 grid-cols-1 divide-x divide-neutral-10 overflow-hidden xl:grid-cols-[auto_1fr]">
|
||||
<div className="flex divide-x divide-default-neutral-20 overflow-auto">
|
||||
<div className="flex w-full">
|
||||
{isLoading ? (
|
||||
{isWalletLoading || isLoading ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<AssetsList
|
||||
@@ -99,6 +120,11 @@ function RouteComponent() {
|
||||
<div className="hidden basis-1/2 flex-col xl:flex">Detail</div>
|
||||
</div>
|
||||
</div>
|
||||
{isPinExtension && (
|
||||
<div className="absolute right-5 top-20">
|
||||
<PinExtension onClose={handleClose} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/async-retry": "^1.4.9",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
@@ -67,23 +67,27 @@
|
||||
"@status-im/colors": "workspace:*",
|
||||
"@status-im/components": "workspace:*",
|
||||
"@status-im/icons": "workspace:*",
|
||||
"@trpc/react-query": "10.45.2",
|
||||
"@trpc/server": "10.45.2",
|
||||
"@types/async-retry": "^1.4.9",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"async-retry": "^1.3.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"cva": "^1.0.0-beta.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"@trpc/server": "10.45.2",
|
||||
"@trpc/react-query": "10.45.2",
|
||||
"next": "15.1.6",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react-aria-components": "^1.3.3",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-hook-form": "^7.57.0",
|
||||
"react-swipeable": "^7.0.1",
|
||||
"superjson": "^2.2.1",
|
||||
"ts-pattern": "^5.3.1",
|
||||
"zustand": "^4.3.7",
|
||||
"zod": "^3.23.8"
|
||||
"zod": "^3.23.8",
|
||||
"zustand": "^4.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hookform/devtools": "^4.3.1",
|
||||
"@status-im/eslint-config": "workspace:*",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.1",
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import {
|
||||
Button,
|
||||
// , Switch
|
||||
} from '@status-im/components'
|
||||
import { AlertIcon, InfoIcon, PositiveStateIcon } from '@status-im/icons/16'
|
||||
import { LoadingIcon } from '@status-im/icons/20'
|
||||
import { FormProvider, useForm } from 'react-hook-form'
|
||||
import { z } from 'zod'
|
||||
|
||||
import {
|
||||
MAX_PASSWORD_LENGTH,
|
||||
MIN_PASSWORD_LENGTH,
|
||||
PasswordInput,
|
||||
} from '../password-input'
|
||||
import { PasswordStrength } from '../password-strength'
|
||||
|
||||
const passwordSchema = z
|
||||
.string()
|
||||
.min(
|
||||
MIN_PASSWORD_LENGTH,
|
||||
`Password must be at least ${MIN_PASSWORD_LENGTH} characters long`,
|
||||
)
|
||||
.max(
|
||||
MAX_PASSWORD_LENGTH,
|
||||
`Password must not exceed ${MAX_PASSWORD_LENGTH} characters`,
|
||||
)
|
||||
.refine(password => /[a-z]/.test(password), {
|
||||
message: 'Password must contain at least one lowercase letter',
|
||||
})
|
||||
.refine(password => /[A-Z]/.test(password), {
|
||||
message: 'Password must contain at least one uppercase letter',
|
||||
})
|
||||
.refine(password => /[0-9]/.test(password), {
|
||||
message: 'Password must contain at least one number',
|
||||
})
|
||||
.refine(password => /[^a-zA-Z0-9]/.test(password), {
|
||||
message: 'Password must contain at least one symbol',
|
||||
})
|
||||
|
||||
const createPasswordSchema = z
|
||||
.object({
|
||||
password: passwordSchema,
|
||||
confirmPassword: z.string(),
|
||||
isDefaultWallet: z.boolean().default(true),
|
||||
})
|
||||
.refine(data => data.password === data.confirmPassword, {
|
||||
message: "Passwords don't match",
|
||||
path: ['confirmPassword'],
|
||||
})
|
||||
|
||||
type FormValues = z.infer<typeof createPasswordSchema>
|
||||
|
||||
type CreatePasswordFormProps = {
|
||||
onSubmit: (data: FormValues) => void
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const CreatePasswordForm = ({
|
||||
onSubmit,
|
||||
loading = false,
|
||||
}: CreatePasswordFormProps) => {
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(createPasswordSchema),
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
isDefaultWallet: true,
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { errors },
|
||||
} = form
|
||||
|
||||
const isPasswordValid = form.watch('password').length >= MIN_PASSWORD_LENGTH
|
||||
const doPasswordsMatch =
|
||||
form.watch('password') === form.watch('confirmPassword')
|
||||
const confirmPassword = form.watch('confirmPassword')
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="flex flex-1 flex-col"
|
||||
>
|
||||
<div className="flex flex-1 flex-col gap-4">
|
||||
<div>
|
||||
<PasswordInput
|
||||
name="password"
|
||||
label="Password"
|
||||
placeholder="Type password"
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`mt-2 flex items-center gap-1 text-13 ${
|
||||
errors.password
|
||||
? 'text-danger-50'
|
||||
: isPasswordValid
|
||||
? 'text-success-50'
|
||||
: 'text-neutral-50'
|
||||
}`}
|
||||
>
|
||||
{errors.password ? (
|
||||
<>
|
||||
<AlertIcon /> {errors.password.message}
|
||||
</>
|
||||
) : isPasswordValid ? (
|
||||
<>
|
||||
<PositiveStateIcon /> Minimum {MIN_PASSWORD_LENGTH} characters
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<InfoIcon /> Minimum {MIN_PASSWORD_LENGTH} characters
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<PasswordInput
|
||||
name="confirmPassword"
|
||||
label="Confirm password"
|
||||
placeholder="Repeat password"
|
||||
/>
|
||||
|
||||
{(confirmPassword || errors.confirmPassword) && (
|
||||
<div
|
||||
className={`mt-2 flex items-center gap-1 text-13 ${
|
||||
errors.confirmPassword
|
||||
? 'text-danger-50'
|
||||
: doPasswordsMatch
|
||||
? 'text-success-50'
|
||||
: 'text-danger-50'
|
||||
}`}
|
||||
>
|
||||
{errors.confirmPassword ? (
|
||||
<>
|
||||
<AlertIcon /> {errors.confirmPassword.message}
|
||||
</>
|
||||
) : doPasswordsMatch ? (
|
||||
<>
|
||||
<PositiveStateIcon /> Passwords match
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<AlertIcon /> Passwords do not match
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-auto flex flex-col gap-5">
|
||||
<PasswordStrength password={form.watch('password')} />
|
||||
{/* <div className="flex w-full items-center gap-2 rounded-12 border border-neutral-20 bg-neutral-5 px-4 py-3 text-13">
|
||||
<div className="text-13">
|
||||
Set status as your default wallet to ensure seamless dApp
|
||||
connections
|
||||
</div>
|
||||
<Switch
|
||||
{...form.register('isDefaultWallet')}
|
||||
checked={form.watch('isDefaultWallet')}
|
||||
onCheckedChange={value =>
|
||||
form.setValue('isDefaultWallet', value)
|
||||
}
|
||||
/>
|
||||
</div> */}
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={
|
||||
!isPasswordValid ||
|
||||
!doPasswordsMatch ||
|
||||
!!form.formState.errors.password ||
|
||||
!!form.formState.errors.confirmPassword
|
||||
}
|
||||
>
|
||||
{loading ? (
|
||||
<LoadingIcon className="animate-spin text-white-100" />
|
||||
) : (
|
||||
'Continue'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export { CreatePasswordForm }
|
||||
export type { FormValues as CreatePasswordFormValues }
|
||||
@@ -3,6 +3,10 @@ export * from '../utils/variants'
|
||||
export { AccountMenu } from './account-menu'
|
||||
export { type Account, Address, type AddressProps } from './address'
|
||||
export { AssetsList } from './assets-list'
|
||||
export {
|
||||
CreatePasswordForm,
|
||||
type CreatePasswordFormValues,
|
||||
} from './create-password-form'
|
||||
export { CurrencyAmount } from './currency-amount'
|
||||
export { DeleteAddressAlert } from './delete-address-alert'
|
||||
export { Image, type ImageProps } from './image'
|
||||
@@ -11,6 +15,7 @@ export { Navbar } from './nav-bar'
|
||||
export { NetworkExplorerLogo } from './network-explorer-logo'
|
||||
export { NetworkLogo } from './network-logo'
|
||||
export { PercentageChange } from './percentage-change'
|
||||
export { PinExtension } from './pin-extension'
|
||||
export { SettingsPopover } from './settings-popover'
|
||||
export {
|
||||
ShortenAddress,
|
||||
|
||||
@@ -2,17 +2,8 @@
|
||||
|
||||
// import { ConnectButton } from '../connect-button'
|
||||
import { Logo } from '../logo'
|
||||
import { SettingsPopover } from '../settings-popover'
|
||||
|
||||
type Props = {
|
||||
pathname: string
|
||||
}
|
||||
|
||||
const Navbar = (props: Props) => {
|
||||
const { pathname } = props
|
||||
|
||||
const isRoot = pathname === '-/'
|
||||
|
||||
const Navbar = () => {
|
||||
return (
|
||||
<div
|
||||
data-theme="dark"
|
||||
@@ -26,13 +17,6 @@ const Navbar = (props: Props) => {
|
||||
<Logo isTopbarDesktop />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{!isRoot && (
|
||||
<div className="flex items-center gap-2 pr-2 xl:pr-6">
|
||||
{/* <ConnectButton variant="outline" size="32" label="Connect" /> */}
|
||||
<SettingsPopover />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
import { HideIcon, RevealIcon } from '@status-im/icons/20'
|
||||
import { cx } from 'class-variance-authority'
|
||||
import { useController } from 'react-hook-form'
|
||||
|
||||
import type { UseControllerProps } from 'react-hook-form'
|
||||
|
||||
export const MIN_PASSWORD_LENGTH = 12
|
||||
export const MAX_PASSWORD_LENGTH = 128
|
||||
|
||||
type Props = React.ComponentPropsWithoutRef<'input'> &
|
||||
UseControllerProps & {
|
||||
label: string
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
const PasswordInput = (props: Props) => {
|
||||
const { label, placeholder, ...inputProps } = props
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
|
||||
const { field, fieldState } = useController(props)
|
||||
const invalid = fieldState.invalid
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<input
|
||||
{...field}
|
||||
{...inputProps}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
placeholder={placeholder}
|
||||
data-invalid={invalid}
|
||||
aria-label={label}
|
||||
autoComplete="off"
|
||||
maxLength={MAX_PASSWORD_LENGTH}
|
||||
className={cx(
|
||||
'h-10 w-full rounded-12 border border-solid bg-white-100 px-4 text-15 font-medium placeholder:font-regular max-sm:text-[1rem]',
|
||||
'border-neutral-30 data-[invalid=true]:border-danger-50 focus:border-neutral-40',
|
||||
'disabled:cursor-not-allowed disabled:border-0 disabled:bg-neutral-5 disabled:text-neutral-50',
|
||||
)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-neutral-50 hover:text-neutral-70"
|
||||
>
|
||||
{showPassword ? <HideIcon /> : <RevealIcon />}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { PasswordInput }
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { zxcvbn } from '@zxcvbn-ts/core'
|
||||
|
||||
const STRENGTH_LEVELS = {
|
||||
0: {
|
||||
label: 'Very weak',
|
||||
color: 'text-danger-50',
|
||||
},
|
||||
1: {
|
||||
label: 'Weak',
|
||||
color: 'text-customisation-orange-60',
|
||||
},
|
||||
2: {
|
||||
label: 'Okay',
|
||||
color: 'text-customisation-yellow-50',
|
||||
},
|
||||
3: {
|
||||
label: 'Strong',
|
||||
color: 'text-success-50',
|
||||
},
|
||||
4: {
|
||||
label: 'Very strong',
|
||||
color: 'text-success-50',
|
||||
},
|
||||
} as const
|
||||
|
||||
type StrengthScore = keyof typeof STRENGTH_LEVELS
|
||||
|
||||
export const usePasswordStrength = (password: string) => {
|
||||
const defaultStrength = STRENGTH_LEVELS[0]
|
||||
|
||||
return useMemo(() => {
|
||||
const requirements = [
|
||||
{
|
||||
label: 'Lowercase',
|
||||
rule: /[a-z]/.test(password),
|
||||
},
|
||||
{
|
||||
label: 'Uppercase',
|
||||
rule: /[A-Z]/.test(password),
|
||||
},
|
||||
{
|
||||
label: 'Number',
|
||||
rule: /[0-9]/.test(password),
|
||||
},
|
||||
{
|
||||
label: 'Symbol',
|
||||
rule: /[^a-zA-Z0-9]/.test(password),
|
||||
},
|
||||
]
|
||||
|
||||
const result = password ? zxcvbn(password) : { score: 0 }
|
||||
const score = result.score as StrengthScore
|
||||
const strengthLevel = STRENGTH_LEVELS[score] || defaultStrength
|
||||
|
||||
return {
|
||||
requirements,
|
||||
score: result.score,
|
||||
label: strengthLevel.label,
|
||||
color: strengthLevel.color,
|
||||
characterCount: password.length,
|
||||
}
|
||||
}, [password, defaultStrength])
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { cx } from 'class-variance-authority'
|
||||
|
||||
import { usePasswordStrength } from './hooks/use-password-strength'
|
||||
import { PasswordStrengthIndicator } from './indicator'
|
||||
|
||||
type Props = {
|
||||
password: string
|
||||
}
|
||||
|
||||
const PasswordStrength = ({ password }: Props) => {
|
||||
const { score, label, color, requirements } = usePasswordStrength(password)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
{password.length <= 0 ? (
|
||||
<div className="text-13 text-neutral-50">
|
||||
Tip: Include a mixture of numbers, capitals and symbols
|
||||
</div>
|
||||
) : (
|
||||
<div className={cx('flex items-center gap-1', color)}>
|
||||
<PasswordStrengthIndicator score={score} />
|
||||
<span className={cx('text-13 font-medium', color)}>{label}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1">
|
||||
{requirements.map((requirement, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex w-full items-center justify-between text-neutral-50"
|
||||
>
|
||||
<span
|
||||
className={cx(requirement.rule && 'text-neutral-30 line-through')}
|
||||
>
|
||||
{requirement.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { PasswordStrength }
|
||||
@@ -0,0 +1,39 @@
|
||||
export const PasswordStrengthIndicator = ({
|
||||
score,
|
||||
size = 16,
|
||||
}: {
|
||||
score: number
|
||||
size?: number
|
||||
}) => {
|
||||
const radius = (size - 1.2) / 2
|
||||
const circumference = 2 * Math.PI * radius
|
||||
const strokeDashoffset = circumference * (1 - (score * 25) / 100)
|
||||
const center = size / 2
|
||||
|
||||
return (
|
||||
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
||||
<circle
|
||||
cx={center}
|
||||
cy={center}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.2}
|
||||
strokeOpacity="0.2"
|
||||
/>
|
||||
|
||||
<circle
|
||||
cx={center}
|
||||
cy={center}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.2}
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
transform={`rotate(-90 ${center} ${center})`}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Button } from '@status-im/components'
|
||||
import { CloseIcon, PinIcon, PuzzleIcon } from '@status-im/icons/16'
|
||||
import { cx } from 'class-variance-authority'
|
||||
|
||||
import { Logo } from '../logo'
|
||||
|
||||
type Props = {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export const PinExtension = ({ onClose }: Props) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 rounded-16 bg-white-100 px-3 py-[10px] shadow-2">
|
||||
<div className="absolute right-3 top-[10px]">
|
||||
<Button
|
||||
size="24"
|
||||
onPress={onClose}
|
||||
variant="ghost"
|
||||
icon={<CloseIcon />}
|
||||
aria-label="Close"
|
||||
/>
|
||||
</div>
|
||||
<Logo />
|
||||
<div>
|
||||
<h2 className="text-15 font-600">Pin the Status extension</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
Click <FakeIconButton icon={<PuzzleIcon />} /> then
|
||||
<FakeIconButton icon={<PinIcon />} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FakeIconButton = ({ icon }: { icon: React.ReactNode }) => {
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'inline-flex shrink-0 select-none items-center justify-center gap-1 text-center font-medium outline-none transition-all',
|
||||
'size-[24px] rounded-8 px-2 text-13',
|
||||
'bg-neutral-10 text-neutral-100 dark:bg-neutral-80 dark:text-white-100',
|
||||
)}
|
||||
>
|
||||
<span className="shrink-0 [&>svg]:size-full">{icon}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Generated
+163
-19
@@ -95,13 +95,13 @@ importers:
|
||||
version: 10.45.2(@trpc/server@10.45.2)
|
||||
'@trpc/next':
|
||||
specifier: 10.45.2
|
||||
version: 10.45.2(@tanstack/react-query@5.75.5(react@19.1.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@11.1.0(@tanstack/react-query@5.75.5(react@19.1.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3))(@trpc/server@10.45.2)(next@15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.80.4))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
version: 10.45.2(@tanstack/react-query@5.75.5(react@19.1.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@11.1.0(@tanstack/react-query@5.75.5(react@19.1.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3))(@trpc/server@10.45.2)(next@15.3.0(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.80.4))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@trpc/server':
|
||||
specifier: 10.45.2
|
||||
version: 10.45.2
|
||||
next:
|
||||
specifier: 15.3.0
|
||||
version: 15.3.0(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.80.4)
|
||||
version: 15.3.0(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.80.4)
|
||||
react:
|
||||
specifier: ^19.0.0
|
||||
version: 19.1.0
|
||||
@@ -852,6 +852,9 @@ importers:
|
||||
'@cardano-sdk/key-management':
|
||||
specifier: ^0.27.5
|
||||
version: 0.27.9(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
||||
'@hookform/resolvers':
|
||||
specifier: ^3.1.1
|
||||
version: 3.10.0(react-hook-form@7.56.3(react@19.1.0))
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -1303,6 +1306,9 @@ importers:
|
||||
|
||||
packages/wallet:
|
||||
dependencies:
|
||||
'@hookform/resolvers':
|
||||
specifier: ^3.3.4
|
||||
version: 3.10.0(react-hook-form@7.57.0(react@18.3.1))
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -1354,6 +1360,9 @@ importers:
|
||||
'@types/async-retry':
|
||||
specifier: ^1.4.9
|
||||
version: 1.4.9
|
||||
'@zxcvbn-ts/core':
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4
|
||||
async-retry:
|
||||
specifier: ^1.3.3
|
||||
version: 1.3.3
|
||||
@@ -1368,7 +1377,7 @@ importers:
|
||||
version: 2.30.0
|
||||
next:
|
||||
specifier: 15.1.6
|
||||
version: 15.1.6(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4)
|
||||
version: 15.1.6(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4)
|
||||
qrcode.react:
|
||||
specifier: ^3.1.0
|
||||
version: 3.2.0(react@18.3.1)
|
||||
@@ -1378,6 +1387,9 @@ importers:
|
||||
react-day-picker:
|
||||
specifier: ^8.7.1
|
||||
version: 8.7.1(date-fns@2.30.0)(react@18.3.1)
|
||||
react-hook-form:
|
||||
specifier: ^7.57.0
|
||||
version: 7.57.0(react@18.3.1)
|
||||
react-swipeable:
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.2(react@18.3.1)
|
||||
@@ -1394,6 +1406,9 @@ importers:
|
||||
specifier: ^4.3.7
|
||||
version: 4.3.7(react@18.3.1)
|
||||
devDependencies:
|
||||
'@hookform/devtools':
|
||||
specifier: ^4.3.1
|
||||
version: 4.4.0(@types/react@19.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@status-im/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../eslint-config
|
||||
@@ -8689,6 +8704,9 @@ packages:
|
||||
'@wxt-dev/storage@1.1.1':
|
||||
resolution: {integrity: sha512-H1vYWeoWz03INV4r+sLYDFil88b3rgMMfgGp/EXy3bLbveJeiMiFs/G0bsBN2Ra87Iqlf2oVYRb/ABQpAugbew==}
|
||||
|
||||
'@zxcvbn-ts/core@3.0.4':
|
||||
resolution: {integrity: sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw==}
|
||||
|
||||
'@zxing/text-encoding@0.9.0':
|
||||
resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==}
|
||||
|
||||
@@ -8998,6 +9016,7 @@ packages:
|
||||
|
||||
auri@1.0.2:
|
||||
resolution: {integrity: sha512-OC54Bv+hAPvYlo98ZwK3cTo2ijg0CBylaAO5dZ/xZQ7e897k0o4qtxkUPQHruJrFUnL5BebCXz+bUAeXQHp9dg==}
|
||||
deprecated: No longer supported
|
||||
hasBin: true
|
||||
|
||||
auto-bind@4.0.0:
|
||||
@@ -11134,6 +11153,10 @@ packages:
|
||||
fast-url-parser@1.1.3:
|
||||
resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==}
|
||||
|
||||
fastest-levenshtein@1.0.16:
|
||||
resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
|
||||
engines: {node: '>= 4.9.1'}
|
||||
|
||||
fastq@1.17.1:
|
||||
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
|
||||
|
||||
@@ -15176,6 +15199,12 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
|
||||
react-hook-form@7.57.0:
|
||||
resolution: {integrity: sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
@@ -17998,7 +18027,7 @@ snapshots:
|
||||
'@babel/core': 7.25.2
|
||||
'@babel/generator': 7.25.6
|
||||
'@babel/parser': 7.25.6
|
||||
'@babel/runtime': 7.22.5
|
||||
'@babel/runtime': 7.27.1
|
||||
'@babel/traverse': 7.25.6(supports-color@5.5.0)
|
||||
'@babel/types': 7.25.6
|
||||
babel-preset-fbjs: 3.4.0(@babel/core@7.25.2)
|
||||
@@ -19100,8 +19129,8 @@ snapshots:
|
||||
|
||||
'@emotion/babel-plugin@11.13.5':
|
||||
dependencies:
|
||||
'@babel/helper-module-imports': 7.24.7(supports-color@5.5.0)
|
||||
'@babel/runtime': 7.22.5
|
||||
'@babel/helper-module-imports': 7.27.1
|
||||
'@babel/runtime': 7.27.1
|
||||
'@emotion/hash': 0.9.2
|
||||
'@emotion/memoize': 0.9.0
|
||||
'@emotion/serialize': 1.3.3
|
||||
@@ -19138,9 +19167,25 @@ snapshots:
|
||||
|
||||
'@emotion/memoize@0.9.0': {}
|
||||
|
||||
'@emotion/react@11.14.0(@types/react@19.1.0)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.1
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
'@emotion/cache': 11.14.0
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
|
||||
'@emotion/utils': 1.4.2
|
||||
'@emotion/weak-memoize': 0.4.0
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.5
|
||||
'@babel/runtime': 7.27.1
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
'@emotion/cache': 11.14.0
|
||||
'@emotion/serialize': 1.3.3
|
||||
@@ -19164,9 +19209,24 @@ snapshots:
|
||||
|
||||
'@emotion/sheet@1.4.0': {}
|
||||
|
||||
'@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@18.3.1))(@types/react@19.1.0)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.1
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
'@emotion/is-prop-valid': 1.3.1
|
||||
'@emotion/react': 11.14.0(@types/react@19.1.0)(react@18.3.1)
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
|
||||
'@emotion/utils': 1.4.2
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0))(@types/react@19.1.0)(react@19.1.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.5
|
||||
'@babel/runtime': 7.27.1
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
'@emotion/is-prop-valid': 1.3.1
|
||||
'@emotion/react': 11.14.0(@types/react@19.1.0)(react@19.1.0)
|
||||
@@ -19185,6 +19245,10 @@ snapshots:
|
||||
|
||||
'@emotion/unitless@0.7.5': {}
|
||||
|
||||
'@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
'@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.1.0)':
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
@@ -20252,6 +20316,22 @@ snapshots:
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
||||
'@hookform/devtools@4.4.0(@types/react@19.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.1.0)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@18.3.1))(@types/react@19.1.0)(react@18.3.1)
|
||||
'@types/lodash': 4.14.191
|
||||
little-state-machine: 4.8.1(react@18.3.1)
|
||||
lodash: 4.17.21
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-simple-animate: 3.5.3(react-dom@18.3.1(react@18.3.1))
|
||||
use-deep-compare-effect: 1.8.1(react@18.3.1)
|
||||
uuid: 8.3.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- supports-color
|
||||
|
||||
'@hookform/devtools@4.4.0(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.1.0)(react@19.1.0)
|
||||
@@ -20272,6 +20352,10 @@ snapshots:
|
||||
dependencies:
|
||||
react-hook-form: 7.56.3(react@19.1.0)
|
||||
|
||||
'@hookform/resolvers@3.10.0(react-hook-form@7.57.0(react@18.3.1))':
|
||||
dependencies:
|
||||
react-hook-form: 7.57.0(react@18.3.1)
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
|
||||
'@humanfs/node@0.16.6':
|
||||
@@ -26349,7 +26433,7 @@ snapshots:
|
||||
|
||||
'@svgr/hast-util-to-babel-ast@6.5.1':
|
||||
dependencies:
|
||||
'@babel/types': 7.25.6
|
||||
'@babel/types': 7.27.1
|
||||
entities: 4.4.0
|
||||
|
||||
'@svgr/hast-util-to-babel-ast@8.0.0':
|
||||
@@ -26669,7 +26753,7 @@ snapshots:
|
||||
'@testing-library/dom@10.4.0':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.7
|
||||
'@babel/runtime': 7.22.5
|
||||
'@babel/runtime': 7.27.1
|
||||
'@types/aria-query': 5.0.1
|
||||
aria-query: 5.3.0
|
||||
chalk: 4.1.2
|
||||
@@ -26781,13 +26865,13 @@ snapshots:
|
||||
'@trpc/server': 10.45.2
|
||||
typescript: 5.8.3
|
||||
|
||||
'@trpc/next@10.45.2(@tanstack/react-query@5.75.5(react@19.1.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@11.1.0(@tanstack/react-query@5.75.5(react@19.1.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3))(@trpc/server@10.45.2)(next@15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.80.4))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
'@trpc/next@10.45.2(@tanstack/react-query@5.75.5(react@19.1.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@11.1.0(@tanstack/react-query@5.75.5(react@19.1.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3))(@trpc/server@10.45.2)(next@15.3.0(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.80.4))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@tanstack/react-query': 5.75.5(react@19.1.0)
|
||||
'@trpc/client': 10.45.2(@trpc/server@10.45.2)
|
||||
'@trpc/react-query': 11.1.0(@tanstack/react-query@5.75.5(react@19.1.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
|
||||
'@trpc/server': 10.45.2
|
||||
next: 15.3.0(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.80.4)
|
||||
next: 15.3.0(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.80.4)
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
||||
@@ -28626,6 +28710,10 @@ snapshots:
|
||||
async-mutex: 0.5.0
|
||||
dequal: 2.0.3
|
||||
|
||||
'@zxcvbn-ts/core@3.0.4':
|
||||
dependencies:
|
||||
fastest-levenshtein: 1.0.16
|
||||
|
||||
'@zxing/text-encoding@0.9.0':
|
||||
optional: true
|
||||
|
||||
@@ -29004,7 +29092,7 @@ snapshots:
|
||||
|
||||
babel-plugin-macros@3.1.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.5
|
||||
'@babel/runtime': 7.27.1
|
||||
cosmiconfig: 7.1.0
|
||||
resolve: 1.22.8
|
||||
|
||||
@@ -31600,6 +31688,8 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 1.4.1
|
||||
|
||||
fastest-levenshtein@1.0.16: {}
|
||||
|
||||
fastq@1.17.1:
|
||||
dependencies:
|
||||
reusify: 1.0.4
|
||||
@@ -33259,7 +33349,7 @@ snapshots:
|
||||
|
||||
json-schema-to-ts@2.9.2:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.5
|
||||
'@babel/runtime': 7.27.1
|
||||
'@types/json-schema': 7.0.9
|
||||
ts-algebra: 1.2.2
|
||||
|
||||
@@ -33663,6 +33753,10 @@ snapshots:
|
||||
lit-element: 4.2.0
|
||||
lit-html: 3.3.0
|
||||
|
||||
little-state-machine@4.8.1(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
little-state-machine@4.8.1(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
@@ -35151,7 +35245,7 @@ snapshots:
|
||||
minimist: 1.2.8
|
||||
next: 15.3.0(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.80.4)
|
||||
|
||||
next@15.1.6(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4):
|
||||
next@15.1.6(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.80.4):
|
||||
dependencies:
|
||||
'@next/env': 15.1.6
|
||||
'@swc/counter': 0.1.3
|
||||
@@ -35161,7 +35255,7 @@ snapshots:
|
||||
postcss: 8.4.31
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
styled-jsx: 5.1.6(react@18.3.1)
|
||||
styled-jsx: 5.1.6(@babel/core@7.27.1)(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 15.1.6
|
||||
'@next/swc-darwin-x64': 15.1.6
|
||||
@@ -35205,6 +35299,33 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
next@15.3.0(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.80.4):
|
||||
dependencies:
|
||||
'@next/env': 15.3.0
|
||||
'@swc/counter': 0.1.3
|
||||
'@swc/helpers': 0.5.15
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: 1.0.30001660
|
||||
postcss: 8.4.31
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
styled-jsx: 5.1.6(@babel/core@7.27.1)(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 15.3.0
|
||||
'@next/swc-darwin-x64': 15.3.0
|
||||
'@next/swc-linux-arm64-gnu': 15.3.0
|
||||
'@next/swc-linux-arm64-musl': 15.3.0
|
||||
'@next/swc-linux-x64-gnu': 15.3.0
|
||||
'@next/swc-linux-x64-musl': 15.3.0
|
||||
'@next/swc-win32-arm64-msvc': 15.3.0
|
||||
'@next/swc-win32-x64-msvc': 15.3.0
|
||||
'@opentelemetry/api': 1.9.0
|
||||
sass: 1.80.4
|
||||
sharp: 0.34.1
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
nlcst-is-literal@2.1.1:
|
||||
dependencies:
|
||||
'@types/nlcst': 1.0.4
|
||||
@@ -36592,6 +36713,10 @@ snapshots:
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
|
||||
react-hook-form@7.57.0(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@17.0.2: {}
|
||||
@@ -36707,6 +36832,10 @@ snapshots:
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
||||
react-simple-animate@3.5.3(react-dom@18.3.1(react@18.3.1)):
|
||||
dependencies:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
react-simple-animate@3.5.3(react-dom@19.1.0(react@19.1.0)):
|
||||
dependencies:
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
@@ -37065,7 +37194,7 @@ snapshots:
|
||||
|
||||
relay-runtime@12.0.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.5
|
||||
'@babel/runtime': 7.27.1
|
||||
fbjs: 3.0.5
|
||||
invariant: 2.2.4
|
||||
transitivePeerDependencies:
|
||||
@@ -38899,10 +39028,19 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.25.2
|
||||
|
||||
styled-jsx@5.1.6(react@18.3.1):
|
||||
styled-jsx@5.1.6(@babel/core@7.27.1)(react@18.3.1):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.27.1
|
||||
|
||||
styled-jsx@5.1.6(@babel/core@7.27.1)(react@19.1.0):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 19.1.0
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.27.1
|
||||
|
||||
stylis@4.2.0: {}
|
||||
|
||||
@@ -39844,9 +39982,15 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.0
|
||||
|
||||
use-deep-compare-effect@1.8.1(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.1
|
||||
dequal: 2.0.3
|
||||
react: 18.3.1
|
||||
|
||||
use-deep-compare-effect@1.8.1(react@19.1.0):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.5
|
||||
'@babel/runtime': 7.27.1
|
||||
dequal: 2.0.3
|
||||
react: 19.1.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user