feat: web infinite loading

pull/715/head
Travis Fischer 2025-06-17 07:40:34 +07:00
rodzic 01b8063d68
commit c171ee61cf
7 zmienionych plików z 202 dodań i 238 usunięć

Wyświetl plik

@ -99,8 +99,8 @@ export const teamSlugSchema = z
})
export const paginationSchema = z.object({
offset: z.number().int().nonnegative().default(0).optional(),
limit: z.number().int().positive().max(100).default(10).optional(),
offset: z.coerce.number().int().nonnegative().default(0).optional(),
limit: z.coerce.number().int().positive().max(100).default(10).optional(),
sort: z.enum(['asc', 'desc']).default('desc').optional(),
sortBy: z.enum(['createdAt', 'updatedAt']).default('createdAt').optional()
})

Wyświetl plik

@ -42,6 +42,7 @@
"posthog-js": "^1.252.0",
"react": "catalog:",
"react-dom": "catalog:",
"react-infinite-scroll-hook": "^6.0.0",
"react-lottie-player": "^2.1.0",
"react-use": "^17.6.0",
"sonner": "^2.0.5",

Wyświetl plik

@ -1,7 +1,8 @@
'use client'
import { useQuery } from '@tanstack/react-query'
import { useInfiniteQuery } from '@tanstack/react-query'
import Link from 'next/link'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useAuthenticatedAgentic } from '@/components/agentic-provider'
import { LoadingIndicator } from '@/components/loading-indicator'
@ -9,22 +10,51 @@ import { toastError } from '@/lib/notifications'
export function AppIndex() {
const ctx = useAuthenticatedAgentic()
const limit = 10
const {
data: projects,
data,
isLoading,
isError
} = useQuery({
isError,
hasNextPage,
fetchNextPage,
isFetchingNextPage
} = useInfiniteQuery({
queryKey: ['projects'],
queryFn: () =>
ctx?.api
.listProjects({ populate: ['lastPublishedDeployment'] })
queryFn: ({ pageParam = 0 }) =>
ctx!.api
.listProjects({
populate: ['lastPublishedDeployment'],
offset: pageParam,
limit
})
.then(async (projects) => {
return {
projects,
offset: pageParam,
limit,
nextOffset:
projects.length >= limit ? pageParam + projects.length : undefined
}
})
.catch((err: any) => {
void toastError(err, { label: 'Failed to fetch projects' })
throw err
}),
enabled: !!ctx
getNextPageParam: (lastGroup) => lastGroup?.nextOffset,
enabled: !!ctx,
initialPageParam: 0
})
const [sentryRef] = useInfiniteScroll({
loading: isLoading || isFetchingNextPage,
hasNextPage,
onLoadMore: fetchNextPage,
disabled: !ctx || isError,
rootMargin: '0px 0px 200px 0px'
})
const projects = data ? data.pages.flatMap((p) => p.projects) : []
return (
<>
<section>
@ -44,7 +74,7 @@ export function AppIndex() {
{isError ? (
<p>Error fetching projects</p>
) : !projects?.length ? (
) : !projects.length ? (
<p>
No projects found. Create your first project to get started!
</p>
@ -71,6 +101,12 @@ export function AppIndex() {
)}
</Link>
))}
{hasNextPage && (
<div ref={sentryRef} className=''>
{isLoading || (isFetchingNextPage && <LoadingIndicator />)}
</div>
)}
</div>
)}
</div>

Wyświetl plik

