diff --git a/apps/web/components/settings/account.tsx b/apps/web/components/settings/account.tsx index 31768b9a..96cf4baa 100644 --- a/apps/web/components/settings/account.tsx +++ b/apps/web/components/settings/account.tsx @@ -26,6 +26,12 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@ui/components/dropdown-menu" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@ui/components/dialog" import { useCustomer } from "autumn-js/react" import { useMutation, useQuery } from "@tanstack/react-query" import { @@ -38,6 +44,7 @@ import { Mail, MoreHorizontal, UserMinus, + ShieldCheck, X, } from "lucide-react" import { useMemo, useState } from "react" @@ -98,6 +105,27 @@ 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 if ( @@ -169,6 +197,7 @@ export default function Account() { const autumn = useCustomer() const [switchingOrgId, setSwitchingOrgId] = useState(null) const [orgMenuOpen, setOrgMenuOpen] = useState(false) + const [inviteDialogOpen, setInviteDialogOpen] = useState(false) const [inviteEmail, setInviteEmail] = useState("") const [inviteRole, setInviteRole] = useState("member") const canSwitchOrg = (allOrgs?.length ?? 0) > 1 @@ -244,6 +273,8 @@ export default function Account() { }, onSuccess: async (invitation) => { setInviteEmail("") + setInviteRole("member") + setInviteDialogOpen(false) await refetchActiveOrg() toast.success("Invitation sent", { description: invitation?.email @@ -549,60 +580,43 @@ export default function Account() {
{canManageTeam ? ( -
- -
- - setInviteEmail(event.target.value)} - placeholder="teammate@company.com" - autoComplete="email" - className={cn( - dmSans125ClassName(), - "h-10 w-full rounded-[10px] border border-white/[0.08] bg-[#0D0F14] pl-9 pr-3 text-[14px] text-[#FAFAFA] placeholder:text-[#525D6E] outline-none transition-colors focus:border-[#4BA0FA]/50", - )} - /> +
+
+
+ +
+
+

+ Invite teammate +

+

+ Choose role and permission preset before sending. +

+
- - +
) : (
@@ -830,6 +844,200 @@ export default function Account() {
+ + { + setInviteDialogOpen(open) + if (!open && !inviteMemberMutation.isPending) { + setInviteEmail("") + setInviteRole("member") + } + }} + > + + + + Invite teammate + + +
+
+ +
+ + setInviteEmail(event.target.value)} + placeholder="teammate@company.com" + autoComplete="email" + className={cn( + dmSans125ClassName(), + "h-10 w-full rounded-[10px] border border-white/[0.08] bg-[#0D0F14] pl-9 pr-3 text-[14px] text-[#FAFAFA] placeholder:text-[#525D6E] outline-none transition-colors focus:border-[#4BA0FA]/50", + )} + /> +
+
+ +
+

+ Role +

+
+ {(["member", "admin"] as const).map((role) => { + const selected = inviteRole === role + return ( + + ) + })} +
+
+ +
+

+ Permissions +

+
+ {(["member", "admin"] as const).map((role) => { + const option = INVITE_PERMISSION_OPTIONS[role] + const selected = inviteRole === role + return ( + + ) + })} +
+
+ +
+ + +
+
+
+
) }