Skip to content

Commit 5e94226

Browse files
committed
Use warning modal for maintainer removal as well
1 parent 2d9039e commit 5e94226

1 file changed

Lines changed: 116 additions & 12 deletions

File tree

app/components/Package/Maintainers.vue

Lines changed: 116 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,20 @@ const {
1313
addOperation,
1414
listPackageCollaborators,
1515
listTeamUsers,
16+
error: connectorError,
1617
} = useConnector()
1718
1819
const showAddOwner = shallowRef(false)
1920
const newOwnerUsername = shallowRef('')
2021
const isAdding = shallowRef(false)
2122
const 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+
2330
const 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

Comments
 (0)