@@ -13,13 +13,20 @@ const {
1313 addOperation,
1414 listPackageCollaborators,
1515 listTeamUsers,
16+ error : connectorError,
1617} = useConnector ()
1718
1819const showAddOwner = shallowRef (false )
1920const newOwnerUsername = shallowRef (' ' )
2021const isAdding = shallowRef (false )
2122const showAllMaintainers = shallowRef (false )
2223
24+ // Remove owner confirmation state
25+ const removeDialogRef = useTemplateRef (' removeDialogRef' )
26+ const removeTarget = shallowRef <{ username: string } | null >(null )
27+ const isRemoving = shallowRef (false )
28+ const removeError = shallowRef <string | null >(null )
29+
2330const DEFAULT_VISIBLE_MAINTAINERS = 5
2431
2532// Show admin controls when connected (let npm CLI handle permission errors)
@@ -141,18 +148,49 @@ async function handleAddOwner() {
141148 }
142149}
143150
144- async function handleRemoveOwner(username : string ) {
145- const operation: NewOperation = {
146- type: ' owner:rm' ,
147- params: {
148- user: username ,
149- pkg: props .packageName ,
150- },
151- description: ` Remove @${username } from ${props .packageName } ` ,
152- command: ` npm owner rm ${username } ${props .packageName } ` ,
153- }
151+ // Open remove owner confirmation dialog
152+ function openRemoveDialog(username : string ) {
153+ removeTarget .value = { username }
154+ removeError .value = null
155+ removeDialogRef .value ?.showModal ()
156+ }
157+
158+ // Close remove owner confirmation dialog
159+ function closeRemoveDialog() {
160+ removeDialogRef .value ?.close ()
161+ removeTarget .value = null
162+ removeError .value = null
163+ }
154164
155- await addOperation (operation )
165+ // Remove owner (after confirmation)
166+ async function handleRemoveOwner() {
167+ if (! removeTarget .value ) return
168+
169+ isRemoving .value = true
170+ removeError .value = null
171+
172+ try {
173+ const operation: NewOperation = {
174+ type: ' owner:rm' ,
175+ params: {
176+ user: removeTarget .value .username ,
177+ pkg: props .packageName ,
178+ },
179+ description: ` Remove @${removeTarget .value .username } from ${props .packageName } ` ,
180+ command: ` npm owner rm ${removeTarget .value .username } ${props .packageName } ` ,
181+ }
182+
183+ const result = await addOperation (operation )
184+ if (result ) {
185+ closeRemoveDialog ()
186+ } else {
187+ removeError .value = connectorError .value || ' Failed to queue remove operation'
188+ }
189+ } catch (err ) {
190+ removeError .value = err instanceof Error ? err .message : ' Failed to remove owner'
191+ } finally {
192+ isRemoving .value = false
193+ }
156194}
157195
158196// Load access info when connected and for scoped packages
@@ -226,7 +264,7 @@ watch(
226264 name: maintainer.name,
227265 })
228266 "
229- @click =" handleRemoveOwner (maintainer.name)"
267+ @click =" openRemoveDialog (maintainer.name)"
230268 >
231269 <span class =" i-lucide:x w-3.5 h-3.5" aria-hidden =" true" />
232270 </ButtonBase >
@@ -279,4 +317,70 @@ watch(
279317 </ButtonBase >
280318 </div >
281319 </CollapsibleSection >
320+
321+ <!-- Remove Owner Confirmation Modal -->
322+ <ClientOnly >
323+ <Modal
324+ ref =" removeDialogRef"
325+ :modal-title =" $t('package.maintainers.remove.title')"
326+ id =" remove-owner-modal"
327+ class =" max-w-sm"
328+ >
329+ <div class =" space-y-4" >
330+ <!-- Warning message -->
331+ <div
332+ class =" p-3 text-sm text-yellow-400 bg-yellow-500/10 border border-yellow-500/20 rounded-md"
333+ >
334+ <p class =" font-medium mb-1" >{{ $t('package.maintainers.remove.warning') }}</p >
335+ <p class =" text-xs text-yellow-400/80" >
336+ {{
337+ $t('package.maintainers.remove.impact', {
338+ user: removeTarget?.username,
339+ package: packageName,
340+ })
341+ }}
342+ </p >
343+ </div >
344+
345+ <!-- User being removed -->
346+ <div class =" flex items-center gap-2 p-3 bg-bg-subtle border border-border rounded-md" >
347+ <span class =" i-lucide:user w-4 h-4 text-fg-subtle shrink-0" aria-hidden =" true" />
348+ <span class =" font-mono text-sm text-fg" >@{{ removeTarget?.username }}</span >
349+ </div >
350+
351+ <!-- Error message -->
352+ <div
353+ v-if =" removeError"
354+ class =" p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"
355+ role =" alert"
356+ >
357+ {{ removeError }}
358+ </div >
359+
360+ <!-- Actions -->
361+ <div class =" flex gap-3" >
362+ <button
363+ type =" button"
364+ class =" flex-1 px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover focus-visible:outline-accent/70"
365+ :disabled =" isRemoving"
366+ @click =" closeRemoveDialog"
367+ >
368+ {{ $t('common.close') }}
369+ </button >
370+ <button
371+ type =" button"
372+ class =" flex-1 px-4 py-2 font-mono text-sm text-white bg-red-600 rounded-md transition-colors duration-200 hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-accent/70"
373+ :disabled =" isRemoving"
374+ @click =" handleRemoveOwner"
375+ >
376+ <span v-if =" isRemoving" class =" flex items-center justify-center gap-2" >
377+ <span class =" i-svg-spinners:ring-resize w-4 h-4 animate-spin" aria-hidden =" true" />
378+ {{ $t('package.maintainers.remove.removing') }}
379+ </span >
380+ <span v-else >{{ $t('package.maintainers.remove.confirm') }}</span >
381+ </button >
382+ </div >
383+ </div >
384+ </Modal >
385+ </ClientOnly >
282386</template >
0 commit comments