diff --git a/src/app/_components/dashboard.tsx b/src/app/_components/dashboard.tsx
index 7a204de3..ae2ef2c2 100644
--- a/src/app/_components/dashboard.tsx
+++ b/src/app/_components/dashboard.tsx
@@ -29,7 +29,7 @@ export default async function Layout(props: { children: React.ReactNode }) {
+
{props.children}
diff --git a/src/app/boards/[...id]/DeleteListConfirmation.tsx b/src/app/boards/[...id]/DeleteListConfirmation.tsx
new file mode 100644
index 00000000..9971cb09
--- /dev/null
+++ b/src/app/boards/[...id]/DeleteListConfirmation.tsx
@@ -0,0 +1,58 @@
+"use client";
+
+import { api } from "~/trpc/react";
+import { useBoard } from "~/app/providers/board";
+import { useModal } from "~/app/providers/modal";
+
+interface DeleteListConfirmationProps {
+ listPublicId: string;
+}
+
+export function DeleteListConfirmation({
+ listPublicId,
+}: DeleteListConfirmationProps) {
+ const utils = api.useUtils();
+ const { boardData } = useBoard();
+ const { closeModal } = useModal();
+
+ const refetchBoard = () =>
+ utils.board.byId.refetch({ id: boardData.publicId });
+
+ const deleteList = api.list.delete.useMutation({
+ onSuccess: async () => {
+ closeModal();
+ await refetchBoard();
+ },
+ });
+
+ return (
+ <>
+
+
+ Are you sure you want to delete this list?
+
+
+ {"This action can't be undone."}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/boards/[...id]/ListDropdown.tsx b/src/app/boards/[...id]/ListDropdown.tsx
new file mode 100644
index 00000000..34d35e32
--- /dev/null
+++ b/src/app/boards/[...id]/ListDropdown.tsx
@@ -0,0 +1,54 @@
+import { Fragment } from "react";
+import { Menu, Transition } from "@headlessui/react";
+import { HiEllipsisHorizontal } from "react-icons/hi2";
+import { useModal } from "~/app/providers/modal";
+
+interface ListDropdownProps {
+ setSelectedPublicListId: () => void;
+}
+
+export default function ListDropdown({
+ setSelectedPublicListId,
+}: ListDropdownProps) {
+ const { openModal } = useModal();
+
+ const handleOpenDeleteListConfirmation = () => {
+ setSelectedPublicListId();
+ openModal("DELETE_LIST");
+ };
+
+ return (
+
+ );
+}
diff --git a/src/app/boards/[...id]/page.tsx b/src/app/boards/[...id]/page.tsx
index 929ebb1c..bcad63ef 100644
--- a/src/app/boards/[...id]/page.tsx
+++ b/src/app/boards/[...id]/page.tsx
@@ -18,6 +18,8 @@ import { useModal } from "~/app/providers/modal";
import Modal from "~/app/_components/modal";
+import { DeleteListConfirmation } from "./DeleteListConfirmation";
+import ListDropdown from "./ListDropdown";
import { NewCardForm } from "./NewCardForm";
import { NewListForm } from "./NewListForm";
@@ -210,15 +212,22 @@ export default function BoardPage() {
{list.name}
-
+
{(provided) => (
@@ -283,6 +292,9 @@ export default function BoardPage() {
+ {modalContentType === "DELETE_LIST" && (
+
+ )}
{modalContentType === "NEW_CARD" && (
)}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index c25bbc7b..9b20f0df 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -26,7 +26,7 @@ export default function RootLayout({
}) {
return (
-
+
{children}
diff --git a/src/server/api/routers/board.ts b/src/server/api/routers/board.ts
index d62992aa..27293753 100644
--- a/src/server/api/routers/board.ts
+++ b/src/server/api/routers/board.ts
@@ -35,6 +35,7 @@ export const boardRouter = createTRPCRouter({
with: {
lists: {
orderBy: [asc(lists.index)],
+ where: isNull(cards.deletedAt),
columns: {
publicId: true,
name: true,
diff --git a/src/server/api/routers/list.ts b/src/server/api/routers/list.ts
index 64e0a1ae..e52d0e23 100644
--- a/src/server/api/routers/list.ts
+++ b/src/server/api/routers/list.ts
@@ -1,7 +1,7 @@
import { z } from "zod";
import { desc, eq, sql } from "drizzle-orm";
-import { boards, lists } from "~/server/db/schema";
+import { boards, cards, lists } from "~/server/db/schema";
import { generateUID } from "~/utils/generateUID";
import {
@@ -79,5 +79,29 @@ export const listRouter = createTRPCRouter({
WHERE ${lists.boardId} = ${list.boardId};
`);
})
- })
+ }),
+ delete: publicProcedure
+ .input(
+ z.object({
+ listPublicId: z.string().min(12),
+ }))
+ .mutation(({ ctx, input }) => {
+ const userId = ctx.session?.user.id;
+
+ if (!userId) return;
+
+ return ctx.db.transaction(async (tx) => {
+ const list = await tx.query.lists.findFirst({
+ where: eq(lists.publicId, input.listPublicId),
+ })
+
+ 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;`);
+ })
+ }),
});
\ No newline at end of file
diff --git a/src/server/db/migrations/0005_hard_colossus.sql b/src/server/db/migrations/0005_hard_colossus.sql
new file mode 100644
index 00000000..e8ab0416
--- /dev/null
+++ b/src/server/db/migrations/0005_hard_colossus.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `list` ADD `deletedAt` timestamp;--> statement-breakpoint
+ALTER TABLE `list` ADD `deletedBy` varchar(256);
\ No newline at end of file
diff --git a/src/server/db/migrations/meta/0005_snapshot.json b/src/server/db/migrations/meta/0005_snapshot.json
new file mode 100644
index 00000000..c322eb5e
--- /dev/null
+++ b/src/server/db/migrations/meta/0005_snapshot.json
@@ -0,0 +1,661 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "263f81b0-eac9-47a0-9d24-ed61d6dd6061",
+ "prevId": "52bf8e38-66ad-4bb6-9924-798177dbd1b5",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "userId_idx": {
+ "name": "userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId": {
+ "name": "account_provider_providerAccountId",
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "board": {
+ "name": "board",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "onUpdate": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "board_id": {
+ "name": "board_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "board_publicId_unique": {
+ "name": "board_publicId_unique",
+ "columns": [
+ "publicId"
+ ]
+ }
+ }
+ },
+ "card": {
+ "name": "card",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "varchar(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "onUpdate": true
+ },
+ "listId": {
+ "name": "listId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "index": {
+ "name": "index",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "deletedAt": {
+ "name": "deletedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deletedBy": {
+ "name": "deletedBy",
+ "type": "varchar(256)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "card_id": {
+ "name": "card_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "card_publicId_unique": {
+ "name": "card_publicId_unique",
+ "columns": [
+ "publicId"
+ ]
+ }
+ }
+ },
+ "card_label": {
+ "name": "card_label",
+ "columns": {
+ "cardId": {
+ "name": "cardId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "labelId": {
+ "name": "labelId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "card_label_cardId_card_id_fk": {
+ "name": "card_label_cardId_card_id_fk",
+ "tableFrom": "card_label",
+ "tableTo": "card",
+ "columnsFrom": [
+ "cardId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "card_label_labelId_label_id_fk": {
+ "name": "card_label_labelId_label_id_fk",
+ "tableFrom": "card_label",
+ "tableTo": "label",
+ "columnsFrom": [
+ "labelId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "card_label_cardId_labelId": {
+ "name": "card_label_cardId_labelId",
+ "columns": [
+ "cardId",
+ "labelId"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "label": {
+ "name": "label",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "colourCode": {
+ "name": "colourCode",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "varchar(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "onUpdate": true
+ },
+ "boardId": {
+ "name": "boardId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "label_id": {
+ "name": "label_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "label_publicId_unique": {
+ "name": "label_publicId_unique",
+ "columns": [
+ "publicId"
+ ]
+ }
+ }
+ },
+ "list": {
+ "name": "list",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "publicId": {
+ "name": "publicId",
+ "type": "varchar(12)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdBy": {
+ "name": "createdBy",
+ "type": "varchar(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "onUpdate": true
+ },
+ "boardId": {
+ "name": "boardId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "index": {
+ "name": "index",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "deletedAt": {
+ "name": "deletedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deletedBy": {
+ "name": "deletedBy",
+ "type": "varchar(256)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "list_id": {
+ "name": "list_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "list_publicId_unique": {
+ "name": "list_publicId_unique",
+ "columns": [
+ "publicId"
+ ]
+ }
+ }
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "userId_idx": {
+ "name": "userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "session_sessionToken": {
+ "name": "session_sessionToken",
+ "columns": [
+ "sessionToken"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP(3)"
+ },
+ "image": {
+ "name": "image",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "user_id": {
+ "name": "user_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ]
+ }
+ }
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token": {
+ "name": "verificationToken_identifier_token",
+ "columns": [
+ "identifier",
+ "token"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ }
+ },
+ "schemas": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ }
+}
\ No newline at end of file
diff --git a/src/server/db/migrations/meta/_journal.json b/src/server/db/migrations/meta/_journal.json
index 82124e39..382539d4 100644
--- a/src/server/db/migrations/meta/_journal.json
+++ b/src/server/db/migrations/meta/_journal.json
@@ -36,6 +36,13 @@
"when": 1702924641678,
"tag": "0004_tidy_lord_hawal",
"breakpoints": true
+ },
+ {
+ "idx": 5,
+ "version": "5",
+ "when": 1704193352729,
+ "tag": "0005_hard_colossus",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts
index 72e43d06..75d0facd 100644
--- a/src/server/db/schema.ts
+++ b/src/server/db/schema.ts
@@ -103,7 +103,9 @@ export const lists = mySqlTable(
.notNull(),
updatedAt: timestamp("updatedAt").onUpdateNow(),
boardId: bigint("boardId", { mode: "number" }).notNull(),
- index: int("index").notNull()
+ index: int("index").notNull(),
+ deletedAt: timestamp("deletedAt"),
+ deletedBy: varchar("deletedBy", { length: 256 })
}
);
@@ -116,7 +118,11 @@ export const listsRelations = relations(lists, ({ one, many }) => ({
fields: [lists.boardId],
references: [boards.id],
}),
- cards: many(cards)
+ cards: many(cards),
+ deletedBy: one(users, {
+ fields: [lists.deletedBy],
+ references: [users.id],
+ }),
}));
export const cards = mySqlTable(
@@ -151,7 +157,6 @@ export const cardsRelations = relations(cards, ({ one, many }) => ({
fields: [cards.deletedBy],
references: [users.id],
}),
-
labels: many(cardsToLabels)
}));