diff --git a/apps/web/package.json b/apps/web/package.json
index e85fe8ea..5ccd7a0a 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -30,6 +30,8 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-form": "^1.12.3",
+ "@tanstack/react-query": "^5.80.7",
+ "@tanstack/react-query-devtools": "^5.80.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"ky": "catalog:",
diff --git a/apps/web/src/app/app/page.tsx b/apps/web/src/app/app/page.tsx
index 8b7766e2..09c6ea40 100644
--- a/apps/web/src/app/app/page.tsx
+++ b/apps/web/src/app/app/page.tsx
@@ -1,12 +1,20 @@
'use client'
+import { useQuery } from '@tanstack/react-query'
+
import { useAuthenticatedAgentic } from '@/components/agentic-provider'
+import { LoadingIndicator } from '@/components/loading-indicator'
export default function AppIndexPage() {
const ctx = useAuthenticatedAgentic()
-
- // TODO: loading
- if (!ctx) return null
+ const { data: projects, isLoading } = useQuery({
+ queryKey: ['projects'],
+ queryFn: () =>
+ ctx?.api
+ .listProjects({ populate: ['lastPublishedDeployment'] })
+ .catch(() => []),
+ enabled: !!ctx
+ })
return (
<>
@@ -15,7 +23,39 @@ export default function AppIndexPage() {
Authenticated Dashboard
-
Auth Session: {JSON.stringify(ctx.api.authSession, null, 2)}
+ {!ctx || isLoading ? (
+
+ ) : (
+
+
Your Projects
+ {projects?.length === 0 ? (
+
+ No projects found. Create your first project to get started!
+
+ ) : (
+
+ {projects?.map((project) => (
+
+
{project.name}
+
+ {project.identifier}
+
+ {project.lastPublishedDeployment && (
+
+ Last published:{' '}
+ {project.lastPublishedDeployment.version ||
+ project.lastPublishedDeployment.hash}
+
+ )}
+
+ ))}
+
+ )}
+
+ )}
>
)
diff --git a/apps/web/src/app/auth/[provider]/success/success-page.tsx b/apps/web/src/app/auth/[provider]/success/success-page.tsx
index 6b5b54b4..18dca26b 100644
--- a/apps/web/src/app/auth/[provider]/success/success-page.tsx
+++ b/apps/web/src/app/auth/[provider]/success/success-page.tsx
@@ -4,6 +4,7 @@ import { redirect, RedirectType, useSearchParams } from 'next/navigation'
import { useEffect } from 'react'
import { useUnauthenticatedAgentic } from '@/components/agentic-provider'
+import { LoadingIndicator } from '@/components/loading-indicator'
import { toastError } from '@/lib/notifications'
export function SuccessPage({
@@ -41,6 +42,5 @@ export function SuccessPage({
})()
}, [code, ctx])
- // TODO: show a loading state
- return null
+ return
}
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index 8ee8d91d..6784ec12 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -5,14 +5,12 @@ import cs from 'clsx'
import { Geist } from 'next/font/google'
import { Toaster } from 'sonner'
-import { AgenticProvider } from '@/components/agentic-provider'
import { Bootstrap } from '@/components/bootstrap'
import { Footer } from '@/components/footer'
import { Header } from '@/components/header'
-import { PostHogProvider } from '@/components/posthog-provider'
-import { ThemeProvider } from '@/components/theme-provider'
import * as config from '@/lib/config'
+import Providers from './providers'
import styles from './styles.module.css'
const geist = Geist({
@@ -50,28 +48,20 @@ export default function RootLayout({
return (
-
-
-
-
-
+
+
+
-
- {children}
-
+
+ {children}
+
-
-
-
-
-
-
+
+
+
-
+
+
)
diff --git a/apps/web/src/app/providers.tsx b/apps/web/src/app/providers.tsx
new file mode 100644
index 00000000..54b5cca2
--- /dev/null
+++ b/apps/web/src/app/providers.tsx
@@ -0,0 +1,30 @@
+'use client'
+
+import type React from 'react'
+import { QueryClientProvider } from '@tanstack/react-query'
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
+
+import { AgenticProvider } from '@/components/agentic-provider'
+import { PostHogProvider } from '@/components/posthog-provider'
+import { ThemeProvider } from '@/components/theme-provider'
+import { queryClient } from '@/lib/query-client'
+
+export default function Providers({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+
+
+ )
+}
diff --git a/apps/web/src/components/agentic-provider.tsx b/apps/web/src/components/agentic-provider.tsx
index 5c54ff8d..82a86bd8 100644
--- a/apps/web/src/components/agentic-provider.tsx
+++ b/apps/web/src/components/agentic-provider.tsx
@@ -37,16 +37,16 @@ export function AgenticProvider({ children }: { children: ReactNode }) {
api: new AgenticApiClient({
apiBaseUrl: config.apiBaseUrl,
onUpdateAuth: (updatedAuthSession) => {
- console.log('onUpdateAuth', updatedAuthSession)
+ // console.log('onUpdateAuth', updatedAuthSession)
if (
!!authSession !== !!updatedAuthSession &&
authSession?.token !== updatedAuthSession?.token
) {
- console.log('setAuthSession', updatedAuthSession)
+ // console.log('setAuthSession', updatedAuthSession)
setAuthSession(updatedAuthSession)
} else {
- console.log('auth session not updated')
+ // console.log('auth session not updated')
}
}
}),
@@ -54,7 +54,7 @@ export function AgenticProvider({ children }: { children: ReactNode }) {
})
useEffect(() => {
- console.log('updating session from localStorage', authSession?.token)
+ // console.log('updating session from localStorage', authSession?.token)
if (agenticContext.current) {
if (authSession) {
agenticContext.current.api.authSession = authSession
diff --git a/apps/web/src/components/bootstrap.tsx b/apps/web/src/components/bootstrap.tsx
index c4926552..b663574a 100644
--- a/apps/web/src/components/bootstrap.tsx
+++ b/apps/web/src/components/bootstrap.tsx
@@ -11,5 +11,6 @@ export function Bootstrap() {
bootstrap()
}
+ // Return `null` so we can use this as a react component
return null
}
diff --git a/apps/web/src/components/header/styles.module.css b/apps/web/src/components/header/styles.module.css
index e83d5ee0..b385bd48 100644
--- a/apps/web/src/components/header/styles.module.css
+++ b/apps/web/src/components/header/styles.module.css
@@ -26,13 +26,6 @@
backdrop-filter: saturate(180%) blur(20px);
}
-/* Workaround for Firefox not supporting backdrop-filter yet */
-@-moz-document url-prefix() {
- :global(.dark) .header {
- background: hsla(203, 8%, 20%, 0.8);
- }
-}
-
.headerContent {
align-self: center;
width: 100%;
diff --git a/apps/web/src/components/loading-indicator/index.tsx b/apps/web/src/components/loading-indicator/index.tsx
index fd4c1587..89fb377c 100644
--- a/apps/web/src/components/loading-indicator/index.tsx
+++ b/apps/web/src/components/loading-indicator/index.tsx
@@ -1,10 +1,11 @@
'use client'
-import cs from 'clsx'
-import { AnimatePresence, motion } from 'motion/react'
+// import { AnimatePresence, motion } from 'motion/react'
import dynamic from 'next/dynamic'
import { useTheme } from 'next-themes'
+import { cn } from '@/lib/utils'
+
import loadingDark from './loading-dark.json'
import loadingLight from './loading-light.json'
import styles from './styles.module.css'
@@ -13,44 +14,35 @@ const Lottie = dynamic(() => import('react-lottie-player'), {
ssr: false
})
-export function LoadingIndicator({
- isLoading = true,
- fill = false,
- className,
- initial,
- animate,
- exit,
- ...rest
-}: {
- isLoading?: boolean
- fill?: boolean
- className?: string
- initial?: any
- animate?: any
- exit?: any
-}) {
+export function LoadingIndicator({ className }: { className?: string }) {
const { resolvedTheme } = useTheme()
return (
-
- {isLoading ? (
-
-
-
- ) : null}
-
+
+
+ //
+ // {isLoading ? (
+ //
+ //
+ //
+ // ) : null}
+ //
)
}
diff --git a/apps/web/src/components/loading-indicator/styles.module.css b/apps/web/src/components/loading-indicator/styles.module.css
index 38c30e88..106b53e8 100644
--- a/apps/web/src/components/loading-indicator/styles.module.css
+++ b/apps/web/src/components/loading-indicator/styles.module.css
@@ -15,6 +15,7 @@
}
.loadingAnimation {
+ pointer-events: none;
width: 200px;
height: 200px;
max-width: 50vw;
diff --git a/apps/web/src/lib/query-client.ts b/apps/web/src/lib/query-client.ts
new file mode 100644
index 00000000..9f692601
--- /dev/null
+++ b/apps/web/src/lib/query-client.ts
@@ -0,0 +1,3 @@
+import { QueryClient } from '@tanstack/react-query'
+
+export const queryClient = new QueryClient()
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7ee7761b..173ef1a0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -536,6 +536,12 @@ importers:
'@tanstack/react-form':
specifier: ^1.12.3
version: 1.12.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tanstack/react-query':
+ specifier: ^5.80.7
+ version: 5.80.7(react@19.1.0)
+ '@tanstack/react-query-devtools':
+ specifier: ^5.80.7
+ version: 5.80.7(@tanstack/react-query@5.80.7(react@19.1.0))(react@19.1.0)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -3261,6 +3267,12 @@ packages:
'@tanstack/form-core@1.12.3':
resolution: {integrity: sha512-H59XYP8Jxg8vT4IYIZa1BHkYiyiZqFcLSD2HpxefHP/vlG06/spCySVe/vGAP7IJgHHSAlEqBhQoy1Mg2ruTRA==}
+ '@tanstack/query-core@5.80.7':
+ resolution: {integrity: sha512-s09l5zeUKC8q7DCCCIkVSns8zZrK4ZDT6ryEjxNBFi68G4z2EBobBS7rdOY3r6W1WbUDpc1fe5oY+YO/+2UVUg==}
+
+ '@tanstack/query-devtools@5.80.0':
+ resolution: {integrity: sha512-D6gH4asyjaoXrCOt5vG5Og/YSj0D/TxwNQgtLJIgWbhbWCC/emu2E92EFoVHh4ppVWg1qT2gKHvKyQBEFZhCuA==}
+
'@tanstack/react-form@1.12.3':
resolution: {integrity: sha512-IlWLVKizjV+CzMgzaSac61bS4/UeSL2gceGOVIv+gKs2SriDIyylJd8AcqsKE/kNplm1K0NYXIF2Vk/re+JfOg==}
peerDependencies:
@@ -3273,6 +3285,17 @@ packages:
vinxi:
optional: true
+ '@tanstack/react-query-devtools@5.80.7':
+ resolution: {integrity: sha512-7Dz/19fVo0i+jgLVBabV5vfGOlLyN5L1w8w1/ogFhe6ItNNsNA+ZgNTbtiKpbR3CcX2WDRRTInz1uMSmHzTsoQ==}
+ peerDependencies:
+ '@tanstack/react-query': ^5.80.7
+ react: ^18 || ^19
+
+ '@tanstack/react-query@5.80.7':
+ resolution: {integrity: sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ==}
+ peerDependencies:
+ react: ^18 || ^19
+
'@tanstack/react-store@0.7.1':
resolution: {integrity: sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA==}
peerDependencies:
@@ -9013,6 +9036,10 @@ snapshots:
dependencies:
'@tanstack/store': 0.7.1
+ '@tanstack/query-core@5.80.7': {}
+
+ '@tanstack/query-devtools@5.80.0': {}
+
'@tanstack/react-form@1.12.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@tanstack/form-core': 1.12.3
@@ -9023,6 +9050,17 @@ snapshots:
transitivePeerDependencies:
- react-dom
+ '@tanstack/react-query-devtools@5.80.7(@tanstack/react-query@5.80.7(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@tanstack/query-devtools': 5.80.0
+ '@tanstack/react-query': 5.80.7(react@19.1.0)
+ react: 19.1.0
+
+ '@tanstack/react-query@5.80.7(react@19.1.0)':
+ dependencies:
+ '@tanstack/query-core': 5.80.7
+ react: 19.1.0
+
'@tanstack/react-store@0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@tanstack/store': 0.7.1