diff --git a/.github/workflows/buildtest.yml b/.github/workflows/buildtest.yml new file mode 100644 index 000000000..90eddbbfd --- /dev/null +++ b/.github/workflows/buildtest.yml @@ -0,0 +1,23 @@ +name: Test Builds + +on: + pull_request: + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x] # Specify node versions you want to test against + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 2 # Checkout HEAD^ + + - name: Build packages + run: docker compose --profile building build diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 42307bfac..8c4dd3509 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -50,7 +50,7 @@ jobs: run: pnpm install - name: Use Turbo to build affected packages - run: pnpm turbo build --cache-dir=.turbo --filter="./packages/*" + run: pnpm turbo build --cache-dir=.turbo --filter="./packages/**" - name: Prettier run: pnpm prettier:check diff --git a/.gitignore b/.gitignore index 005843d7e..2c9817b77 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ node_modules # Local env files .env .env.local +.env.development .env.development.local .env.test.local .env.production.local diff --git a/apps/gateway/package.json b/apps/gateway/package.json index 1d4baba7f..53f7e38b3 100644 --- a/apps/gateway/package.json +++ b/apps/gateway/package.json @@ -21,6 +21,7 @@ "@latitude-data/core": "workspace:^", "@latitude-data/env": "workspace:^", "@latitude-data/jobs": "workspace:^", + "@latitude-data/mailers": "workspace:^", "@t3-oss/env-core": "^0.10.1", "drizzle-orm": "^0.33.0", "hono": "^4.5.3", diff --git a/apps/gateway/src/common/pipeToStream.ts b/apps/gateway/src/common/pipeToStream.ts index a87f3c941..6e0bcec06 100644 --- a/apps/gateway/src/common/pipeToStream.ts +++ b/apps/gateway/src/common/pipeToStream.ts @@ -1,4 +1,4 @@ -import { streamToGenerator } from '@latitude-data/core' +import { streamToGenerator } from '@latitude-data/core/lib/streamToGenerator' import { SSEStreamingApi } from 'hono/streaming' export async function pipeToStream( diff --git a/apps/gateway/src/middlewares/auth.ts b/apps/gateway/src/middlewares/auth.ts index f2dfdf694..edee22d7f 100644 --- a/apps/gateway/src/middlewares/auth.ts +++ b/apps/gateway/src/middlewares/auth.ts @@ -1,8 +1,8 @@ +import type { ApiKey, Workspace } from '@latitude-data/core/browser' import { unsafelyFindWorkspace, unsafelyGetApiKeyByToken, -} from '@latitude-data/core' -import type { ApiKey, Workspace } from '@latitude-data/core/browser' +} from '@latitude-data/core/data-access' import { bearerAuth } from 'hono/bearer-auth' declare module 'hono' { diff --git a/apps/gateway/src/middlewares/errorHandler.ts b/apps/gateway/src/middlewares/errorHandler.ts index 7cc836f33..cb70d7b9e 100644 --- a/apps/gateway/src/middlewares/errorHandler.ts +++ b/apps/gateway/src/middlewares/errorHandler.ts @@ -1,4 +1,7 @@ -import { LatitudeError, UnprocessableEntityError } from '@latitude-data/core' +import { + LatitudeError, + UnprocessableEntityError, +} from '@latitude-data/core/lib/errors' import { createMiddleware } from 'hono/factory' import HttpStatusCodes from '../common/httpStatusCodes' diff --git a/apps/gateway/src/routes/api/v1/chats/handlers/addMessage.test.ts b/apps/gateway/src/routes/api/v1/chats/handlers/addMessage.test.ts index 6b1d91452..ee3c58727 100644 --- a/apps/gateway/src/routes/api/v1/chats/handlers/addMessage.test.ts +++ b/apps/gateway/src/routes/api/v1/chats/handlers/addMessage.test.ts @@ -1,4 +1,3 @@ -import { apiKeys, database, Result } from '@latitude-data/core' import { ApiKey, ChainEventTypes, @@ -6,7 +5,10 @@ import { StreamEventTypes, Workspace, } from '@latitude-data/core/browser' +import { database } from '@latitude-data/core/client' import { createProject } from '@latitude-data/core/factories' +import { Result } from '@latitude-data/core/lib/Result' +import { apiKeys } from '@latitude-data/core/schema' import app from '$/routes/app' import { eq } from 'drizzle-orm' import { testConsumeStream } from 'test/helpers' @@ -48,14 +50,17 @@ const mocks = vi.hoisted(() => ({ }, })) -vi.mock('@latitude-data/core', async (importOriginal) => { - const original = (await importOriginal()) as typeof importOriginal +vi.mock( + '@latitude-data/core/services/documentLogs/index', + async (importOriginal) => { + const original = (await importOriginal()) as typeof importOriginal - return { - ...original, - addMessages: mocks.addMessages, - } -}) + return { + ...original, + addMessages: mocks.addMessages, + } + }, +) vi.mock('$/jobs', () => ({ queues: mocks.queues, @@ -90,7 +95,6 @@ describe('POST /add-message', () => { workspace = wsp // TODO: move to core const key = await database.query.apiKeys.findFirst({ - // @ts-ignore where: eq(apiKeys.workspaceId, workspace.id), }) apiKey = key! diff --git a/apps/gateway/src/routes/api/v1/chats/handlers/addMessage.ts b/apps/gateway/src/routes/api/v1/chats/handlers/addMessage.ts index 953274c5a..8fdc34d98 100644 --- a/apps/gateway/src/routes/api/v1/chats/handlers/addMessage.ts +++ b/apps/gateway/src/routes/api/v1/chats/handlers/addMessage.ts @@ -1,5 +1,6 @@ import { zValidator } from '@hono/zod-validator' -import { addMessages, LogSources } from '@latitude-data/core' +import { LogSources } from '@latitude-data/core/browser' +import { addMessages } from '@latitude-data/core/services/documentLogs/index' import { messageSchema } from '$/common/messageSchema' import { pipeToStream } from '$/common/pipeToStream' import { queues } from '$/jobs' diff --git a/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/_shared.ts b/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/_shared.ts index 6dc21f921..5d51b08a2 100644 --- a/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/_shared.ts +++ b/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/_shared.ts @@ -1,9 +1,9 @@ +import type { Workspace } from '@latitude-data/core/browser' import { CommitsRepository, DocumentVersionsRepository, ProjectsRepository, -} from '@latitude-data/core' -import type { Workspace } from '@latitude-data/core/browser' +} from '@latitude-data/core/repositories' export const getData = async ({ workspace, diff --git a/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/get.test.ts b/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/get.test.ts index 6b218c747..9a6ed4096 100644 --- a/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/get.test.ts +++ b/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/get.test.ts @@ -1,14 +1,12 @@ -import { - apiKeys, - database, - DocumentVersionsRepository, - mergeCommit, -} from '@latitude-data/core' +import { database } from '@latitude-data/core/client' import { createDocumentVersion, createDraft, createProject, } from '@latitude-data/core/factories' +import { DocumentVersionsRepository } from '@latitude-data/core/repositories' +import { apiKeys } from '@latitude-data/core/schema' +import { mergeCommit } from '@latitude-data/core/services/commits/merge' import app from '$/routes/app' import { eq } from 'drizzle-orm' import { describe, expect, it, vi } from 'vitest' diff --git a/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/run.test.ts b/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/run.test.ts index d2b0ac408..826bd3535 100644 --- a/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/run.test.ts +++ b/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/run.test.ts @@ -1,4 +1,3 @@ -import { apiKeys, database, mergeCommit, Result } from '@latitude-data/core' import { ChainEventTypes, Commit, @@ -7,11 +6,15 @@ import { StreamEventTypes, Workspace, } from '@latitude-data/core/browser' +import { database } from '@latitude-data/core/client' import { createDocumentVersion, createDraft, createProject, } from '@latitude-data/core/factories' +import { Result } from '@latitude-data/core/lib/Result' +import { apiKeys } from '@latitude-data/core/schema' +import { mergeCommit } from '@latitude-data/core/services/commits/merge' import app from '$/routes/app' import { eq } from 'drizzle-orm' import { testConsumeStream } from 'test/helpers' @@ -29,14 +32,17 @@ const mocks = vi.hoisted(() => ({ }, })) -vi.mock('@latitude-data/core', async (importOriginal) => { - const original = (await importOriginal()) as typeof importOriginal +vi.mock( + '@latitude-data/core/services/commits/runDocumentAtCommit', + async (importOriginal) => { + const original = (await importOriginal()) as typeof importOriginal - return { - ...original, - runDocumentAtCommit: mocks.runDocumentAtCommit, - } -}) + return { + ...original, + runDocumentAtCommit: mocks.runDocumentAtCommit, + } + }, +) vi.mock('$/jobs', () => ({ queues: mocks.queues, diff --git a/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/run.ts b/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/run.ts index d445571ee..0587cd265 100644 --- a/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/run.ts +++ b/apps/gateway/src/routes/api/v1/projects/:projectId/commits/:commitUuid/documents/handlers/run.ts @@ -1,5 +1,6 @@ import { zValidator } from '@hono/zod-validator' -import { LogSources, runDocumentAtCommit } from '@latitude-data/core' +import { LogSources } from '@latitude-data/core/browser' +import { runDocumentAtCommit } from '@latitude-data/core/services/commits/runDocumentAtCommit' import { pipeToStream } from '$/common/pipeToStream' import { queues } from '$/jobs' import { Factory } from 'hono/factory' @@ -41,8 +42,7 @@ export const runHandler = factory.createHandlers( commit, parameters, providerLogHandler: (log) => { - // TODO: review why this is possibly undefined now - queues.defaultQueue.jobs.enqueueCreateProviderLogJob!({ + queues.defaultQueue.jobs.enqueueCreateProviderLogJob({ ...log, source, apiKeyId: apiKey.id, @@ -52,8 +52,7 @@ export const runHandler = factory.createHandlers( await pipeToStream(stream, result.stream) - // TODO: review why this is possibly undefined now - queues.defaultQueue.jobs.enqueueCreateDocumentLogJob!({ + queues.defaultQueue.jobs.enqueueCreateDocumentLogJob({ commit, data: { uuid: result.documentLogUuid, diff --git a/apps/gateway/tsconfig.json b/apps/gateway/tsconfig.json index 59a128fcf..7cfca447d 100644 --- a/apps/gateway/tsconfig.json +++ b/apps/gateway/tsconfig.json @@ -9,8 +9,13 @@ "$/*": ["./src/*"], "acorn": ["node_modules/@latitude-data/typescript-config/types/acorn"] }, - "types": ["node"] + "types": ["node"], + "jsx": "preserve" }, - "include": ["src/**/*.ts", "test/**/*.ts"], + "include": [ + "src/**/*.ts", + "src/**/*.test.ts", + "test/**/*.ts" + ], "exclude": ["node_modules"] } diff --git a/apps/gateway/tsup.config.ts b/apps/gateway/tsup.config.ts index e97a65809..50bcd90d7 100644 --- a/apps/gateway/tsup.config.ts +++ b/apps/gateway/tsup.config.ts @@ -27,5 +27,6 @@ export default defineConfig({ '@latitude-data/env', '@latitude-data/core', '@latitude-data/jobs', + '@latitude-data/mailers', ], }) diff --git a/apps/web/package.json b/apps/web/package.json index e86090d11..fd472c514 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -19,6 +19,7 @@ "@latitude-data/core": "workspace:*", "@latitude-data/env": "workspace:^", "@latitude-data/jobs": "workspace:*", + "@latitude-data/mailers": "workspace:^", "@latitude-data/sdk-js": "workspace:*", "@latitude-data/web-ui": "workspace:*", "@lucia-auth/adapter-drizzle": "^1.0.7", diff --git a/apps/web/public/logodark.png b/apps/web/public/logodark.png new file mode 100644 index 000000000..70988345c Binary files /dev/null and b/apps/web/public/logodark.png differ diff --git a/apps/web/src/actions/commits/create.ts b/apps/web/src/actions/commits/create.ts index 1f5c5a460..3d303aeed 100644 --- a/apps/web/src/actions/commits/create.ts +++ b/apps/web/src/actions/commits/create.ts @@ -1,6 +1,6 @@ 'use server' -import { createCommit } from '@latitude-data/core' +import { createCommit } from '@latitude-data/core/services/commits/create' import { z } from 'zod' import { withProject } from '../procedures' diff --git a/apps/web/src/actions/commits/deleteDraftCommit.test.ts b/apps/web/src/actions/commits/deleteDraftCommit.test.ts index 751c13125..9329597ce 100644 --- a/apps/web/src/actions/commits/deleteDraftCommit.test.ts +++ b/apps/web/src/actions/commits/deleteDraftCommit.test.ts @@ -1,10 +1,10 @@ -import { database } from '@latitude-data/core' import type { Commit, Project, SafeUser, Workspace, } from '@latitude-data/core/browser' +import { database } from '@latitude-data/core/client' import * as factories from '@latitude-data/core/factories' import { deleteDraftCommitAction } from '$/actions/commits/deleteDraftCommitAction' import { beforeEach, describe, expect, it, vi } from 'vitest' diff --git a/apps/web/src/actions/commits/deleteDraftCommitAction.ts b/apps/web/src/actions/commits/deleteDraftCommitAction.ts index 53cb14af9..fa3874bb3 100644 --- a/apps/web/src/actions/commits/deleteDraftCommitAction.ts +++ b/apps/web/src/actions/commits/deleteDraftCommitAction.ts @@ -1,6 +1,7 @@ 'use server' -import { CommitsRepository, deleteCommitDraft } from '@latitude-data/core' +import { CommitsRepository } from '@latitude-data/core/repositories' +import { deleteCommitDraft } from '@latitude-data/core/services/commits/delete' import { z } from 'zod' import { withProject } from '../procedures' diff --git a/apps/web/src/actions/commits/fetchCommitsByProjectAction.test.ts b/apps/web/src/actions/commits/fetchCommitsByProjectAction.test.ts index 90d56e30d..592208ded 100644 --- a/apps/web/src/actions/commits/fetchCommitsByProjectAction.test.ts +++ b/apps/web/src/actions/commits/fetchCommitsByProjectAction.test.ts @@ -1,5 +1,4 @@ -import { CommitStatus } from '@latitude-data/core' -import type { Workspace } from '@latitude-data/core/browser' +import { CommitStatus, type Workspace } from '@latitude-data/core/browser' import * as factories from '@latitude-data/core/factories' import { beforeEach, describe, expect, it, vi } from 'vitest' diff --git a/apps/web/src/actions/commits/fetchCommitsByProjectAction.ts b/apps/web/src/actions/commits/fetchCommitsByProjectAction.ts index 194c7a8bd..14991a442 100644 --- a/apps/web/src/actions/commits/fetchCommitsByProjectAction.ts +++ b/apps/web/src/actions/commits/fetchCommitsByProjectAction.ts @@ -1,6 +1,7 @@ 'use server' -import { CommitsRepository, CommitStatus } from '@latitude-data/core' +import { CommitStatus } from '@latitude-data/core/browser' +import { CommitsRepository } from '@latitude-data/core/repositories' import { z } from 'zod' import { withProject } from '../procedures' diff --git a/apps/web/src/actions/commits/getChangedDocumentsInDraftAction.ts b/apps/web/src/actions/commits/getChangedDocumentsInDraftAction.ts index 0b1dd00cd..6032829a9 100644 --- a/apps/web/src/actions/commits/getChangedDocumentsInDraftAction.ts +++ b/apps/web/src/actions/commits/getChangedDocumentsInDraftAction.ts @@ -1,6 +1,6 @@ 'use server' -import { CommitsRepository } from '@latitude-data/core' +import { CommitsRepository } from '@latitude-data/core/repositories' import { z } from 'zod' import { withProject } from '../procedures' diff --git a/apps/web/src/actions/commits/publishDraftCommit.test.ts b/apps/web/src/actions/commits/publishDraftCommit.test.ts index 2b1334b30..62fcab963 100644 --- a/apps/web/src/actions/commits/publishDraftCommit.test.ts +++ b/apps/web/src/actions/commits/publishDraftCommit.test.ts @@ -1,4 +1,3 @@ -import { updateDocument } from '@latitude-data/core' import type { Commit, DocumentVersion, @@ -7,6 +6,7 @@ import type { Workspace, } from '@latitude-data/core/browser' import * as factories from '@latitude-data/core/factories' +import { updateDocument } from '@latitude-data/core/services/documents/update' import { publishDraftCommitAction } from '$/actions/commits/publishDraftCommitAction' import { beforeEach, describe, expect, it, vi } from 'vitest' diff --git a/apps/web/src/actions/commits/publishDraftCommitAction.ts b/apps/web/src/actions/commits/publishDraftCommitAction.ts index 70ffc2810..60cedc534 100644 --- a/apps/web/src/actions/commits/publishDraftCommitAction.ts +++ b/apps/web/src/actions/commits/publishDraftCommitAction.ts @@ -1,6 +1,7 @@ 'use server' -import { CommitsRepository, mergeCommit } from '@latitude-data/core' +import { CommitsRepository } from '@latitude-data/core/repositories' +import { mergeCommit } from '@latitude-data/core/services/commits/merge' import { z } from 'zod' import { withProject } from '../procedures' diff --git a/apps/web/src/actions/documents/create.ts b/apps/web/src/actions/documents/create.ts index f8b7fae74..24d9a2907 100644 --- a/apps/web/src/actions/documents/create.ts +++ b/apps/web/src/actions/documents/create.ts @@ -1,6 +1,7 @@ 'use server' -import { CommitsRepository, createNewDocument } from '@latitude-data/core' +import { CommitsRepository } from '@latitude-data/core/repositories' +import { createNewDocument } from '@latitude-data/core/services/documents/create' import { z } from 'zod' import { withProject } from '../procedures' diff --git a/apps/web/src/actions/documents/destroyDocumentAction/index.test.ts b/apps/web/src/actions/documents/destroyDocumentAction/index.test.ts index 0dd53ed48..03a0bd513 100644 --- a/apps/web/src/actions/documents/destroyDocumentAction/index.test.ts +++ b/apps/web/src/actions/documents/destroyDocumentAction/index.test.ts @@ -1,11 +1,12 @@ -import { database, documentVersions } from '@latitude-data/core' import { Commit, DocumentVersion, Project, SafeUser, } from '@latitude-data/core/browser' +import { database } from '@latitude-data/core/client' import { createDraft, createProject } from '@latitude-data/core/factories' +import { documentVersions } from '@latitude-data/core/schema' import { and, eq } from 'drizzle-orm' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -94,6 +95,7 @@ describe('destroyDocumentAction', async () => { }) // TODO: move to core const documents = await database.query.documentVersions.findMany({ + // @ts-ignore where: and(eq(documentVersions.documentUuid, document.documentUuid)), }) diff --git a/apps/web/src/actions/documents/destroyDocumentAction/index.ts b/apps/web/src/actions/documents/destroyDocumentAction/index.ts index 2fc3c749d..a6dc7db14 100644 --- a/apps/web/src/actions/documents/destroyDocumentAction/index.ts +++ b/apps/web/src/actions/documents/destroyDocumentAction/index.ts @@ -2,9 +2,9 @@ import { CommitsRepository, - destroyDocument, DocumentVersionsRepository, -} from '@latitude-data/core' +} from '@latitude-data/core/repositories' +import { destroyDocument } from '@latitude-data/core/services/documents/destroyDocument' import { withProject } from '$/actions/procedures' import { z } from 'zod' diff --git a/apps/web/src/actions/documents/destroyFolderAction.ts b/apps/web/src/actions/documents/destroyFolderAction.ts index 07c433683..2b349fd4d 100644 --- a/apps/web/src/actions/documents/destroyFolderAction.ts +++ b/apps/web/src/actions/documents/destroyFolderAction.ts @@ -1,6 +1,7 @@ 'use server' -import { CommitsRepository, destroyFolder } from '@latitude-data/core' +import { CommitsRepository } from '@latitude-data/core/repositories' +import { destroyFolder } from '@latitude-data/core/services/documents/destroyFolder' import { z } from 'zod' import { withProject } from '../procedures' diff --git a/apps/web/src/actions/documents/getDocumentsAtCommitAction.ts b/apps/web/src/actions/documents/getDocumentsAtCommitAction.ts index 408cc6d80..3c2b2d878 100644 --- a/apps/web/src/actions/documents/getDocumentsAtCommitAction.ts +++ b/apps/web/src/actions/documents/getDocumentsAtCommitAction.ts @@ -3,7 +3,7 @@ import { CommitsRepository, DocumentVersionsRepository, -} from '@latitude-data/core' +} from '@latitude-data/core/repositories' import { z } from 'zod' import { withProject } from '../procedures' diff --git a/apps/web/src/actions/documents/updateContent.test.ts b/apps/web/src/actions/documents/updateContent.test.ts index 7ab209b47..b7e1340ca 100644 --- a/apps/web/src/actions/documents/updateContent.test.ts +++ b/apps/web/src/actions/documents/updateContent.test.ts @@ -1,6 +1,6 @@ -import { updateDocument } from '@latitude-data/core' import { DocumentVersion, Project, SafeUser } from '@latitude-data/core/browser' import * as factories from '@latitude-data/core/factories' +import { updateDocument } from '@latitude-data/core/services/documents/update' import { beforeEach, describe, expect, it, vi } from 'vitest' import { updateDocumentContentAction } from './updateContent' diff --git a/apps/web/src/actions/documents/updateContent.ts b/apps/web/src/actions/documents/updateContent.ts index 7ad75f4a3..e508a8bfc 100644 --- a/apps/web/src/actions/documents/updateContent.ts +++ b/apps/web/src/actions/documents/updateContent.ts @@ -3,8 +3,8 @@ import { CommitsRepository, DocumentVersionsRepository, - updateDocument, -} from '@latitude-data/core' +} from '@latitude-data/core/repositories' +import { updateDocument } from '@latitude-data/core/services/documents/update' import { z } from 'zod' import { withProject } from '../procedures' diff --git a/apps/web/src/actions/invitations/accept.ts b/apps/web/src/actions/invitations/accept.ts new file mode 100644 index 000000000..0b9f8826c --- /dev/null +++ b/apps/web/src/actions/invitations/accept.ts @@ -0,0 +1,46 @@ +'use server' + +import { + unsafelyFindMembershipByToken, + unsafelyFindWorkspace, + unsafelyGetUser, +} from '@latitude-data/core/data-access' +import { NotFoundError } from '@latitude-data/core/lib/errors' +import { acceptInvitation } from '@latitude-data/core/services/invitations/accept' +import { setSession } from '$/services/auth/setSession' +import { ROUTES } from '$/services/routes' +import { redirect } from 'next/navigation' +import { z } from 'zod' +import { createServerAction } from 'zsa' + +export const acceptInvitationAction = createServerAction() + .input( + z.object({ + membershipToken: z.string(), + email: z.string().optional(), + }), + { type: 'formData' }, + ) + .handler(async ({ input }) => { + const { membershipToken } = input + const membership = await unsafelyFindMembershipByToken( + membershipToken, + ).then((r) => r.unwrap()) + const workspace = await unsafelyFindWorkspace(membership.workspaceId).then( + (r) => r.unwrap(), + ) + + const user = await unsafelyGetUser(membership.userId) + if (!user) throw new NotFoundError('User not found') + + await acceptInvitation({ membership, user }) + + setSession({ + sessionData: { + user, + workspace: { id: Number(workspace.id), name: workspace.name }, + }, + }) + + return redirect(ROUTES.root) + }) diff --git a/apps/web/src/actions/magicLinkTokens/confirm.ts b/apps/web/src/actions/magicLinkTokens/confirm.ts new file mode 100644 index 000000000..fed6a4683 --- /dev/null +++ b/apps/web/src/actions/magicLinkTokens/confirm.ts @@ -0,0 +1,39 @@ +'use server' + +import { unsafelyGetUser } from '@latitude-data/core/data-access' +import { NotFoundError } from '@latitude-data/core/lib/errors' +import { confirmMagicLinkToken } from '@latitude-data/core/services/magicLinkTokens/confirm' +import { getFirstWorkspace } from '$/data-access' +import { setSession } from '$/services/auth/setSession' +import { ROUTES } from '$/services/routes' +import { redirect } from 'next/navigation' +import { z } from 'zod' +import { createServerAction } from 'zsa' + +export const confirmMagicLinkTokenAction = createServerAction() + .input( + z.object({ + token: z.string(), + }), + ) + .handler(async ({ input }) => { + const magicLinkToken = await confirmMagicLinkToken(input.token).then((r) => + r.unwrap(), + ) + + const user = await unsafelyGetUser(magicLinkToken.userId) + if (!user) throw new NotFoundError('User not found') + + const workspace = await getFirstWorkspace({ userId: user.id }).then((r) => + r.unwrap(), + ) + + setSession({ + sessionData: { + user, + workspace, + }, + }) + + redirect(ROUTES.root) + }) diff --git a/apps/web/src/actions/memberships/destroy.ts b/apps/web/src/actions/memberships/destroy.ts new file mode 100644 index 000000000..447a21a47 --- /dev/null +++ b/apps/web/src/actions/memberships/destroy.ts @@ -0,0 +1,24 @@ +'use server' + +import { MembershipsRepository } from '@latitude-data/core/repositories' +import { destroyMembership } from '@latitude-data/core/services/memberships/destroy' +import { z } from 'zod' + +import { authProcedure } from '../procedures' + +export const destroyMembershipAction = authProcedure + .createServerAction() + .input( + z.object({ + userId: z.string(), + }), + ) + .handler(async ({ input, ctx }) => { + const { userId } = input + const membershipsScope = new MembershipsRepository(ctx.workspace.id) + const membership = await membershipsScope + .findByUserId(userId) + .then((r) => r.unwrap()) + + return await destroyMembership(membership).then((r) => r.unwrap()) + }) diff --git a/apps/web/src/actions/procedures/index.ts b/apps/web/src/actions/procedures/index.ts index 9e75e0b85..966d57a3f 100644 --- a/apps/web/src/actions/procedures/index.ts +++ b/apps/web/src/actions/procedures/index.ts @@ -1,4 +1,5 @@ -import { ProjectsRepository, UnauthorizedError } from '@latitude-data/core' +import { UnauthorizedError } from '@latitude-data/core/lib/errors' +import { ProjectsRepository } from '@latitude-data/core/repositories' import { getCurrentUser } from '$/services/auth/getCurrentUser' import { z } from 'zod' import { createServerActionProcedure } from 'zsa' diff --git a/apps/web/src/actions/providerApiKeys/create.ts b/apps/web/src/actions/providerApiKeys/create.ts index 8526e0fdb..181994b88 100644 --- a/apps/web/src/actions/providerApiKeys/create.ts +++ b/apps/web/src/actions/providerApiKeys/create.ts @@ -1,7 +1,7 @@ 'use server' -import { createProviderApiKey } from '@latitude-data/core' import { Providers } from '@latitude-data/core/browser' +import { createProviderApiKey } from '@latitude-data/core/services/providerApiKeys/create' import providerApiKeyPresenter from '$/presenters/providerApiKeyPresenter' import { z } from 'zod' diff --git a/apps/web/src/actions/providerApiKeys/destroy.ts b/apps/web/src/actions/providerApiKeys/destroy.ts index 08e30b439..c8eb4468d 100644 --- a/apps/web/src/actions/providerApiKeys/destroy.ts +++ b/apps/web/src/actions/providerApiKeys/destroy.ts @@ -1,9 +1,7 @@ 'use server' -import { - destroyProviderApiKey, - ProviderApiKeysRepository, -} from '@latitude-data/core' +import { ProviderApiKeysRepository } from '@latitude-data/core/repositories' +import { destroyProviderApiKey } from '@latitude-data/core/services/providerApiKeys/destroy' import providerApiKeyPresenter from '$/presenters/providerApiKeyPresenter' import { z } from 'zod' diff --git a/apps/web/src/actions/providerApiKeys/fetch.test.ts b/apps/web/src/actions/providerApiKeys/fetch.test.ts index 6add1d0f6..d1630538f 100644 --- a/apps/web/src/actions/providerApiKeys/fetch.test.ts +++ b/apps/web/src/actions/providerApiKeys/fetch.test.ts @@ -1,6 +1,6 @@ -import { createProviderApiKey } from '@latitude-data/core' import { Providers } from '@latitude-data/core/browser' import { createWorkspace } from '@latitude-data/core/factories' +import { createProviderApiKey } from '@latitude-data/core/services/providerApiKeys/create' import { getSession } from '$/services/auth/getSession' import { beforeEach, describe, expect, it, Mock, vi } from 'vitest' diff --git a/apps/web/src/actions/providerApiKeys/fetch.ts b/apps/web/src/actions/providerApiKeys/fetch.ts index 7c15b306e..ff74e3c9d 100644 --- a/apps/web/src/actions/providerApiKeys/fetch.ts +++ b/apps/web/src/actions/providerApiKeys/fetch.ts @@ -1,6 +1,6 @@ 'use server' -import { ProviderApiKeysRepository } from '@latitude-data/core' +import { ProviderApiKeysRepository } from '@latitude-data/core/repositories' import providerApiKeyPresenter from '$/presenters/providerApiKeyPresenter' import { authProcedure } from '../procedures' diff --git a/apps/web/src/actions/providerLogs/getProviderLogsForDocumentLogAction.ts b/apps/web/src/actions/providerLogs/getProviderLogsForDocumentLogAction.ts index c56e5dddb..ac7ab44ee 100644 --- a/apps/web/src/actions/providerLogs/getProviderLogsForDocumentLogAction.ts +++ b/apps/web/src/actions/providerLogs/getProviderLogsForDocumentLogAction.ts @@ -1,6 +1,6 @@ 'use server' -import { ProviderLogsRepository } from '@latitude-data/core' +import { ProviderLogsRepository } from '@latitude-data/core/repositories' import { z } from 'zod' import { authProcedure } from '../procedures' diff --git a/apps/web/src/actions/sdk/runDocumentAction.ts b/apps/web/src/actions/sdk/runDocumentAction.ts index a4434c3fa..47b0188e0 100644 --- a/apps/web/src/actions/sdk/runDocumentAction.ts +++ b/apps/web/src/actions/sdk/runDocumentAction.ts @@ -1,6 +1,6 @@ 'use server' -import { LogSources } from '@latitude-data/core' +import { LogSources } from '@latitude-data/core/browser' import { type ChainEvent, type StreamChainResponse, diff --git a/apps/web/src/actions/user/loginAction.ts b/apps/web/src/actions/user/loginAction.ts index 78808526b..82de69a19 100644 --- a/apps/web/src/actions/user/loginAction.ts +++ b/apps/web/src/actions/user/loginAction.ts @@ -1,7 +1,7 @@ 'use server' +import { createMagicLinkToken } from '@latitude-data/core/services/magicLinkTokens/create' import { getUserFromCredentials } from '$/data-access' -import { setSession } from '$/services/auth/setSession' import { ROUTES } from '$/services/routes' import { redirect } from 'next/navigation' import { z } from 'zod' @@ -11,14 +11,12 @@ export const loginAction = createServerAction() .input( z.object({ email: z.string().email(), - password: z.string().min(3), }), { type: 'formData' }, ) .handler(async ({ input }) => { - const result = await getUserFromCredentials(input) - const sessionData = result.unwrap() + const { user } = await getUserFromCredentials(input).then((r) => r.unwrap()) + await createMagicLinkToken({ user }).then((r) => r.unwrap()) - setSession({ sessionData }) - redirect(ROUTES.root) + redirect(ROUTES.auth.magicLinkSent(user.email)) }) diff --git a/apps/web/src/actions/user/setupAction.ts b/apps/web/src/actions/user/setupAction.ts index 1d8570d22..f69aa078d 100644 --- a/apps/web/src/actions/user/setupAction.ts +++ b/apps/web/src/actions/user/setupAction.ts @@ -1,6 +1,5 @@ 'use server' -import { isWorkspaceCreated } from '$/data-access' import { setSession } from '$/services/auth/setSession' import { ROUTES } from '$/services/routes' import setupService from '$/services/user/setupService' @@ -13,9 +12,6 @@ export const setupAction = createServerAction() z.object({ name: z.string().min(1, { message: 'Name is a required field' }), email: z.string().email(), - password: z - .string() - .min(8, { message: 'Password must be at least 8 characters' }), companyName: z .string() .min(1, { message: 'Workspace name is a required field' }), @@ -23,14 +19,10 @@ export const setupAction = createServerAction() { type: 'formData' }, ) .handler(async ({ input }) => { - const itWasAlreadySetup = await isWorkspaceCreated() - if (itWasAlreadySetup) { - throw new Error('Workspace already created') - } - const result = await setupService(input) const sessionData = result.unwrap() setSession({ sessionData }) + redirect(ROUTES.root) }) diff --git a/apps/web/src/actions/users/fetch.test.ts b/apps/web/src/actions/users/fetch.test.ts index a14609b99..e0a450a00 100644 --- a/apps/web/src/actions/users/fetch.test.ts +++ b/apps/web/src/actions/users/fetch.test.ts @@ -37,8 +37,6 @@ describe('getUsersAction', () => { expect(error).toBeNull() expect(data?.length).toEqual(1) - // @ts-ignore - encryptedPassword is not defined on the User type for some reason? - expect(data![0]!.encryptedPassword).toBeUndefined() }) }) }) diff --git a/apps/web/src/actions/users/fetch.ts b/apps/web/src/actions/users/fetch.ts index 3f6ab09e6..b152707e9 100644 --- a/apps/web/src/actions/users/fetch.ts +++ b/apps/web/src/actions/users/fetch.ts @@ -1,7 +1,6 @@ 'use server' -import { UsersRepository } from '@latitude-data/core' -import userPresenter from '$/presenters/userPresenter' +import { UsersRepository } from '@latitude-data/core/repositories' import { authProcedure } from '../procedures' @@ -10,8 +9,5 @@ export const getUsersActions = authProcedure .handler(async ({ ctx }) => { const usersScope = new UsersRepository(ctx.workspace.id) - return usersScope - .findAll() - .then((r) => r.unwrap()) - .then((r) => r.map(userPresenter)) + return usersScope.findAll().then((r) => r.unwrap()) }) diff --git a/apps/web/src/actions/users/invite.ts b/apps/web/src/actions/users/invite.ts new file mode 100644 index 000000000..eb4191ff3 --- /dev/null +++ b/apps/web/src/actions/users/invite.ts @@ -0,0 +1,23 @@ +'use server' + +import { inviteUser } from '@latitude-data/core/services/users/invite' +import { z } from 'zod' + +import { authProcedure } from '../procedures' + +export const inviteUserAction = authProcedure + .createServerAction() + .input( + z.object({ + email: z.string(), + name: z.string(), + }), + ) + .handler(async ({ input, ctx }) => + inviteUser({ + email: input.email, + name: input.name, + workspace: ctx.workspace, + author: ctx.user, + }).then((r) => r.unwrap()), + ) diff --git a/apps/web/src/actions/workspaces/update.ts b/apps/web/src/actions/workspaces/update.ts index 9f992394a..a0e014e2b 100644 --- a/apps/web/src/actions/workspaces/update.ts +++ b/apps/web/src/actions/workspaces/update.ts @@ -1,6 +1,7 @@ 'use server' -import { updateWorkspace, WorkspacesRepository } from '@latitude-data/core' +import { WorkspacesRepository } from '@latitude-data/core/repositories' +import { updateWorkspace } from '@latitude-data/core/services/workspaces/update' import { z } from 'zod' import { authProcedure } from '../procedures' diff --git a/apps/web/src/app/(private)/_data-access/index.ts b/apps/web/src/app/(private)/_data-access/index.ts index 8a998ac99..07c00eb05 100644 --- a/apps/web/src/app/(private)/_data-access/index.ts +++ b/apps/web/src/app/(private)/_data-access/index.ts @@ -1,13 +1,13 @@ import { cache } from 'react' +import { type Commit, type Project } from '@latitude-data/core/browser' +import { NotFoundError } from '@latitude-data/core/lib/errors' import { CommitsRepository, DocumentLogsRepository, DocumentVersionsRepository, - NotFoundError, ProjectsRepository, -} from '@latitude-data/core' -import type { Commit, Project } from '@latitude-data/core/browser' +} from '@latitude-data/core/repositories/index' import { getCurrentUser } from '$/services/auth/getCurrentUser' import { notFound } from 'next/navigation' diff --git a/apps/web/src/app/(private)/_lib/createSdk.ts b/apps/web/src/app/(private)/_lib/createSdk.ts index 59b02230b..1098cce04 100644 --- a/apps/web/src/app/(private)/_lib/createSdk.ts +++ b/apps/web/src/app/(private)/_lib/createSdk.ts @@ -1,4 +1,5 @@ -import { LatitudeApiKeysRepository, Result } from '@latitude-data/core' +import { Result } from '@latitude-data/core/lib/Result' +import { LatitudeApiKeysRepository } from '@latitude-data/core/repositories' import { LatitudeSdk } from '@latitude-data/sdk-js' import env from '$/env' import { getCurrentUser } from '$/services/auth/getCurrentUser' diff --git a/apps/web/src/app/(private)/page.tsx b/apps/web/src/app/(private)/page.tsx index 1b69d000f..e0191f70b 100644 --- a/apps/web/src/app/(private)/page.tsx +++ b/apps/web/src/app/(private)/page.tsx @@ -1,5 +1,5 @@ -import { NotFoundError } from '@latitude-data/core' import { type Project } from '@latitude-data/core/browser' +import { NotFoundError } from '@latitude-data/core/lib/errors' import { getFirstProjectCached } from '$/app/(private)/_data-access' import { getCurrentUser, SessionData } from '$/services/auth/getCurrentUser' import { ROUTES } from '$/services/routes' diff --git a/apps/web/src/app/(private)/projects/[projectId]/page.tsx b/apps/web/src/app/(private)/projects/[projectId]/page.tsx index 134833a3d..d5a897941 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/page.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/page.tsx @@ -1,5 +1,5 @@ -import { HEAD_COMMIT, NotFoundError } from '@latitude-data/core' -import type { Project } from '@latitude-data/core/browser' +import { HEAD_COMMIT, type Project } from '@latitude-data/core/browser' +import { NotFoundError } from '@latitude-data/core/lib/errors' import { findCommitCached, findProjectCached, diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/PublishDraftCommitModal/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/PublishDraftCommitModal/index.tsx index 3138a3e01..34933f1a0 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/PublishDraftCommitModal/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/PublishDraftCommitModal/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from 'react' -import { ChangedDocument } from '@latitude-data/core' import { Commit, ModifiedDocumentType } from '@latitude-data/core/browser' +import { ChangedDocument } from '@latitude-data/core/repositories' import { cn, colors, diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/index.tsx index 2bb56ca78..cc548f8ca 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/index.tsx @@ -1,5 +1,10 @@ -import { CommitsRepository, CommitStatus } from '@latitude-data/core' -import { Commit, DocumentVersion, Project } from '@latitude-data/core/browser' +import { + Commit, + CommitStatus, + DocumentVersion, + Project, +} from '@latitude-data/core/browser' +import { CommitsRepository } from '@latitude-data/core/repositories/index' import { DocumentSidebar } from '@latitude-data/web-ui' import { fetchCommitsByProjectAction } from '$/actions/commits/fetchCommitsByProjectAction' import { getDocumentsAtCommitCached } from '$/app/(private)/_data-access' diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/Metadata.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/Metadata.tsx index 017049760..9b94e7328 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/Metadata.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/Metadata.tsx @@ -1,7 +1,7 @@ import { ReactNode, useMemo } from 'react' -import type { DocumentLogWithMetadata } from '@latitude-data/core' import { ProviderLog } from '@latitude-data/core/browser' +import { DocumentLogWithMetadata } from '@latitude-data/core/repositories' import { ClickToCopy, Icons, diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/index.tsx index 13c379d09..5e8380a5f 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/index.tsx @@ -2,8 +2,8 @@ import { useState } from 'react' -import type { DocumentLogWithMetadata } from '@latitude-data/core' import { ProviderLog } from '@latitude-data/core/browser' +import { DocumentLogWithMetadata } from '@latitude-data/core/repositories' import { TabSelector } from '@latitude-data/web-ui' import { DocumentLogMessages } from './Messages' diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogsTable.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogsTable.tsx index 16b3c4840..6b6a20ce1 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogsTable.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogsTable.tsx @@ -1,6 +1,6 @@ 'use client' -import { DocumentLogWithMetadata } from '@latitude-data/core' +import { DocumentLogWithMetadata } from '@latitude-data/core/repositories' import { Badge, cn, diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/index.tsx index d15ee6066..02ad672da 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/index.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' -import { DocumentLogWithMetadata } from '@latitude-data/core' +import { DocumentLogWithMetadata } from '@latitude-data/core/repositories' import useProviderLogs from '$/stores/providerLogs' import { DocumentLogInfo } from './DocumentLogInfo' 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 index 85728a56e..aca1abe88 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx @@ -1,12 +1,15 @@ import { ReactNode } from 'react' import { - CommitsRepository, HEAD_COMMIT, - NotFoundError, + type Commit, + type Project, +} from '@latitude-data/core/browser' +import { NotFoundError } from '@latitude-data/core/lib/errors' +import { + CommitsRepository, ProjectsRepository, -} from '@latitude-data/core' -import type { Commit, Project } from '@latitude-data/core/browser' +} from '@latitude-data/core/repositories/index' import { CommitProvider, ProjectProvider } from '@latitude-data/web-ui' import { BreadcrumpBadge } from '@latitude-data/web-ui/browser' import { NAV_LINKS } from '$/app/(private)/_lib/constants' diff --git a/apps/web/src/app/(private)/settings/_components/Memberships/New/index.tsx b/apps/web/src/app/(private)/settings/_components/Memberships/New/index.tsx new file mode 100644 index 000000000..c569fe3ba --- /dev/null +++ b/apps/web/src/app/(private)/settings/_components/Memberships/New/index.tsx @@ -0,0 +1,59 @@ +import { + Button, + CloseTrigger, + FormWrapper, + Input, + Modal, +} from '@latitude-data/web-ui' +import { useFormAction } from '$/hooks/useFormAction' +import useUsers from '$/stores/users' + +export default function NewUser({ + open, + setOpen, +}: { + open: boolean + setOpen: (open: boolean) => void +}) { + const { invite } = useUsers() + const { data, action } = useFormAction(invite, { + onSuccess: () => setOpen(false), + }) + return ( + + + + + } + > +
+ + + + +
+
+ ) +} diff --git a/apps/web/src/app/(private)/settings/_components/Memberships/index.tsx b/apps/web/src/app/(private)/settings/_components/Memberships/index.tsx new file mode 100644 index 000000000..7c30dee18 --- /dev/null +++ b/apps/web/src/app/(private)/settings/_components/Memberships/index.tsx @@ -0,0 +1,86 @@ +'use client' + +import { useState } from 'react' + +import type { SafeUser } from '@latitude-data/core/browser' +import { + Button, + Icons, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + Text, +} from '@latitude-data/web-ui' +import useUsers from '$/stores/users' + +import NewUser from './New' + +export default function Memberships() { + const [open, setOpen] = useState(false) + const { data: users, destroy } = useUsers() + + return ( +
+ +
+ Workspace Users + +
+
+ {users.length > 0 && } +
+
+ ) +} + +function UsersTable({ + users, + destroy, +}: { + users: SafeUser[] + destroy: Function +}) { + return ( + + + + Name + Email + Confirmed At + + + + + {users.map((user) => ( + + + {user.name} + + + {user.email} + + + + {user.confirmedAt?.toISOString() || '-'} + + + + + + + ))} + +
+ ) +} diff --git a/apps/web/src/app/(private)/settings/page.tsx b/apps/web/src/app/(private)/settings/page.tsx index a5c72c892..434b11e62 100644 --- a/apps/web/src/app/(private)/settings/page.tsx +++ b/apps/web/src/app/(private)/settings/page.tsx @@ -1,3 +1,4 @@ +import Memberships from './_components/Memberships' import ProviderApiKeys from './_components/ProviderApiKeys' import WorkspaceName from './_components/WorkspaceName' @@ -7,6 +8,7 @@ export default function SettingsPage() {
+
) diff --git a/apps/web/src/app/(public)/_components/Footer/index.tsx b/apps/web/src/app/(public)/_components/Footer/index.tsx index 2320f8cef..2b1ef6609 100644 --- a/apps/web/src/app/(public)/_components/Footer/index.tsx +++ b/apps/web/src/app/(public)/_components/Footer/index.tsx @@ -2,7 +2,7 @@ import { LATITUDE_DOCS_URL, LATITUDE_EMAIL, LATITUDE_SLACK_URL, -} from '@latitude-data/core' +} from '@latitude-data/core/browser' import { Text } from '@latitude-data/web-ui' export default function AuthFooter() { diff --git a/apps/web/src/app/(public)/_data_access/index.ts b/apps/web/src/app/(public)/_data_access/index.ts new file mode 100644 index 000000000..17b91e435 --- /dev/null +++ b/apps/web/src/app/(public)/_data_access/index.ts @@ -0,0 +1,19 @@ +import { cache } from 'react' + +import { + unsafelyFindMembershipByToken, + unsafelyFindWorkspace, + unsafelyGetUser, +} from '@latitude-data/core/data-access' + +export const findMembershipByTokenCache = cache(async (token: string) => { + return await unsafelyFindMembershipByToken(token).then((r) => r.unwrap()) +}) + +export const findWorkspaceCache = cache(async (id: number) => { + return await unsafelyFindWorkspace(id).then((r) => r.unwrap()) +}) + +export const findUserCache = cache(async (id: string) => { + return await unsafelyGetUser(id) +}) diff --git a/apps/web/src/app/(public)/invitations/[token]/InvitationForm/index.tsx b/apps/web/src/app/(public)/invitations/[token]/InvitationForm/index.tsx new file mode 100644 index 000000000..ed835c4fa --- /dev/null +++ b/apps/web/src/app/(public)/invitations/[token]/InvitationForm/index.tsx @@ -0,0 +1,65 @@ +'use client' + +import { ReactNode } from 'react' + +import { Membership, SafeUser } from '@latitude-data/core/browser' +import { Button, FormWrapper, Input, useToast } from '@latitude-data/web-ui' +import { acceptInvitationAction } from '$/actions/invitations/accept' +import { useServerAction } from 'zsa-react' + +export default function InvitationForm({ + user, + membership, + footer, +}: { + user: SafeUser + membership: Membership + footer: ReactNode +}) { + const { toast } = useToast() + const { isPending, error, executeFormAction } = useServerAction( + acceptInvitationAction, + { + onError: ({ err }) => { + if (err.code === 'ERROR') { + toast({ + title: 'Saving failed', + description: err.message, + variant: 'destructive', + }) + } + }, + }, + ) + const errors = error?.fieldErrors + + return ( +
+ + + {!user.confirmedAt && ( + <> + + + )} + + + {footer} + +
+ ) +} diff --git a/apps/web/src/app/(public)/invitations/[token]/page.tsx b/apps/web/src/app/(public)/invitations/[token]/page.tsx new file mode 100644 index 000000000..2c589ea2f --- /dev/null +++ b/apps/web/src/app/(public)/invitations/[token]/page.tsx @@ -0,0 +1,47 @@ +import { Card, CardContent, FocusHeader } from '@latitude-data/web-ui' +import { FocusLayout } from '$/components/layouts' +import { ROUTES } from '$/services/routes' +import { redirect } from 'next/navigation' + +import AuthFooter from '../../_components/Footer' +import { + findMembershipByTokenCache, + findUserCache, + findWorkspaceCache, +} from '../../_data_access' +import InvitationForm from './InvitationForm' + +export const dynamic = 'force-dynamic' + +export default async function InvitationPage({ + params, +}: { + params: { token: string } +}) { + const { token } = params + const m = await findMembershipByTokenCache(token as string) + if (!m) return redirect(ROUTES.root) + + const workspace = await findWorkspaceCache(m.workspaceId) + if (!workspace) return redirect(ROUTES.root) + + const user = await findUserCache(m.userId) + if (!user) return redirect(ROUTES.root) + + return ( + + } + > + + + } /> + + + + ) +} diff --git a/apps/web/src/app/(public)/login/LoginForm/index.tsx b/apps/web/src/app/(public)/login/LoginForm/index.tsx index 354dd0ccf..ef9065175 100644 --- a/apps/web/src/app/(public)/login/LoginForm/index.tsx +++ b/apps/web/src/app/(public)/login/LoginForm/index.tsx @@ -12,7 +12,7 @@ export default function LoginForm({ footer }: { footer: ReactNode }) { onError: ({ err }) => { if (err.code === 'ERROR') { toast({ - title: 'Saving failed', + title: 'Error', description: err.message, variant: 'destructive', }) @@ -31,14 +31,6 @@ export default function LoginForm({ footer }: { footer: ReactNode }) { placeholder='Ex.: jon@example.com' errors={errors?.email} /> - diff --git a/apps/web/src/app/(public)/magic-links/confirm/[token]/ConfirmMagicLink.tsx b/apps/web/src/app/(public)/magic-links/confirm/[token]/ConfirmMagicLink.tsx new file mode 100644 index 000000000..6f9ebdf75 --- /dev/null +++ b/apps/web/src/app/(public)/magic-links/confirm/[token]/ConfirmMagicLink.tsx @@ -0,0 +1,30 @@ +import { confirmMagicLinkToken } from '@latitude-data/core/services/magicLinkTokens/confirm' +import { FocusHeader } from '@latitude-data/web-ui' +import { FocusLayout } from '$/components/layouts' +import { ROUTES } from '$/services/routes' +import { redirect } from 'next/navigation' + +export default async function ConfirmMagicLink({ + params, +}: { + params: { token: string } +}) { + const { token } = params + + await confirmMagicLinkToken(token).then((r) => r.unwrap()) + + setTimeout(() => { + redirect(ROUTES.root) + }, 5000) + + return ( + + } + /> + ) +} diff --git a/apps/web/src/app/(public)/magic-links/confirm/[token]/page.tsx b/apps/web/src/app/(public)/magic-links/confirm/[token]/page.tsx new file mode 100644 index 000000000..57e8c0631 --- /dev/null +++ b/apps/web/src/app/(public)/magic-links/confirm/[token]/page.tsx @@ -0,0 +1,34 @@ +'use client' + +import { useEffect } from 'react' + +import { FocusHeader } from '@latitude-data/web-ui' +import { confirmMagicLinkTokenAction } from '$/actions/magicLinkTokens/confirm' +import { FocusLayout } from '$/components/layouts' +import useLatitudeAction from '$/hooks/useLatitudeAction' + +export default function ConfirmMagicLink({ + params, +}: { + params: { token: string } +}) { + const { token } = params + const { execute } = useLatitudeAction(confirmMagicLinkTokenAction, { + onSuccess: () => {}, // We don't want the default toast message in this case + }) + + useEffect(() => { + setTimeout(() => execute({ token }), 1000) + }, [execute]) + + return ( + + } + /> + ) +} diff --git a/apps/web/src/app/(public)/magic-links/sent/page.tsx b/apps/web/src/app/(public)/magic-links/sent/page.tsx new file mode 100644 index 000000000..2263bdd6d --- /dev/null +++ b/apps/web/src/app/(public)/magic-links/sent/page.tsx @@ -0,0 +1,24 @@ +import { FocusHeader } from '@latitude-data/web-ui' +import { FocusLayout } from '$/components/layouts' +import { ROUTES } from '$/services/routes' +import { redirect } from 'next/navigation' + +export default function MagicLinkSent({ + searchParams, +}: { + searchParams: { email?: string } +}) { + const { email } = searchParams + if (!email) return redirect(ROUTES.root) + + return ( + + } + /> + ) +} diff --git a/apps/web/src/app/(public)/setup/SetupForm/index.tsx b/apps/web/src/app/(public)/setup/SetupForm/index.tsx index 26c1e25ba..f2b87f221 100644 --- a/apps/web/src/app/(public)/setup/SetupForm/index.tsx +++ b/apps/web/src/app/(public)/setup/SetupForm/index.tsx @@ -28,28 +28,20 @@ export default function SetupForm({ footer }: { footer: ReactNode }) { name='name' autoComplete='name' label='Name' - placeholder='Your name' + placeholder='Jon Snow' errors={errors?.name} /> -