Re-wording, chips

This commit is contained in:
binwiederhier
2026-06-12 22:33:09 -04:00
parent 4516adea36
commit f558935c1e
7 changed files with 39 additions and 27 deletions
+1 -1
View File
@@ -157,7 +157,7 @@ var (
errHTTPConflictProvisionedUserChange = &errHTTP{40905, http.StatusConflict, "conflict: cannot change or delete provisioned user", "", nil}
errHTTPConflictProvisionedTokenChange = &errHTTP{40906, http.StatusConflict, "conflict: cannot change or delete provisioned token", "", nil}
errHTTPConflictEmailExists = &errHTTP{40907, http.StatusConflict, "conflict: email address already exists", "", nil}
errHTTPConflictEmailPrimaryElsewhere = &errHTTP{40908, http.StatusConflict, "conflict: email address is the recovery email on another account", "", nil}
errHTTPConflictEmailPrimaryElsewhere = &errHTTP{40908, http.StatusConflict, "conflict: email address is the primary email on another account", "", nil}
errHTTPGonePhoneVerificationExpired = &errHTTP{41001, http.StatusGone, "phone number verification expired or does not exist", "", nil}
errHTTPEntityTooLargeAttachment = &errHTTP{41301, http.StatusRequestEntityTooLarge, "attachment too large, or bandwidth limit reached", "https://ntfy.sh/docs/publish/#limitations", nil}
errHTTPEntityTooLargeMatrixRequest = &errHTTP{41302, http.StatusRequestEntityTooLarge, "Matrix request is larger than the max allowed length", "", nil}
+1 -1
View File
@@ -296,7 +296,7 @@ var (
ErrPhoneNumberExists = errors.New("phone number already exists")
ErrEmailNotFound = errors.New("email not found")
ErrEmailExists = errors.New("email already exists")
ErrEmailPrimaryElsewhere = errors.New("email is the recovery email on another account")
ErrEmailPrimaryElsewhere = errors.New("email is the primary email on another account")
ErrMagicLinkNotFound = errors.New("magic link not found")
ErrProvisionedUserChange = errors.New("cannot change or delete provisioned user")
ErrProvisionedTokenChange = errors.New("cannot change or delete provisioned token")
+10 -9
View File
@@ -29,11 +29,11 @@
"login_link_signup": "Sign up",
"login_link_forgot_password": "Forgot password?",
"reset_password_request_title": "Reset password",
"reset_password_request_description": "Enter your username or email address. If an account exists, we'll email a link to reset your password.",
"reset_password_request_description": "Enter your username or email address. If an account exists, a link to reset your password will be emailed.",
"reset_password_request_identifier_label": "Username or email",
"reset_password_request_button_submit": "Send reset link",
"reset_password_sent_title": "Check your inbox",
"reset_password_sent_description": "If an account exists, we've emailed a link to reset your password.",
"reset_password_sent_description": "If an account exists, a link to reset your password has been emailed.",
"reset_password_back_to_login": "Back to sign-in",
"reset_password_disabled": "Password reset is not enabled on this server.",
"reset_password_title": "Set a new password",
@@ -247,16 +247,17 @@
"account_basics_emails_primary_badge": "Primary",
"account_basics_emails_chip_actions": "Click for actions",
"account_basics_emails_unverified": "unverified",
"account_basics_emails_set_primary": "Set as recovery email",
"account_basics_emails_set_primary": "Set as primary email",
"account_basics_emails_delete": "Remove",
"account_basics_emails_cancel": "Cancel",
"account_basics_emails_resend": "Resend verification email",
"account_basics_emails_resent": "Verification email sent, check your inbox",
"account_basics_emails_primary_elsewhere": "This email is the recovery email on another account",
"account_basics_emails_no_recovery_warning": "Add a recovery email address to ensure you can reset your password.",
"account_basics_emails_provisioned_info": "Provisioned users cannot add a recovery email address.",
"account_basics_emails_primary_elsewhere": "This email is the primary email on another account",
"account_basics_emails_no_recovery_warning": "Add at least one email address to ensure you can recover your account if you lose your password.",
"account_basics_emails_no_primary_warning": "Add a primary email address to ensure you can recover your account if you lose your password.",
"account_basics_emails_provisioned_info": "Provisioned users cannot add a primary email address, but you can still add an email address for notifications.",
"account_basics_emails_dialog_title": "Add email address",
"account_basics_emails_dialog_description": "Enter an email address to add it to your account. We will send a verification link to confirm it is yours.",
"account_basics_emails_dialog_description": "Enter an email address to add it to your account. A verification link will be sent to confirm it is yours.",
"account_basics_emails_dialog_email_label": "Email address",
"account_basics_emails_dialog_email_placeholder": "e.g. user@example.com",
"account_basics_emails_dialog_verify_button": "Send verification link",
@@ -269,8 +270,8 @@
"account_basics_tier_title": "Account type",
"account_basics_tier_description": "Your account's power level",
"account_basics_tier_admin": "Admin",
"account_basics_tier_admin_suffix_with_tier": "(with {{tier}} tier)",
"account_basics_tier_admin_suffix_no_tier": "(no tier)",
"account_basics_tier_admin_suffix_with_tier": "with {{tier}} tier",
"account_basics_tier_admin_suffix_no_tier": "no tier",
"account_basics_tier_basic": "Basic",
"account_basics_tier_provisioned": "Provisioned",
"account_basics_tier_free": "Free",
+23 -12
View File
@@ -269,22 +269,23 @@ const AccountType = () => {
}
};
// The account type is a base label ("Admin", "Basic", "Free", or the tier name) plus an optional
// qualifier chip (admin tier status, or the billing interval).
let accountType;
let qualifierChip;
if (account.role === Role.ADMIN) {
const tierSuffix = account.tier
? t("account_basics_tier_admin_suffix_with_tier", {
tier: account.tier.name,
})
accountType = t("account_basics_tier_admin");
qualifierChip = account.tier
? t("account_basics_tier_admin_suffix_with_tier", { tier: account.tier.name })
: t("account_basics_tier_admin_suffix_no_tier");
accountType = `${t("account_basics_tier_admin")} ${tierSuffix}`;
} else if (!account.tier) {
accountType = config.enable_payments ? t("account_basics_tier_free") : t("account_basics_tier_basic");
} else {
accountType = account.tier.name;
if (account.billing?.interval === SubscriptionInterval.MONTH) {
accountType += ` (${t("account_basics_tier_interval_monthly")})`;
qualifierChip = t("account_basics_tier_interval_monthly");
} else if (account.billing?.interval === SubscriptionInterval.YEAR) {
accountType += ` (${t("account_basics_tier_interval_yearly")})`;
qualifierChip = t("account_basics_tier_interval_yearly");
}
}
@@ -296,6 +297,7 @@ const AccountType = () => {
>
<div>
{accountType}
{qualifierChip && <Chip size="small" label={qualifierChip} sx={{ ml: 1 }} />}
{account.provisioned && <Chip size="small" label={t("account_basics_tier_provisioned")} sx={{ ml: 1 }} />}
{account.billing?.paid_until && !account.billing?.cancel_at && (
<Tooltip
@@ -449,9 +451,13 @@ const Emails = () => {
const verifiedEmails = emails.filter((e) => !e.pending);
const pendingEmails = emails.filter((e) => e.pending);
const primaryEmail = verifiedEmails.find((e) => e.primary)?.address ?? "";
// Provisioned users get their password from the server config and cannot reset it, so they don't
// get the "no recovery email" nudge (the Add-email dialog explains the recovery-email limitation).
const showNoRecoveryWarning = config.enable_reset_password && primaryEmail === "" && !account?.provisioned;
// Recovery nudges (skipped for provisioned users -- they can't reset, and the Add-email dialog
// explains the limitation): prompt for a first email when there are none, or for a primary when
// there are emails but none is primary.
const recoveryRelevant = config.enable_reset_password && !account?.provisioned;
const hasNoEmails = verifiedEmails.length === 0 && pendingEmails.length === 0;
const showNoEmailWarning = recoveryRelevant && hasNoEmails;
const showNoPrimaryWarning = recoveryRelevant && !hasNoEmails && primaryEmail === "";
return (
<Pref labelId={labelId} alignTop title={t("account_basics_emails_title")} description={t("account_basics_emails_description")}>
@@ -468,7 +474,7 @@ const Emails = () => {
variant="outlined"
onClick={(ev) => openMenu(ev, email)}
onDelete={() => handleDelete(email.address)}
sx={email.primary ? { "& .MuiChip-icon": { color: "primary.main" } } : undefined}
sx={email.primary ? { "& .MuiChip-icon": { color: "#fbc02d" } } : undefined}
/>
))}
{pendingEmails.map((email) => (
@@ -491,11 +497,16 @@ const Emails = () => {
<IconButton onClick={handleDialogOpen} aria-label={t("account_basics_emails_dialog_title")}>
<AddIcon />
</IconButton>
{showNoRecoveryWarning && (
{showNoEmailWarning && (
<Alert severity="warning" sx={{ mt: 1 }}>
{t("account_basics_emails_no_recovery_warning")}
</Alert>
)}
{showNoPrimaryWarning && (
<Alert severity="warning" sx={{ mt: 1 }}>
{t("account_basics_emails_no_primary_warning")}
</Alert>
)}
</div>
<Menu anchorEl={menuAnchor} open={Boolean(menuAnchor)} onClose={closeMenu}>
<MenuItem onClick={() => runMenuAction(handleCopy)}>
+2 -2
View File
@@ -49,7 +49,7 @@ const EmailVerify = () => {
{status === "success" && (
<>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<CheckCircleOutlineIcon color="success" sx={{ fontSize: 32 }} />
<CheckCircleOutlineIcon color="success" sx={{ fontSize: 28 }} />
<Typography sx={{ typography: "h6" }}>{t("email_verify_success_title")}</Typography>
</Box>
<Typography sx={{ mt: 1, textAlign: "center" }}>{t("email_verify_success_description")}</Typography>
@@ -61,7 +61,7 @@ const EmailVerify = () => {
{status === "error" && (
<>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<ErrorOutlineIcon color="error" sx={{ fontSize: 32 }} />
<ErrorOutlineIcon color="error" sx={{ fontSize: 28 }} />
<Typography sx={{ typography: "h6" }}>{t("email_verify_error_title")}</Typography>
</Box>
<Typography sx={{ mt: 1, textAlign: "center" }}>{t("email_verify_error_description")}</Typography>
+1 -1
View File
@@ -51,7 +51,7 @@ const PasswordReset = () => {
return (
<AvatarBox>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<CheckCircleOutlineIcon color="success" sx={{ fontSize: 32 }} />
<CheckCircleOutlineIcon color="success" sx={{ fontSize: 28 }} />
<Typography sx={{ typography: "h6" }}>{t("reset_password_success_title")}</Typography>
</Box>
<Typography sx={{ mt: 1, textAlign: "center" }}>{t("reset_password_success_description")}</Typography>
+1 -1
View File
@@ -48,7 +48,7 @@ const ResetPassword = () => {
return (
<AvatarBox>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<CheckCircleOutlineIcon color="success" sx={{ fontSize: 32 }} />
<CheckCircleOutlineIcon color="success" sx={{ fontSize: 28 }} />
<Typography sx={{ typography: "h6" }}>{t("reset_password_sent_title")}</Typography>
</Box>
<Typography sx={{ mt: 1, textAlign: "center" }}>{t("reset_password_sent_description")}</Typography>