fix(notifications): a notice value is no plain string, so a translated name cannot freeze in one language
A name goes in as raw(), a translated word as its own msg(): tsc now lists every value, and the ones that froze are said when shown — Watch later and an untitled channel in the pin and channel notices, a Plex season's name in its removal toast (a new seasonTitleText, which seasonTitle says).
This commit is contained in:
@@ -24,7 +24,7 @@ import {
|
||||
type PendingAccessRow,
|
||||
} from "../../lib/api";
|
||||
import { displayName } from "../../lib/api/account";
|
||||
import { msg, type NotifText, notify } from "../../lib/notifications";
|
||||
import { msg, type NotifText, notify, raw } from "../../lib/notifications";
|
||||
import { formatDateTime, relativeTime } from "../../lib/format";
|
||||
import { invalidateUserAccess } from "../../lib/adminUsers";
|
||||
import { ADMIN_USERS_TAB_KEY, USERS_TAB_URL_KEY, usersTabParams } from "../../lib/adminUsersTab";
|
||||
@@ -144,7 +144,7 @@ function UsersRoles({ me }: { me: Me }) {
|
||||
onMutate: (vars) => startPending(vars.id),
|
||||
onSuccess: (_d, vars) => {
|
||||
qc.invalidateQueries({ queryKey: qk.adminUsers() });
|
||||
notify({ level: "success", message: msg("users.roles.updated", { email: vars.email }) });
|
||||
notify({ level: "success", message: msg("users.roles.updated", { email: raw(vars.email) }) });
|
||||
},
|
||||
onSettled: (_d, _e, vars) => endPending(vars.id),
|
||||
// Guards (last admin / self) surface via the global error dialog with the server's detail; what
|
||||
@@ -161,7 +161,7 @@ function UsersRoles({ me }: { me: Me }) {
|
||||
notify({
|
||||
level: "success",
|
||||
message: msg(vars.suspended ? "users.suspend.done" : "users.suspend.undone", {
|
||||
email: vars.email,
|
||||
email: raw(vars.email),
|
||||
}),
|
||||
});
|
||||
},
|
||||
@@ -174,7 +174,7 @@ function UsersRoles({ me }: { me: Me }) {
|
||||
onMutate: (vars) => startPending(vars.id),
|
||||
onSuccess: (_d, vars) => {
|
||||
qc.invalidateQueries({ queryKey: qk.adminUsers() });
|
||||
notify({ level: "success", message: msg("users.delete.done", { email: vars.email }) });
|
||||
notify({ level: "success", message: msg("users.delete.done", { email: raw(vars.email) }) });
|
||||
},
|
||||
onSettled: (_d, _e, vars) => endPending(vars.id),
|
||||
onError: (e) => onGoneOrFailed(e, msg("users.delete.failed")),
|
||||
@@ -209,7 +209,7 @@ function UsersRoles({ me }: { me: Me }) {
|
||||
const onDelete = async (u: AdminUserRow) => {
|
||||
const ok = await confirm({
|
||||
title: t("users.delete.title"),
|
||||
message: t("users.delete.confirm", { email: u.email }),
|
||||
message: t("users.delete.confirm", { email: raw(u.email) }),
|
||||
confirmLabel: t("users.delete.action"),
|
||||
danger: true,
|
||||
});
|
||||
@@ -369,7 +369,7 @@ function PendingAccess() {
|
||||
refresh();
|
||||
notify({
|
||||
level: "success",
|
||||
message: msg("users.pendingAccess.activated", { email: u.email }),
|
||||
message: msg("users.pendingAccess.activated", { email: raw(u.email) }),
|
||||
});
|
||||
},
|
||||
onError: (err) => notifyActionError(err, msg("users.pendingAccess.activateFailed")),
|
||||
@@ -379,7 +379,10 @@ function PendingAccess() {
|
||||
mutationFn: (u: PendingAccessRow) => api.resendUserVerification(u.id),
|
||||
onSuccess: (_r, u) => {
|
||||
refresh();
|
||||
notify({ level: "success", message: msg("users.pendingAccess.resent", { email: u.email }) });
|
||||
notify({
|
||||
level: "success",
|
||||
message: msg("users.pendingAccess.resent", { email: raw(u.email) }),
|
||||
});
|
||||
},
|
||||
// The public resend is deliberately silent; this one is not, and the server's reason says WHY
|
||||
// (no working SMTP, a refused send) — a success toast for a mail never sent would be the lie.
|
||||
@@ -548,7 +551,7 @@ function AdminInvites() {
|
||||
refresh();
|
||||
notify({
|
||||
level: "success",
|
||||
message: msg("settings.invites.approved", { email: vars.email }),
|
||||
message: msg("settings.invites.approved", { email: raw(vars.email) }),
|
||||
});
|
||||
},
|
||||
onError: (e) => notifyActionError(e, msg("settings.invites.approveFailed")),
|
||||
@@ -615,7 +618,7 @@ function AdminInvites() {
|
||||
refresh();
|
||||
notify({
|
||||
level: "success",
|
||||
message: msg("settings.invites.invited", { email: v.inv.email }),
|
||||
message: msg("settings.invites.invited", { email: raw(v.inv.email) }),
|
||||
});
|
||||
},
|
||||
onError: (err) => notifyActionError(err, msg("settings.invites.inviteFailed")),
|
||||
@@ -657,7 +660,10 @@ function AdminInvites() {
|
||||
onSuccess: (_d, email) => {
|
||||
setNewEmail("");
|
||||
refresh();
|
||||
notify({ level: "success", message: msg("settings.invites.addedToWhitelist", { email }) });
|
||||
notify({
|
||||
level: "success",
|
||||
message: msg("settings.invites.addedToWhitelist", { email: raw(email) }),
|
||||
});
|
||||
},
|
||||
onError: (e) => notifyActionError(e, msg("settings.invites.addFailed")),
|
||||
});
|
||||
|
||||
@@ -81,7 +81,7 @@ vi.mock("../../lib/api", async (importOriginal) => {
|
||||
|
||||
import { HttpError } from "../../lib/api";
|
||||
import AdminUsers from "./AdminUsers";
|
||||
import { msg, sayNotif, type NotifText } from "../../lib/notifications";
|
||||
import { msg, type NotifText, raw, sayNotif } from "../../lib/notifications";
|
||||
import i18n from "../../i18n";
|
||||
|
||||
const invite = (over: Partial<Record<string, unknown>> = {}) => ({
|
||||
@@ -264,7 +264,7 @@ describe("inviting someone the admin whitelisted", () => {
|
||||
await waitFor(() => expect(inviteMock).toHaveBeenCalledWith(1, "en"));
|
||||
expect(notifyMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: msg("settings.invites.invited", { email: "asker@example.com" }),
|
||||
message: msg("settings.invites.invited", { email: raw("asker@example.com") }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@ vi.mock("../../lib/api", async (importOriginal) => {
|
||||
});
|
||||
|
||||
import AdminUsers from "./AdminUsers";
|
||||
import { msg, sayNotif, type NotifText } from "../../lib/notifications";
|
||||
import { msg, type NotifText, raw, sayNotif } from "../../lib/notifications";
|
||||
import i18n from "../../i18n";
|
||||
|
||||
/** A row as /api/admin/pending-access serves it. */
|
||||
@@ -116,7 +116,7 @@ describe("the stuck sign-ups section", () => {
|
||||
await waitFor(() => expect(activateMock).toHaveBeenCalledWith(7));
|
||||
expect(notifyMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: msg("users.pendingAccess.activated", { email: "later@example.com" }),
|
||||
message: msg("users.pendingAccess.activated", { email: raw("later@example.com") }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user