diff --git a/api-schema.graphql b/api-schema.graphql index 8f8bac0a..e95c26da 100644 --- a/api-schema.graphql +++ b/api-schema.graphql @@ -886,6 +886,7 @@ input UserAddIdentityGrantInput { input UserCollectionAssetFindManyInput { collectionId: String! search: String + searchByOwnerWallet: String } input UserCollectionCreateInput { diff --git a/libs/api/collection/data-access/src/lib/api-collection-assets.service.ts b/libs/api/collection/data-access/src/lib/api-collection-assets.service.ts index 11bb4f74..3628c826 100644 --- a/libs/api/collection/data-access/src/lib/api-collection-assets.service.ts +++ b/libs/api/collection/data-access/src/lib/api-collection-assets.service.ts @@ -13,7 +13,11 @@ import { Collection } from './entity/collection.entity' export class ApiCollectionAssetService { constructor(private readonly core: ApiCoreService, private readonly cache: ApiCacheService) {} - async findMany({ collectionId, search }: UserCollectionAssetFindManyInput): Promise { + async findMany({ + collectionId, + search, + searchByOwnerWallet, + }: UserCollectionAssetFindManyInput): Promise { const collection = await this.ensureCollection(collectionId) if (!collection.token) { throw new Error(`Token for collection ${collection.slug} not found`) @@ -36,15 +40,21 @@ export class ApiCollectionAssetService { attributes: renameAttributes(asset.content?.metadata?.attributes ?? []), })) - return assets.filter((assets) => { - if (!search?.length) { - return true + return assets.filter((asset) => { + let matchesSearch = true + let matchesOwner = true + + if (search?.length) { + matchesSearch = + asset.name.toLowerCase().includes(search.toLowerCase()) || + asset.description.toLowerCase().includes(search.toLowerCase()) + } + + if (searchByOwnerWallet?.length) { + matchesOwner = asset.owner.toLowerCase() === searchByOwnerWallet.toLowerCase() } - return ( - assets.name.toLowerCase().includes(search.toLowerCase()) || - assets.description.toLowerCase().includes(search.toLowerCase()) - ) + return matchesSearch && matchesOwner }) } catch (e) { console.log('error', e) diff --git a/libs/api/collection/data-access/src/lib/dto/user-collection-find-many.input.ts b/libs/api/collection/data-access/src/lib/dto/user-collection-find-many.input.ts index 7b7d40e0..cabe9a2b 100644 --- a/libs/api/collection/data-access/src/lib/dto/user-collection-find-many.input.ts +++ b/libs/api/collection/data-access/src/lib/dto/user-collection-find-many.input.ts @@ -14,4 +14,6 @@ export class UserCollectionAssetFindManyInput { collectionId!: string @Field({ nullable: true }) search?: string + @Field({ nullable: true }) + searchByOwnerWallet?: string } diff --git a/libs/sdk/src/generated/graphql-sdk.ts b/libs/sdk/src/generated/graphql-sdk.ts index b43b6564..af7ebff3 100644 --- a/libs/sdk/src/generated/graphql-sdk.ts +++ b/libs/sdk/src/generated/graphql-sdk.ts @@ -1520,6 +1520,7 @@ export type UserAddIdentityGrantInput = { export type UserCollectionAssetFindManyInput = { collectionId: Scalars['String']['input'] search?: InputMaybe + searchByOwnerWallet?: InputMaybe } export type UserCollectionCreateInput = { @@ -14609,6 +14610,7 @@ export function UserCollectionAssetFindManyInputSchema(): z.ZodObject(props?.search ?? '') - console.log('search', search) + const [search] = useQueryState('search', parseAsString.withDefault('')) + const [attributeFilters] = useQueryState('filters', parseAsArrayOf(parseAsString).withDefault([])) + const [searchByOwnerWallet] = useQueryState('owner', parseAsString.withDefault('')) const input: UserCollectionAssetFindManyInput = { collectionId: props.collectionId, search, + searchByOwnerWallet, } const query = useQuery({ queryKey: ['user', 'collection', 'find-many-asset', input], queryFn: () => sdk.userCollectionAssetFindMany({ input }).then((res) => res.data.items ?? []), }) + const filteredItems = useMemo(() => { + const allItems = query.data ?? [] + + if (attributeFilters.length === 0) { + return allItems + } + + const filtersByKey = attributeFilters.reduce((acc, filter) => { + const [key, value] = filter.split(':') + if (!acc[key]) acc[key] = [] + acc[key].push(value) + return acc + }, {} as Record) + + return allItems.filter((asset) => { + return Object.entries(filtersByKey).every(([key, values]) => { + return asset.attributes?.some((attr) => attr.key === key && values.includes(attr.value ?? '')) + }) + }) + }, [query.data, attributeFilters]) + return { - items: query.data ?? [], + items: filteredItems, query, - setSearch: (q: string) => { - setSearch(q) - }, } } diff --git a/libs/web/collection/feature/src/lib/collection-ui-attribute-tree.tsx b/libs/web/collection/feature/src/lib/collection-ui-attribute-tree.tsx index 0d3e57be..f13f772a 100644 --- a/libs/web/collection/feature/src/lib/collection-ui-attribute-tree.tsx +++ b/libs/web/collection/feature/src/lib/collection-ui-attribute-tree.tsx @@ -1,6 +1,7 @@ -import { NavLink } from '@mantine/core' +import { ActionIcon, Badge, NavLink } from '@mantine/core' +import { IconX } from '@tabler/icons-react' import { CollectionAssetAttribute } from '@pubkey-link/sdk' -import React from 'react' +import { useQueryState, parseAsArrayOf, parseAsString } from 'nuqs' function groupAttributesByKey(attributes: CollectionAssetAttribute[]): { key: string @@ -20,11 +21,71 @@ function groupAttributesByKey(attributes: CollectionAssetAttribute[]): { export function CollectionUiAttributeTree({ attributes }: { attributes: CollectionAssetAttribute[] }) { const groups = groupAttributesByKey(attributes) - return groups.map((group) => ( - - {group.attributes.map((attr) => ( - - ))} - - )) + + const [selectedFilters, setSelectedFilters] = useQueryState('filters', parseAsArrayOf(parseAsString).withDefault([])) + + function handleAttributeClick(key: string, value: string) { + const filterKey = `${key}:${value}` + const isSelected = selectedFilters.includes(filterKey) + + if (isSelected) { + setSelectedFilters(selectedFilters.filter((filter) => filter !== filterKey)) + } else { + setSelectedFilters([...selectedFilters, filterKey]) + } + } + + return groups.map((group) => { + const selectedCount = selectedFilters.filter((filter) => filter.startsWith(`${group.key}:`)).length + + return ( + + {group.key} + {selectedCount > 0 && ( + + {selectedCount} + + )} + + } + > + {group.attributes.map((attr) => { + const filterKey = `${attr.key}:${attr.value}` + const isSelected = selectedFilters.includes(filterKey) + + return ( + + + {attr.value} ({attr.count}) + + {isSelected && ( + { + e.stopPropagation() + handleAttributeClick(attr.key, attr.value ?? '') + }} + > + + + )} + + } + active={isSelected} + onClick={() => handleAttributeClick(attr.key, attr.value ?? '')} + style={{ cursor: 'pointer' }} + /> + ) + })} + + ) + }) } diff --git a/libs/web/collection/feature/src/lib/user-collection-detail-feature.tsx b/libs/web/collection/feature/src/lib/user-collection-detail-feature.tsx index 95e2e386..c22cfac5 100644 --- a/libs/web/collection/feature/src/lib/user-collection-detail-feature.tsx +++ b/libs/web/collection/feature/src/lib/user-collection-detail-feature.tsx @@ -1,13 +1,20 @@ -import { Box, Flex, Grid } from '@mantine/core' +import { Box, Button, Flex, Grid, SegmentedControl, Stack } from '@mantine/core' import { useUserCollectionAssetFindMany, useUserCollectionFindMany, useUserCollectionFindOne, } from '@pubkey-link/web-collection-data-access' -import { CollectionUiAssetGrid, CollectionUiAssetSearch, CollectionUiSelect } from '@pubkey-link/web-collection-ui' +import { + CollectionUiAssetGrid, + CollectionUiAssetSearch, + CollectionUiOwnerSearch, + CollectionUiSelect, +} from '@pubkey-link/web-collection-ui' import { UiError, UiLoader, UiPage } from '@pubkey-ui/core' import { IconImageInPicture } from '@tabler/icons-react' import { useNavigate, useParams } from 'react-router-dom' +import { useState, useMemo } from 'react' +import { useQueryState, parseAsArrayOf, parseAsString } from 'nuqs' import { CollectionUiAttributeTree } from './collection-ui-attribute-tree' import { Collection } from '@pubkey-link/sdk' @@ -35,9 +42,45 @@ export function UserCollectionDetailLoaded({ communityId: string }) { const navigate = useNavigate() + const [cols, setCols] = useState(4) + + const [selectedFilters, setSelectedFilters] = useQueryState('filters', parseAsArrayOf(parseAsString).withDefault([])) + const [search, setSearch] = useQueryState('search', parseAsString.withDefault('')) + const [ownerSearch, setOwnerSearch] = useQueryState('owner', parseAsString.withDefault('')) const { data: collections } = useUserCollectionFindMany({ communityId }) - const { items: assets, setSearch } = useUserCollectionAssetFindMany({ collectionId: collection.id }) + const { items: assets } = useUserCollectionAssetFindMany({ + collectionId: collection.id, + }) + + const filteredAttributes = useMemo(() => { + if (!assets || assets.length === 0) return [] + + const attributeMap = new Map>() + + assets.forEach((asset) => { + asset.attributes?.forEach((attr) => { + if (!attributeMap.has(attr.key)) { + attributeMap.set(attr.key, new Map()) + } + const valueMap = attributeMap.get(attr.key) + if (valueMap) { + const currentCount = valueMap.get(attr.value ?? '') || 0 + valueMap.set(attr.value ?? '', currentCount + 1) + } + }) + }) + + const result: Array<{ key: string; value: string; count: number }> = [] + + attributeMap.forEach((valueMap, key) => { + valueMap.forEach((count, value) => { + result.push({ key, value, count }) + }) + }) + + return result + }, [assets]) return ( }> @@ -58,16 +101,48 @@ export function UserCollectionDetailLoaded({ - + + + + + + setCols(parseInt(value))} + data={[ + { label: '4x', value: '4' }, + { label: '8x', value: '8' }, + { label: '12x', value: '12' }, + ]} + withItemsBorders={false} + /> + - + + {(selectedFilters.length > 0 || search || ownerSearch) && ( + + )} + + - {assets?.length ? :
No assets found
} + {assets?.length ? :
No assets found
}
diff --git a/libs/web/collection/ui/src/index.ts b/libs/web/collection/ui/src/index.ts index 1feca88f..d1d47003 100644 --- a/libs/web/collection/ui/src/index.ts +++ b/libs/web/collection/ui/src/index.ts @@ -5,3 +5,4 @@ export * from './lib/collection-ui-grid-item' export * from './lib/collection-ui-layout' export * from './lib/collection-ui-select' export * from './lib/collection-ui-asset-search' +export * from './lib/collection-ui-owner-search' diff --git a/libs/web/collection/ui/src/lib/collection-ui-asset-grid-item.tsx b/libs/web/collection/ui/src/lib/collection-ui-asset-grid-item.tsx index 0474418d..3263376b 100644 --- a/libs/web/collection/ui/src/lib/collection-ui-asset-grid-item.tsx +++ b/libs/web/collection/ui/src/lib/collection-ui-asset-grid-item.tsx @@ -1,10 +1,15 @@ -import { AspectRatio, Box, Group, Image, Popover, SimpleGrid, Text } from '@mantine/core' +import { AspectRatio, Badge, Box, Group, Image, Popover, SimpleGrid, Text } from '@mantine/core' import { useDisclosure } from '@mantine/hooks' -import { CollectionAsset } from '@pubkey-link/sdk' -import React from 'react' +import { CollectionAsset, IdentityProvider } from '@pubkey-link/sdk' +import { useAuth } from '@pubkey-link/web-auth-data-access' -export function CollectionUiAssetGridItem({ asset }: { asset: CollectionAsset }) { +export function CollectionUiAssetGridItem({ asset, cols }: { asset: CollectionAsset; cols: number }) { const [opened, { close, open }] = useDisclosure(false) + const { user } = useAuth() + + const userWallets = user?.identities?.filter((identity) => identity.provider === IdentityProvider.Solana) + + const isOwned = userWallets?.some((wallet) => wallet.providerId === asset.owner) const items = asset?.attributes?.map((stat) => (
@@ -21,13 +26,44 @@ export function CollectionUiAssetGridItem({ asset }: { asset: CollectionAsset }) return null } + const nameSize: Record = { + 4: 'md', + 8: 'sm', + 12: 'xs', + } + + const needResponsiveBadge = cols > 8 + return ( - - + + + {isOwned && ( + + {needResponsiveBadge ? null : ( + + Owned + + )} + + )} + + {asset.name} + diff --git a/libs/web/collection/ui/src/lib/collection-ui-asset-grid.tsx b/libs/web/collection/ui/src/lib/collection-ui-asset-grid.tsx index b644b032..e795aa37 100644 --- a/libs/web/collection/ui/src/lib/collection-ui-asset-grid.tsx +++ b/libs/web/collection/ui/src/lib/collection-ui-asset-grid.tsx @@ -1,36 +1,72 @@ -import { Group, SimpleGrid, Slider, Stack } from '@mantine/core' +import { Stack } from '@mantine/core' import { CollectionAsset } from '@pubkey-link/sdk' -import React from 'react' +import { useMemo, useState, useEffect } from 'react' +import { Grid, CellComponentProps } from 'react-window' import { CollectionUiAssetGridItem } from './collection-ui-asset-grid-item' -export function CollectionUiAssetGrid({ assets }: { assets: CollectionAsset[] }) { - const marks = Array.from({ length: 5 }, (_, index) => ({ - value: index * 4, - label: `${index + 1}`, - })) - const [cols, setCols] = React.useState(marks[2].value) +interface GridItemData { + assets: CollectionAsset[] + cols: number +} + +function GridItem({ columnIndex, rowIndex, style, ...cellProps }: CellComponentProps) { + const { assets, cols } = cellProps + const index = rowIndex * cols + columnIndex + const asset = assets[index] + + if (!asset) return null + + return ( +
+ +
+ ) +} + +export function CollectionUiAssetGrid({ assets, cols = 4 }: { assets: CollectionAsset[]; cols?: number }) { + const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }) + + useEffect(() => { + function handleResize() { + const container = document.getElementById('asset-grid-container') + if (container) { + const availableHeight = window.innerHeight - container.getBoundingClientRect().top - 60 // bottom margin + setContainerSize({ + width: container.clientWidth, + height: Math.max(availableHeight, 400), + }) + } + } + + handleResize() + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + }, []) + + const cellProps = useMemo(() => ({ assets, cols }), [assets, cols]) + + const rowCount = Math.ceil(assets.length / cols) + const itemWidth = containerSize.width / cols + const itemHeight = itemWidth + 60 // Extra space for text + + if (containerSize.width === 0) { + return
+ } return ( - - - setCols(val)} + +
+ - - - {assets.map((asset) => ( - - ))} - +
) } diff --git a/libs/web/collection/ui/src/lib/collection-ui-asset-search.tsx b/libs/web/collection/ui/src/lib/collection-ui-asset-search.tsx index 01538519..f331c89d 100644 --- a/libs/web/collection/ui/src/lib/collection-ui-asset-search.tsx +++ b/libs/web/collection/ui/src/lib/collection-ui-asset-search.tsx @@ -1,11 +1,10 @@ import { type TextInputProps } from '@mantine/core' import { UiSearchField } from '@pubkey-link/web-core-ui' import { IconSearch } from '@tabler/icons-react' +import { useQueryState, parseAsString } from 'nuqs' -export interface CollectionUiAssetSearchProps extends TextInputProps { - setSearch: (val: string) => void -} +export function CollectionUiAssetSearch(props: Omit) { + const [, setSearch] = useQueryState('search', parseAsString.withDefault('')) -export function CollectionUiAssetSearch({ setSearch, ...props }: CollectionUiAssetSearchProps) { return } setSearch={setSearch} {...props} /> } diff --git a/libs/web/collection/ui/src/lib/collection-ui-owner-search.tsx b/libs/web/collection/ui/src/lib/collection-ui-owner-search.tsx new file mode 100644 index 00000000..9ef4e54a --- /dev/null +++ b/libs/web/collection/ui/src/lib/collection-ui-owner-search.tsx @@ -0,0 +1,84 @@ +import { ActionIcon, Button, Popover, Stack, TextInput } from '@mantine/core' +import { IconChevronDown, IconClipboard, IconSearch } from '@tabler/icons-react' +import { useState } from 'react' +import { useQueryState, parseAsString } from 'nuqs' + +export function CollectionUiOwnerSearch() { + const [opened, setOpened] = useState(false) + + const [ownerSearch, setOwnerSearch] = useQueryState('owner', parseAsString.withDefault('')) + + const [searchValue, setSearchValue] = useState(ownerSearch) + + function handleApply() { + setOwnerSearch(searchValue.trim()) + setOpened(false) + } + + async function handlePaste() { + try { + const text = await navigator.clipboard.readText() + setSearchValue(text.trim()) + } catch (err) { + console.error('Failed to read clipboard:', err) + } + } + + return ( + + + + + + + +
+

Owner

+ + setSearchValue(e.target.value)} + leftSection={} + rightSection={ + + + + } + /> +
+ + + + {ownerSearch && ( + + )} +
+
+
+ ) +} diff --git a/libs/web/core/feature/src/lib/web-core-providers.tsx b/libs/web/core/feature/src/lib/web-core-providers.tsx index 64efa4df..3d8d8d93 100644 --- a/libs/web/core/feature/src/lib/web-core-providers.tsx +++ b/libs/web/core/feature/src/lib/web-core-providers.tsx @@ -2,6 +2,7 @@ import { AuthProvider } from '@pubkey-link/web-auth-data-access' import { AppConfigProvider, SdkProvider } from '@pubkey-link/web-core-data-access' import { toastError } from '@pubkey-ui/core' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { NuqsAdapter } from 'nuqs/adapters/react-router/v6' import { ReactNode } from 'react' import { BrowserRouter } from 'react-router-dom' @@ -18,13 +19,15 @@ const client = new QueryClient({ export function WebCoreProviders({ children }: { children: ReactNode }) { return ( - - - - {children} - - - + + + + + {children} + + + + ) } diff --git a/package.json b/package.json index 3181015a..f80d1d9c 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "@tabler/icons-react": "^2.45.0", "@tanstack/react-query": "^5.17.9", "@telegram-auth/server": "^1.0.4", + "@types/react-window": "^1.8.8", "@willsoto/nestjs-prometheus": "^6.0.0", "axios": "1.6.7", "bcrypt": "^5.1.1", @@ -114,6 +115,7 @@ "linkifyjs": "^4.1.3", "lru-cache": "^10.2.0", "mantine-datatable": "^7.12.4", + "nuqs": "^2.5.2", "passport": "^0.7.0", "passport-discord": "^0.1.4", "passport-github": "^1.1.0", @@ -127,6 +129,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-router-dom": "6.21.1", + "react-window": "^2.0.1", "reflect-metadata": "^0.1.13", "remove": "^0.1.5", "rxjs": "^7.8.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cae8201c..91236ef7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,6 +176,9 @@ importers: '@telegram-auth/server': specifier: ^1.0.4 version: 1.0.4 + '@types/react-window': + specifier: ^1.8.8 + version: 1.8.8 '@willsoto/nestjs-prometheus': specifier: ^6.0.0 version: 6.0.0(@nestjs/common@10.3.0(reflect-metadata@0.1.14)(rxjs@7.8.1))(prom-client@15.1.2) @@ -251,6 +254,9 @@ importers: mantine-datatable: specifier: ^7.12.4 version: 7.12.4(@mantine/core@7.13.4(@mantine/hooks@7.13.4(react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.13.4(react@18.3.1))(clsx@2.1.0)(react@18.3.1) + nuqs: + specifier: ^2.5.2 + version: 2.5.2(react-router-dom@6.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.21.1(react@18.3.1))(react@18.3.1) passport: specifier: ^0.7.0 version: 0.7.0 @@ -290,6 +296,9 @@ importers: react-router-dom: specifier: 6.21.1 version: 6.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-window: + specifier: ^2.0.1 + version: 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) reflect-metadata: specifier: ^0.1.13 version: 0.1.14 @@ -4256,6 +4265,9 @@ packages: '@stablelib/utf8@1.0.2': resolution: {integrity: sha512-sDL1aB2U8FIpj7SjQJMxbOFIFkKvDKQGPHSrYejHZhtLNSK3qHe6ZIfa0woWkOiaJsdYslFzrc0VWXJZHmSIQQ==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@supercharge/promise-pool@2.4.0': resolution: {integrity: sha512-O9CMipBlq5OObdt1uKJGIzm9cdjpPWfj+a+Zw9EgWKxaMNHKC7EU7X9taj3H0EGQNLOSq2jAcOa3EzxlfHsD6w==} engines: {node: '>=8'} @@ -4690,6 +4702,9 @@ packages: '@types/react-dom@18.3.0': resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + '@types/react-window@1.8.8': + resolution: {integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==} + '@types/react@18.3.1': resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} @@ -9205,6 +9220,27 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + nuqs@2.5.2: + resolution: {integrity: sha512-vzKeoYlMRmNYPWECdn53Nmh/jM+r/iSezEin342EVXPogT6KzALwdnYbZxASE5vTdXRUtOymtPkgsarLipKetg==} + peerDependencies: + '@remix-run/react': '>=2' + '@tanstack/react-router': ^1 + next: '>=14.2.0' + react: '>=18.2.0 || ^19.0.0-0' + react-router: ^6 || ^7 + react-router-dom: ^6 || ^7 + peerDependenciesMeta: + '@remix-run/react': + optional: true + '@tanstack/react-router': + optional: true + next: + optional: true + react-router: + optional: true + react-router-dom: + optional: true + nwsapi@2.2.10: resolution: {integrity: sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==} @@ -10411,6 +10447,12 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' + react-window@2.0.1: + resolution: {integrity: sha512-soWFoMBAMrhYBg80A+bv/m0a7Tfj8OM5SDKHBVbpp5NhQwXJIwLFEd7jej6hAJG3xNt9fughlCFSHEv0l2LdMw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -12532,8 +12574,8 @@ snapshots: '@aws-crypto/sha1-browser': 3.0.0 '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0 - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-bucket-endpoint': 3.575.0 @@ -12590,11 +12632,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.575.0': + '@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -12633,6 +12675,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: + - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso@3.575.0': @@ -12678,11 +12721,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': + '@aws-sdk/client-sts@3.575.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0 + '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -12721,7 +12764,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/core@3.575.0': @@ -12755,7 +12797,7 @@ snapshots: '@aws-sdk/credential-provider-ini@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0)': dependencies: - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/credential-provider-env': 3.575.0 '@aws-sdk/credential-provider-process': 3.575.0 '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) @@ -12812,7 +12854,7 @@ snapshots: '@aws-sdk/credential-provider-web-identity@3.575.0(@aws-sdk/client-sts@3.575.0)': dependencies: - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/types': 3.575.0 '@smithy/property-provider': 3.0.0 '@smithy/types': 3.0.0 @@ -12928,7 +12970,7 @@ snapshots: '@aws-sdk/token-providers@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.575.0 + '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) '@aws-sdk/types': 3.575.0 '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 @@ -16800,7 +16842,7 @@ snapshots: '@react-native-community/cli-debugger-ui@13.6.6': dependencies: - serve-static: 1.15.0 + serve-static: 1.16.2 transitivePeerDependencies: - supports-color @@ -16872,7 +16914,7 @@ snapshots: errorhandler: 1.5.1 nocache: 3.0.4 pretty-format: 26.6.2 - serve-static: 1.15.0 + serve-static: 1.16.2 ws: 6.2.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -17032,7 +17074,7 @@ snapshots: nullthrows: 1.1.1 open: 7.4.2 selfsigned: 2.4.1 - serve-static: 1.15.0 + serve-static: 1.16.2 temp-dir: 2.0.0 ws: 6.2.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) transitivePeerDependencies: @@ -18193,6 +18235,8 @@ snapshots: '@stablelib/utf8@1.0.2': {} + '@standard-schema/spec@1.0.0': {} + '@supercharge/promise-pool@2.4.0': {} '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.24.5)': @@ -18686,6 +18730,10 @@ snapshots: dependencies: '@types/react': 18.3.1 + '@types/react-window@1.8.8': + dependencies: + '@types/react': 18.3.1 + '@types/react@18.3.1': dependencies: '@types/prop-types': 15.7.12 @@ -24449,6 +24497,14 @@ snapshots: nullthrows@1.1.1: {} + nuqs@2.5.2(react-router-dom@6.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.21.1(react@18.3.1))(react@18.3.1): + dependencies: + '@standard-schema/spec': 1.0.0 + react: 18.3.1 + optionalDependencies: + react-router: 6.21.1(react@18.3.1) + react-router-dom: 6.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nwsapi@2.2.10: {} nx@20.0.7(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)): @@ -25718,6 +25774,11 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-window@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react@18.3.1: dependencies: loose-envify: 1.4.0