diff --git a/apps/web/src/actions/procedures/index.ts b/apps/web/src/actions/procedures/index.ts
index c999072e3..70c25bc8e 100644
--- a/apps/web/src/actions/procedures/index.ts
+++ b/apps/web/src/actions/procedures/index.ts
@@ -9,7 +9,7 @@ import { createServerActionProcedure } from 'zsa'
* Docs: https://zsa.vercel.app/docs/procedures
*/
export const authProcedure = createServerActionProcedure().handler(async () => {
- const data = (await getCurrentUser()).unwrap()
+ const data = await getCurrentUser()
return { session: data.session!, workspace: data.workspace, user: data.user }
})
diff --git a/apps/web/src/app/(private)/_components/CommitDetailPage/BreadcrumpBadge/index.tsx b/apps/web/src/app/(private)/_components/CommitDetailPage/BreadcrumpBadge/index.tsx
new file mode 100644
index 000000000..a92e97daf
--- /dev/null
+++ b/apps/web/src/app/(private)/_components/CommitDetailPage/BreadcrumpBadge/index.tsx
@@ -0,0 +1,41 @@
+'use client'
+
+import { useCallback } from 'react'
+
+import { HEAD_COMMIT } from '@latitude-data/core/browser'
+import { Badge, Icons, Text, Tooltip } from '@latitude-data/web-ui'
+
+export default function BreadcrumpCommit({
+ uuid: fullUuid,
+ title,
+ isLatest,
+}: {
+ uuid: string
+ title?: string | null
+ isLatest: boolean
+}) {
+ const [uuid] = isLatest ? [HEAD_COMMIT] : fullUuid.split('-')
+ const onCopy = useCallback(() => {
+ navigator.clipboard.writeText(fullUuid)
+ }, [fullUuid])
+ return (
+
+ {title ?? 'Untitled'}
+
+ {uuid}
+
+
+ }
+ >
+ Click to copy: {fullUuid}
+
+
+ )
+}
diff --git a/apps/web/src/app/(private)/_components/CommitDetailPage/ClientPushNavigation/index.ts b/apps/web/src/app/(private)/_components/CommitDetailPage/ClientPushNavigation/index.ts
new file mode 100644
index 000000000..9da7cdcbf
--- /dev/null
+++ b/apps/web/src/app/(private)/_components/CommitDetailPage/ClientPushNavigation/index.ts
@@ -0,0 +1,21 @@
+'use client'
+
+import { useEffect } from 'react'
+
+export default function ClientPushNavigation({
+ url,
+}: {
+ url: string | undefined
+}) {
+ useEffect(() => {
+ if (!url) return
+
+ window.history.replaceState(
+ { ...window.history.state, as: url, url },
+ '',
+ url,
+ )
+ }, [url])
+
+ return null
+}
diff --git a/apps/web/src/app/(private)/_components/CommitDetailPage/index.tsx b/apps/web/src/app/(private)/_components/CommitDetailPage/index.tsx
new file mode 100644
index 000000000..13568e9fa
--- /dev/null
+++ b/apps/web/src/app/(private)/_components/CommitDetailPage/index.tsx
@@ -0,0 +1,46 @@
+import { type Commit, type Project } from '@latitude-data/core'
+import { CommitDetail } from '@latitude-data/web-ui'
+import BreadcrumpCommit from '$/app/(private)/_components/CommitDetailPage/BreadcrumpBadge'
+import ClientPushNavigation from '$/app/(private)/_components/CommitDetailPage/ClientPushNavigation'
+import { NAV_LINKS } from '$/app/(private)/_lib/constants'
+import { SessionData } from '$/services/auth/getCurrentUser'
+
+type Props = {
+ session: SessionData
+ project: Project
+ commit: Commit
+ isLatest?: boolean
+ url?: string
+}
+export default function CommitDetailPage({
+ session,
+ project,
+ commit,
+ url,
+ isLatest = true,
+}: Props) {
+ return (
+ <>
+
+
+ ),
+ },
+ ],
+ }}
+ />
+ >
+ )
+}
diff --git a/apps/web/src/app/(private)/_data-access/index.ts b/apps/web/src/app/(private)/_data-access/index.ts
new file mode 100644
index 000000000..4db2d4ff6
--- /dev/null
+++ b/apps/web/src/app/(private)/_data-access/index.ts
@@ -0,0 +1,36 @@
+import { cache } from 'react'
+
+import {
+ findCommit as originalfindCommit,
+ findProject as originalFindProject,
+ getFirstProject as originalGetFirstProject,
+ type FindCommitProps,
+ type FindProjectProps,
+} from '@latitude-data/core'
+
+export const getFirstProject = cache(
+ async ({ workspaceId }: { workspaceId: number }) => {
+ const result = await originalGetFirstProject({ workspaceId })
+ const project = result.unwrap()
+
+ return project
+ },
+)
+
+export const findProject = cache(
+ async ({ projectId, workspaceId }: FindProjectProps) => {
+ const result = await originalFindProject({ projectId, workspaceId })
+ const project = result.unwrap()
+
+ return project
+ },
+)
+
+export const findCommit = cache(
+ async ({ isLatest, uuid, projectId }: FindCommitProps) => {
+ const result = await originalfindCommit({ isLatest, uuid, projectId })
+ const commit = result.unwrap()
+
+ return commit
+ },
+)
diff --git a/apps/web/src/app/(private)/_lib/constants.ts b/apps/web/src/app/(private)/_lib/constants.ts
new file mode 100644
index 000000000..d971b4f2f
--- /dev/null
+++ b/apps/web/src/app/(private)/_lib/constants.ts
@@ -0,0 +1,12 @@
+import {
+ LATITUDE_CHANGELOG_URL,
+ LATITUDE_DOCS_URL,
+ LATITUDE_HELP_URL,
+} from '@latitude-data/core/browser'
+
+export const NAV_LINKS = [
+ { label: 'Feedback', href: '' },
+ { label: 'Docs', href: LATITUDE_DOCS_URL },
+ { label: 'Help', href: LATITUDE_HELP_URL },
+ { label: 'Changelog', href: LATITUDE_CHANGELOG_URL },
+]
diff --git a/apps/web/src/app/(private)/error.tsx b/apps/web/src/app/(private)/error.tsx
new file mode 100644
index 000000000..a58d89aaf
--- /dev/null
+++ b/apps/web/src/app/(private)/error.tsx
@@ -0,0 +1,34 @@
+'use client'
+
+import { useEffect } from 'react'
+
+import {
+ AppLayout,
+ ErrorComponent,
+ useSession,
+} from '@latitude-data/web-ui/browser'
+import { NAV_LINKS } from '$/app/(private)/_lib/constants'
+
+export default function Error({
+ error,
+}: {
+ error: Error & { digest?: string }
+ reset: () => void // Re-render of page
+}) {
+ const session = useSession()
+ useEffect(() => {
+ console.error(error)
+ }, [error])
+ return (
+
+
+
+ )
+}
diff --git a/apps/web/src/app/(private)/layout.tsx b/apps/web/src/app/(private)/layout.tsx
index 709e988ba..1887322ed 100644
--- a/apps/web/src/app/(private)/layout.tsx
+++ b/apps/web/src/app/(private)/layout.tsx
@@ -1,25 +1,15 @@
import { ReactNode } from 'react'
-import Sidebar from '$/components/Sidebar'
-import { getSession } from '$/services/auth/getSession'
-import { ROUTES } from '$/services/routes'
-import { redirect } from 'next/navigation'
+import { SessionProvider } from '@latitude-data/web-ui/browser'
+import { getCurrentUser } from '$/services/auth/getCurrentUser'
export default async function PrivateLayout({
children,
-}: {
- children: ReactNode
-}) {
- const data = await getSession()
- if (!data.session) {
- return redirect(ROUTES.auth.login)
- }
+}: Readonly<{ children: ReactNode }>) {
+ const session = await getCurrentUser()
return (
-
-
-
-
- {children}
-
+
+ {children}
+
)
}
diff --git a/apps/web/src/app/(private)/page.tsx b/apps/web/src/app/(private)/page.tsx
index 8fc6009c5..128c0eeb0 100644
--- a/apps/web/src/app/(private)/page.tsx
+++ b/apps/web/src/app/(private)/page.tsx
@@ -1,21 +1,38 @@
-import { FocusHeader, FocusLayout } from '@latitude-data/web-ui'
-import DummyLogoutButton from '$/app/(private)/_components/DummyLogoutButton'
+import { Commit, NotFoundError, Project } from '@latitude-data/core'
+import CommitDetailPage from '$/app/(private)/_components/CommitDetailPage'
+import { findCommit, getFirstProject } from '$/app/(private)/_data-access'
+import { getCurrentUser, SessionData } from '$/services/auth/getCurrentUser'
+import { ROUTES } from '$/services/routes'
+import { notFound } from 'next/navigation'
export const dynamic = 'force-dynamic'
+const PROJECT_ROUTE = ROUTES.projects.detail
+
+export default async function AppRoot() {
+ let session: SessionData
+ let project: Project
+ let commit: Commit
+ let url
+
+ try {
+ session = await getCurrentUser()
+ project = await getFirstProject({ workspaceId: session.workspace.id })
+ commit = await findCommit({ isLatest: true, projectId: project.id })
+ url = PROJECT_ROUTE({ id: project.id }).commits.latest
+ } catch (error) {
+ if (error instanceof NotFoundError) {
+ return notFound()
+ }
+
+ throw error
+ }
-export default async function Home() {
return (
-
- }
- >
-
-
-
-
+
)
}
diff --git a/apps/web/src/app/(private)/projects/[id]/page.tsx b/apps/web/src/app/(private)/projects/[id]/page.tsx
new file mode 100644
index 000000000..81f0b52d9
--- /dev/null
+++ b/apps/web/src/app/(private)/projects/[id]/page.tsx
@@ -0,0 +1,43 @@
+import { Commit, NotFoundError, Project } from '@latitude-data/core'
+import CommitDetailPage from '$/app/(private)/_components/CommitDetailPage'
+import { findCommit, findProject } from '$/app/(private)/_data-access'
+import { getCurrentUser, SessionData } from '$/services/auth/getCurrentUser'
+import { ROUTES } from '$/services/routes'
+import { notFound } from 'next/navigation'
+
+export const dynamic = 'force-dynamic'
+const PROJECT_ROUTE = ROUTES.projects.detail
+
+export type ProjectPageParams = {
+ params: { id: string }
+}
+export default async function ProjectPage({ params }: ProjectPageParams) {
+ let session: SessionData
+ let project: Project
+ let commit: Commit
+ let url
+
+ try {
+ session = await getCurrentUser()
+ project = await findProject({
+ projectId: params.id,
+ workspaceId: session.workspace.id,
+ })
+ commit = await findCommit({ isLatest: true, projectId: project.id })
+ url = PROJECT_ROUTE({ id: +project.id }).commits.latest
+ } catch (error) {
+ if (error instanceof NotFoundError) {
+ return notFound()
+ }
+ throw error
+ }
+
+ return (
+
+ )
+}
diff --git a/apps/web/src/app/(private)/projects/[id]/versions/[uuid]/page.tsx b/apps/web/src/app/(private)/projects/[id]/versions/[uuid]/page.tsx
new file mode 100644
index 000000000..c351e5ea4
--- /dev/null
+++ b/apps/web/src/app/(private)/projects/[id]/versions/[uuid]/page.tsx
@@ -0,0 +1,49 @@
+import {
+ Commit,
+ HEAD_COMMIT,
+ NotFoundError,
+ Project,
+} from '@latitude-data/core'
+import CommitDetailPage from '$/app/(private)/_components/CommitDetailPage'
+import { findCommit, findProject } from '$/app/(private)/_data-access'
+import { ProjectPageParams } from '$/app/(private)/projects/[id]/page'
+import { getCurrentUser, SessionData } from '$/services/auth/getCurrentUser'
+import { notFound } from 'next/navigation'
+
+export const dynamic = 'force-dynamic'
+
+export type CommitPageParams = {
+ params: ProjectPageParams['params'] & { uuid: string }
+}
+export default async function CommitRoot({ params }: CommitPageParams) {
+ const isLatest = params.uuid === HEAD_COMMIT
+ let session: SessionData
+ let project: Project
+ let commit: Commit
+ try {
+ session = await getCurrentUser()
+ project = await findProject({
+ projectId: params.id,
+ workspaceId: session.workspace.id,
+ })
+ commit = await findCommit({
+ isLatest,
+ uuid: params.uuid,
+ projectId: project.id,
+ })
+ } catch (error) {
+ if (error instanceof NotFoundError) {
+ return notFound()
+ }
+ throw error
+ }
+
+ return (
+
+ )
+}
diff --git a/apps/web/src/app/(private)/projects/[id]/versions/page.tsx b/apps/web/src/app/(private)/projects/[id]/versions/page.tsx
new file mode 100644
index 000000000..f8858e89f
--- /dev/null
+++ b/apps/web/src/app/(private)/projects/[id]/versions/page.tsx
@@ -0,0 +1,5 @@
+import ProjectPage from '$/app/(private)/projects/[id]/page'
+
+export const dynamic = 'force-dynamic'
+
+export default ProjectPage
diff --git a/apps/web/src/app/(private)/projects/page.tsx b/apps/web/src/app/(private)/projects/page.tsx
new file mode 100644
index 000000000..7fe055c5f
--- /dev/null
+++ b/apps/web/src/app/(private)/projects/page.tsx
@@ -0,0 +1,5 @@
+import AppRoot from '$/app/(private)/page'
+
+export const dynamic = 'force-dynamic'
+
+export default AppRoot
diff --git a/apps/web/src/app/(public)/login/page.tsx b/apps/web/src/app/(public)/login/page.tsx
index d2eb8db18..6bc9f334f 100644
--- a/apps/web/src/app/(public)/login/page.tsx
+++ b/apps/web/src/app/(public)/login/page.tsx
@@ -1,9 +1,5 @@
-import {
- Card,
- CardContent,
- FocusHeader,
- FocusLayout,
-} from '@latitude-data/web-ui'
+import { Card, CardContent, FocusHeader } from '@latitude-data/web-ui'
+import { FocusLayout } from '@latitude-data/web-ui/browser'
import AuthFooter from '$/app/(public)/_components/Footer'
import { isWorkspaceCreated } from '$/data-access'
import { ROUTES } from '$/services/routes'
diff --git a/apps/web/src/app/(public)/setup/page.tsx b/apps/web/src/app/(public)/setup/page.tsx
index 5f42f6aeb..7004ae9c7 100644
--- a/apps/web/src/app/(public)/setup/page.tsx
+++ b/apps/web/src/app/(public)/setup/page.tsx
@@ -1,9 +1,5 @@
-import {
- Card,
- CardContent,
- FocusHeader,
- FocusLayout,
-} from '@latitude-data/web-ui'
+import { Card, CardContent, FocusHeader } from '@latitude-data/web-ui'
+import { FocusLayout } from '@latitude-data/web-ui/browser'
import AuthFooter from '$/app/(public)/_components/Footer'
import SetupForm from './SetupForm'
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index 8ff171dcb..bf7bc25c8 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -4,7 +4,7 @@ import type { Metadata } from 'next'
import '@latitude-data/web-ui/styles.css'
-import { ToastProvider } from '@latitude-data/web-ui'
+import { ToastProvider, TooltipProvider } from '@latitude-data/web-ui'
import localFont from 'next/font/local'
const fontSans = localFont({
@@ -157,7 +157,7 @@ export default function RootLayout({
- {children}
+ {children}