diff --git a/apps/web/components/settings/account.tsx b/apps/web/components/settings/account.tsx
index 155659b58..fa3d7dc9d 100644
--- a/apps/web/components/settings/account.tsx
+++ b/apps/web/components/settings/account.tsx
@@ -4,15 +4,8 @@ import { dmSans125ClassName } from "@/lib/fonts"
import { cn } from "@lib/utils"
import { useAuth } from "@lib/auth-context"
import { authClient } from "@lib/auth"
-import { useOrgSummaries } from "@/hooks/use-org-summaries"
-import { OrgPlanBadge, resolveOrgPlan } from "@/components/org-plan-badge"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
-import {
- PLAN_RANK,
- useTokenUsage,
- type PlanType,
-} from "@/hooks/use-token-usage"
-import { Popover, PopoverContent, PopoverTrigger } from "@ui/components/popover"
+import { Popover, PopoverContent } from "@ui/components/popover"
import {
Select,
SelectContent,
@@ -28,23 +21,21 @@ import {
} from "@ui/components/dropdown-menu"
import { Dialog, DialogContent, DialogTitle } from "@ui/components/dialog"
import * as DialogPrimitive from "@radix-ui/react-dialog"
-import { useCustomer } from "autumn-js/react"
import { useMutation, useQuery } from "@tanstack/react-query"
import {
- Check,
LoaderIcon,
ChevronDown,
- Building2,
Users,
UserPlus,
Mail,
MoreHorizontal,
UserMinus,
X,
+ Pencil,
Tag,
Plus,
} from "lucide-react"
-import { useMemo, useRef, useState } from "react"
+import { useEffect, useMemo, useRef, useState } from "react"
import { toast } from "sonner"
import { useContainerTags } from "@/hooks/use-container-tags"
import { PopoverAnchor } from "@ui/components/popover"
@@ -135,16 +126,7 @@ function isPendingInvitation(invitation: {
}
export default function Account() {
- const {
- user,
- org,
- organizations: allOrgs,
- setActiveOrg,
- refetchActiveOrg,
- } = useAuth()
- const autumn = useCustomer()
- const [switchingOrgId, setSwitchingOrgId] = useState(null)
- const [orgMenuOpen, setOrgMenuOpen] = useState(false)
+ const { user, org, refetchActiveOrg, refetchOrganizations } = useAuth()
const [inviteDialogOpen, setInviteDialogOpen] = useState(false)
const [inviteEmail, setInviteEmail] = useState("")
const [inviteRole, setInviteRole] = useState("member")
@@ -156,6 +138,8 @@ export default function Account() {
>([])
const [tagQuery, setTagQuery] = useState("")
const [tagDropdownOpen, setTagDropdownOpen] = useState(false)
+ const [isEditingOrgName, setIsEditingOrgName] = useState(false)
+ const [orgNameDraft, setOrgNameDraft] = useState("")
const tagInputRef = useRef(null)
const tagAnchorRef = useRef(null)
const { allProjects: allContainerTags } = useContainerTags()
@@ -174,28 +158,17 @@ export default function Account() {
const showAccessType = inviteRole === "member"
const showTagPicker =
inviteRole === "member" && inviteAccessType === "restricted"
- const canSwitchOrg = (allOrgs?.length ?? 0) > 1
- const { data: orgSummaries } = useOrgSummaries()
-
- const handleOrgSwitch = async (orgSlug: string, orgId: string) => {
- if (orgId === org?.id) return
- setSwitchingOrgId(orgId)
- try {
- await setActiveOrg(orgSlug)
- window.location.reload()
- } catch (error) {
- console.error("Failed to switch organization:", error)
- setSwitchingOrgId(null)
- }
- }
- const { currentPlan } = useTokenUsage(autumn)
+ useEffect(() => {
+ setOrgNameDraft(org?.name ?? "")
+ setIsEditingOrgName(false)
+ }, [org?.name])
const activeMemberRoleQuery = useQuery({
queryKey: ["organization", org?.id, "active-member-role"],
queryFn: async () => {
if (!org?.id) return null
- const result = await authClient.organization.getActiveMemberRole({
+ const result = await authClient.organization.getActiveMember({
query: { organizationId: org.id },
})
if (result.error) {
@@ -346,40 +319,50 @@ export default function Account() {
},
})
+ const updateOrgNameMutation = useMutation({
+ mutationFn: async (name: string) => {
+ if (!org?.id) throw new Error("No active organization")
+ const trimmed = name.trim()
+ if (!trimmed) throw new Error("Enter an organization name")
+ const result = await authClient.organization.update({
+ organizationId: org.id,
+ data: { name: trimmed },
+ })
+ if (result.error) {
+ throw new Error(
+ result.error.message ?? "Failed to update organization name",
+ )
+ }
+ return trimmed
+ },
+ onSuccess: async (name) => {
+ setOrgNameDraft(name)
+ setIsEditingOrgName(false)
+ await Promise.all([refetchActiveOrg(), refetchOrganizations()])
+ toast.success("Organization name updated")
+ },
+ onError: (error) => {
+ toast.error(getErrorMessage(error, "Failed to update organization name"))
+ },
+ })
+
const handleInviteSubmit = (event: React.FormEvent) => {
event.preventDefault()
if (!canManageTeam || inviteMemberMutation.isPending) return
inviteMemberMutation.mutate()
}
- const planByOrgId = useMemo(() => {
- const map = new Map()
- for (const summary of orgSummaries ?? []) {
- map.set(summary.orgId, summary.plan)
+ const handleOrgNameSubmit = (event: React.FormEvent) => {
+ event.preventDefault()
+ if (!canManageTeam || updateOrgNameMutation.isPending) return
+ const trimmed = orgNameDraft.trim()
+ if (!trimmed || trimmed === org?.name) {
+ setOrgNameDraft(org?.name ?? "")
+ setIsEditingOrgName(false)
+ return
}
- return map
- }, [orgSummaries])
-
- const sortedOrgsForMenu = useMemo(() => {
- if (!allOrgs?.length) return []
- return [...allOrgs].sort((a, b) => {
- const planA = resolveOrgPlan(
- a.id,
- a.id === org?.id,
- currentPlan,
- planByOrgId,
- )
- const planB = resolveOrgPlan(
- b.id,
- b.id === org?.id,
- currentPlan,
- planByOrgId,
- )
- const rankDiff = PLAN_RANK[planB] - PLAN_RANK[planA]
- if (rankDiff !== 0) return rankDiff
- return a.name.localeCompare(b.name)
- })
- }, [allOrgs, org?.id, currentPlan, planByOrgId])
+ updateOrgNameMutation.mutate(trimmed)
+ }
const memberSince = user?.createdAt
? new Date(user.createdAt).toLocaleDateString("en-US", {
@@ -438,84 +421,84 @@ export default function Account() {
>
Organization
- {canSwitchOrg ? (
-
-
+ setOrgNameDraft(event.target.value)}
+ disabled={updateOrgNameMutation.isPending}
+ maxLength={80}
className={cn(
- "flex min-w-0 max-w-full items-center gap-2 transition-opacity",
- "cursor-pointer hover:opacity-90",
dmSans125ClassName(),
+ "h-9 min-w-0 flex-1 rounded-[9px] border border-white/10 bg-black/30 px-3 text-[14px] font-medium tracking-[-0.14px] text-[#FAFAFA] outline-none transition-colors placeholder:text-[#525252] focus:border-[#4BA0FA]/60",
)}
- >
-
+
+
+
{
+ setOrgNameDraft(org?.name ?? "")
+ setIsEditingOrgName(false)
+ }}
+ className={cn(
+ "inline-flex size-8 items-center justify-center rounded-full bg-[#0D121A] text-[#737373] shadow-inside-out transition-colors hover:text-[#FAFAFA] disabled:cursor-not-allowed disabled:opacity-50",
+ )}
+ >
+
+
+
+
) : (
-
- {org?.name ?? "Personal"}
-
+
+
+ {org?.name ?? "Personal"}
+
+ {canManageTeam ? (
+
{
+ setOrgNameDraft(org?.name ?? "")
+ setIsEditingOrgName(true)
+ }}
+ className={cn(
+ "inline-flex size-7 shrink-0 items-center justify-center rounded-md text-[#FAFAFA] transition-colors hover:bg-white/5",
+ )}
+ >
+
+
+ ) : null}
+
)}