kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: web app work
rodzic
2c8eb5de2d
commit
109993943b
|
@ -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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,57 +1,6 @@
|
||||||
'use client'
|
import { AppConsumersList } from '@/components/app-consumers-list'
|
||||||
|
|
||||||
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 AppConsumersIndex() {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<section>
|
<section>
|
||||||
|
@ -62,46 +11,7 @@ export function AppConsumersIndex() {
|
||||||
Your Subscriptions
|
Your Subscriptions
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{!ctx || isLoading ? (
|
<AppConsumersList />
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { AppIndex } from './app-index'
|
import { AppDashboard } from './app-dashboard'
|
||||||
|
|
||||||
export default function AppIndexPage() {
|
export default function AppIndexPage() {
|
||||||
return <AppIndex />
|
return <AppDashboard />
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { AppProjectsIndex } from './app-projects-index'
|
||||||
|
|
||||||
|
export default function AppProjectsIndexPage() {
|
||||||
|
return <AppProjectsIndex />
|
||||||
|
}
|
|
@ -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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
Ładowanie…
Reference in New Issue