From b2abde4acce8c0d8783ece2f2d64deae3f664a49 Mon Sep 17 00:00:00 2001 From: andresgutgon Date: Mon, 15 Jul 2024 14:13:16 +0200 Subject: [PATCH] Create first project when setup is done + app layout header --- apps/web/src/actions/procedures/index.ts | 2 +- .../_components/DummyLogoutButton/index.tsx | 25 -- .../src/app/(private)/_data-access/index.ts | 36 +++ apps/web/src/app/(private)/_lib/constants.ts | 12 + apps/web/src/app/(private)/error.tsx | 34 +++ apps/web/src/app/(private)/layout.tsx | 15 +- apps/web/src/app/(private)/page.tsx | 42 +-- .../commits/[commitUuid]/layout.tsx | 28 -- .../(private)/projects/[projectId]/layout.tsx | 15 - .../(private)/projects/[projectId]/page.tsx | 34 +++ .../versions/[commitUuid]/layout.tsx | 78 +++++ .../versions/[commitUuid]/page.tsx | 5 + .../projects/[projectId]/versions/page.tsx | 5 + apps/web/src/app/(private)/projects/page.tsx | 5 + apps/web/src/app/(public)/login/page.tsx | 8 +- apps/web/src/app/(public)/setup/page.tsx | 8 +- .../commits/[commitUuid]/documents/route.ts | 8 +- apps/web/src/app/layout.tsx | 4 +- apps/web/src/app/not-found.tsx | 19 ++ .../components/Sidebar/DocumentTree/index.tsx | 26 +- apps/web/src/components/Sidebar/toTree.ts | 15 +- apps/web/src/data-access/users.ts | 32 +- apps/web/src/data-access/workspaces.ts | 9 +- apps/web/src/helpers/apiHandler.ts | 18 -- apps/web/src/services/auth/getCurrentUser.ts | 36 ++- apps/web/src/services/auth/index.ts | 21 +- apps/web/src/services/routes.ts | 22 +- package.json | 12 + packages/core/src/constants.ts | 4 +- packages/core/src/data-access/commits.ts | 11 +- .../core/src/data-access/documentVersions.ts | 4 +- packages/core/src/data-access/projects.ts | 38 ++- packages/core/src/data-access/users.ts | 6 - packages/core/src/lib/Transaction.ts | 13 +- packages/core/src/lib/commonTypes.ts | 9 + packages/core/src/lib/errors.ts | 2 + packages/core/src/lib/index.ts | 2 + packages/core/src/services/commits/create.ts | 13 +- .../src/services/documentVersions/create.ts | 7 +- packages/core/src/services/projects/create.ts | 38 +++ packages/core/src/services/projects/index.ts | 1 + .../core/src/services/workspaces/create.ts | 2 + packages/core/src/tests/factories/commits.ts | 13 +- packages/core/src/tests/factories/projects.ts | 25 +- packages/web-ui/package.json | 7 +- packages/web-ui/src/browser.ts | 3 + packages/web-ui/src/ds/atoms/Avatar/index.tsx | 75 +++++ packages/web-ui/src/ds/atoms/Badge/index.tsx | 38 +++ .../Icons/custom-icons/LogoMonochrome.tsx | 18 ++ .../src/ds/atoms/Icons/custom-icons/index.ts | 1 + packages/web-ui/src/ds/atoms/Icons/index.tsx | 6 +- .../src/ds/atoms/Modal/Primitives/index.tsx | 131 ++++++++ packages/web-ui/src/ds/atoms/Modal/index.tsx | 46 +++ .../web-ui/src/ds/atoms/Popover/index.tsx | 44 +++ packages/web-ui/src/ds/atoms/Text/index.tsx | 2 +- .../web-ui/src/ds/atoms/Tooltip/index.tsx | 2 +- packages/web-ui/src/ds/atoms/index.ts | 3 + .../ds/molecules/BreadcrumpBadge/index.tsx | 41 +++ .../src/ds/molecules/ErrorComponent/index.tsx | 26 ++ .../src/ds/molecules/FocusHeader/index.tsx | 4 +- packages/web-ui/src/ds/molecules/browser.ts | 2 + packages/web-ui/src/index.ts | 1 - .../src/layouts/AppLayout/Header/index.tsx | 109 +++++++ .../web-ui/src/layouts/AppLayout/index.tsx | 26 ++ packages/web-ui/src/layouts/index.ts | 2 + packages/web-ui/src/lib/commonTypes.ts | 7 - packages/web-ui/src/lib/getUserInfo.ts | 44 +++ .../web-ui/src/providers/CommitProvider.tsx | 29 +- .../web-ui/src/providers/ProjectProvider.tsx | 16 +- .../src/providers/SessionProvider/index.tsx | 40 +++ packages/web-ui/src/providers/index.ts | 1 + .../src/sections/DocumentEditor/index.tsx | 10 +- packages/web-ui/tailwind.config.js | 37 +-- packages/web-ui/tsconfig.json | 5 +- pnpm-lock.yaml | 280 ++++++++++++------ 75 files changed, 1426 insertions(+), 392 deletions(-) delete mode 100644 apps/web/src/app/(private)/_components/DummyLogoutButton/index.tsx create mode 100644 apps/web/src/app/(private)/_data-access/index.ts create mode 100644 apps/web/src/app/(private)/_lib/constants.ts create mode 100644 apps/web/src/app/(private)/error.tsx delete mode 100644 apps/web/src/app/(private)/projects/[projectId]/commits/[commitUuid]/layout.tsx delete mode 100644 apps/web/src/app/(private)/projects/[projectId]/layout.tsx create mode 100644 apps/web/src/app/(private)/projects/[projectId]/page.tsx create mode 100644 apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx create mode 100644 apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/page.tsx create mode 100644 apps/web/src/app/(private)/projects/[projectId]/versions/page.tsx create mode 100644 apps/web/src/app/(private)/projects/page.tsx create mode 100644 apps/web/src/app/not-found.tsx delete mode 100644 apps/web/src/helpers/apiHandler.ts create mode 100644 packages/core/src/lib/commonTypes.ts create mode 100644 packages/core/src/services/projects/create.ts create mode 100644 packages/core/src/services/projects/index.ts create mode 100644 packages/web-ui/src/browser.ts create mode 100644 packages/web-ui/src/ds/atoms/Avatar/index.tsx create mode 100644 packages/web-ui/src/ds/atoms/Badge/index.tsx create mode 100644 packages/web-ui/src/ds/atoms/Icons/custom-icons/LogoMonochrome.tsx create mode 100644 packages/web-ui/src/ds/atoms/Modal/Primitives/index.tsx create mode 100644 packages/web-ui/src/ds/atoms/Modal/index.tsx create mode 100644 packages/web-ui/src/ds/atoms/Popover/index.tsx create mode 100644 packages/web-ui/src/ds/molecules/BreadcrumpBadge/index.tsx create mode 100644 packages/web-ui/src/ds/molecules/ErrorComponent/index.tsx create mode 100644 packages/web-ui/src/ds/molecules/browser.ts create mode 100644 packages/web-ui/src/layouts/AppLayout/Header/index.tsx create mode 100644 packages/web-ui/src/layouts/AppLayout/index.tsx create mode 100644 packages/web-ui/src/lib/getUserInfo.ts create mode 100644 packages/web-ui/src/providers/SessionProvider/index.tsx 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/DummyLogoutButton/index.tsx b/apps/web/src/app/(private)/_components/DummyLogoutButton/index.tsx deleted file mode 100644 index 9fd6bc7d4..000000000 --- a/apps/web/src/app/(private)/_components/DummyLogoutButton/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -'use client' - -import { Button, useToast } from '@latitude-data/web-ui' -import { logoutAction } from '$/actions/user/logoutAction' -import { useServerAction } from 'zsa-react' - -export default function DummyLogoutButton() { - const { toast } = useToast() - const { executeFormAction } = useServerAction(logoutAction, { - onError: ({ err }) => { - if (err.code === 'ERROR') { - toast({ - title: 'Error while logging out', - description: err.message, - variant: 'destructive', - }) - } - }, - }) - 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..389cebf5b --- /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 ({ uuid, projectId }: FindCommitProps) => { + const result = await originalfindCommit({ 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 63e0f847d..e83440bf2 100644 --- a/apps/web/src/app/(private)/layout.tsx +++ b/apps/web/src/app/(private)/layout.tsx @@ -1,17 +1,24 @@ import { ReactNode } from 'react' +import { SessionProvider } from '@latitude-data/web-ui/browser' +import { getCurrentUser } from '$/services/auth/getCurrentUser' import { getSession } from '$/services/auth/getSession' import { ROUTES } from '$/services/routes' import { redirect } from 'next/navigation' export default async function PrivateLayout({ children, -}: { - children: ReactNode -}) { +}: Readonly<{ children: ReactNode }>) { const data = await getSession() + if (!data.session) { return redirect(ROUTES.auth.login) } - return children + + const session = await getCurrentUser() + return ( + + {children} + + ) } diff --git a/apps/web/src/app/(private)/page.tsx b/apps/web/src/app/(private)/page.tsx index 8fc6009c5..5f57c6971 100644 --- a/apps/web/src/app/(private)/page.tsx +++ b/apps/web/src/app/(private)/page.tsx @@ -1,21 +1,29 @@ -import { FocusHeader, FocusLayout } from '@latitude-data/web-ui' -import DummyLogoutButton from '$/app/(private)/_components/DummyLogoutButton' +import { HEAD_COMMIT, NotFoundError, Project } from '@latitude-data/core' +import { findCommit, getFirstProject } from '$/app/(private)/_data-access' +import { getCurrentUser, SessionData } from '$/services/auth/getCurrentUser' +import { ROUTES } from '$/services/routes' +import { notFound, redirect } from 'next/navigation' export const dynamic = 'force-dynamic' +const PROJECT_ROUTE = ROUTES.projects.detail -export default async function Home() { - return ( - - } - > -
- -
-
- ) +export default async function AppRoot() { + let session: SessionData + let project: Project + let url + + try { + session = await getCurrentUser() + project = await getFirstProject({ workspaceId: session.workspace.id }) + await findCommit({ uuid: HEAD_COMMIT, projectId: project.id }) + url = PROJECT_ROUTE({ id: project.id }).commits.latest + } catch (error) { + if (error instanceof NotFoundError) { + return notFound() + } + + throw error + } + + return redirect(url) } diff --git a/apps/web/src/app/(private)/projects/[projectId]/commits/[commitUuid]/layout.tsx b/apps/web/src/app/(private)/projects/[projectId]/commits/[commitUuid]/layout.tsx deleted file mode 100644 index 17ee3b1d0..000000000 --- a/apps/web/src/app/(private)/projects/[projectId]/commits/[commitUuid]/layout.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { ReactNode } from 'react' - -import { CommitProvider } from '@latitude-data/web-ui' -import Sidebar from '$/components/Sidebar' -import { getCommitMergedAt } from '$core/data-access' - -export default async function PrivateLayout({ - children, - params, -}: { - children: ReactNode - params: { commitUuid: string; projectId: number } -}) { - const { commitUuid, projectId } = params - const commitMergeTime = await getCommitMergedAt({ projectId, commitUuid }) - const isDraft = commitMergeTime.unwrap() === null - - return ( - -
-
- -
-
{children}
-
-
- ) -} diff --git a/apps/web/src/app/(private)/projects/[projectId]/layout.tsx b/apps/web/src/app/(private)/projects/[projectId]/layout.tsx deleted file mode 100644 index b0b841869..000000000 --- a/apps/web/src/app/(private)/projects/[projectId]/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { ReactNode } from 'react' - -import { ProjectProvider } from '@latitude-data/web-ui' - -export default async function PrivateLayout({ - children, - params, -}: { - children: ReactNode - params: { projectId: number } -}) { - const { projectId } = params - - return {children} -} diff --git a/apps/web/src/app/(private)/projects/[projectId]/page.tsx b/apps/web/src/app/(private)/projects/[projectId]/page.tsx new file mode 100644 index 000000000..428460402 --- /dev/null +++ b/apps/web/src/app/(private)/projects/[projectId]/page.tsx @@ -0,0 +1,34 @@ +import { HEAD_COMMIT, NotFoundError, Project } from '@latitude-data/core' +import { findCommit, findProject } from '$/app/(private)/_data-access' +import { getCurrentUser, SessionData } from '$/services/auth/getCurrentUser' +import { ROUTES } from '$/services/routes' +import { notFound, redirect } from 'next/navigation' + +export const dynamic = 'force-dynamic' +const PROJECT_ROUTE = ROUTES.projects.detail + +export type ProjectPageParams = { + params: { projectId: string } +} +export default async function ProjectPage({ params }: ProjectPageParams) { + let session: SessionData + let project: Project + let url + + try { + session = await getCurrentUser() + project = await findProject({ + projectId: params.projectId, + workspaceId: session.workspace.id, + }) + await findCommit({ uuid: HEAD_COMMIT, projectId: project.id }) + url = PROJECT_ROUTE({ id: +project.id }).commits.latest + } catch (error) { + if (error instanceof NotFoundError) { + return notFound() + } + throw error + } + + return redirect(url) +} diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx new file mode 100644 index 000000000..6fc107970 --- /dev/null +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx @@ -0,0 +1,78 @@ +import { ReactNode } from 'react' + +import { + Commit, + HEAD_COMMIT, + NotFoundError, + Project, +} from '@latitude-data/core' +import { CommitProvider, ProjectProvider } from '@latitude-data/web-ui' +import { AppLayout, BreadcrumpBadge } from '@latitude-data/web-ui/browser' +import { findCommit, findProject } from '$/app/(private)/_data-access' +import { NAV_LINKS } from '$/app/(private)/_lib/constants' +import { ProjectPageParams } from '$/app/(private)/projects/[projectId]/page' +import Sidebar from '$/components/Sidebar' +import { getCurrentUser, SessionData } from '$/services/auth/getCurrentUser' +import { notFound } from 'next/navigation' + +export type CommitPageParams = { + children: ReactNode + params: ProjectPageParams['params'] & { commitUuid: string } +} + +export default async function CommitLayout({ + children, + params, +}: CommitPageParams) { + const isHead = params.commitUuid === HEAD_COMMIT + let session: SessionData + let project: Project + let commit: Commit + try { + session = await getCurrentUser() + project = await findProject({ + projectId: params.projectId, + workspaceId: session.workspace.id, + }) + commit = await findCommit({ + uuid: params.commitUuid, + projectId: project.id, + }) + } catch (error) { + if (error instanceof NotFoundError) { + return notFound() + } + throw error + } + + return ( + + + + ), + }, + ]} + > +
+
+ +
+
{children}
+
+
+
+
+ ) +} diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/page.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/page.tsx new file mode 100644 index 000000000..4fa07c7a1 --- /dev/null +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/page.tsx @@ -0,0 +1,5 @@ +export const dynamic = 'force-dynamic' + +export default async function CommitRoot() { + return
Commits home
+} diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/page.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/page.tsx new file mode 100644 index 000000000..503b53d99 --- /dev/null +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/page.tsx @@ -0,0 +1,5 @@ +import ProjectPage from '$/app/(private)/projects/[projectId]/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/api/projects/[projectId]/commits/[commitUuid]/documents/route.ts b/apps/web/src/app/api/projects/[projectId]/commits/[commitUuid]/documents/route.ts index 9f152dd40..88da2a0fa 100644 --- a/apps/web/src/app/api/projects/[projectId]/commits/[commitUuid]/documents/route.ts +++ b/apps/web/src/app/api/projects/[projectId]/commits/[commitUuid]/documents/route.ts @@ -3,15 +3,17 @@ import { NextRequest, NextResponse } from 'next/server' export async function GET( _: NextRequest, - { commitUuid, projectId }: { commitUuid: string; projectId: number }, + { + params: { commitUuid, projectId }, + }: { params: { commitUuid: string; projectId: number } }, ) { try { const documents = await materializeDocumentsAtCommit({ commitUuid, - projectId, + projectId: Number(projectId), }) - return NextResponse.json(documents) + return NextResponse.json(documents.unwrap()) } catch (err: unknown) { const error = err as Error return NextResponse.json({ error: error.message }, { status: 500 }) 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} diff --git a/apps/web/src/app/not-found.tsx b/apps/web/src/app/not-found.tsx new file mode 100644 index 000000000..762275cbc --- /dev/null +++ b/apps/web/src/app/not-found.tsx @@ -0,0 +1,19 @@ +import { AppLayout, ErrorComponent } from '@latitude-data/web-ui/browser' +import { NAV_LINKS } from '$/app/(private)/_lib/constants' +import { getSafeCurrentUser } from '$/services/auth/getCurrentUser' + +export default async function GlobalNoFound() { + const session = await getSafeCurrentUser() + return ( + + + + ) +} diff --git a/apps/web/src/components/Sidebar/DocumentTree/index.tsx b/apps/web/src/components/Sidebar/DocumentTree/index.tsx index 69f1bf477..8676c76de 100644 --- a/apps/web/src/components/Sidebar/DocumentTree/index.tsx +++ b/apps/web/src/components/Sidebar/DocumentTree/index.tsx @@ -21,9 +21,13 @@ export function CreateNode({ parentId }: { parentId?: number }) { } function CreateFolder({ parentId }: { parentId?: number }) { - const { commitUuid, isDraft } = useCurrentCommit() - const { projectId } = useCurrentProject() - const { create } = useDocumentVersions({ projectId, commitUuid }) + const { commit } = useCurrentCommit() + const isDraft = !commit.mergedAt + const { project } = useCurrentProject() + const { create } = useDocumentVersions({ + projectId: project.id, + commitUuid: commit.uuid, + }) return (