diff --git a/frontend/src/app/invite/page.tsx b/frontend/src/app/invite/page.tsx
index 6441cd3..e1a61bc 100644
--- a/frontend/src/app/invite/page.tsx
+++ b/frontend/src/app/invite/page.tsx
@@ -2,7 +2,7 @@
export const dynamic = "force-dynamic";
-import { useEffect, useMemo, useState } from "react";
+import { Suspense, useEffect, useMemo, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
@@ -13,7 +13,7 @@ import { BrandMark } from "@/components/atoms/BrandMark";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
-export default function InvitePage() {
+function InviteContent() {
const router = useRouter();
const searchParams = useSearchParams();
const { isSignedIn } = useAuth();
@@ -146,3 +146,26 @@ export default function InvitePage() {
);
}
+
+export default function InvitePage() {
+ return (
+
+
+
+
+
+
+ }
+ >
+
+
+ );
+}
diff --git a/frontend/src/app/organization/page.tsx b/frontend/src/app/organization/page.tsx
index 2c26bdc..6932e13 100644
--- a/frontend/src/app/organization/page.tsx
+++ b/frontend/src/app/organization/page.tsx
@@ -2,7 +2,7 @@
export const dynamic = "force-dynamic";
-import { useEffect, useMemo, useState } from "react";
+import { useMemo, useState } from "react";
import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import { useQueryClient } from "@tanstack/react-query";
@@ -328,12 +328,11 @@ export default function OrganizationPage() {
const [accessDialogOpen, setAccessDialogOpen] = useState(false);
const [activeMemberId, setActiveMemberId] = useState(null);
- const [accessScope, setAccessScope] = useState("all");
- const [accessAllRead, setAccessAllRead] = useState(false);
- const [accessAllWrite, setAccessAllWrite] = useState(false);
- const [accessRole, setAccessRole] = useState("member");
- const [accessMap, setAccessMap] =
- useState(defaultBoardAccess);
+ const [accessScope, setAccessScope] = useState(null);
+ const [accessAllRead, setAccessAllRead] = useState(null);
+ const [accessAllWrite, setAccessAllWrite] = useState(null);
+ const [accessRole, setAccessRole] = useState(null);
+ const [accessMap, setAccessMap] = useState(null);
const [accessError, setAccessError] = useState(null);
const orgQuery = useGetMyOrgApiV1OrganizationsMeGet<
@@ -426,6 +425,45 @@ export default function OrganizationPage() {
},
});
+ const memberDetails =
+ memberDetailsQuery.data?.status === 200
+ ? memberDetailsQuery.data.data
+ : null;
+
+ const defaultAccess = useMemo(() => {
+ if (!memberDetails) {
+ return {
+ role: "member",
+ scope: "all" as AccessScope,
+ allRead: false,
+ allWrite: false,
+ access: {},
+ };
+ }
+ const isAll =
+ memberDetails.all_boards_read || memberDetails.all_boards_write;
+ const nextAccess: BoardAccessState = {};
+ for (const entry of memberDetails.board_access ?? []) {
+ nextAccess[entry.board_id] = {
+ read: entry.can_read || entry.can_write,
+ write: entry.can_write,
+ };
+ }
+ return {
+ role: memberDetails.role,
+ scope: isAll ? "all" : ("custom" as AccessScope),
+ allRead: memberDetails.all_boards_read,
+ allWrite: memberDetails.all_boards_write,
+ access: nextAccess,
+ };
+ }, [memberDetails]);
+
+ const resolvedAccessRole = accessRole ?? defaultAccess.role;
+ const resolvedAccessScope = accessScope ?? defaultAccess.scope;
+ const resolvedAccessAllRead = accessAllRead ?? defaultAccess.allRead;
+ const resolvedAccessAllWrite = accessAllWrite ?? defaultAccess.allWrite;
+ const resolvedAccessMap = accessMap ?? defaultAccess.access;
+
const createInviteMutation =
useCreateOrgInviteApiV1OrganizationsMeInvitesPost({
mutation: {
@@ -505,37 +543,31 @@ export default function OrganizationPage() {
},
});
- useEffect(() => {
- if (memberDetailsQuery.data?.status !== 200) return;
- const member = memberDetailsQuery.data.data;
- setAccessRole(member.role);
- const isAll = member.all_boards_read || member.all_boards_write;
- setAccessScope(isAll ? "all" : "custom");
- setAccessAllRead(member.all_boards_read);
- setAccessAllWrite(member.all_boards_write);
- const nextAccess: BoardAccessState = {};
- for (const entry of member.board_access ?? []) {
- nextAccess[entry.board_id] = {
- read: entry.can_read || entry.can_write,
- write: entry.can_write,
- };
- }
- setAccessMap(nextAccess);
+ const resetAccessState = () => {
+ setAccessRole(null);
+ setAccessScope(null);
+ setAccessAllRead(null);
+ setAccessAllWrite(null);
+ setAccessMap(null);
setAccessError(null);
- }, [memberDetailsQuery.data]);
+ };
- useEffect(() => {
- if (!accessDialogOpen) {
+ const handleAccessDialogChange = (open: boolean) => {
+ setAccessDialogOpen(open);
+ if (!open) {
setActiveMemberId(null);
setAccessError(null);
+ return;
}
- }, [accessDialogOpen]);
+ resetAccessState();
+ };
- useEffect(() => {
- if (!inviteDialogOpen) {
+ const handleInviteDialogChange = (open: boolean) => {
+ setInviteDialogOpen(open);
+ if (!open) {
setInviteError(null);
}
- }, [inviteDialogOpen]);
+ };
const orgName =
orgQuery.data?.status === 200 ? orgQuery.data.data.name : "Organization";
@@ -620,15 +652,18 @@ export default function OrganizationPage() {
const openAccessDialog = (memberId: string) => {
setActiveMemberId(memberId);
setAccessDialogOpen(true);
+ resetAccessState();
};
const handleSaveAccess = async () => {
if (!activeMemberId || !isAdmin) return;
const hasAllAccess =
- accessScope === "all" && (accessAllRead || accessAllWrite);
- const accessList = buildAccessList(accessMap);
- const hasCustomAccess = accessScope === "custom" && accessList.length > 0;
+ resolvedAccessScope === "all" &&
+ (resolvedAccessAllRead || resolvedAccessAllWrite);
+ const accessList = buildAccessList(resolvedAccessMap);
+ const hasCustomAccess =
+ resolvedAccessScope === "custom" && accessList.length > 0;
if (!hasAllAccess && !hasCustomAccess) {
setAccessError("Select read or write access for at least one board.");
@@ -638,12 +673,11 @@ export default function OrganizationPage() {
setAccessError(null);
try {
- if (memberDetailsQuery.data?.status === 200) {
- const member = memberDetailsQuery.data.data;
- if (member.role !== accessRole) {
+ if (memberDetails) {
+ if (memberDetails.role !== resolvedAccessRole) {
await updateMemberRoleMutation.mutateAsync({
- memberId: member.id,
- data: { role: accessRole },
+ memberId: memberDetails.id,
+ data: { role: resolvedAccessRole },
});
}
}
@@ -651,9 +685,11 @@ export default function OrganizationPage() {
await updateMemberAccessMutation.mutateAsync({
memberId: activeMemberId,
data: {
- all_boards_read: accessScope === "all" ? accessAllRead : false,
- all_boards_write: accessScope === "all" ? accessAllWrite : false,
- board_access: accessScope === "custom" ? accessList : [],
+ all_boards_read:
+ resolvedAccessScope === "all" ? resolvedAccessAllRead : false,
+ all_boards_write:
+ resolvedAccessScope === "all" ? resolvedAccessAllWrite : false,
+ board_access: resolvedAccessScope === "custom" ? accessList : [],
},
});
@@ -953,7 +989,7 @@ export default function OrganizationPage() {
-