feat: delete workspace
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: "primary" | "secondary";
|
||||
variant?: "primary" | "secondary" | "danger";
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,11 @@ const Button = ({
|
||||
"bg-light-1000 dark:bg-dark-1000 dark:text-dark-50",
|
||||
variant === "secondary" &&
|
||||
"border-[1px] border-light-600 bg-light-50 dark:border-dark-600 dark:bg-dark-300 dark:text-dark-1000",
|
||||
variant === "danger" &&
|
||||
"dark:text-red-1000 border-[1px] border-red-600 bg-red-50 dark:border-red-600 dark:bg-red-500",
|
||||
props.disabled && "opacity-50",
|
||||
)}
|
||||
disabled={isLoading}
|
||||
disabled={isLoading ?? props.disabled}
|
||||
{...props}
|
||||
>
|
||||
<span className="relative flex items-center justify-center">
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { useEffect } from "react";
|
||||
import { api } from "~/utils/api";
|
||||
import { HiXMark } from "react-icons/hi2";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { useWorkspace } from "~/providers/workspace";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { HiXMark } from "react-icons/hi2";
|
||||
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { useWorkspace } from "~/providers/workspace";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import Input from "~/components/Input";
|
||||
|
||||
interface FormValues {
|
||||
name: string;
|
||||
@@ -11,20 +16,23 @@ interface FormValues {
|
||||
|
||||
export function NewWorkspaceForm() {
|
||||
const { closeModal } = useModal();
|
||||
const { showPopup } = usePopup();
|
||||
const { switchWorkspace } = useWorkspace();
|
||||
const { register, handleSubmit } = useForm<FormValues>();
|
||||
|
||||
const createWorkspace = api.workspace.create.useMutation({
|
||||
onSuccess: (values) => {
|
||||
try {
|
||||
if (values?.publicId && values.name) {
|
||||
switchWorkspace({ publicId: values.publicId, name: values.name });
|
||||
closeModal();
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (values?.publicId && values.name) {
|
||||
switchWorkspace({ publicId: values.publicId, name: values.name });
|
||||
closeModal();
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
showPopup({
|
||||
header: "Unable to create workspace",
|
||||
message: "Please try again later, or contact customer support.",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -40,40 +48,36 @@ export function NewWorkspaceForm() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-5">
|
||||
<div className="flex w-full items-center justify-between pb-4">
|
||||
<h2 className="text-sm font-bold text-neutral-900 dark:text-dark-1000">
|
||||
New workspace
|
||||
</h2>
|
||||
<button
|
||||
className="rounded p-1 hover:bg-light-200 focus:outline-none dark:hover:bg-dark-300"
|
||||
onClick={() => closeModal()}
|
||||
>
|
||||
<HiXMark size={18} className="text-light-900 dark:text-dark-900" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<label
|
||||
htmlFor="workspace-name"
|
||||
className="block pb-2 text-sm font-normal leading-6 text-neutral-900 dark:text-dark-1000"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
id="workspace-name"
|
||||
{...register("name", { required: true })}
|
||||
className="block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset focus:ring-light-600 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6"
|
||||
/>
|
||||
<div className="mt-5 sm:mt-6">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="px-5 pt-5">
|
||||
<div className="flex w-full items-center justify-between pb-4">
|
||||
<h2 className="text-sm font-bold text-neutral-900 dark:text-dark-1000">
|
||||
New workspace
|
||||
</h2>
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex w-full justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
|
||||
className="rounded p-1 hover:bg-light-200 focus:outline-none dark:hover:bg-dark-300"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Create workspace
|
||||
<HiXMark size={18} className="text-light-900 dark:text-dark-900" />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
id="workspace-name"
|
||||
placeholder="Workspace name"
|
||||
{...register("name")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||
<div>
|
||||
<Button type="submit" isLoading={createWorkspace.isPending}>
|
||||
Create workspace
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,20 +114,20 @@ export const boardRouter = createTRPCRouter({
|
||||
|
||||
const deletedAt = new Date().toISOString();
|
||||
|
||||
await boardRepo.destroy(ctx.db, {
|
||||
await boardRepo.softDelete(ctx.db, {
|
||||
boardId: board.id,
|
||||
deletedAt,
|
||||
deletedBy: userId,
|
||||
});
|
||||
|
||||
if (listIds.length) {
|
||||
await listRepo.destroyAllByBoardId(ctx.db, {
|
||||
await listRepo.softDeleteAllByBoardId(ctx.db, {
|
||||
boardId: board.id,
|
||||
deletedAt,
|
||||
deletedBy: userId,
|
||||
});
|
||||
|
||||
await cardRepo.destroyAllByListIds(ctx.db, {
|
||||
await cardRepo.softDeleteAllByListIds(ctx.db, {
|
||||
listIds,
|
||||
deletedAt,
|
||||
deletedBy: userId,
|
||||
|
||||
@@ -149,7 +149,7 @@ export const cardRouter = createTRPCRouter({
|
||||
);
|
||||
|
||||
if (existingLabel) {
|
||||
await cardRepo.destroyCardLabelRelationship(ctx.db, cardLabelIds);
|
||||
await cardRepo.hardDeleteCardLabelRelationship(ctx.db, cardLabelIds);
|
||||
|
||||
return { newLabel: false };
|
||||
}
|
||||
@@ -200,7 +200,7 @@ export const cardRouter = createTRPCRouter({
|
||||
);
|
||||
|
||||
if (existingMember) {
|
||||
await cardRepo.destroyCardMemberRelationship(ctx.db, cardMemberIds);
|
||||
await cardRepo.hardDeleteCardMemberRelationship(ctx.db, cardMemberIds);
|
||||
|
||||
return { newMember: false };
|
||||
}
|
||||
@@ -272,7 +272,7 @@ export const cardRouter = createTRPCRouter({
|
||||
|
||||
const deletedAt = new Date().toISOString();
|
||||
|
||||
await cardRepo.destroy(ctx.db, {
|
||||
await cardRepo.softDelete(ctx.db, {
|
||||
cardId: card.id,
|
||||
deletedAt,
|
||||
deletedBy: userId,
|
||||
|
||||
@@ -81,9 +81,9 @@ export const labelRouter = createTRPCRouter({
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
await cardRepo.destroyAllCardLabelRelationships(ctx.db, label.id);
|
||||
await cardRepo.hardDeleteAllCardLabelRelationships(ctx.db, label.id);
|
||||
|
||||
await labelRepo.destroy(ctx.db, label.id);
|
||||
await labelRepo.hardDelete(ctx.db, label.id);
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
@@ -98,13 +98,13 @@ export const listRouter = createTRPCRouter({
|
||||
|
||||
const deletedAt = new Date().toISOString();
|
||||
|
||||
await listRepo.destroyById(ctx.db, {
|
||||
await listRepo.softDeleteById(ctx.db, {
|
||||
listId: list.id,
|
||||
deletedAt,
|
||||
deletedBy: userId,
|
||||
});
|
||||
|
||||
await cardRepo.destroyAllByListIds(ctx.db, {
|
||||
await cardRepo.softDeleteAllByListIds(ctx.db, {
|
||||
listIds: [list.id],
|
||||
deletedAt,
|
||||
deletedBy: userId,
|
||||
|
||||
@@ -49,6 +49,22 @@ export const workspaceRouter = createTRPCRouter({
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
if (!result?.publicId)
|
||||
throw new TRPCError({
|
||||
message: `Unable to create workspace`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
return result;
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ workspacePublicId: z.string().min(12) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { data } = await workspaceRepo.hardDelete(
|
||||
ctx.db,
|
||||
input.workspacePublicId,
|
||||
);
|
||||
|
||||
return data;
|
||||
}),
|
||||
});
|
||||
|
||||
66
src/server/db/migrations/0002_clever_robin_chapel.sql
Normal file
66
src/server/db/migrations/0002_clever_robin_chapel.sql
Normal file
@@ -0,0 +1,66 @@
|
||||
DROP TABLE IF EXISTS "account";--> statement-breakpoint
|
||||
DROP TABLE IF EXISTS "session";--> statement-breakpoint
|
||||
DROP TABLE IF EXISTS "verificationToken";--> statement-breakpoint
|
||||
ALTER TABLE "board" DROP CONSTRAINT "board_workspaceId_workspace_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "_card_workspace_members" DROP CONSTRAINT "_card_workspace_members_cardId_card_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "card" DROP CONSTRAINT "card_listId_list_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "_card_labels" DROP CONSTRAINT IF EXISTS "_card_labels_cardId_card_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "_card_labels" DROP CONSTRAINT IF EXISTS "_card_labels_labelId_label_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "label" DROP CONSTRAINT "label_boardId_board_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "list" DROP CONSTRAINT "list_boardId_board_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "workspace_members" DROP CONSTRAINT "workspace_members_workspaceId_workspace_id_fk";
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "board" ADD CONSTRAINT "board_workspaceId_workspace_id_fk" FOREIGN KEY ("workspaceId") REFERENCES "workspace"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_card_workspace_members" ADD CONSTRAINT "_card_workspace_members_workspaceMemberId_workspace_members_id_fk" FOREIGN KEY ("workspaceMemberId") REFERENCES "workspace_members"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "card" ADD CONSTRAINT "card_listId_list_id_fk" FOREIGN KEY ("listId") REFERENCES "list"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_card_labels" ADD CONSTRAINT "_card_labels_cardId_card_id_fk" FOREIGN KEY ("cardId") REFERENCES "card"("id") ON DELETE CASCADE;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_card_labels" ADD CONSTRAINT "_card_labels_labelId_label_id_fk" FOREIGN KEY ("labelId") REFERENCES "label"("id") ON DELETE CASCADE;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "label" ADD CONSTRAINT "label_boardId_board_id_fk" FOREIGN KEY ("boardId") REFERENCES "board"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "list" ADD CONSTRAINT "list_boardId_board_id_fk" FOREIGN KEY ("boardId") REFERENCES "board"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_workspaceId_workspace_id_fk" FOREIGN KEY ("workspaceId") REFERENCES "workspace"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
1003
src/server/db/migrations/meta/0002_snapshot.json
Normal file
1003
src/server/db/migrations/meta/0002_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,13 @@
|
||||
"when": 1713021051974,
|
||||
"tag": "0001_stale_mattie_franklin",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "5",
|
||||
"when": 1724967733894,
|
||||
"tag": "0002_clever_robin_chapel",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ export const update = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
export const destroy = async (
|
||||
export const softDelete = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: {
|
||||
boardId: number;
|
||||
@@ -160,3 +160,12 @@ export const destroy = async (
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const hardDelete = async (
|
||||
db: SupabaseClient<Database>,
|
||||
workspaceId: number,
|
||||
) => {
|
||||
const result = db.from("board").delete().eq("workspaceId", workspaceId);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -74,39 +74,6 @@ export const update = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
export const destroy = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: {
|
||||
cardId: number;
|
||||
deletedAt: string;
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
.from("card")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.eq("id", args.cardId);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const destroyAllByListIds = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: {
|
||||
listIds: number[];
|
||||
deletedAt: string;
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
.from("card")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.in("listId", args.listIds)
|
||||
.is("deletedAt", null);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const getCardWithListByPublicId = async (
|
||||
db: SupabaseClient<Database>,
|
||||
cardPublicId: string,
|
||||
@@ -168,28 +135,6 @@ export const bulkCreate = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
export const destroyCardLabelRelationship = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: { cardId: number; labelId: number },
|
||||
) => {
|
||||
const result = await db
|
||||
.from("_card_labels")
|
||||
.delete()
|
||||
.eq("cardId", args.cardId)
|
||||
.eq("labelId", args.labelId);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const destroyAllCardLabelRelationships = async (
|
||||
db: SupabaseClient<Database>,
|
||||
labelId: number,
|
||||
) => {
|
||||
const result = await db.from("_card_labels").delete().eq("labelId", labelId);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const createCardLabelRelationship = async (
|
||||
db: SupabaseClient<Database>,
|
||||
cardLabelRelationshipInput: { cardId: number; labelId: number },
|
||||
@@ -217,19 +162,6 @@ export const getCardMemberRelationship = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
export const destroyCardMemberRelationship = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: { cardId: number; memberId: number },
|
||||
) => {
|
||||
const result = await db
|
||||
.from("_card_workspace_members")
|
||||
.delete()
|
||||
.eq("cardId", args.cardId)
|
||||
.eq("workspaceMemberId", args.memberId);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const createCardMemberRelationship = async (
|
||||
db: SupabaseClient<Database>,
|
||||
cardMemberRelationshipInput: { cardId: number; memberId: number },
|
||||
@@ -353,3 +285,71 @@ export const pushIndex = async (
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const softDelete = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: {
|
||||
cardId: number;
|
||||
deletedAt: string;
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
.from("card")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.eq("id", args.cardId);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const softDeleteAllByListIds = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: {
|
||||
listIds: number[];
|
||||
deletedAt: string;
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
.from("card")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.in("listId", args.listIds)
|
||||
.is("deletedAt", null);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const hardDeleteCardMemberRelationship = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: { cardId: number; memberId: number },
|
||||
) => {
|
||||
const result = await db
|
||||
.from("_card_workspace_members")
|
||||
.delete()
|
||||
.eq("cardId", args.cardId)
|
||||
.eq("workspaceMemberId", args.memberId);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const hardDeleteCardLabelRelationship = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: { cardId: number; labelId: number },
|
||||
) => {
|
||||
const result = await db
|
||||
.from("_card_labels")
|
||||
.delete()
|
||||
.eq("cardId", args.cardId)
|
||||
.eq("labelId", args.labelId);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const hardDeleteAllCardLabelRelationships = async (
|
||||
db: SupabaseClient<Database>,
|
||||
labelId: number,
|
||||
) => {
|
||||
const result = await db.from("_card_labels").delete().eq("labelId", labelId);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -79,7 +79,7 @@ export const update = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
export const destroy = async (
|
||||
export const hardDelete = async (
|
||||
db: SupabaseClient<Database>,
|
||||
labelId: number,
|
||||
) => {
|
||||
|
||||
@@ -85,40 +85,6 @@ export const update = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
export const destroyAllByBoardId = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: {
|
||||
boardId: number;
|
||||
deletedAt: string;
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
.from("list")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.eq("boardId", args.boardId)
|
||||
.is("deletedAt", null);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const destroyById = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: {
|
||||
listId: number;
|
||||
deletedAt: string;
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
.from("list")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.eq("id", args.listId)
|
||||
.is("deletedAt", null);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const reorder = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: {
|
||||
@@ -152,3 +118,37 @@ export const shiftIndex = async (
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const softDeleteAllByBoardId = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: {
|
||||
boardId: number;
|
||||
deletedAt: string;
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
.from("list")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.eq("boardId", args.boardId)
|
||||
.is("deletedAt", null);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const softDeleteById = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: {
|
||||
listId: number;
|
||||
deletedAt: string;
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
.from("list")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.eq("id", args.listId)
|
||||
.is("deletedAt", null);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -127,3 +127,15 @@ export const getAllMembersByPublicIds = async (
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const hardDelete = async (
|
||||
db: SupabaseClient<Database>,
|
||||
workspacePublicId: string,
|
||||
) => {
|
||||
const result = db
|
||||
.from("workspace")
|
||||
.delete()
|
||||
.eq("publicId", workspacePublicId);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -12,217 +12,237 @@ import {
|
||||
bigint,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const importSourceEnum = pgEnum('source', ['trello']);
|
||||
export const importStatusEnum = pgEnum('status', ['started', 'success', 'failed']);
|
||||
export const memberRoleEnum = pgEnum('role', ['admin', 'member', 'guest']);
|
||||
export const importSourceEnum = pgEnum("source", ["trello"]);
|
||||
export const importStatusEnum = pgEnum("status", [
|
||||
"started",
|
||||
"success",
|
||||
"failed",
|
||||
]);
|
||||
export const memberRoleEnum = pgEnum("role", ["admin", "member", "guest"]);
|
||||
|
||||
export const boards = pgTable(
|
||||
"board",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
workspaceId: bigint("workspaceId", { mode: "number" }).notNull().references(() => workspaces.id),
|
||||
},
|
||||
);
|
||||
export const boards = pgTable("board", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
workspaceId: bigint("workspaceId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => workspaces.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const boardsRelations = relations(boards, ({ one, many }) => ({
|
||||
createdBy: one(users, {
|
||||
fields: [boards.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
createdBy: one(users, {
|
||||
fields: [boards.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
lists: many(lists),
|
||||
labels: many(labels),
|
||||
deletedBy: one(users, {
|
||||
fields: [boards.deletedBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
fields: [boards.deletedBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
import: one(imports, {
|
||||
fields: [boards.importId],
|
||||
references: [imports.id],
|
||||
}),
|
||||
fields: [boards.importId],
|
||||
references: [imports.id],
|
||||
}),
|
||||
workspace: one(workspaces, {
|
||||
fields: [boards.workspaceId],
|
||||
references: [workspaces.id],
|
||||
}),
|
||||
fields: [boards.workspaceId],
|
||||
references: [workspaces.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const imports = pgTable(
|
||||
"import",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
source: importSourceEnum('source').notNull(),
|
||||
status: importStatusEnum('status').notNull(),
|
||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
},
|
||||
);
|
||||
export const imports = pgTable("import", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
source: importSourceEnum("source").notNull(),
|
||||
status: importStatusEnum("status").notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const importsRelations = relations(imports, ({ one, many }) => ({
|
||||
createdBy: one(users, {
|
||||
fields: [imports.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
createdBy: one(users, {
|
||||
fields: [imports.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
boards: many(boards),
|
||||
cards: many(cards),
|
||||
lists: many(lists),
|
||||
labels: many(labels)
|
||||
labels: many(labels),
|
||||
}));
|
||||
|
||||
export const labels = pgTable(
|
||||
"label",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
colourCode: varchar("colourCode", { length: 12 }),
|
||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
boardId: bigint("boardId", { mode: "number" }).notNull().references(() => boards.id),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
}
|
||||
);
|
||||
export const labels = pgTable("label", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
colourCode: varchar("colourCode", { length: 12 }),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
boardId: bigint("boardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
});
|
||||
|
||||
export const labelsRelations = relations(labels, ({ one, many }) => ({
|
||||
createdBy: one(users, {
|
||||
fields: [labels.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
createdBy: one(users, {
|
||||
fields: [labels.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
board: one(boards, {
|
||||
fields: [labels.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
fields: [labels.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
cards: many(cardsToLabels),
|
||||
import: one(imports, {
|
||||
fields: [labels.importId],
|
||||
references: [imports.id],
|
||||
}),
|
||||
fields: [labels.importId],
|
||||
references: [imports.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const cardsToLabels = pgTable(
|
||||
"_card_labels",
|
||||
{
|
||||
cardId: bigint("cardId", { mode: "number" }).notNull().references(() => cards.id),
|
||||
labelId: bigint("labelId", { mode: "number" }).notNull().references(() => labels.id),
|
||||
}, (t) => ({
|
||||
cardId: bigint("cardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => cards.id),
|
||||
labelId: bigint("labelId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => labels.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(t) => ({
|
||||
pk: primaryKey(t.cardId, t.labelId),
|
||||
}),
|
||||
);
|
||||
|
||||
export const cardToLabelsRelations = relations(cardsToLabels, ({ one }) => ({
|
||||
card: one(cards, {
|
||||
fields: [cardsToLabels.cardId],
|
||||
references: [cards.id],
|
||||
}),
|
||||
card: one(cards, {
|
||||
fields: [cardsToLabels.cardId],
|
||||
references: [cards.id],
|
||||
}),
|
||||
label: one(labels, {
|
||||
fields: [cardsToLabels.labelId],
|
||||
references: [labels.id],
|
||||
}),
|
||||
fields: [cardsToLabels.labelId],
|
||||
references: [labels.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const cardToWorkspaceMembers = pgTable(
|
||||
"_card_workspace_members",
|
||||
{
|
||||
cardId: bigint("cardId", { mode: "number" }).notNull().references(() => cards.id),
|
||||
workspaceMemberId: bigint("workspaceMemberId", { mode: "number" }).notNull().references(() => workspaceMembers.id),
|
||||
}, (t) => ({
|
||||
cardId: bigint("cardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => cards.id),
|
||||
workspaceMemberId: bigint("workspaceMemberId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => workspaceMembers.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(t) => ({
|
||||
pk: primaryKey(t.cardId, t.workspaceMemberId),
|
||||
}),
|
||||
);
|
||||
|
||||
export const cardToWorkspaceMembersRelations = relations(cardToWorkspaceMembers, ({ one }) => ({
|
||||
card: one(cards, {
|
||||
fields: [cardToWorkspaceMembers.cardId],
|
||||
references: [cards.id],
|
||||
}),
|
||||
member: one(workspaceMembers, {
|
||||
fields: [cardToWorkspaceMembers.workspaceMemberId],
|
||||
references: [workspaceMembers.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const lists = pgTable(
|
||||
"list",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
index: integer("index").notNull(),
|
||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
boardId: bigint("boardId", { mode: "number" }).notNull().references(() => boards.id),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
}
|
||||
export const cardToWorkspaceMembersRelations = relations(
|
||||
cardToWorkspaceMembers,
|
||||
({ one }) => ({
|
||||
card: one(cards, {
|
||||
fields: [cardToWorkspaceMembers.cardId],
|
||||
references: [cards.id],
|
||||
}),
|
||||
member: one(workspaceMembers, {
|
||||
fields: [cardToWorkspaceMembers.workspaceMemberId],
|
||||
references: [workspaceMembers.id],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const lists = pgTable("list", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
index: integer("index").notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
boardId: bigint("boardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
});
|
||||
|
||||
export const listsRelations = relations(lists, ({ one, many }) => ({
|
||||
createdBy: one(users, {
|
||||
fields: [lists.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
createdBy: one(users, {
|
||||
fields: [lists.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
board: one(boards, {
|
||||
fields: [lists.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
fields: [lists.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
cards: many(cards),
|
||||
deletedBy: one(users, {
|
||||
fields: [lists.deletedBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
fields: [lists.deletedBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
import: one(imports, {
|
||||
fields: [lists.importId],
|
||||
references: [imports.id],
|
||||
}),
|
||||
fields: [lists.importId],
|
||||
references: [imports.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const cards = pgTable(
|
||||
"card",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
title: varchar("title", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
index: integer("index").notNull(),
|
||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
listId: bigint("listId", { mode: "number" }).notNull().references(() => lists.id),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
}
|
||||
);
|
||||
export const cards = pgTable("card", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
title: varchar("title", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
index: integer("index").notNull(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
listId: bigint("listId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => lists.id, { onDelete: "cascade" }),
|
||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||
});
|
||||
|
||||
export const cardsRelations = relations(cards, ({ one, many }) => ({
|
||||
createdBy: one(users, {
|
||||
fields: [cards.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
createdBy: one(users, {
|
||||
fields: [cards.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
list: one(lists, {
|
||||
fields: [cards.listId],
|
||||
references: [lists.id],
|
||||
}),
|
||||
fields: [cards.listId],
|
||||
references: [lists.id],
|
||||
}),
|
||||
deletedBy: one(users, {
|
||||
fields: [cards.deletedBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
fields: [cards.deletedBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
labels: many(cardsToLabels),
|
||||
members: many(cardToWorkspaceMembers),
|
||||
import: one(imports, {
|
||||
fields: [cards.importId],
|
||||
references: [imports.id],
|
||||
}),
|
||||
fields: [cards.importId],
|
||||
references: [imports.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const users = pgTable("user", {
|
||||
@@ -241,51 +261,55 @@ export const usersRelations = relations(users, ({ many }) => ({
|
||||
workspaces: many(workspaces),
|
||||
}));
|
||||
|
||||
export const workspaces = pgTable(
|
||||
"workspace",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
}
|
||||
);
|
||||
export const workspaces = pgTable("workspace", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||
createdBy: uuid("createdBy")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||
});
|
||||
|
||||
export const workspaceRelations = relations(workspaces, ({ one, many }) => ({
|
||||
user: one(users, { fields: [workspaces.createdBy], references: [users.id] }),
|
||||
members: many(workspaceMembers)
|
||||
members: many(workspaceMembers),
|
||||
}));
|
||||
|
||||
export const workspaceMembers = pgTable(
|
||||
"workspace_members",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
userId: uuid("userId").notNull().references(() => users.id),
|
||||
workspaceId: bigint("workspaceId", { mode: "number" }).notNull().references(() => workspaces.id),
|
||||
createdBy: uuid("createdBy").notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
role: memberRoleEnum('role').notNull(),
|
||||
},
|
||||
export const workspaceMembers = pgTable("workspace_members", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
userId: uuid("userId")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
workspaceId: bigint("workspaceId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => workspaces.id, { onDelete: "cascade" }),
|
||||
createdBy: uuid("createdBy").notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
role: memberRoleEnum("role").notNull(),
|
||||
});
|
||||
|
||||
export const usersToWorkspacesRelations = relations(
|
||||
workspaceMembers,
|
||||
({ one }) => ({
|
||||
addedBy: one(users, {
|
||||
fields: [workspaceMembers.createdBy],
|
||||
references: [users.id],
|
||||
}),
|
||||
user: one(users, {
|
||||
fields: [workspaceMembers.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
workspace: one(workspaces, {
|
||||
fields: [workspaceMembers.workspaceId],
|
||||
references: [workspaces.id],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const usersToWorkspacesRelations = relations(workspaceMembers, ({ one }) => ({
|
||||
addedBy: one(users, { fields: [workspaceMembers.createdBy], references: [users.id] }),
|
||||
user: one(users, {
|
||||
fields: [workspaceMembers.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
workspace: one(workspaces, {
|
||||
fields: [workspaceMembers.workspaceId],
|
||||
references: [workspaces.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { api } from "~/utils/api";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { useWorkspace } from "~/providers/workspace";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
|
||||
export function DeleteWorkspaceConfirmation() {
|
||||
const { closeModal } = useModal();
|
||||
const { workspace, switchWorkspace, availableWorkspaces } = useWorkspace();
|
||||
const { showPopup } = usePopup();
|
||||
const router = useRouter();
|
||||
|
||||
const [isAcknowledgmentChecked, setIsAcknowledgmentChecked] = useState(false);
|
||||
|
||||
const deleteWorkspaceMutation = api.workspace.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
closeModal();
|
||||
const filteredWorkspaces = availableWorkspaces.filter(
|
||||
(ws) => ws.publicId !== workspace?.publicId,
|
||||
);
|
||||
if (filteredWorkspaces.length > 0 && filteredWorkspaces[0]) {
|
||||
switchWorkspace(filteredWorkspaces[0]);
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
closeModal();
|
||||
showPopup({
|
||||
header: "Error deleting workspace",
|
||||
message: "Please try again later, or contact customer support.",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleDeleteWorkspace = () => {
|
||||
deleteWorkspaceMutation.mutate({
|
||||
workspacePublicId: workspace?.publicId,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-5">
|
||||
<div className="flex w-full flex-col justify-between pb-4">
|
||||
<h2 className="text-md pb-4 font-medium text-neutral-900 dark:text-dark-1000">
|
||||
{`Are you sure you want to delete the workspace ${workspace?.name}?`}
|
||||
</h2>
|
||||
<p className="mb-4 text-sm text-light-900 dark:text-dark-900">
|
||||
Keep in mind that this action is irreversible.
|
||||
</p>
|
||||
<p className="text-sm text-light-900 dark:text-dark-900">
|
||||
This will result in the permanent deletion of all data associated with
|
||||
this workspace.
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex h-6 items-center">
|
||||
<input
|
||||
id="acknowledgment"
|
||||
name="acknowledgment"
|
||||
type="checkbox"
|
||||
aria-describedby="acknowledgment-description"
|
||||
className="mt-2 h-[14px] w-[14px] rounded border-gray-300 bg-transparent text-indigo-600 focus:shadow-none focus:ring-0 focus:ring-offset-0"
|
||||
checked={isAcknowledgmentChecked}
|
||||
onChange={() =>
|
||||
setIsAcknowledgmentChecked(!isAcknowledgmentChecked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm leading-6">
|
||||
<p id="comments-description" className="text-dark-1000">
|
||||
I acknowledge that all of the workspace data will be permanently
|
||||
deleted and want to proceed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex justify-end space-x-2 sm:mt-6">
|
||||
<Button variant="secondary" onClick={() => closeModal()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={handleDeleteWorkspace}
|
||||
disabled={!isAcknowledgmentChecked}
|
||||
isLoading={deleteWorkspaceMutation.isPending}
|
||||
>
|
||||
Delete workspace
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useModal } from "~/providers/modal";
|
||||
import Modal from "~/components/modal";
|
||||
|
||||
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
|
||||
import Button from "~/components/Button";
|
||||
import { DeleteWorkspaceConfirmation } from "./components/DeleteWorkspaceConfirmation";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { modalContentType } = useModal();
|
||||
const { modalContentType, openModal } = useModal();
|
||||
|
||||
return (
|
||||
<div className="px-28 py-12">
|
||||
@@ -14,16 +14,23 @@ export default function SettingsPage() {
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flow-root">
|
||||
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-light-300 dark:border-dark-300">
|
||||
<h2 className="mt-8 text-[14px] text-neutral-900 dark:text-dark-1000">
|
||||
Delete workspace
|
||||
</h2>
|
||||
<p className="mb-8 mt-2 text-sm text-neutral-500 dark:text-dark-900">
|
||||
Once you delete your workspace, there is no going back. Please be
|
||||
certain.
|
||||
</p>
|
||||
<Button variant="primary" onClick={() => openModal("DELETE_WORKSPACE")}>
|
||||
Delete workspace
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Modal>
|
||||
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
|
||||
{modalContentType === "DELETE_WORKSPACE" && (
|
||||
<DeleteWorkspaceConfirmation />
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -207,10 +207,25 @@ USING (
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Allow access to user's workspaces"
|
||||
CREATE POLICY "Allow viewing user's workspaces"
|
||||
ON public.workspace
|
||||
AS PERMISSIVE
|
||||
FOR ALL
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
id IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
OR
|
||||
"createdBy" = auth.uid()
|
||||
);
|
||||
|
||||
CREATE POLICY "Allow updating user's workspaces"
|
||||
ON public.workspace
|
||||
AS PERMISSIVE
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (
|
||||
id IN (
|
||||
@@ -220,6 +235,26 @@ USING (
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Allow deleting user's workspaces"
|
||||
ON public.workspace
|
||||
AS PERMISSIVE
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (
|
||||
id IN (
|
||||
SELECT "workspaceId"
|
||||
FROM workspace_members
|
||||
WHERE "userId" = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Allow authenticated users to create workspaces"
|
||||
ON public.workspace
|
||||
AS PERMISSIVE
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
USING (true);
|
||||
|
||||
CREATE POLICY "Allow access to user's own workspace membership"
|
||||
ON public.workspace_members
|
||||
AS PERMISSIVE
|
||||
|
||||
Reference in New Issue
Block a user