fix(web): navigate back after hiding skill detail

This commit is contained in:
huihan3 2026-04-02 16:03:38 +08:00
parent cbc1ef25f7
commit 2119bb221b
2 changed files with 45 additions and 4 deletions

View file

@ -252,6 +252,20 @@ describe('SkillDetailPage', () => {
expect(html).not.toContain('__RATING_WIDGET__')
})
it('does not render access denied content for logged-in forbidden responses', () => {
useSkillDetailMock.mockReturnValue({
data: undefined,
isLoading: false,
isFetching: false,
error: new Error('HTTP 403'),
})
const html = renderToStaticMarkup(<SkillDetailPage />)
expect(html).toBe('')
expect(html).not.toContain('skillDetail.accessDenied')
})
it('renders rejected owner preview without pending-review copy', () => {
useSkillDetailMock.mockReturnValue({
data: createSkill({

View file

@ -240,7 +240,17 @@ export function SkillDetailPage() {
const hideMutation = useMutation({
mutationFn: () => adminApi.hideSkill(skill!.id),
onSuccess: refreshSkill,
onSuccess: () => {
// After hiding, this detail route can become inaccessible for current viewer context.
// Navigate away immediately to avoid landing on an access-denied state.
queryClient.invalidateQueries({ queryKey: ['skills'] })
const returnTo = normalizeSkillDetailReturnTo(search.returnTo)
if (returnTo) {
void navigate({ to: returnTo, replace: true })
return
}
void navigate({ to: '/search', search: getSkillSquareSearch(), replace: true })
},
})
const unhideMutation = useMutation({
@ -392,6 +402,9 @@ export function SkillDetailPage() {
const isLastVersion = versions?.length === 1
const canWithdrawVersion = (status?: string) => status === 'PENDING_REVIEW'
const canRereleaseVersion = (status?: string) => status === 'PUBLISHED'
const isForbiddenError = skillError instanceof ApiError
? skillError.status === 403
: skillError instanceof Error && skillError.message.includes('403')
const isNotFoundError = skillError instanceof ApiError
? skillError.status === 400 || skillError.status === 404 || skillError.serverMessageKey === 'skill.not_found'
: false
@ -403,6 +416,18 @@ export function SkillDetailPage() {
clearDeletedSkillQueries(queryClient, namespace, slug, skill?.id)
}, [isNotFoundError, namespace, queryClient, skill?.id, slug])
useEffect(() => {
if (!isForbiddenError || !user) {
return
}
const returnTo = normalizeSkillDetailReturnTo(search.returnTo)
if (returnTo) {
void navigate({ to: returnTo, replace: true })
return
}
void navigate({ to: '/search', search: getSkillSquareSearch(), replace: true })
}, [isForbiddenError, navigate, search.returnTo, user])
const metadataDiffEntries = (() => {
const source = parseMetadataJson(diffSourceDetail?.parsedMetadataJson)
const compare = parseMetadataJson(diffCompareDetail?.parsedMetadataJson)
@ -610,9 +635,7 @@ export function SkillDetailPage() {
}
if (skillError) {
const isForbidden = skillError instanceof Error && skillError.message.includes('403')
if (isForbidden && !user) {
if (isForbiddenError && !user) {
return (
<div className="text-center py-20 animate-fade-up">
<h2 className="text-2xl font-bold font-heading mb-2">{t('skillDetail.loginRequired')}</h2>
@ -622,6 +645,10 @@ export function SkillDetailPage() {
)
}
if (isForbiddenError) {
return null
}
if (isNotFoundError) {
return (
<div className="text-center py-20 animate-fade-up">