add new wallet flow (#696)

Co-authored-by: Felicio <felicio@users.noreply.github.com>
This commit is contained in:
Jakub
2025-06-19 20:20:53 +09:00
committed by GitHub
co-authored by Felicio
parent b10aa44d83
commit d354913420
26 changed files with 904 additions and 197 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@status-im/wallet': patch
'wallet': patch
---
add new wallet flow
+3 -1
View File
@@ -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 }
+1
View File
@@ -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:*",
+1 -1
View File
@@ -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,
+5 -1
View File
@@ -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 {
+5 -1
View File
@@ -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

+16 -13
View File
@@ -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> */}
+9 -1
View File
@@ -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() {
+13 -5
View File
@@ -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>
)
}
+45 -14
View File
@@ -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>
)
}
+39 -107
View File
@@ -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>
)
}
+34 -8
View File
@@ -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>
)
}
+9 -5
View File
@@ -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 }
+5
View File
@@ -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>
)
}
+163 -19
View File
@@ -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