fix: show the reason the server gave for a failed action
Every failure in the management screens printed a guess. Deleting a working copy said "it may be published, or something may reference it, or someone may have edited it first" — three maybes, while the server had answered with exactly one: "이 기록을 참조하는 곳이 있어 삭제할 수 없습니다". A version conflict read as "in use" because the same sentence covered both, and an author watching some deletions succeed and others fail had no way to tell them apart. The gateway now carries the server's client-safe message and the screens show it. The canned sentences remain only as a fallback for a failure that never reached the server. The topic status label said "사용 중" for every active topic, including one created seconds earlier that nothing references. Next to a refusal about records that use a topic, the two read as the same statement. It says "활성" now, which is what the status means. Publishing also stops demanding a finished document — the mock validator moves with the real one, so what an author sees against fixtures matches production.
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useState, type FormEvent } from "react";
|
||||
|
||||
import type { ListDocumentsQuery } from "../../../application/ports/studio-gateway.ts";
|
||||
import type { DocumentPage } from "../../../contracts/studio/contract.ts";
|
||||
import { managementFailureMessage } from "../../../application/ports/management-gateway-error.ts";
|
||||
import { useStudio } from "../use-studio.ts";
|
||||
import { GuardedStudioLink } from "./guarded-studio-link.tsx";
|
||||
|
||||
@@ -98,10 +99,10 @@ export function DocumentList() {
|
||||
? { ...current, items: current.items.filter((row) => row.id !== item.id) }
|
||||
: current,
|
||||
);
|
||||
} catch {
|
||||
setDeleteError(
|
||||
"삭제하지 못했습니다. 게시 중이거나, 이 기록을 참조하는 곳이 있거나, 다른 곳에서 먼저 수정되었을 수 있습니다.",
|
||||
);
|
||||
} catch (error) {
|
||||
// 서버는 무엇이 막았는지 정확히 알고 그것을 보내 준다. 예전에는 그 문구를 버리고 추측 셋을
|
||||
// 늘어놓아, 버전 충돌도 "사용 중" 으로 읽혔다.
|
||||
setDeleteError(managementFailureMessage(error, "삭제하지 못했습니다."));
|
||||
} finally {
|
||||
setDeletingId(null);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
ReleaseIndexItem,
|
||||
ReleaseUpdateRequest,
|
||||
} from "../../../contracts/management/contract.ts";
|
||||
import { managementFailureMessage } from "../../../application/ports/management-gateway-error.ts";
|
||||
import { useStudio } from "../use-studio.ts";
|
||||
|
||||
/**
|
||||
@@ -150,8 +151,8 @@ export function ReleaseManager() {
|
||||
setRequestAnnouncement(`릴리즈 ${title} 초안을 만들었습니다.`);
|
||||
setSelectedId(created.id);
|
||||
reload();
|
||||
} catch {
|
||||
setError("릴리즈를 만들지 못했습니다.");
|
||||
} catch (error) {
|
||||
setError(managementFailureMessage(error, "릴리즈를 만들지 못했습니다."));
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
@@ -180,8 +181,8 @@ export function ReleaseManager() {
|
||||
setDraft(toDraft(saved));
|
||||
setRequestAnnouncement(`릴리즈 ${saved.versionLabel} 을(를) 저장했습니다.`);
|
||||
reload();
|
||||
} catch {
|
||||
setError("저장하지 못했습니다. 같은 버전이 이미 있거나 다른 곳에서 먼저 수정되었을 수 있습니다.");
|
||||
} catch (error) {
|
||||
setError(managementFailureMessage(error, "저장하지 못했습니다."));
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
@@ -214,8 +215,8 @@ export function ReleaseManager() {
|
||||
await managementGateway.archiveRelease(selectedId, draft.expectedVersion);
|
||||
setRequestAnnouncement("릴리즈를 공개에서 내렸습니다.");
|
||||
reload();
|
||||
} catch {
|
||||
setError("공개에서 내리지 못했습니다.");
|
||||
} catch (error) {
|
||||
setError(managementFailureMessage(error, "공개에서 내리지 못했습니다."));
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
@@ -230,8 +231,8 @@ export function ReleaseManager() {
|
||||
if (selectedId === release.id) setSelectedId(null);
|
||||
setRequestAnnouncement(`릴리즈 ${release.versionLabel} 을(를) 삭제했습니다.`);
|
||||
reload();
|
||||
} catch {
|
||||
setError("삭제하지 못했습니다. 공개된 릴리즈는 삭제 대신 공개에서 내려야 합니다.");
|
||||
} catch (error) {
|
||||
setError(managementFailureMessage(error, "삭제하지 못했습니다."));
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useState, type FormEvent } from "react";
|
||||
|
||||
import type { ProjectIndexItem, TopicEdit } from "../../../contracts/management/contract.ts";
|
||||
import { managementFailureMessage } from "../../../application/ports/management-gateway-error.ts";
|
||||
import { slugFromName } from "./slug-from-name.ts";
|
||||
import { useStudio } from "../use-studio.ts";
|
||||
|
||||
@@ -75,8 +76,8 @@ export function TaxonomyManager() {
|
||||
setTopicSlug("");
|
||||
setRequestAnnouncement(`주제 ${name} 을(를) 만들었습니다.`);
|
||||
reload();
|
||||
} catch {
|
||||
setError("주제를 만들지 못했습니다. 같은 이름이나 slug 가 이미 있을 수 있습니다.");
|
||||
} catch (error) {
|
||||
setError(managementFailureMessage(error, "주제를 만들지 못했습니다."));
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
@@ -97,8 +98,8 @@ export function TaxonomyManager() {
|
||||
setProjectTitle("");
|
||||
setRequestAnnouncement(`프로젝트 ${title} 을(를) 만들었습니다.`);
|
||||
reload();
|
||||
} catch {
|
||||
setError("프로젝트를 만들지 못했습니다.");
|
||||
} catch (error) {
|
||||
setError(managementFailureMessage(error, "프로젝트를 만들지 못했습니다."));
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
@@ -112,8 +113,8 @@ export function TaxonomyManager() {
|
||||
await managementGateway.deleteTopic(topic.id, topic.version);
|
||||
setRequestAnnouncement(`주제 ${topic.name} 을(를) 삭제했습니다.`);
|
||||
reload();
|
||||
} catch {
|
||||
setError("주제를 삭제하지 못했습니다. 이 주제를 쓰는 기록이 있을 수 있습니다.");
|
||||
} catch (error) {
|
||||
setError(managementFailureMessage(error, "주제를 삭제하지 못했습니다."));
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
@@ -127,8 +128,8 @@ export function TaxonomyManager() {
|
||||
await managementGateway.deleteProject(project.id, project.version);
|
||||
setRequestAnnouncement(`프로젝트 ${project.name} 을(를) 삭제했습니다.`);
|
||||
reload();
|
||||
} catch {
|
||||
setError("프로젝트를 삭제하지 못했습니다. 연결된 기록이 있을 수 있습니다.");
|
||||
} catch (error) {
|
||||
setError(managementFailureMessage(error, "프로젝트를 삭제하지 못했습니다."));
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
@@ -210,7 +211,12 @@ export function TaxonomyManager() {
|
||||
<dl>
|
||||
<div>
|
||||
<dt>상태</dt>
|
||||
<dd>{topic.status === "ARCHIVED" ? "보관" : "사용 중"}</dd>
|
||||
{/*
|
||||
`ACTIVE` 를 "사용 중" 이라 적었더니, 방금 만들어 아무도 쓰지 않는 주제까지
|
||||
"사용 중" 으로 보였다. 그 상태에서 삭제가 거절되면 작성자는 둘을 같은
|
||||
말로 읽는다 — 실제로는 서로 다른 이야기다.
|
||||
*/}
|
||||
<dd>{topic.status === "ARCHIVED" ? "보관" : "활성"}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user