fix(submissions): surface each linked build's outcome to the submitter

/me/submissions (and the admin queue) now attach build_status/build_error by
a read-only Builder.Get — until now a failed build was visible only on the
admin-tier /images/build routes, so the person who submitted the modpack
never learned the build died. A missing build row renders as "no outcome";
any other lookup failure surfaces instead of being swallowed. The panel's
My Submissions page renders the outcome in the expanded row, localised.
This commit is contained in:
Lemon-miaow committed 2026-09-23 06:31:13 +08:00
1 parent 4933c075b0
commit 72c4aa3895
9 files changed
+225 -8

No files matched your search

@@ -19,6 +19,13 @@
"table_created_at": "Submitted At",
"table_reviewed_by": "Reviewed By",
"table_reject_reason": "Reject Reason",
"table_build_status": "Build Status",
"table_build_error": "Build Error",
"build_status_pending": "Queued",
"build_status_building": "Building",
"build_status_succeeded": "Succeeded",
"build_status_failed": "Failed",
"build_status_cancelled": "Cancelled",
"no_submissions_title": "No Submissions Found",
"no_submissions_hint": "You haven't submitted any modpacks yet.",
"search_placeholder": "Search submissions...",
@@ -19,6 +19,13 @@
"table_created_at": "提交时间",
"table_reviewed_by": "审核人",
"table_reject_reason": "拒绝原因",
"table_build_status": "构建状态",
"table_build_error": "构建失败原因",
"build_status_pending": "排队中",
"build_status_building": "构建中",
"build_status_succeeded": "构建成功",
"build_status_failed": "构建失败",
"build_status_cancelled": "已取消",
"no_submissions_title": "暂无提交记录",
"no_submissions_hint": "您还没有提交过任何模组包。",
"search_placeholder": "搜索提交记录...",
+2
View File
@@ -263,6 +263,8 @@ export interface Submission {
status: SubmissionStatus;
image_ref?: string;
build_id?: string;
build_status?: BuildStatus;
build_error?: string;
reviewed_by?: string;
reject_reason?: string;
created_at: string;
+47 -1
View File
@@ -20,6 +20,7 @@ import {
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
@@ -37,10 +38,29 @@ import { SubmissionStatusBadge } from "@/components/SubmissionStatusBadge";
import { api, humanizeError } from "@/lib/api";
import { useAsync } from "@/lib/hooks";
import { formatRelative, formatAbsolute } from "@/lib/format";
import type { Submission, SubmissionStatus } from "@/lib/types";
import type { BuildStatus, Submission, SubmissionStatus } from "@/lib/types";
const PAGE_SIZE = 10;
// The linked build's outcome as shown in a row's expanded details. Colors mirror
// the admin build page; the labels are player-facing, so they come from this
// page's namespace instead of the raw status string.
const BUILD_STATUS_STYLE: Record<BuildStatus, string> = {
pending: "bg-amber-500/10 text-amber-500 border-amber-500/20 animate-pulse",
building: "bg-blue-500/10 text-blue-500 border-blue-500/20 animate-pulse",
succeeded: "bg-emerald-500/10 text-emerald-500 border-emerald-500/20",
failed: "bg-rose-500/10 text-rose-500 border-rose-500/20",
cancelled: "bg-zinc-500/10 text-zinc-400 border-zinc-500/20",
};
const BUILD_STATUS_I18N: Record<BuildStatus, string> = {
pending: "build_status_pending",
building: "build_status_building",
succeeded: "build_status_succeeded",
failed: "build_status_failed",
cancelled: "build_status_cancelled",
};
function formatBytes(bytes: number, decimals = 2) {
if (bytes === 0) return "0 Bytes";
const k = 1024;
@@ -411,6 +431,32 @@ export function MySubmissionsPage() {
)}
</div>
)}
{/* Linked build outcome — a submitter's only view of a
failed build (the build pages are admin-tier). */}
{sub.build_id && sub.build_status && (
<div className="pt-1 border-t border-border/20 mt-2 flex flex-col gap-1.5">
<div className="flex items-center gap-2 flex-wrap">
<span className="font-semibold text-foreground">{t("table_build_status")}:</span>
<Badge
variant="outline"
className={cn(
"font-medium select-none pointer-events-none",
BUILD_STATUS_STYLE[sub.build_status],
)}
>
{t(BUILD_STATUS_I18N[sub.build_status])}
</Badge>
<code className="font-mono text-[10px] text-muted-foreground/75">{sub.build_id}</code>
</div>
{sub.build_status === "failed" && sub.build_error && (
<div className="text-rose-500 font-medium break-all whitespace-pre-wrap">
<span className="font-semibold text-foreground mr-1.5">{t("table_build_error")}:</span>
{sub.build_error}
</div>
)}
</div>
)}
</div>
)}
</div>