Skip to content

Commit 0786c87

Browse files
committed
feat: update ui
1 parent 5ab94a5 commit 0786c87

6 files changed

Lines changed: 267 additions & 4 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<script setup lang="ts">
2+
import type { NewOperation } from '~/composables/useConnector'
3+
4+
const props = withDefaults(
5+
defineProps<{
6+
packageName: string
7+
version?: string
8+
}>(),
9+
{ version: '' },
10+
)
11+
12+
const { t } = useI18n()
13+
const { isConnected, state, addOperation, approveOperation, executeOperations, refreshState } =
14+
useConnector()
15+
16+
const deprecateMessage = ref('')
17+
const deprecateVersion = ref(props.version)
18+
const isDeprecating = shallowRef(false)
19+
const deprecateSuccess = shallowRef(false)
20+
const deprecateError = shallowRef<string | null>(null)
21+
22+
const connectorModal = useModal('connector-modal')
23+
24+
const modalTitle = computed(() =>
25+
deprecateVersion.value
26+
? `${$t('package.deprecation.modal.title')} ${props.packageName}@${deprecateVersion.value}`
27+
: `${$t('package.deprecation.modal.title')} ${props.packageName}`,
28+
)
29+
30+
async function handleDeprecate() {
31+
const message = deprecateMessage.value.trim()
32+
if (!message || !isConnected.value) return
33+
34+
isDeprecating.value = true
35+
deprecateError.value = null
36+
37+
try {
38+
const params: Record<string, string> = {
39+
pkg: props.packageName,
40+
message,
41+
}
42+
if (deprecateVersion.value.trim()) {
43+
params.version = deprecateVersion.value.trim()
44+
}
45+
46+
const command = params.version
47+
? `npm deprecate ${props.packageName}@${params.version} "${message}"`
48+
: `npm deprecate ${props.packageName} "${message}"`
49+
50+
const operation = await addOperation({
51+
type: 'package:deprecate',
52+
params,
53+
description: params.version
54+
? `Deprecate ${props.packageName}@${params.version}`
55+
: `Deprecate ${props.packageName}`,
56+
command,
57+
} as NewOperation)
58+
59+
if (!operation) {
60+
throw new Error('Failed to create operation')
61+
}
62+
63+
await approveOperation(operation.id)
64+
await executeOperations()
65+
await refreshState()
66+
67+
const completedOp = state.value.operations.find(op => op.id === operation.id)
68+
if (completedOp?.status === 'completed') {
69+
deprecateSuccess.value = true
70+
} else if (completedOp?.status === 'failed') {
71+
if (completedOp.result?.requiresOtp) {
72+
close()
73+
connectorModal.open()
74+
} else {
75+
deprecateError.value = completedOp.result?.stderr || t('deprecate.modal.failed')
76+
}
77+
} else {
78+
close()
79+
connectorModal.open()
80+
}
81+
} catch (err) {
82+
deprecateError.value = err instanceof Error ? err.message : t('deprecate.modal.failed')
83+
} finally {
84+
isDeprecating.value = false
85+
}
86+
}
87+
88+
const dialogRef = ref<HTMLDialogElement>()
89+
90+
function open() {
91+
deprecateError.value = null
92+
deprecateSuccess.value = false
93+
deprecateMessage.value = ''
94+
deprecateVersion.value = props.version ?? ''
95+
dialogRef.value?.showModal()
96+
}
97+
98+
function close() {
99+
dialogRef.value?.close()
100+
}
101+
102+
defineExpose({ open, close })
103+
</script>
104+
105+
<template>
106+
<Modal ref="dialogRef" :modal-title="modalTitle" id="deprecate-package-modal" class="max-w-md">
107+
<!-- Success state -->
108+
<div v-if="deprecateSuccess" class="space-y-4">
109+
<div
110+
class="flex items-center gap-3 p-4 bg-green-500/10 border border-green-500/20 rounded-lg"
111+
>
112+
<span class="i-carbon-checkmark-filled text-green-500 w-6 h-6" aria-hidden="true" />
113+
<div>
114+
<p class="font-mono text-sm text-fg">{{ $t('package.deprecation.modal.success') }}</p>
115+
<p class="text-xs text-fg-muted">
116+
{{ $t('package.deprecation.modal.success_detail') }}
117+
</p>
118+
</div>
119+
</div>
120+
<button
121+
type="button"
122+
class="w-full 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-none focus-visible:ring-2 focus-visible:ring-fg/50"
123+
@click="close"
124+
>
125+
{{ $t('common.close') }}
126+
</button>
127+
</div>
128+
129+
<!-- Form (only shown when connected; parent only opens modal when isConnected) -->
130+
<div v-else class="space-y-4">
131+
<div>
132+
<label for="deprecate-message" class="block text-sm font-medium text-fg mb-1">
133+
{{ $t('package.deprecation.modal.reason') }}
134+
</label>
135+
<textarea
136+
id="deprecate-message"
137+
v-model="deprecateMessage"
138+
rows="3"
139+
class="w-full px-3 py-2 font-mono text-sm bg-bg border border-border rounded-md text-fg placeholder:text-fg-muted focus:outline-none focus:ring-2 focus:ring-fg/50"
140+
:placeholder="$t('package.deprecation.modal.reason_placeholder')"
141+
/>
142+
</div>
143+
<div>
144+
<label for="deprecate-version" class="block text-sm font-medium text-fg mb-1">
145+
{{ $t('package.deprecation.modal.version') }}
146+
</label>
147+
<input
148+
id="deprecate-version"
149+
v-model="deprecateVersion"
150+
type="text"
151+
class="w-full px-3 py-2 font-mono text-sm bg-bg border border-border rounded-md text-fg placeholder:text-fg-muted focus:outline-none focus:ring-2 focus:ring-fg/50"
152+
:placeholder="$t('package.deprecation.modal.version_placeholder')"
153+
/>
154+
</div>
155+
<div
156+
v-if="deprecateError"
157+
role="alert"
158+
class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"
159+
>
160+
{{ deprecateError }}
161+
</div>
162+
<button
163+
type="button"
164+
:disabled="isDeprecating || !deprecateMessage.trim()"
165+
class="w-full px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-colors duration-200 hover:bg-fg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
166+
@click="handleDeprecate"
167+
>
168+
{{
169+
isDeprecating
170+
? $t('package.deprecation.modal.deprecating')
171+
: $t('package.deprecation.action')
172+
}}
173+
</button>
174+
</div>
175+
</Modal>
176+
</template>

