@@ -13,14 +13,17 @@ import {
1313 toast ,
1414} from '@sim/emcn'
1515import { PanelLeft , Send , Settings } from '@sim/emcn/icons'
16+ import { getErrorMessage } from '@sim/utils/errors'
1617import { useRouter } from 'next/navigation'
1718import { IdentityTile } from '@/components/identity-tile/identity-tile'
1819import { getOrganizationSettingsHref } from '@/components/settings/navigation'
1920import { SettingsGuardedLink } from '@/components/settings/settings-guarded-link'
2021import type { OrganizationSurfaceOrganization } from '@/lib/organizations/surface'
2122import { LOGO_ACCEPT_ATTRIBUTE } from '@/lib/uploads/client/logo-file'
2223import { InviteModal } from '@/app/workspace/[workspaceId]/components/invite-modal'
24+ import { SidebarRenameRow } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-rename-row'
2325import { SIDEBAR_RAIL_CHIP_CLASS } from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
26+ import { useRenameOrganization } from '@/hooks/queries/organization'
2427import { useUploadOrganizationLogo } from '@/hooks/queries/organization-logo'
2528
2629function getOrganizationInitial ( name : string ) : string {
@@ -29,7 +32,8 @@ function getOrganizationInitial(name: string): string {
2932
3033interface OrganizationHeaderProps {
3134 organization : OrganizationSurfaceOrganization
32- canEditLogo : boolean
35+ /** Organization admins may change its logo and name. */
36+ canEditIdentity : boolean
3337 canInviteMembers : boolean
3438 isCollapsed : boolean
3539 /** Expands the rail; the collapsed header is itself the expand control. */
@@ -42,10 +46,11 @@ interface OrganizationHeaderProps {
4246 * into its settings; collapsed, it becomes the rail's expand control, swapping
4347 * the mark for a panel glyph on hover exactly as the workspace header does. The
4448 * mark is the organization's uploaded logo or its initial on the neutral tile.
49+ * Admins change the logo and rename the organization from the card.
4550 */
4651export function OrganizationHeader ( {
4752 organization,
48- canEditLogo ,
53+ canEditIdentity ,
4954 canInviteMembers,
5055 isCollapsed,
5156 onExpandSidebar,
@@ -55,7 +60,9 @@ export function OrganizationHeader({
5560 const { mutate : uploadLogo , isPending : isUploadingLogo } = useUploadOrganizationLogo (
5661 organization . id
5762 )
63+ const renameOrganization = useRenameOrganization ( )
5864 const [ isInviteModalOpen , setIsInviteModalOpen ] = useState ( false )
65+ const [ draftName , setDraftName ] = useState < string | null > ( null )
5966 const initial = getOrganizationInitial ( organization . name )
6067
6168 if ( isCollapsed ) {
@@ -85,13 +92,28 @@ export function OrganizationHeader({
8592 }
8693
8794 const { memberCount } = organization
95+ const stopRenaming = ( ) => {
96+ setDraftName ( null )
97+ renameOrganization . reset ( )
98+ }
99+
100+ const submitRename = ( ) => {
101+ if ( draftName === null || renameOrganization . isPending ) return
102+ const name = draftName . trim ( )
103+ if ( ! name || name === organization . name ) {
104+ stopRenaming ( )
105+ return
106+ }
107+ renameOrganization . mutate ( { orgId : organization . id , name } , { onSuccess : stopRenaming } )
108+ }
109+
88110 const logo = (
89111 < IdentityTile size = 'lg' initial = { initial } logoUrl = { organization . logo } alt = { organization . name } />
90112 )
91113
92114 return (
93115 < div className = 'min-w-0 flex-1' >
94- { canEditLogo && (
116+ { canEditIdentity && (
95117 < input
96118 ref = { fileInputRef }
97119 type = 'file'
@@ -110,7 +132,11 @@ export function OrganizationHeader({
110132 } }
111133 />
112134 ) }
113- < DropdownMenu >
135+ < DropdownMenu
136+ onOpenChange = { ( open ) => {
137+ if ( ! open ) stopRenaming ( )
138+ } }
139+ >
114140 < DropdownMenuTrigger asChild >
115141 < Chip
116142 aria-label = 'Organization menu'
@@ -128,7 +154,7 @@ export function OrganizationHeader({
128154 className = 'w-64 max-w-[calc(100vw-24px)]'
129155 >
130156 < div className = 'flex items-center gap-2 px-2 py-1.5' >
131- { canEditLogo ? (
157+ { canEditIdentity ? (
132158 < Tooltip . Root >
133159 < Tooltip . Trigger asChild >
134160 < DropdownMenuItem
@@ -150,8 +176,50 @@ export function OrganizationHeader({
150176 ) : (
151177 logo
152178 ) }
153- < div className = 'flex min-w-0 flex-col' >
154- < OverflowText label = { organization . name } />
179+ < div className = 'flex min-w-0 flex-1 flex-col' >
180+ { draftName !== null ? (
181+ < SidebarRenameRow
182+ aria-label = 'Organization name'
183+ aria-invalid = { renameOrganization . isError || undefined }
184+ autoFocus
185+ value = { draftName }
186+ disabled = { renameOrganization . isPending }
187+ onFocus = { ( event ) => event . currentTarget . select ( ) }
188+ onChange = { ( event ) => {
189+ setDraftName ( event . target . value )
190+ if ( renameOrganization . isError ) renameOrganization . reset ( )
191+ } }
192+ onKeyDown = { ( event ) => {
193+ if ( event . key === 'Enter' ) {
194+ event . preventDefault ( )
195+ submitRename ( )
196+ } else if ( event . key === 'Escape' ) {
197+ event . preventDefault ( )
198+ stopRenaming ( )
199+ }
200+ } }
201+ onBlur = { submitRename }
202+ />
203+ ) : canEditIdentity ? (
204+ < DropdownMenuItem
205+ aria-label = 'Rename organization'
206+ textValue = 'Rename organization'
207+ className = 'h-auto min-w-0 p-0 hover-hover:opacity-70 focus-visible:opacity-70'
208+ onSelect = { ( event ) => {
209+ event . preventDefault ( )
210+ setDraftName ( organization . name )
211+ } }
212+ >
213+ < OverflowText label = { organization . name } />
214+ </ DropdownMenuItem >
215+ ) : (
216+ < OverflowText label = { organization . name } />
217+ ) }
218+ { renameOrganization . isError && (
219+ < span role = 'alert' className = 'text-[var(--text-error)] text-caption' >
220+ { getErrorMessage ( renameOrganization . error , 'Failed to rename organization' ) }
221+ </ span >
222+ ) }
155223 < span className = 'text-[var(--text-muted)] text-caption' >
156224 { memberCount } { memberCount === 1 ? 'member' : 'members' }
157225 </ span >
@@ -176,7 +244,7 @@ export function OrganizationHeader({
176244 open = { isInviteModalOpen }
177245 onOpenChange = { setIsInviteModalOpen }
178246 organizationId = { organization . id }
179- isOrganizationAdmin = { canEditLogo }
247+ isOrganizationAdmin = { canEditIdentity }
180248 canInvite = { canInviteMembers }
181249 />
182250 ) }
0 commit comments