feat: web app work

pull/715/head
Travis Fischer 2025-06-18 11:56:47 +07:00
rodzic 2c8eb5de2d
commit 109993943b
8 zmienionych plików z 244 dodań i 205 usunięć

Wyświetl plik

@ -0,0 +1,23 @@
import { AppConsumersList } from '@/components/app-consumers-list'
import { AppProjectsList } from '@/components/app-projects-list'
export function AppDashboard() {
return (
<>
<section>
<h1
className='text-center text-balance leading-snug md:leading-none
text-4xl font-extrabold mb-8'
>
Dashboard
</h1>
<div className='flex gap-8 space-around'>
<AppConsumersList />
<AppProjectsList />
</div>
</section>
</>
)
}

Wyświetl plik

@ -1,111 +0,0 @@
'use client'
import Link from 'next/link'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useAuthenticatedAgentic } from '@/components/agentic-provider'
import { LoadingIndicator } from '@/components/loading-indicator'
import { useInfiniteQuery } from '@/lib/query-client'
export function AppIndex() {
const ctx = useAuthenticatedAgentic()
const limit = 10
const {
data,
isLoading,
isError,
hasNextPage,
fetchNextPage,
isFetchingNextPage
} = useInfiniteQuery({
queryKey: ['projects', ctx?.api.authSession?.user?.id],
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
}
}),
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>
<h1
className='text-center text-balance leading-snug md:leading-none
text-4xl font-extrabold'
>
Dashboard
</h1>
{!ctx || isLoading ? (
<LoadingIndicator />
) : (
<div className='mt-8'>
<h2 className='text-xl font-semibold mb-4'>Your Projects</h2>
{isError ? (
<p>Error fetching projects</p>
) : !projects.length ? (
<p>
No projects found. Create your first project to get started!
</p>
) : (
<div className='grid gap-4'>
{projects.map((project) => (
<Link
key={project.id}
className='p-4 border rounded-lg hover:border-gray-400 transition-colors'
href={`/app/projects/${project.identifier}`}
>
<h3 className='font-medium'>{project.name}</h3>
<p className='text-sm text-gray-500'>
{project.identifier}
</p>
{project.lastPublishedDeployment && (
<p className='text-sm text-gray-500 mt-1'>
Last published:{' '}
{project.lastPublishedDeployment.version ||
project.lastPublishedDeployment.hash}
</p>
)}
</Link>
))}
{hasNextPage && (
<div ref={sentryRef} className=''>
{isLoading || (isFetchingNextPage && <LoadingIndicator />)}
</div>
)}
</div>
)}
</div>
)}
</section>
</>
)
}

Wyświetl plik

