Skip to content

Commit

Permalink
feature: sidebar implementation (#34)
Browse files Browse the repository at this point in the history
This commits implements a wakky first version of the dashboard Sidebar
and all the plumbing required:
- client-side stores
- db operations
- server/client component splits where needed
- server REST API
  • Loading branch information
geclos authored Jul 15, 2024
1 parent 53c1e99 commit fe7fea7
Show file tree
Hide file tree
Showing 66 changed files with 1,961 additions and 587 deletions.
7 changes: 4 additions & 3 deletions apps/api/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
FROM latitudedata/base AS installer
FROM latitudedata/base AS base

WORKDIR /app
COPY . .

RUN turbo prune @latitude-data/api --docker

FROM installer AS builder
FROM base AS builder

COPY .gitignore .gitignore
COPY --from=installer /app/out/full .
COPY --from=base /app/out/full .

WORKDIR /app/apps/api

Expand Down
3 changes: 2 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@
"bullmq": "^5.8.5",
"ioredis": "^5.4.1",
"lucia": "^3.2.0",
"bcrypt": "^5.1.1",
"next": "^14.3.0-canary.87",
"react": "19.0.0-rc-378b305958-20240710",
"react-dom": "19.0.0-rc-378b305958-20240710",
"swr": "^2.2.5",
"zod": "^3.23.8",
"zsa": "^0.5.0",
"zsa-react": "^0.2.2"
},
"devDependencies": {
"@faker-js/faker": "^8.4.1",
"@latitude-data/eslint-config": "workspace:*",
"@latitude-data/typescript-config": "workspace:*",
"@next/eslint-plugin-next": "^14.2.4",
Expand Down
24 changes: 24 additions & 0 deletions apps/web/src/actions/documents/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use server'

import { createDocumentVersion, DocumentType } from '@latitude-data/core'
import { z } from 'zod'

import { authProcedure } from '../procedures'

export const createDocumentVersionAction = authProcedure
.createServerAction()
.input(
z.object({
commitUuid: z.string(),
documentType: z
.enum([
'folder' as DocumentType.Folder,
'document' as DocumentType.Document,
])
.optional(),
name: z.string(),
parentId: z.number().optional(),
}),
{ type: 'json' },
)
.handler(async ({ input }) => createDocumentVersion(input))
13 changes: 0 additions & 13 deletions apps/web/src/actions/example-enqueu.ts

This file was deleted.

2 changes: 1 addition & 1 deletion apps/web/src/actions/procedures/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getCurrentUser } from '$/lib/auth/getCurrentUser'
import { getCurrentUser } from '$/services/auth/getCurrentUser'
import { createServerActionProcedure } from 'zsa'

