feat: allow users to delete entities they have created

This commit is contained in:
Henry
2026-01-29 20:50:38 +00:00
parent 62725ffa38
commit e3640542b3
11 changed files with 175 additions and 41 deletions

View File

@@ -9,6 +9,8 @@ import {
HiOutlineTrash,
} from "react-icons/hi2";
import { authClient } from "@kan/auth/client";
import Dropdown from "~/components/Dropdown";
import { usePermissions } from "~/hooks/usePermissions";
import { useModal } from "~/providers/modal";
@@ -24,6 +26,7 @@ interface ListProps {
interface List {
publicId: string;
name: string;
createdBy?: string | null;
}
interface FormValues {
@@ -41,6 +44,9 @@ export default function List({
}: ListProps) {
const { openModal } = useModal();
const { canCreateCard, canEditList, canDeleteList } = usePermissions();
const { data: session } = authClient.useSession();
const isCreator = list.createdBy && session?.user.id === list.createdBy;
const canEdit = canEditList || isCreator;
const openNewCardForm = (publicListId: PublicListId) => {
if (!canCreateCard) return;
@@ -62,7 +68,7 @@ export default function List({
});
const onSubmit = (values: FormValues) => {
if (!canEditList) return;
if (!canEdit) return;
updateList.mutate({
listPublicId: values.listPublicId,
name: values.name,
@@ -94,7 +100,7 @@ export default function List({
type="text"
{...register("name")}
onBlur={handleSubmit(onSubmit)}
readOnly={!canEditList}
readOnly={!canEdit}
className="w-full border-0 bg-transparent px-4 pt-1 text-sm font-medium text-neutral-900 focus:ring-0 focus-visible:outline-none dark:text-dark-1000"
/>
</form>
@@ -124,7 +130,7 @@ export default function List({
},
]
: []),
...(canDeleteList
...(canDeleteList || isCreator
? [
{
label: t`Delete list`,

View File

@@ -91,7 +91,7 @@ const Comment = ({
},
]
: []),
...((isAuthor || isAdmin) && canDeleteComment
...((isAuthor || canDeleteComment)
? [
{
label: t`Delete comment`,

View File

@@ -5,13 +5,21 @@ import {
HiOutlineTrash,
} from "react-icons/hi2";
import { authClient } from "@kan/auth/client";
import Dropdown from "~/components/Dropdown";
import { usePermissions } from "~/hooks/usePermissions";
import { useModal } from "~/providers/modal";
export default function CardDropdown() {
export default function CardDropdown({
cardCreatedBy,
}: {
cardCreatedBy?: string | null;
}) {
const { openModal } = useModal();
const { canEditCard, canDeleteCard } = usePermissions();
const { data: session } = authClient.useSession();
const isCreator = cardCreatedBy && session?.user.id === cardCreatedBy;
const items = [
...(canEditCard
@@ -25,7 +33,7 @@ export default function CardDropdown() {
},
]
: []),
...(canDeleteCard
...(canDeleteCard || isCreator
? [
{
label: t`Delete card`,

View File

@@ -14,6 +14,8 @@ import Modal from "~/components/modal";
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
import { PageHead } from "~/components/PageHead";
import { EditYouTubeModal } from "~/components/YouTubeEmbed/EditYouTubeModal";
import { authClient } from "@kan/auth/client";
import { usePermissions } from "~/hooks/usePermissions";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
@@ -46,6 +48,7 @@ interface FormValues {
export function CardRightPanel({ isTemplate }: { isTemplate?: boolean }) {
const router = useRouter();
const { canEditCard } = usePermissions();
const { data: session } = authClient.useSession();
const cardId = Array.isArray(router.query.cardId)
? router.query.cardId[0]
: router.query.cardId;
@@ -54,6 +57,9 @@ export function CardRightPanel({ isTemplate }: { isTemplate?: boolean }) {
cardPublicId: cardId ?? "",
});
const isCreator = card?.createdBy && session?.user.id === card.createdBy;
const canEdit = canEditCard || isCreator;
const board = card?.list.board;
const labels = board?.labels;
const workspaceMembers = board?.workspace.members;
@@ -112,7 +118,7 @@ export function CardRightPanel({ isTemplate }: { isTemplate?: boolean }) {
return (
<div className="h-full w-[360px] border-l-[1px] border-light-300 bg-light-50 p-8 text-light-900 dark:border-dark-300 dark:bg-dark-50 dark:text-dark-900">
{canEditCard && (
{canEdit && (
<>
<div className="mb-4 flex w-full flex-row pt-[18px]">
<p className="my-2 mb-2 w-[100px] text-sm font-medium">{t`List`}</p>
@@ -169,6 +175,7 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
const { showPopup } = usePopup();
const { workspace } = useWorkspace();
const { canEditCard } = usePermissions();
const { data: session } = authClient.useSession();
const [activeChecklistForm, setActiveChecklistForm] = useState<string | null>(
null,
);
@@ -181,6 +188,9 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
cardPublicId: cardId ?? "",
});
const isCreator = card?.createdBy && session?.user.id === card.createdBy;
const canEdit = canEditCard || isCreator;
const refetchCard = async () => {
if (cardId) await utils.card.byId.refetch({ cardPublicId: cardId });
};
@@ -305,7 +315,7 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
</Link>
</div>
<div className="flex items-center gap-2">
<Dropdown />
<Dropdown cardCreatedBy={card?.createdBy} />
</div>
</>
)}
@@ -333,10 +343,10 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
<textarea
id="title"
{...register("title")}
onBlur={canEditCard ? handleSubmit(onSubmit) : undefined}
onBlur={canEdit ? handleSubmit(onSubmit) : undefined}
rows={1}
disabled={!canEditCard}
className={`block w-full resize-none overflow-hidden border-0 bg-transparent p-0 py-0 font-bold leading-relaxed text-neutral-900 focus:ring-0 dark:text-dark-1000 sm:text-[1.2rem] ${!canEditCard ? "cursor-default" : ""}`}
disabled={!canEdit}
className={`block w-full resize-none overflow-hidden border-0 bg-transparent p-0 py-0 font-bold leading-relaxed text-neutral-900 focus:ring-0 dark:text-dark-1000 sm:text-[1.2rem] ${!canEdit ? "cursor-default" : ""}`}
onInput={(e) => {
const target = e.target as HTMLTextAreaElement;
target.style.height = "auto";
@@ -362,10 +372,10 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
<div className="mt-2">
<Editor
content={card.description}
onChange={canEditCard ? (e) => setValue("description", e) : undefined}
onBlur={canEditCard ? () => handleSubmit(onSubmit)() : undefined}
onChange={canEdit ? (e) => setValue("description", e) : undefined}
onBlur={canEdit ? () => handleSubmit(onSubmit)() : undefined}
workspaceMembers={board?.workspace.members ?? []}
readOnly={!canEditCard}
readOnly={!canEdit}
/>
</div>
</form>
@@ -375,7 +385,7 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
cardPublicId={cardId}
activeChecklistForm={activeChecklistForm}
setActiveChecklistForm={setActiveChecklistForm}
viewOnly={!canEditCard}
viewOnly={!canEdit}
/>
{!isTemplate && (
<>
@@ -384,11 +394,11 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
<AttachmentThumbnails
attachments={card.attachments}
cardPublicId={cardId ?? ""}
isReadOnly={!canEditCard}
isReadOnly={!canEdit}
/>
</div>
)}
{canEditCard && (
{canEdit && (
<div className="mt-6">
<AttachmentUpload cardPublicId={cardId} />
</div>