@ -19,7 +19,7 @@ export function AppProjectIndex({
} = useQuery({
queryKey: ['project', projectIdentifier],
queryFn: () =>
ctx?.api
ctx!.api
.getProjectByIdentifier({
projectIdentifier,
populate: ['lastPublishedDeployment']

Wyświetl plik

@ -20,7 +20,6 @@ export default async function AppProjectIndexPage({
const namespace = decodeURIComponent(rawNamespace)
const projectName = decodeURIComponent(rawProjectName)
console.log('parsing project identifier', { namespace, projectName })
const { projectIdentifier } = parseProjectIdentifier(
`${namespace}/${projectName}`,
{ strict: true }

Wyświetl plik

@ -0,0 +1,127 @@
await new Promise((resolve) => setTimeout(resolve, 2000))
console.log(projects)
const p = [
{
id: 'proj_pk4ui2lpcepx1aaf21zlf0lj' + pageParam,
createdAt: '2025-06-16 01:50:23.948105',
updatedAt: '2025-06-16 01:50:23.948105',
identifier: '@dev/test-everything-openapi',
name: 'test-everything-openapi',
userId: 'user_x7awoo6vxk7acinkjx1fc6kf',
lastDeploymentId: 'depl_ub815xwoj8bzj1gqdlfzim91',
applicationFeePercent: 20,
defaultPricingInterval: 'month',
pricingCurrency: 'usd'
},
{
id: 'proj_j5lvlamp2fax4n7kx09eypjx' + pageParam,
createdAt: '2025-06-16 01:50:20.288698',
updatedAt: '2025-06-16 01:50:20.288698',
identifier: '@dev/test-basic-mcp',
name: 'test-basic-mcp',
userId: 'user_x7awoo6vxk7acinkjx1fc6kf',
lastDeploymentId: 'depl_krkn9nedwes1s662kky7a991',
applicationFeePercent: 20,
defaultPricingInterval: 'month',
pricingCurrency: 'usd'
},
{
id: 'proj_xrt1zzegoa3sun3kynh7itss' + pageParam,
createdAt: '2025-06-16 01:50:17.138037',
updatedAt: '2025-06-16 01:50:17.138037',
identifier: '@dev/test-basic-openapi',
name: 'test-basic-openapi',
userId: 'user_x7awoo6vxk7acinkjx1fc6kf',
lastDeploymentId: 'depl_frbnya7wukdto64y93osfp8a',
applicationFeePercent: 20,
defaultPricingInterval: 'month',
pricingCurrency: 'usd'
},
{
id: 'proj_pk4ui2lpcepx1aaf21zlf0lj' + 'foo' + pageParam,
createdAt: '2025-06-16 01:50:23.948105',
updatedAt: '2025-06-16 01:50:23.948105',
identifier: '@dev/test-everything-openapi',
name: 'test-everything-openapi',
userId: 'user_x7awoo6vxk7acinkjx1fc6kf',
lastDeploymentId: 'depl_ub815xwoj8bzj1gqdlfzim91',
applicationFeePercent: 20,
defaultPricingInterval: 'month',
pricingCurrency: 'usd'
},
{
id: 'proj_j5lvlamp2fax4n7kx09eypjx' + 'foo' + pageParam,
createdAt: '2025-06-16 01:50:20.288698',
updatedAt: '2025-06-16 01:50:20.288698',
identifier: '@dev/test-basic-mcp',
name: 'test-basic-mcp',
userId: 'user_x7awoo6vxk7acinkjx1fc6kf',
lastDeploymentId: 'depl_krkn9nedwes1s662kky7a991',
applicationFeePercent: 20,
defaultPricingInterval: 'month',
pricingCurrency: 'usd'
},
{
id: 'proj_xrt1zzegoa3sun3kynh7itss' + 'foo' + pageParam,
createdAt: '2025-06-16 01:50:17.138037',
updatedAt: '2025-06-16 01:50:17.138037',
identifier: '@dev/test-basic-openapi',
name: 'test-basic-openapi',
userId: 'user_x7awoo6vxk7acinkjx1fc6kf',
lastDeploymentId: 'depl_frbnya7wukdto64y93osfp8a',
applicationFeePercent: 20,
defaultPricingInterval: 'month',
pricingCurrency: 'usd'
},
{
id: 'proj_pk4ui2lpcepx1aaf21zlf0lj' + 'bar' + pageParam,
createdAt: '2025-06-16 01:50:23.948105',
updatedAt: '2025-06-16 01:50:23.948105',
identifier: '@dev/test-everything-openapi',
name: 'test-everything-openapi',
userId: 'user_x7awoo6vxk7acinkjx1fc6kf',
lastDeploymentId: 'depl_ub815xwoj8bzj1gqdlfzim91',
applicationFeePercent: 20,
defaultPricingInterval: 'month',
pricingCurrency: 'usd'
},
{
id: 'proj_j5lvlamp2fax4n7kx09eypjx' + 'bar' + pageParam,
createdAt: '2025-06-16 01:50:20.288698',
updatedAt: '2025-06-16 01:50:20.288698',
identifier: '@dev/test-basic-mcp',
name: 'test-basic-mcp',
userId: 'user_x7awoo6vxk7acinkjx1fc6kf',
lastDeploymentId: 'depl_krkn9nedwes1s662kky7a991',
applicationFeePercent: 20,
defaultPricingInterval: 'month',
pricingCurrency: 'usd'
},
{
id: 'proj_xrt1zzegoa3sun3kynh7itss' + 'bar' + pageParam,
createdAt: '2025-06-16 01:50:17.138037',
updatedAt: '2025-06-16 01:50:17.138037',
identifier: '@dev/test-basic-openapi',
name: 'test-basic-openapi',
userId: 'user_x7awoo6vxk7acinkjx1fc6kf',
lastDeploymentId: 'depl_frbnya7wukdto64y93osfp8a',
applicationFeePercent: 20,
defaultPricingInterval: 'month',
pricingCurrency: 'usd'
},
{
id: 'proj_pk4ui2lpcepx1aaf21zlf0lj' + 'baz' + pageParam,
createdAt: '2025-06-16 01:50:23.948105',
updatedAt: '2025-06-16 01:50:23.948105',
identifier: '@dev/test-everything-openapi',
name: 'test-everything-openapi',
userId: 'user_x7awoo6vxk7acinkjx1fc6kf',
lastDeploymentId: 'depl_ub815xwoj8bzj1gqdlfzim91',
applicationFeePercent: 20,
defaultPricingInterval: 'month',
pricingCurrency: 'usd'
}
]
if (pageParam < 200) {
projects = p as any
}

Wyświetl plik

@ -6,252 +6,27 @@ settings:
catalogs:
default:
'@apideck/better-ajv-errors':
specifier: ^0.3.6
version: 0.3.6
'@clack/prompts':
specifier: ^0.11.0
version: 0.11.0
'@cloudflare/workers-types':
specifier: ^4.20250614.0
version: 4.20250614.0
'@commander-js/extra-typings':
specifier: ^14.0.0
version: 14.0.0
'@edge-runtime/vm':
specifier: ^5.0.0
version: 5.0.0
'@fisch0920/config':
specifier: ^1.1.2
version: 1.1.2
'@fisch0920/drizzle-orm':
specifier: ^0.43.7
version: 0.43.7
'@fisch0920/drizzle-zod':
specifier: ^0.7.9
version: 0.7.9
'@hono/node-server':
specifier: ^1.14.4
version: 1.14.4
'@hono/sentry':
specifier: ^1.2.2
version: 1.2.2
'@hono/zod-openapi':
specifier: ^0.19.8
version: 0.19.8
'@hono/zod-validator':
specifier: ^0.7.0
version: 0.7.0
'@modelcontextprotocol/sdk':
specifier: ^1.12.3
version: 1.12.3
'@paralleldrive/cuid2':
specifier: ^2.2.2
version: 2.2.2
'@react-email/components':
specifier: ^0.0.42
version: 0.0.42
'@redocly/openapi-core':
specifier: ^1.34.3
version: 1.34.3
'@sentry/cli':
specifier: ^2.46.0
version: 2.46.0
'@sentry/cloudflare':
specifier: ^9.29.0
version: 9.29.0
'@sentry/core':
specifier: ^9.29.0
version: 9.29.0
'@sentry/node':
specifier: ^9.29.0
version: 9.29.0
'@types/ms':
specifier: ^2.1.0
version: 2.1.0
'@types/node':
specifier: ^24.0.1
version: 24.0.1
'@types/react':
specifier: ^19.1.8
version: 19.1.8
'@types/react-dom':
specifier: ^19.1.6
version: 19.1.6
'@types/semver':
specifier: ^7.7.0
version: 7.7.0
agents:
specifier: ^0.0.95
version: 0.0.95
ajv:
specifier: ^8.17.1
version: 8.17.1
ajv-formats:
specifier: ^3.0.1
version: 3.0.1
camelcase:
specifier: ^8.0.0
version: 8.0.0
commander:
specifier: ^14.0.0
version: 14.0.0
conf:
specifier: ^14.0.0
version: 14.0.0
decamelize:
specifier: ^6.0.0
version: 6.0.0
del-cli:
specifier: ^6.0.0
version: 6.0.0
drizzle-kit:
specifier: ^0.31.1
version: 0.31.1
drizzle-orm:
specifier: ^0.44.2
version: 0.44.2
email-validator:
specifier: ^2.0.4
version: 2.0.4
eslint:
specifier: ^9.29.0
version: 9.29.0
eslint-plugin-drizzle:
specifier: ^0.2.3
version: 0.2.3
eventid:
specifier: ^2.0.1
version: 2.0.1
exit-hook:
specifier: ^4.0.0
version: 4.0.0
fast-content-type-parse:
specifier: ^3.0.0
version: 3.0.0
fast-uri:
specifier: ^3.0.6
version: 3.0.6
fastmcp:
specifier: ^3.3.0
version: 3.3.0
get-port:
specifier: ^7.1.0
version: 7.1.0
hono:
specifier: ^4.7.11
version: 4.7.11
knip:
specifier: ^5.61.0
version: 5.61.0
ky:
specifier: 1.8.1
version: 1.8.1
lint-staged:
specifier: ^16.1.1
version: 16.1.1
ms:
specifier: ^2.1.3
version: 2.1.3
npm-run-all2:
specifier: ^8.0.4
version: 8.0.4
octokit:
specifier: ^5.0.3
version: 5.0.3
only-allow:
specifier: ^1.2.1
version: 1.2.1
open:
specifier: ^10.1.2
version: 10.1.2
openapi-typescript:
specifier: ^7.8.0
version: 7.8.0
ora:
specifier: ^8.2.0
version: 8.2.0
p-all:
specifier: ^5.0.0
version: 5.0.0
p-map:
specifier: ^7.0.3
version: 7.0.3
parse-json:
specifier: ^8.3.0
version: 8.3.0
plur:
specifier: ^5.1.0
version: 5.1.0
postgres:
specifier: ^3.4.7
version: 3.4.7
prettier:
specifier: ^3.5.3
version: 3.5.3
react:
specifier: ^19.1.0
version: 19.1.0
react-dom:
specifier: ^19.1.0
version: 19.1.0
react-email:
specifier: ^4.0.16
version: 4.0.16
resend:
specifier: ^4.6.0
version: 4.6.0
restore-cursor:
specifier: ^5.1.0
version: 5.1.0
semver:
specifier: ^7.7.2
version: 7.7.2
simple-git-hooks:
specifier: ^2.13.0
version: 2.13.0
sort-keys:
specifier: ^5.1.0
version: 5.1.0
stripe:
specifier: ^18.2.1
version: 18.2.1
tsup:
specifier: ^8.5.0
version: 8.5.0
tsx:
specifier: ^4.20.3
version: 4.20.3
turbo:
specifier: ^2.5.4
version: 2.5.4
type-fest:
specifier: ^4.41.0
version: 4.41.0
typescript:
specifier: ^5.8.3
version: 5.8.3
unconfig:
specifier: ^7.3.2
version: 7.3.2
vite-tsconfig-paths:
specifier: ^5.1.4
version: 5.1.4
vitest:
specifier: ^3.2.3
version: 3.2.3
wrangler:
specifier: ^4.20.0
version: 4.20.0
zod:
specifier: ^3.25.64
version: 3.25.64
zod-to-json-schema:
specifier: ^3.24.5
version: 3.24.5
zod-validation-error:
specifier: ^3.5.0
version: 3.5.0
importers:
@ -572,6 +347,9 @@ importers:
react-dom:
specifier: 'catalog:'
version: 19.1.0(react@19.1.0)
react-infinite-scroll-hook:
specifier: ^6.0.0
version: 6.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-lottie-player:
specifier: ^2.1.0
version: 2.1.0(react@19.1.0)
@ -5945,6 +5723,18 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
react-infinite-scroll-hook@6.0.0:
resolution: {integrity: sha512-kg36byj9/YqPS9bv5u3fIUmn800NO6G4cA6d5DcsaBbm7qbMVxT8qy8Po7Xm0dWBJPFEk/g2arcMb9IorQdwsQ==}
peerDependencies:
react: '>=19'
react-dom: '>=19'
react-intersection-observer-hook@4.0.0:
resolution: {integrity: sha512-g9hWp741AA6w0RlGiaTtmIuceRd61q7soZmx2keB1OPstLcZffCKpTxWaYA2LVKNYME3B3ZOGwX3iuYyCBLAag==}
peerDependencies:
react: '>=19'
react-dom: '>=19'
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@ -11875,6 +11665,17 @@ snapshots:
- supports-color
- utf-8-validate
react-infinite-scroll-hook@6.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react-intersection-observer-hook: 4.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-intersection-observer-hook@4.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react-is@16.13.1: {}
react-lottie-player@2.1.0(react@19.1.0):