/**
Expand Down
7 changes: 3 additions & 4 deletions apps/web/src/actions/user/loginAction.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use server'

import { getUserFromCredentials } from '$/data-access'
import db from '$/db/database'
import { setSession } from '$/lib/auth/setSession'
import { ROUTES } from '$/lib/routes'
import { setSession } from '$/services/auth/setSession'
import { ROUTES } from '$/services/routes'
import { redirect } from 'next/navigation'
import { z } from 'zod'
import { createServerAction } from 'zsa'
Expand All @@ -17,7 +16,7 @@ export const loginAction = createServerAction()
{ type: 'formData' },
)
.handler(async ({ input }) => {
const result = await getUserFromCredentials(input, { db })
const result = await getUserFromCredentials(input)
const sessionData = result.unwrap()

setSession({ sessionData })
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/actions/user/logoutAction.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use server'

import { authProcedure } from '$/actions/procedures'
import { removeSession } from '$/lib/auth/removeSession'
import { ROUTES } from '$/lib/routes'
import { removeSession } from '$/services/auth/removeSession'
import { ROUTES } from '$/services/routes'
import { redirect } from 'next/navigation'

export const logoutAction = authProcedure
Expand Down
7 changes: 3 additions & 4 deletions apps/web/src/actions/user/setupAction.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use server'

import { isWorkspaceCreated } from '$/data-access'
import db from '$/db/database'
import { setSession } from '$/lib/auth/setSession'
import { ROUTES } from '$/lib/routes'
import { setSession } from '$/services/auth/setSession'
import { ROUTES } from '$/services/routes'
import setupService from '$/services/user/setupService'
import { redirect } from 'next/navigation'
import { z } from 'zod'
Expand All @@ -24,7 +23,7 @@ export const setupAction = createServerAction()
{ type: 'formData' },
)
.handler(async ({ input }) => {
const itWasAlreadySetup = await isWorkspaceCreated({ db })
const itWasAlreadySetup = await isWorkspaceCreated()

if (itWasAlreadySetup) {
throw new Error('Workspace already created')
Expand Down
14 changes: 11 additions & 3 deletions apps/web/src/app/(private)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ReactNode } from 'react'

import { getSession } from '$/lib/auth/getSession'
import { ROUTES } from '$/lib/routes'
import Sidebar from '$/components/Sidebar'
import { getSession } from '$/services/auth/getSession'
import { ROUTES } from '$/services/routes'
import { redirect } from 'next/navigation'

export default async function PrivateLayout({
Expand All @@ -13,5 +14,12 @@ export default async function PrivateLayout({
if (!data.session) {
return redirect(ROUTES.auth.login)
}
return <>{children}</>
return (
<main className='flex flex-row w-full'>
<div className='w-[280px]'>
<Sidebar />
</div>
<div className='flex-1'>{children}</div>
</main>
)
}
10 changes: 3 additions & 7 deletions apps/web/src/app/(public)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,16 @@ import {
} from '@latitude-data/web-ui'
import AuthFooter from '$/app/(public)/_components/Footer'
import { isWorkspaceCreated } from '$/data-access'
import db from '$/db/database'
import { ROUTES } from '$/lib/routes'
import { ROUTES } from '$/services/routes'
import { redirect } from 'next/navigation'

import LoginForm from './LoginForm'

export const dynamic = 'force-dynamic'

export default async function LoginPage() {
const isSetup = await isWorkspaceCreated({ db })

if (!isSetup) {
return redirect(ROUTES.auth.setup)
}
const isSetup = await isWorkspaceCreated()
if (!isSetup) return redirect(ROUTES.auth.setup)

return (
<FocusLayout header={<FocusHeader title='Welcome to Latitude' />}>
Expand Down
17 changes: 17 additions & 0 deletions apps/web/src/app/api/commits/[commitUuid]/documents/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { materializeDocumentsAtCommit } from '@latitude-data/core'
import { NextRequest, NextResponse } from 'next/server'

export async function GET(
req: NextRequest,
{ commitUuid }: { commitUuid: string },
) {
try {
const staged = Boolean(req.nextUrl.searchParams.get('staged') || false)
const nodes = await materializeDocumentsAtCommit({ commitUuid, staged })

return NextResponse.json(nodes)
} catch (err: unknown) {
const error = err as Error
return NextResponse.json({ error: error.message }, { status: 500 })
}
}
7 changes: 7 additions & 0 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default async function Home() {
return (
<div className='p-4'>
<h1>Main body</h1>
</div>
)
}
80 changes: 80 additions & 0 deletions apps/web/src/components/Sidebar/DocumentTree/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use client'

import { faker } from '@faker-js/faker'
import type { DocumentType, DocumentVersion } from '@latitude-data/core'
import useDocumentVersions from '$/stores/documentVersions'

import toTree, { Node } from '../toTree'

function generateName() {
return faker.science.chemicalElement().name
}

export function CreateNode({ parentId }: { parentId?: number }) {
return (
<div className='flex flex-row items-center gap-1'>
<CreateFolder parentId={parentId} />
<CreateDocument parentId={parentId} />
</div>
)
}

function CreateFolder({ parentId }: { parentId?: number }) {
const { create } = useDocumentVersions({ staged: true })
return (
<button
onClick={() =>
create({
parentId,
documentType: 'folder' as DocumentType.Folder,
name: generateName(),
})
}
>
+F
</button>
)
}

function CreateDocument({ parentId }: { parentId?: number }) {
const { create } = useDocumentVersions({ staged: true })
return (
<button onClick={() => create({ parentId, name: generateName() })}>
+D
</button>
)
}

function TreeNode({ node, level = 0 }: { node: Node; level?: number }) {
return (
<div key={node.doc?.id || 'root'}>
<div className='flex flex-col gap-2' style={{ paddingLeft: level * 2 }}>
{!!node.doc && (
<div className='flex flex-row align-items justify-between'>
{node.doc.documentType === 'folder' ? (
<>
<p>{node.doc.name}</p>
<CreateNode parentId={node.doc.id} />
</>
) : (
<p className='font-bold'>{node.doc.name}</p>
)}
</div>
)}
{node.children.map((node) => (
<TreeNode node={node} level={level + 1} />
))}
</div>
</div>
)
}

export default function DocumentTree({ nodes }: { nodes: DocumentVersion[] }) {
const { data } = useDocumentVersions(
{ staged: true },
{ fallbackData: nodes },
)
const rootNode = toTree(data)

return <TreeNode node={rootNode} />
}
22 changes: 22 additions & 0 deletions apps/web/src/components/Sidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { HEAD_COMMIT, materializeDocumentsAtCommit } from '@latitude-data/core'

import DocumentTree, { CreateNode } from './DocumentTree'

export default async function Sidebar() {
const nodes = await materializeDocumentsAtCommit({
commitUuid: HEAD_COMMIT,
staged: true,
})

return (
<div className='flex flex-col gap-4 p-4'>
<div className='flex flex-row align-items justify-between'>
<h2>Prompts</h2>
<div className='flex flex-row gap-2 align-items'>
<CreateNode />
</div>
</div>
<DocumentTree nodes={nodes} />
</div>
)
}
39 changes: 39 additions & 0 deletions apps/web/src/components/Sidebar/toTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { DocumentVersion } from '@latitude-data/core'

export class Node {
public doc?: DocumentVersion
public children: Node[] = []
public isRoot: boolean = false

constructor(
doc?: DocumentVersion,
children: Node[] = [],
isRoot: boolean = false,
) {
this.doc = doc
this.children = children
this.isRoot = isRoot
}
}

export default function toTree(docs: DocumentVersion[]) {
function iterate(node: Node) {
let children
if (node.isRoot) {
children = Object.values(docs)
.filter((doc) => !doc.parentId)
.map((doc) => new Node(doc))
} else {
children = docs
.filter((doc) => doc.parentId === node.doc!.id)
.map((doc) => new Node(doc))
}

node.children = children
node.children.forEach(iterate)

return node
}

return iterate(new Node(undefined, [], true))
}
Loading

0 comments on commit fe7fea7

Please sign in to comment.