diff --git a/src/components/NewWorkspaceForm.tsx b/src/components/NewWorkspaceForm.tsx index aa16ce58..aba4af9e 100644 --- a/src/components/NewWorkspaceForm.tsx +++ b/src/components/NewWorkspaceForm.tsx @@ -1,3 +1,4 @@ +import { useEffect } from "react"; import { api } from "~/utils/api"; import { HiXMark } from "react-icons/hi2"; @@ -28,15 +29,23 @@ export function NewWorkspaceForm() { }, }); + useEffect(() => { + const nameElement: HTMLElement | null = + document?.querySelector("#workspace-name"); + if (nameElement) nameElement.focus(); + }, []); + return ( <> -
-

New workspace

+
+

+ New workspace +

@@ -52,20 +61,20 @@ export function NewWorkspaceForm() { >
diff --git a/src/components/SideNavigation.tsx b/src/components/SideNavigation.tsx index 79d5e451..0007984a 100644 --- a/src/components/SideNavigation.tsx +++ b/src/components/SideNavigation.tsx @@ -54,7 +54,7 @@ export default function SideNavigation({ user }: SideNavigationProps) {
  • diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index 526216b6..1d1320f7 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -1,5 +1,4 @@ import { z } from "zod"; -import { generateUID } from "~/utils/generateUID"; import { createTRPCRouter, @@ -10,7 +9,7 @@ export const authRouter = createTRPCRouter({ login: publicProcedure .input(z.object({ email: z.string() })) .mutation(async ({ ctx, input }) => { - const { data, error } = await ctx.supabase.auth.signInWithOtp({ + const { data } = await ctx.supabase.auth.signInWithOtp({ email: input.email, options: { emailRedirectTo: '/boards', diff --git a/src/server/api/routers/board.ts b/src/server/api/routers/board.ts index b9f23214..2b8c6377 100644 --- a/src/server/api/routers/board.ts +++ b/src/server/api/routers/board.ts @@ -107,11 +107,9 @@ export const boardRouter = createTRPCRouter({ .limit(1) .single(); - console.log({ workspace }) - if (!workspace.data) return; - const { data, error } = await ctx.supabase + const { data } = await ctx.supabase .from('board') .insert({ publicId: generateUID(), @@ -168,23 +166,26 @@ export const boardRouter = createTRPCRouter({ const listIds = board.data.lists.map((list) => list.id); - const deletedAt = new Date().toString(); + const deletedAt = new Date().toISOString(); await ctx.supabase .from('board') .update({ deletedAt, deletedBy: userId }) - .eq('id', board.data.id); + .eq('id', board.data.id) + .is('deletedAt', null); if (listIds.length) { await ctx.supabase .from('list') .update({ deletedAt, deletedBy: userId }) - .eq('boardId', board.data.id); + .eq('boardId', board.data.id) + .is('deletedAt', null); await ctx.supabase .from('card') .update({ deletedAt, deletedBy: userId }) - .in('listId', listIds); + .in('listId', listIds) + .is('deletedAt', null); } }) }); diff --git a/src/server/api/routers/label.ts b/src/server/api/routers/label.ts index d5487e8a..597585a2 100644 --- a/src/server/api/routers/label.ts +++ b/src/server/api/routers/label.ts @@ -1,7 +1,4 @@ import { z } from "zod"; -import { and, eq, isNull } from "drizzle-orm"; - -import { cards, cardsToLabels, labels } from "~/server/db/schema"; import { generateUID } from "~/utils/generateUID"; import { @@ -18,41 +15,43 @@ export const labelRouter = createTRPCRouter({ colourCode: z.string().length(7), }), ) - .mutation(({ ctx, input }) => { - const userId = ctx.session?.user.id; + .mutation(async ({ ctx, input }) => { + const userId = ctx.user?.id; if (!userId) return; - return ctx.db.transaction(async (tx) => { - const card = await tx.query.cards.findFirst({ - where: and(eq(cards.publicId, input.cardPublicId), isNull(cards.deletedAt)), - with: { - list: { - with: { - board: true - } - } - } - }); + const card = await ctx.supabase + .from('card') + .select(`id, list (boardId)`) + .eq('publicId', input.cardPublicId) + .is('deletedAt', null) + .limit(1) + .single(); - if (!card) return; + if (!card.data?.list) return; - const newLabel = await tx.insert(labels).values({ + const newLabel = await ctx.supabase + .from('label') + .insert({ publicId: generateUID(), name: input.name, colourCode: input.colourCode, createdBy: userId, - boardId: card.list.boardId, - }).returning({ id: labels.id }); + boardId: card.data.list.boardId, + }) + .select(`id`) + .limit(1) + .single(); - const newLabelId = newLabel[0]?.id + if (!newLabel.data) return; - if (!newLabelId) return; + await ctx.supabase + .from('_card_labels') + .insert({ + cardId: card.data.id, + labelId: newLabel.data.id, + }) - return tx.insert(cardsToLabels).values({ - cardId: card.id, - labelId: newLabelId, - }); - }) + return newLabel.data; }), }); \ No newline at end of file diff --git a/src/server/api/routers/list.ts b/src/server/api/routers/list.ts index 72f493f4..b810d302 100644 --- a/src/server/api/routers/list.ts +++ b/src/server/api/routers/list.ts @@ -1,7 +1,4 @@ import { z } from "zod"; -import { and, desc, eq, sql, isNull } from "drizzle-orm"; - -import { boards, cards, lists } from "~/server/db/schema"; import { generateUID } from "~/utils/generateUID"; import { @@ -17,37 +14,39 @@ export const listRouter = createTRPCRouter({ boardPublicId: z.string().min(12) }), ) - .mutation(({ ctx, input }) => { - const userId = ctx.session?.user.id; + .mutation(async ({ ctx, input }) => { + const userId = ctx.user?.id; if (!userId) return; - return ctx.db.transaction(async (tx) => { - const board = await tx.query.boards.findFirst({ - where: eq(boards.publicId, input.boardPublicId), - columns: { - id: true - } - }); + const board = await ctx.supabase + .from('board') + .select(`id, lists:list (index)`) + .eq('publicId', input.boardPublicId) + .order('index', { foreignTable: 'list', ascending: false }) + .is('list.deletedAt', null) + .limit(1) + .single(); - if (!board) return; + if (!board.data) return; - const latestList = await tx.query.lists.findFirst({ - where: eq(lists.boardId, board.id), - columns: { - index: true - }, - orderBy: desc(lists.index) - }); + const latestListIndex = board.data.lists[0]?.index - return tx.insert(lists).values({ + const { data } = await ctx.supabase + .from('list') + .insert({ publicId: generateUID(), name: input.name, createdBy: userId, - boardId: board.id, - index: latestList ? latestList.index + 1 : 0 - }); - }) + boardId: board.data.id, + index: latestListIndex ? latestListIndex + 1 : 0 + }) + .select(` + publicId, + name + `); + + return data; }), reorder: publicProcedure .input( @@ -57,52 +56,70 @@ export const listRouter = createTRPCRouter({ currentIndex: z.number(), newIndex: z.number(), })) - .mutation(({ ctx, input }) => { - const userId = ctx.session?.user.id; + .mutation(async ({ ctx, input }) => { + const userId = ctx.user?.id; if (!userId) return; - return ctx.db.transaction(async (tx) => { - const [list] = await tx.select({ id: lists.id, boardId: lists.boardId }).from(lists).where(eq(lists.publicId, input.listId)) + const list = await ctx.supabase + .from('list') + .select(`id, boardId`) + .eq('publicId', input.listId) + .limit(1) + .single(); - if (!list) return; + if (!list?.data) return; - await tx.execute(sql` - UPDATE ${lists} - SET index = - CASE - WHEN ${lists.index} = ${input.currentIndex} AND ${lists.id} = ${list.id} THEN ${input.newIndex} - WHEN ${input.currentIndex} < ${input.newIndex} AND ${lists.index} > ${input.currentIndex} AND ${lists.index} <= ${input.newIndex} THEN ${lists.index} - 1 - WHEN ${input.currentIndex} > ${input.newIndex} AND ${lists.index} >= ${input.newIndex} AND ${lists.index} < ${input.currentIndex} THEN ${lists.index} + 1 - ELSE ${lists.index} - END - WHERE ${lists.boardId} = ${list.boardId}; - `); - }) + const { data } = await ctx.supabase + .rpc('reorder_list', { + board_id: list.data.boardId, + list_id: list.data.id, + current_index: input.currentIndex, + new_index: input.newIndex, + }) + + return data; }), delete: publicProcedure .input( z.object({ listPublicId: z.string().min(12), })) - .mutation(({ ctx, input }) => { - const userId = ctx.session?.user.id; + .mutation(async ({ ctx, input }) => { + const userId = ctx.user?.id; if (!userId) return; - return ctx.db.transaction(async (tx) => { - const list = await tx.query.lists.findFirst({ - where: eq(lists.publicId, input.listPublicId), + const list = await ctx.supabase + .from('list') + .select(`id, boardId, index`) + .eq('publicId', input.listPublicId) + .limit(1) + .single(); + + if (!list.data) return; + + const deletedAt = new Date().toISOString(); + + await ctx.supabase + .from('list') + .update({ deletedAt, deletedBy: userId }) + .eq('id', list.data.id) + .is('deletedAt', null); + + await ctx.supabase + .from('card') + .update({ deletedAt, deletedBy: userId }) + .eq('listId', list.data.id) + .is('deletedAt', null); + + const { data } = await ctx.supabase + .rpc('shift_list_index', { + board_id: list.data.boardId, + list_index: list.data.index }) - if (!list) return; - - await tx.update(lists).set({ deletedAt: new Date(), deletedBy: userId}).where(eq(lists.id, list.id)); - - await tx.update(cards).set({ deletedAt: new Date(), deletedBy: userId}).where(eq(cards.listId, list.id)); - - await tx.execute(sql`UPDATE ${lists} SET ${lists.index} = ${lists.index} - 1 WHERE ${lists.boardId} = ${list.boardId} AND ${lists.index} > ${list.index} AND ${lists.deletedAt} IS NULL;`); - }) + return data; }), update: publicProcedure .input( @@ -110,11 +127,18 @@ export const listRouter = createTRPCRouter({ listPublicId: z.string().min(12), name: z.string().min(1), })) - .mutation(({ ctx, input }) => { - const userId = ctx.session?.user.id; + .mutation(async ({ ctx, input }) => { + const userId = ctx.user?.id; if (!userId) return; - return ctx.db.update(lists).set({ name: input.name }).where(and(eq(lists.publicId, input.listPublicId), isNull(lists.deletedAt))); + const { data } = await ctx.supabase + .from('list') + .update({ name: input.name }) + .eq('publicId', input.listPublicId) + .is('deletedAt', null) + .select(`publicId, name`); + + return data; }), }); \ No newline at end of file diff --git a/src/server/api/routers/workspace.ts b/src/server/api/routers/workspace.ts index e211723d..7ec05bb9 100644 --- a/src/server/api/routers/workspace.ts +++ b/src/server/api/routers/workspace.ts @@ -12,56 +12,52 @@ import { export const workspaceRouter = createTRPCRouter({ all: publicProcedure - .query(({ ctx }) => { - const userId = ctx.session?.user.id; + .query(async ({ ctx }) => { + const userId = ctx.user?.id; if (!userId) return; - return ctx.db.query.workspaceMembers.findMany({ - where: and(eq(workspaceMembers.userId, userId), isNull(workspaceMembers.deletedAt)), - columns: { - role: true, - }, - with: { - workspace: { - columns: { - publicId: true, - name: true - } - } - } - }); + const { data } = await ctx.supabase + .from('workspace_members') + .select(` + role, + workspace ( + publicId, + name + ) + `) + .eq('userId', userId) + .is('deletedAt', null); + + return data; }), byId: publicProcedure .input(z.object({ publicId: z.string().min(12) })) - .query(({ ctx, input }) => { - const userId = ctx.session?.user.id; + .query(async ({ ctx, input }) => { + const userId = ctx.user?.id; if (!userId) return; - return ctx.db.query.workspaces.findFirst({ - where: and(eq(workspaces.publicId, input.publicId), isNull(workspaces.deletedAt)), - columns: { - publicId: true, - }, - with: { - members: { - columns: { - publicId: true, - role: true, - }, - with: { - user: { - columns: { - id: true, - name: true, - email: true, - } - } - } - } - } - }); + const { data, error } = await ctx.supabase + .from('workspace') + .select(` + publicId, + members: workspace_members ( + publicId, + role, + user ( + id, + name, + email + ) + ) + `) + .eq('publicId', input.publicId) + .is('deletedAt', null) + .limit(1) + .single(); + + return data; }), create: publicProcedure .input( @@ -70,35 +66,47 @@ export const workspaceRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { - const userId = ctx.session?.user.id; + const userId = ctx.user?.id; + + + console.log('>>> here <<<') if (!userId) return; - const workspace = await ctx.db.insert(workspaces).values({ - publicId: generateUID(), - name: input.name, - slug: input.name.toLowerCase(), - createdBy: userId, - }).returning({ id: workspaces.id }); + const workspace = await ctx.supabase + .from('workspace') + .insert({ + publicId: generateUID(), + name: input.name, + slug: input.name.toLowerCase(), + createdBy: userId, + }) + .select(` + id, + publicId, + name + `) + .limit(1) + .single() - const workspaceId = workspace[0]?.id; + const newWorkspaceId = workspace.data?.id - if (!workspaceId) return; + if (!newWorkspaceId) return; - await ctx.db.insert(workspaceMembers).values({ - publicId: generateUID(), - userId, - workspaceId: workspaceId, - createdBy: userId, - role: 'admin' - }) + await ctx.supabase + .from('workspace_members') + .insert({ + publicId: generateUID(), + userId, + workspaceId: newWorkspaceId, + createdBy: userId, + role: 'admin' + }) - return ctx.db.query.workspaces.findFirst({ - where: eq(workspaces.id, workspaceId), - columns: { - publicId: true, - name: true, - }, - }); + const newWorkspace = { ...workspace.data }; + + delete newWorkspace.id; + + return newWorkspace; }), }); \ No newline at end of file diff --git a/src/types/database.types.ts b/src/types/database.types.ts index e88bc062..729be202 100644 --- a/src/types/database.types.ts +++ b/src/types/database.types.ts @@ -600,7 +600,22 @@ export type Database = { [_ in never]: never } Functions: { - [_ in never]: never + reorder_list: { + Args: { + board_id: number + list_id: number + current_index: number + new_index: number + } + Returns: undefined + } + shift_list_index: { + Args: { + board_id: number + list_index: number + } + Returns: undefined + } } Enums: { role: "admin" | "member" | "guest" diff --git a/src/views/board/index.tsx b/src/views/board/index.tsx index 9535e47c..cb93c25f 100644 --- a/src/views/board/index.tsx +++ b/src/views/board/index.tsx @@ -86,17 +86,20 @@ export default function BoardPage() { enableReinitialize: true, }); - if (!boardId) return <>; - - api.board.byId.useQuery( - { id: boardId }, + const { data, isSuccess } = api.board.byId.useQuery( + { id: boardId ?? "" }, { - onSuccess: (data) => { - if (data) setBoardData(data); - }, + enabled: !!boardId, }, ); + if (isSuccess && data) { + console.log({ data }); + setBoardData(data); + } + + if (!boardId) return <>; + const openNewListForm = (publicBoardId: string) => { openModal("NEW_LIST"); setSelectedPublicListId(publicBoardId); diff --git a/supabase/seed.sql b/supabase/seed.sql index e69de29b..bdfd1c67 100644 --- a/supabase/seed.sql +++ b/supabase/seed.sql @@ -0,0 +1,23 @@ +CREATE OR REPLACE FUNCTION reorder_list(board_id BIGINT, list_id BIGINT, current_index INT, new_index INT) +RETURNS VOID +LANGUAGE SQL +AS $$ + UPDATE list + SET index = + CASE + WHEN index = current_index AND id = list_id THEN new_index + WHEN current_index < new_index AND index > current_index AND index <= new_index THEN index - 1 + WHEN current_index > new_index AND index >= new_index AND index < current_index THEN index + 1 + ELSE index + END + WHERE "boardId" = board_id; +$$; + +CREATE OR REPLACE FUNCTION shift_list_index(board_id BIGINT, list_index INT) +RETURNS VOID +LANGUAGE SQL +AS $$ + UPDATE list + SET index = index - 1 + WHERE "boardId" = board_id AND index > list_index AND "deletedAt" IS NULL; +$$; \ No newline at end of file