This commit is contained in:
k
2026-09-21 17:08:07 +02:00
parent 2e6eb07301
commit 908c673c58
6 changed files with 190 additions and 55 deletions
+15 -14
View File
@@ -120,6 +120,7 @@ function TicketDetail({ request, onChanged }: { request: AccessRequest; onChange
const [draft, setDraft] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const { t } = useT();
const [freshKey, setFreshKey] = useState<string | null>(null);
useEffect(() => {
@@ -138,7 +139,7 @@ function TicketDetail({ request, onChanged }: { request: AccessRequest; onChange
if (res.key) setFreshKey(res.key);
onChanged();
} catch (err) {
setError(err instanceof Error ? err.message : "Update failed");
setError(err instanceof Error ? err.message : t("access.updateFailed"));
} finally {
setBusy(false);
}
@@ -156,7 +157,7 @@ function TicketDetail({ request, onChanged }: { request: AccessRequest; onChange
setDraft("");
onChanged();
} catch (err) {
setError(err instanceof Error ? err.message : "Send failed");
setError(err instanceof Error ? err.message : t("access.sendFailed"));
} finally {
setBusy(false);
}
@@ -166,7 +167,7 @@ function TicketDetail({ request, onChanged }: { request: AccessRequest; onChange
<div className="mt-2 space-y-3 rounded-3xl border border-stone-200/70 bg-stone-50 p-4 dark:border-zinc-800 dark:bg-zinc-900/60">
{freshKey && (
<div className="rounded-2xl border border-accent-500/40 bg-accent-50 p-3 dark:bg-accent-950/30">
<p className="mb-2 flex items-center gap-2 text-sm font-medium"><CheckCircle2 size={15} /> Account created — key shown once, copy it now:</p>
<p className="mb-2 flex items-center gap-2 text-sm font-medium"><CheckCircle2 size={15} /> {t("access.accountCreated")}</p>
<div className="flex items-center gap-2">
<code className="min-w-0 flex-1 truncate rounded-xl bg-white px-3 py-2 text-sm dark:bg-zinc-900">{freshKey}</code>
<CopyButton text={freshKey} />
@@ -184,7 +185,7 @@ function TicketDetail({ request, onChanged }: { request: AccessRequest; onChange
: "rounded-tl-lg bg-white dark:bg-zinc-800",
)}>
<p className="mb-0.5 text-[11px] font-medium opacity-70">
{m.author === "admin" ? "You (admin)" : `@${request.username}`} · {new Date(m.created_at).toLocaleString()}
{m.author === "admin" ? t("access.youAdmin") : `@${request.username}`} · {new Date(m.created_at).toLocaleString()}
</p>
<p className="whitespace-pre-wrap">{m.body}</p>
</div>
@@ -197,14 +198,14 @@ function TicketDetail({ request, onChanged }: { request: AccessRequest; onChange
rows={2}
value={draft}
onChange={(e) => setDraft(e.target.value)}
placeholder="Reply (emailed to the requester)…"
aria-label="Reply to requester"
placeholder={t("access.replyPh")}
aria-label={t("access.replyAria")}
className="max-h-32 flex-1 resize-none rounded-2xl border border-stone-200 bg-white px-3.5 py-2.5 text-sm outline-none transition placeholder:text-stone-400 focus:border-accent-500/60 dark:border-zinc-700 dark:bg-zinc-900 dark:placeholder:text-zinc-500"
/>
<button
type="submit"
disabled={!draft.trim() || busy}
aria-label="Send reply"
aria-label={t("chat.send")}
className="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-accent-600 text-white shadow transition hover:bg-accent-500 active:scale-95 disabled:opacity-40 dark:bg-accent-500 dark:text-zinc-950 dark:hover:bg-accent-400"
>
{busy ? <Spinner size={15} /> : <SendHorizontal size={15} />}
@@ -214,28 +215,28 @@ function TicketDetail({ request, onChanged }: { request: AccessRequest; onChange
{!closed ? (
<div className="flex flex-wrap items-end gap-2 rounded-2xl border border-stone-200 bg-white p-3 dark:border-zinc-700 dark:bg-zinc-900">
<div className="min-w-48 flex-1">
<Field label="Reason (emailed with the decision)">
<Input value={reason} onChange={(e) => setReason(e.target.value)} placeholder="Optional reason…" maxLength={500} />
<Field label={t("access.reasonLabel")}>
<Input value={reason} onChange={(e) => setReason(e.target.value)} placeholder={t("access.reasonPh")} maxLength={500} />
</Field>
</div>
<div className="flex gap-2">
{request.status === "pending" && (
<Button size="sm" variant="secondary" disabled={busy} onClick={() => setStatus("reviewing")}>
<Search size={14} /> Reviewing
<Search size={14} /> {t("access.btnReviewing")}
</Button>
)}
<Button size="sm" disabled={busy} onClick={() => setStatus("accepted")}>
<CheckCircle2 size={14} /> Accept
<CheckCircle2 size={14} /> {t("access.btnAccept")}
</Button>
<Button size="sm" variant="secondary" disabled={busy} onClick={() => setStatus("refused")} className="!text-red-600 dark:!text-red-400">
<XCircle size={14} /> Refuse
<XCircle size={14} /> {t("access.btnRefuse")}
</Button>
</div>
</div>
) : (
<p className="flex items-center gap-2 text-xs opacity-60">
<Clock size={13} /> Ticket closed ({STATUS_STYLE[request.status].label.toLowerCase()}).
{request.reason && <> Reason: <em>{request.reason}</em></>}
<Clock size={13} /> {t("access.closedNote", { status: t(`access.status.${request.status}` as StringKey).toLowerCase() })}
{request.reason && <> {t("access.reasonWord")}: <em>{request.reason}</em></>}
</p>
)}
{error && <p className="text-sm text-red-600 dark:text-red-400">{error}</p>}