@ -1,57 +1,6 @@
'use client'
import Link from 'next/link'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useAuthenticatedAgentic } from '@/components/agentic-provider'
import { LoadingIndicator } from '@/components/loading-indicator'
import { useInfiniteQuery } from '@/lib/query-client'
import { AppConsumersList } from '@/components/app-consumers-list'
export function AppConsumersIndex() {
const ctx = useAuthenticatedAgentic()
const limit = 10
const {
data,
isLoading,
isError,
hasNextPage,
fetchNextPage,
isFetchingNextPage
} = useInfiniteQuery({
queryKey: ['consumers', ctx?.api.authSession?.user?.id],
queryFn: ({ pageParam = 0 }) =>
ctx!.api
.listConsumers({
populate: ['project'],
offset: pageParam,
limit
})
.then(async (consumers) => {
return {
consumers,
offset: pageParam,
limit,
nextOffset:
consumers.length >= limit
? pageParam + consumers.length
: undefined
}
}),
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 consumers = data ? data.pages.flatMap((p) => p.consumers) : []
return (
<>
<section>
@ -62,46 +11,7 @@ export function AppConsumersIndex() {
Your Subscriptions
</h1>
{!ctx || isLoading ? (
<LoadingIndicator />
) : (
<div className='mt-8'>
{isError ? (
<p>Error fetching customer subscriptions</p>
) : !consumers.length ? (
<p>
No subscriptions found. Subscribe to your first project to get
started!
</p>
) : (
<div className='grid gap-4'>
{consumers.map((consumer) => (
<Link
key={consumer.id}
className='p-4 border rounded-lg hover:border-gray-400 transition-colors'
href={`/app/consumers/${consumer.id}`}
>
<h3 className='font-medium'>{consumer.project.name}</h3>
<p className='text-sm text-gray-500'>
{consumer.project.identifier}
</p>
<pre className='max-w-lg'>
{JSON.stringify(consumer, null, 2)}
</pre>
</Link>
))}
{hasNextPage && (
<div ref={sentryRef} className=''>
{isLoading || (isFetchingNextPage && <LoadingIndicator />)}
</div>
)}
</div>
)}
</div>
)}
<AppConsumersList />
</section>
</>
)

Wyświetl plik

@ -1,5 +1,5 @@
import { AppIndex } from './app-index'
import { AppDashboard } from './app-dashboard'
export default function AppIndexPage() {
return <AppIndex />
return <AppDashboard />
}

Wyświetl plik

@ -0,0 +1,13 @@
import { AppProjectsList } from '@/components/app-projects-list'
export function AppProjectsIndex() {
return (
<>
<section>
<div className='flex gap-8 space-around'>
<AppProjectsList />
</div>
</section>
</>
)
}

Wyświetl plik

@ -0,0 +1,5 @@
import { AppProjectsIndex } from './app-projects-index'
export default function AppProjectsIndexPage() {
return <AppProjectsIndex />
}

Wyświetl plik

@ -0,0 +1,101 @@
'use client'
import Link from 'next/link'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useAuthenticatedAgentic } from '@/components/agentic-provider'
import { LoadingIndicator } from '@/components/loading-indicator'
import { useInfiniteQuery } from '@/lib/query-client'
export function AppConsumersList() {
const ctx = useAuthenticatedAgentic()
const limit = 10
const {
data,
isLoading,
isError,
hasNextPage,
fetchNextPage,
isFetchingNextPage
} = useInfiniteQuery({
queryKey: ['consumers', ctx?.api.authSession?.user?.id],
queryFn: ({ pageParam = 0 }) =>
ctx!.api
.listConsumers({
populate: ['project'],
offset: pageParam,
limit
})
.then(async (consumers) => {
return {
consumers,
offset: pageParam,
limit,
nextOffset:
consumers.length >= limit
? pageParam + consumers.length
: undefined
}
}),
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 consumers = data ? data.pages.flatMap((p) => p.consumers) : []
return (
<>
{!ctx || isLoading ? (
<LoadingIndicator />
) : (
<div className='mt-8'>
<h2 className='text-xl font-semibold mb-4'>Your Subscriptions</h2>
{isError ? (
<p>Error fetching customer subscriptions</p>
) : !consumers.length ? (
<p>
No subscriptions found. Subscribe to your first project to get
started!
</p>
) : (
<div className='grid gap-4'>
{consumers.map((consumer) => (
<Link
key={consumer.id}
className='p-4 border rounded-lg hover:border-gray-400 transition-colors overflow-hidden'
href={`/app/consumers/${consumer.id}`}
>
<h3 className='font-medium'>{consumer.project.name}</h3>
<p className='text-sm text-gray-500'>
{consumer.project.identifier}
</p>
<pre className='max-w-lg'>
{JSON.stringify(consumer, null, 2)}
</pre>
</Link>
))}
{hasNextPage && (
<div ref={sentryRef} className=''>
{isLoading || (isFetchingNextPage && <LoadingIndicator />)}
</div>
)}
</div>
)}
</div>
)}
</>
)
}

Wyświetl plik

@ -0,0 +1,98 @@
'use client'
import Link from 'next/link'
import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useAuthenticatedAgentic } from '@/components/agentic-provider'
import { LoadingIndicator } from '@/components/loading-indicator'
import { useInfiniteQuery } from '@/lib/query-client'
export function AppProjectsList() {
const ctx = useAuthenticatedAgentic()
const limit = 10
const {
data,
isLoading,
isError,
hasNextPage,
fetchNextPage,
isFetchingNextPage
} = useInfiniteQuery({
queryKey: ['projects', ctx?.api.authSession?.user?.id],
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
}
}),
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 (
<>
{!ctx || isLoading ? (
<LoadingIndicator />
) : (
<div className='mt-8'>
<h2 className='text-xl font-semibold mb-4'>Your Projects</h2>
{isError ? (
<p>Error fetching projects</p>
) : !projects.length ? (
<p>No projects found. Create your first project to get started!</p>
) : (
<div className='grid gap-4'>
{projects.map((project) => (
<Link
key={project.id}
className='p-4 border rounded-lg hover:border-gray-400 transition-colors overflow-hidden'
href={`/app/projects/${project.identifier}`}
>
<h3 className='font-medium'>{project.name}</h3>
<p className='text-sm text-gray-500'>{project.identifier}</p>
{project.lastPublishedDeployment && (
<p className='text-sm text-gray-500 mt-1'>
Last published:{' '}
{project.lastPublishedDeployment.version ||
project.lastPublishedDeployment.hash}
</p>
)}
</Link>
))}
{hasNextPage && (
<div ref={sentryRef} className=''>
{isLoading || (isFetchingNextPage && <LoadingIndicator />)}
</div>
)}
</div>
)}
</div>
)}
</>
)
}