diff --git a/apps/web/src/components/CheckboxDropdown.tsx b/apps/web/src/components/CheckboxDropdown.tsx
index e10d39fa..bcfec462 100644
--- a/apps/web/src/components/CheckboxDropdown.tsx
+++ b/apps/web/src/components/CheckboxDropdown.tsx
@@ -24,7 +24,10 @@ interface CheckboxDropdownProps {
createNewItemLabel?: string;
menuSpacing?: "sm" | "md" | "lg";
position?: "left" | "right";
- handleSelect: (groupKey: string | null, item: { key: string }) => void;
+ handleSelect: (
+ groupKey: string | null,
+ item: { key: string; value: string },
+ ) => void;
handleEdit?: (key: string) => void;
handleCreate?: () => void;
asChild?: boolean;
@@ -58,7 +61,7 @@ export default function CheckboxDropdown({
className="group flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={(e) => {
e.preventDefault();
- handleSelect(groupKey, { key: item.key });
+ handleSelect(groupKey, { key: item.key, value: item.value });
}}
>
event.stopPropagation()}
- onChange={() => handleSelect(groupKey, { key: item.key })}
+ onChange={() =>
+ handleSelect(groupKey, { key: item.key, value: item.value })
+ }
checked={item.selected}
/>
{item.leftIcon && (
diff --git a/apps/web/src/views/card/components/ActivityList.tsx b/apps/web/src/views/card/components/ActivityList.tsx
index b42fa530..f04b76d4 100644
--- a/apps/web/src/views/card/components/ActivityList.tsx
+++ b/apps/web/src/views/card/components/ActivityList.tsx
@@ -9,7 +9,7 @@ import {
HiOutlineUserPlus,
} from "react-icons/hi2";
-import { type GetCardByIdOutput } from "@kan/api/types";
+import type { GetCardByIdOutput } from "@kan/api/types";
import Avatar from "~/components/Avatar";
import Comment from "./Comment";
@@ -148,7 +148,7 @@ const ActivityList = ({
}) => {
return (
- {activities?.map((activity, index) => {
+ {activities.map((activity, index) => {
const activityText = getActivityText({
type: activity.type,
toTitle: activity.toTitle,
diff --git a/apps/web/src/views/card/components/LabelSelector.tsx b/apps/web/src/views/card/components/LabelSelector.tsx
index 6fd486a2..8bf4bcf3 100644
--- a/apps/web/src/views/card/components/LabelSelector.tsx
+++ b/apps/web/src/views/card/components/LabelSelector.tsx
@@ -14,12 +14,14 @@ interface LabelSelectorProps {
selected: boolean;
leftIcon: React.ReactNode;
}[];
+ handleSelectLabel: (labelPublicId: string) => void;
isLoading: boolean;
}
export default function LabelSelector({
cardPublicId,
labels,
+ handleSelectLabel,
isLoading,
}: LabelSelectorProps) {
const { openModal } = useModal();
@@ -44,9 +46,10 @@ export default function LabelSelector({
) : (
- addOrRemoveLabel.mutate({ cardPublicId, labelPublicId: label.key })
- }
+ handleSelect={(_, label) => {
+ handleSelectLabel(label.key);
+ addOrRemoveLabel.mutate({ cardPublicId, labelPublicId: label.key });
+ }}
handleEdit={(labelPublicId) => openModal("EDIT_LABEL", labelPublicId)}
handleCreate={() => openModal("NEW_LABEL")}
createNewItemLabel="Create new label"
diff --git a/apps/web/src/views/card/components/ListSelector.tsx b/apps/web/src/views/card/components/ListSelector.tsx
index d84ada1a..ebba9353 100644
--- a/apps/web/src/views/card/components/ListSelector.tsx
+++ b/apps/web/src/views/card/components/ListSelector.tsx
@@ -10,12 +10,14 @@ interface ListSelectorProps {
value: string;
selected: boolean;
}[];
+ handleChangeList: (newListPublicId: string, newListName: string) => void;
isLoading: boolean;
}
export default function ListSelector({
cardPublicId,
lists,
+ handleChangeList,
isLoading,
}: ListSelectorProps) {
const utils = api.useUtils();
@@ -39,12 +41,13 @@ export default function ListSelector({
) : (
+ handleSelect={(_, member) => {
+ handleChangeList(member.key, member.value);
updateCardList.mutate({
cardPublicId,
newListPublicId: member.key,
- })
- }
+ });
+ }}
asChild
>
diff --git a/apps/web/src/views/card/components/MemberSelector.tsx b/apps/web/src/views/card/components/MemberSelector.tsx
index 44f9c74f..6c5d724d 100644
--- a/apps/web/src/views/card/components/MemberSelector.tsx
+++ b/apps/web/src/views/card/components/MemberSelector.tsx
@@ -5,6 +5,7 @@ import { HiMiniPlus } from "react-icons/hi2";
import Avatar from "~/components/Avatar";
import CheckboxDropdown from "~/components/CheckboxDropdown";
import { useModal } from "~/providers/modal";
+import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
interface MemberSelectorProps {
@@ -16,16 +17,19 @@ interface MemberSelectorProps {
leftIcon: React.ReactNode;
imageUrl: string | undefined;
}[];
+ handleSelectMember: (memberPublicId: string) => void;
isLoading: boolean;
}
export default function MemberSelector({
cardPublicId,
members,
+ handleSelectMember,
isLoading,
}: MemberSelectorProps) {
const router = useRouter();
const { openModal } = useModal();
+ const { showPopup } = usePopup();
const utils = api.useUtils();
const refetchCard = () => utils.card.byId.refetch({ cardPublicId });
@@ -34,6 +38,14 @@ export default function MemberSelector({
onSuccess: async () => {
await refetchCard();
},
+ onError: async () => {
+ await refetchCard();
+ showPopup({
+ header: "Unable to update members",
+ message: "Please try again later, or contact customer support.",
+ icon: "error",
+ });
+ },
});
const selectedMembers = members.filter((member) => member.selected);
@@ -52,12 +64,13 @@ export default function MemberSelector({
) : (
+ handleSelect={(_, member) => {
+ handleSelectMember(member.key);
addOrRemoveMember.mutate({
cardPublicId,
workspaceMemberPublicId: member.key,
- })
- }
+ });
+ }}
handleCreate={handleInviteMember}
createNewItemLabel="Invite member"
asChild
diff --git a/apps/web/src/views/card/index.tsx b/apps/web/src/views/card/index.tsx
index a7346de9..ae57900f 100644
--- a/apps/web/src/views/card/index.tsx
+++ b/apps/web/src/views/card/index.tsx
@@ -1,9 +1,12 @@
import Link from "next/link";
import { useRouter } from "next/router";
+import { useEffect, useState } from "react";
import ContentEditable from "react-contenteditable";
import { useForm } from "react-hook-form";
import { IoChevronForwardSharp } from "react-icons/io5";
+import type { GetCardByIdOutput } from "@kan/api/types";
+
import Avatar from "~/components/Avatar";
import LabelIcon from "~/components/LabelIcon";
import Modal from "~/components/modal";
@@ -35,6 +38,7 @@ export default function CardPage() {
const utils = api.useUtils();
const { modalContentType, entityId } = useModal();
const { showPopup } = usePopup();
+ const [card, setCard] = useState();
const cardId = Array.isArray(router.query.cardId)
? router.query.cardId[0]
@@ -44,13 +48,85 @@ export default function CardPage() {
cardPublicId: cardId ?? "",
});
- const board = data?.list?.board;
+ useEffect(() => {
+ setCard(data);
+ }, [data]);
+
+ const board = card?.list?.board;
const boardId = board?.publicId;
const labels = board?.labels;
- const activities = data?.activities;
+ const activities = card?.activities;
const workspaceMembers = board?.workspace?.members;
- const selectedLabels = data?.labels;
- const selectedMembers = data?.members;
+ const selectedLabels = card?.labels;
+ const selectedMembers = card?.members;
+
+ const handleChangeList = (newListPublicId: string) => {
+ if (!card) return;
+
+ setCard({
+ ...card,
+ list: {
+ ...card.list,
+ publicId: newListPublicId,
+ name: card.list?.name ?? "",
+ board: card.list?.board ?? null,
+ },
+ });
+ };
+
+ const handleSelectLabel = (labelPublicId: string) => {
+ if (!card) return;
+
+ const isSelected = card.labels.some(
+ (label) => label.publicId === labelPublicId,
+ );
+
+ const updatedLabels = isSelected
+ ? card.labels.filter((label) => label.publicId !== labelPublicId)
+ : [
+ ...(card.labels ?? []),
+ labels?.find((label) => label.publicId === labelPublicId),
+ ].filter(Boolean);
+
+ setCard({
+ ...card,
+ labels: updatedLabels as {
+ publicId: string;
+ name: string;
+ colourCode: string | null;
+ }[],
+ });
+ };
+
+ const handleSelectMember = (memberPublicId: string) => {
+ if (!card) return;
+
+ const isSelected = card.members.some(
+ (member) => member.publicId === memberPublicId,
+ );
+
+ const updatedMembers = isSelected
+ ? card.members.filter((member) => member.publicId !== memberPublicId)
+ : [
+ ...(card.members ?? []),
+ workspaceMembers?.find(
+ (member) => member.publicId === memberPublicId,
+ ),
+ ];
+
+ setCard({
+ ...card,
+ members: updatedMembers.filter(Boolean) as {
+ publicId: string;
+ user: {
+ id: string;
+ name: string | null;
+ email: string;
+ image: string | null;
+ } | null;
+ }[],
+ });
+ };
const formattedLabels =
labels?.map((label) => {
@@ -70,7 +146,7 @@ export default function CardPage() {
board?.lists.map((list) => ({
key: list.publicId,
value: list.name,
- selected: list.publicId === data?.list?.publicId,
+ selected: list.publicId === card?.list?.publicId,
})) ?? [];
const formattedMembers =
@@ -219,6 +295,7 @@ export default function CardPage() {
@@ -227,6 +304,7 @@ export default function CardPage() {
@@ -235,6 +313,7 @@ export default function CardPage() {