From bb068998ec0291f7ef8f6f186b642049aa8a30b3 Mon Sep 17 00:00:00 2001 From: ved015 Date: Sat, 23 May 2026 21:30:45 +0530 Subject: [PATCH] fix invite team member modal --- apps/web/components/settings/account.tsx | 481 +++++++++++++++-------- packages/ui/components/dialog.tsx | 2 +- 2 files changed, 322 insertions(+), 161 deletions(-) diff --git a/apps/web/components/settings/account.tsx b/apps/web/components/settings/account.tsx index 96cf4baa..b08dac71 100644 --- a/apps/web/components/settings/account.tsx +++ b/apps/web/components/settings/account.tsx @@ -44,11 +44,14 @@ import { Mail, MoreHorizontal, UserMinus, - ShieldCheck, X, + Tag, + Plus, } from "lucide-react" -import { useMemo, useState } from "react" +import { useMemo, useRef, useState } from "react" import { toast } from "sonner" +import { useContainerTags } from "@/hooks/use-container-tags" +import { PopoverAnchor } from "@ui/components/popover" function SectionTitle({ children }: { children: React.ReactNode }) { return ( @@ -105,26 +108,6 @@ const ROLE_LABELS: Record = { type InviteRole = "admin" | "member" -const INVITE_PERMISSION_OPTIONS: Record< - InviteRole, - { title: string; description: string; permissions: string[] } -> = { - member: { - title: "Member access", - description: "Use the organization workspace with standard access.", - permissions: ["Read organization access", "Use shared memories"], - }, - admin: { - title: "Admin access", - description: "Manage teammates and organization-level team settings.", - permissions: [ - "Invite and cancel invitations", - "Change member roles", - "Remove members", - "Update organization settings", - ], - }, -} function getErrorMessage(error: unknown, fallback: string) { if (error instanceof Error && error.message) return error.message @@ -200,6 +183,23 @@ export default function Account() { const [inviteDialogOpen, setInviteDialogOpen] = useState(false) const [inviteEmail, setInviteEmail] = useState("") const [inviteRole, setInviteRole] = useState("member") + const [inviteAccessType, setInviteAccessType] = useState<"full" | "restricted">("full") + const [inviteAssignments, setInviteAssignments] = useState<{ containerTag: string; permission: "read" | "write" }[]>([]) + const [tagQuery, setTagQuery] = useState("") + const [tagDropdownOpen, setTagDropdownOpen] = useState(false) + const tagInputRef = useRef(null) + const tagAnchorRef = useRef(null) + const { allProjects: allContainerTags } = useContainerTags() + + const selectedTagSet = new Set(inviteAssignments.map((a) => a.containerTag)) + const filteredTags = useMemo(() => { + const available = (allContainerTags ?? []).map((t) => t.containerTag).filter((t) => !selectedTagSet.has(t)) + if (!tagQuery) return available + return available.filter((t) => t.toLowerCase().includes(tagQuery.toLowerCase())) + }, [allContainerTags, selectedTagSet, tagQuery]) + + const showAccessType = inviteRole === "member" + const showTagPicker = inviteRole === "member" && inviteAccessType === "restricted" const canSwitchOrg = (allOrgs?.length ?? 0) > 1 const { data: orgSummaries } = useOrgSummaries() @@ -255,16 +255,28 @@ export default function Account() { [org?.invitations], ) + const resetInviteForm = () => { + setInviteEmail("") + setInviteRole("member") + setInviteAccessType("full") + setInviteAssignments([]) + setTagQuery("") + } + const inviteMemberMutation = useMutation({ mutationFn: async () => { if (!org?.id) throw new Error("No active organization") const email = inviteEmail.trim().toLowerCase() if (!email) throw new Error("Enter an email address") + const isRestricted = inviteRole === "member" && inviteAccessType === "restricted" const result = await authClient.organization.inviteMember({ email, role: inviteRole, organizationId: org.id, resend: true, + ...(isRestricted && inviteAssignments.length > 0 + ? { data: { accessType: "restricted", containerTags: inviteAssignments } } + : {}), }) if (result.error) { throw new Error(result.error.message ?? "Failed to invite teammate") @@ -272,8 +284,7 @@ export default function Account() { return result.data }, onSuccess: async (invitation) => { - setInviteEmail("") - setInviteRole("member") + resetInviteForm() setInviteDialogOpen(false) await refetchActiveOrg() toast.success("Invitation sent", { @@ -610,10 +621,10 @@ export default function Account() { disabled={!org?.id} className={cn( dmSans125ClassName(), - "inline-flex h-10 items-center justify-center gap-2 rounded-[10px] bg-[#4BA0FA] px-4 text-[14px] font-semibold text-[#00171A] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-45", + "inline-flex h-9 items-center justify-center gap-2 rounded-[10px] border border-[#2261CA33] bg-[#00173C] px-4 text-[13px] font-semibold text-white transition-colors hover:bg-[#002654] disabled:cursor-not-allowed disabled:opacity-45", )} > - + Invite member @@ -850,167 +861,316 @@ export default function Account() { onOpenChange={(open) => { setInviteDialogOpen(open) if (!open && !inviteMemberMutation.isPending) { - setInviteEmail("") - setInviteRole("member") + resetInviteForm() } }} > - - - - Invite teammate - - -
-
-
+ )} -
+