feat(release): publish minimal self-contained Magent source
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { authFetch, getApiBase, clearToken } from "../../../lib/auth";
|
||||
import ResolutionChoice from "../../../ui/ResolutionChoice";
|
||||
|
||||
type Issue = {
|
||||
id: number;
|
||||
kind: string;
|
||||
title: string;
|
||||
status: string;
|
||||
permissions?: { can_confirm_resolution?: boolean };
|
||||
};
|
||||
|
||||
export default function ConfirmIssuePage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const router = useRouter();
|
||||
const [item, setItem] = useState<Issue | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [result, setResult] = useState("");
|
||||
const login = useCallback(() => {
|
||||
clearToken();
|
||||
router.replace(`/login?next=${encodeURIComponent(`/issues/confirm/${id}`)}`);
|
||||
}, [id, router]);
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
setLoading(true);
|
||||
setItem(null);
|
||||
setError("");
|
||||
setResult("");
|
||||
const load = async () => {
|
||||
try {
|
||||
const response = await authFetch(`${getApiBase()}/portal/items/${id}`, {
|
||||
signal: controller.signal,
|
||||
cache: "no-store",
|
||||
});
|
||||
if (response.status === 401) {
|
||||
login();
|
||||
return;
|
||||
}
|
||||
if (!response.ok)
|
||||
throw new Error("This issue is unavailable. Please sign in with the account that reported it.");
|
||||
const data = await response.json();
|
||||
if (data.item?.kind !== "issue") throw new Error("This link does not belong to an issue.");
|
||||
setItem(data.item);
|
||||
} catch (err) {
|
||||
if (!controller.signal.aborted)
|
||||
setError(err instanceof Error ? err.message : "Could not load this issue. Please try again.");
|
||||
} finally {
|
||||
if (!controller.signal.aborted) setLoading(false);
|
||||
}
|
||||
};
|
||||
void load();
|
||||
return () => controller.abort();
|
||||
// The confirmation link identifies one issue. Never submit an answer on GET.
|
||||
}, [id, login]);
|
||||
|
||||
const answer = async (resolved: boolean) => {
|
||||
if (busy) return;
|
||||
setBusy(true);
|
||||
setError("");
|
||||
try {
|
||||
const response = await authFetch(`${getApiBase()}/portal/issues/${id}/resolution-response`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ resolved }),
|
||||
});
|
||||
if (response.status === 401) {
|
||||
login();
|
||||
return;
|
||||
}
|
||||
if (!response.ok)
|
||||
throw new Error(
|
||||
"Your answer could not be saved. This issue may already have been answered. Refresh to check before trying again.",
|
||||
);
|
||||
setResult(
|
||||
resolved
|
||||
? "Thanks! Your issue is now closed."
|
||||
: "Thanks for letting us know. Your issue stays open for another look.",
|
||||
);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Could not save your answer. Please try again.");
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<main className="resolution-response-page">
|
||||
{error && (
|
||||
<p role="alert" className="status-banner">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
{loading ? (
|
||||
<p role="status">Loading your issue…</p>
|
||||
) : result ? (
|
||||
<section className="resolution-choice" role="status">
|
||||
<h2>{result}</h2>
|
||||
<a href="/portal/issues">Back to issues</a>
|
||||
</section>
|
||||
) : item ? (
|
||||
item.status === "awaiting_confirmation" && item.permissions?.can_confirm_resolution ? (
|
||||
<ResolutionChoice title={item.title} busy={busy} onAnswer={(value) => void answer(value)} />
|
||||
) : (
|
||||
<section className="resolution-choice">
|
||||
<h2>
|
||||
{item.status === "awaiting_confirmation"
|
||||
? "This question is for the person who reported the issue."
|
||||
: "No answer is needed right now."}
|
||||
</h2>
|
||||
<p>{item.title}</p>
|
||||
<a href={`/portal/issues?item=${item.id}`}>View issue</a>
|
||||
</section>
|
||||
)
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user