app/pages/package/[...package].vue

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,17 @@ const deprecationNoticeMessage = useMarkdown(() => ({
179179
text: deprecationNotice.value?.message ?? '',
180180
}))
181181
182+
const { isConnected, npmUser } = useConnector()
183+
const deprecateModal = useTemplateRef<{ open: () => void }>('deprecateModal')
184+
185+
const isPackageOwner = computed(() => {
186+
const maintainers = pkg.value?.maintainers
187+
const user = npmUser.value
188+
if (!maintainers?.length || !user) return false
189+
const userLower = user.toLowerCase()
190+
return maintainers.some((m: { name?: string }) => (m.name ?? '').toLowerCase() === userLower)
191+
})
192+
182193
const sizeTooltip = computed(() => {
183194
const chunks = [
184195
displayVersion.value &&
@@ -1054,6 +1065,22 @@ defineOgImageComponent('Package', {
10541065
:peer-dependencies-meta="displayVersion.peerDependenciesMeta"
10551066
:optional-dependencies="displayVersion.optionalDependencies"
10561067
/>
1068+
1069+
<!-- Deprecation (when connected as package owner) -->
1070+
<div v-if="isConnected && resolvedVersion && isPackageOwner" class="space-y-1">
1071+
<button
1072+
type="button"
1073+
class="flex items-center justify-center w-full px-3 py-1.5 bg-bg-subtle rounded text-sm font-mono text-red-400 hover:text-red-500 transition-colors inline-flex items-center gap-1.5 w-full"
1074+
@click="deprecateModal?.open()"
1075+
>
1076+
<span class="i-carbon-warning-alt w-4 h-4 shrink-0" aria-hidden="true" />
1077+
{{
1078+
deprecationNotice
1079+
? $t('package.deprecation.action_change')
1080+
: $t('package.deprecation.action')
1081+
}}
1082+
</button>
1083+
</div>
10571084
</div>
10581085
</div>
10591086
</article>
@@ -1072,6 +1099,14 @@ defineOgImageComponent('Package', {
10721099
</p>
10731100
<NuxtLink to="/" class="btn">{{ $t('common.go_back_home') }}</NuxtLink>
10741101
</div>
1102+
<ClientOnly>
1103+
<PackageDeprecatePackageModal
1104+
v-if="pkg"
1105+
ref="deprecateModal"
1106+
:package-name="pkg.name"
1107+
:version="resolvedVersion ?? ''"
1108+
/>
1109+
</ClientOnly>
10751110
</main>
10761111
</template>
10771112

i18n/locales/en.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,22 @@
124124
"navigation": "Package",
125125
"copy_name": "Copy package name",
126126
"deprecation": {
127+
"action": "Deprecate",
128+
"action_change": "Change deprecation",
127129
"package": "This package has been deprecated.",
128130
"version": "This version has been deprecated.",
129-
"no_reason": "No reason provided"
131+
"no_reason": "No reason provided",
132+
"modal": {
133+
"deprecating": "Deprecating",
134+
"title": "Deprecate",
135+
"title_version": "Deprecate Version",
136+
"reason": "Reason",
137+
"reason_placeholder": "e.g. Use package-x instead. This package is no longer maintained.",
138+
"success": "Package deprecated",
139+
"success_detail": "The package has been deprecated.",
140+
"version": "Version",
141+
"version_placeholder": "Leave empty to deprecate the whole package"
142+
}
130143
},
131144
"replacement": {
132145
"title": "You might not need this dependency.",

i18n/locales/zh-CN.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,22 @@
124124
"navigation": "包导航",
125125
"copy_name": "拷贝包名",
126126
"deprecation": {
127+
"action": "废弃",
128+
"action_change": "修改弃用",
127129
"package": "这个包已经被弃用。",
128130
"version": "这个版本已经被弃用。",
129-
"no_reason": "没有提供原因"
131+
"no_reason": "没有提供原因",
132+
"modal": {
133+
"deprecating": "废弃中",
134+
"title": "废弃",
135+
"title_version": "废弃版本",
136+
"reason": "原因",
137+
"reason_placeholder": "例如:使用 package-x 替代。这个包不再维护。",
138+
"success": "包已废弃",
139+
"success_detail": "这个包已废弃。",
140+
"version": "版本",
141+
"version_placeholder": "留空则废弃整个包"
142+
}
130143
},
131144
"replacement": {
132145
"title": "你可能不需要这个依赖。",

lunaria/files/en-US.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,22 @@
124124
"navigation": "Package",
125125
"copy_name": "Copy package name",
126126
"deprecation": {
127+
"action": "Deprecate",
128+
"action_change": "Change deprecation",
127129
"package": "This package has been deprecated.",
128130
"version": "This version has been deprecated.",
129-
"no_reason": "No reason provided"
131+
"no_reason": "No reason provided",
132+
"modal": {
133+
"deprecating": "Deprecating",
134+
"title": "Deprecate",
135+
"title_version": "Deprecate Version",
136+
"reason": "Reason",
137+
"reason_placeholder": "e.g. Use package-x instead. This package is no longer maintained.",
138+
"success": "Package deprecated",
139+
"success_detail": "The package has been deprecated.",
140+
"version": "Version",
141+
"version_placeholder": "Leave empty to deprecate the whole package"
142+
}
130143
},
131144
"replacement": {
132145
"title": "You might not need this dependency.",

lunaria/files/zh-CN.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,22 @@
124124
"navigation": "包导航",
125125
"copy_name": "拷贝包名",
126126
"deprecation": {
127+
"action": "废弃",
128+
"action_change": "修改弃用",
127129
"package": "这个包已经被弃用。",
128130
"version": "这个版本已经被弃用。",
129-
"no_reason": "没有提供原因"
131+
"no_reason": "没有提供原因",
132+
"modal": {
133+
"deprecating": "废弃中",
134+
"title": "废弃",
135+
"title_version": "废弃版本",
136+
"reason": "原因",
137+
"reason_placeholder": "例如:使用 package-x 替代。这个包不再维护。",
138+
"success": "包已废弃",
139+
"success_detail": "这个包已废弃。",
140+
"version": "版本",
141+
"version_placeholder": "留空则废弃整个包"
142+
}
130143
},
131144
"replacement": {
132145
"title": "你可能不需要这个依赖。",

0 commit comments

Comments
 (0)