feat(account): show the account its own last sign-in
Settings → Account names the latest sign-in on any device (only the admin's user list did, though the 0.88 notes said otherwise), and the role in the reader's language instead of the raw `user`.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { withActiveAccount } from "../lib/activeAccount";
|
||||
import { qk } from "../lib/queryKeys";
|
||||
import { formatDateTime } from "../lib/format";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Activity, Bell, Check, Loader2, Monitor, RefreshCw, Trash2, User } from "lucide-react";
|
||||
@@ -778,6 +779,38 @@ export function DownloadAuth() {
|
||||
);
|
||||
}
|
||||
|
||||
/** Who is signed in: name, address, role — and the latest sign-in on any device, so one the user does
|
||||
* not recognise is something they can see. */
|
||||
export function AccountHeader({ me }: { me: Me }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Section title={t("settings.account.title")}>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Avatar
|
||||
src={me.avatar_url}
|
||||
fallback={me.display_name ?? me.email}
|
||||
className="w-12 h-12 rounded-full"
|
||||
/>
|
||||
<div className="min-w-0">
|
||||
<div className="font-semibold truncate">{me.display_name ?? me.email.split("@")[0]}</div>
|
||||
<div className="text-xs text-muted truncate">{me.email}</div>
|
||||
{/* Translated, like the sidebar's tooltip: the raw `user` read in English under HU. */}
|
||||
<div className="text-xs text-muted">
|
||||
{t(`nav.role.${me.is_demo ? "demo" : me.role === "admin" ? "admin" : "user"}`)}
|
||||
</div>
|
||||
{!me.is_demo && (
|
||||
<div className="text-xs text-muted" data-testid="account-last-login">
|
||||
{me.last_login_at
|
||||
? t("settings.account.lastLogin", { time: formatDateTime(me.last_login_at) })
|
||||
: t("settings.account.lastLoginNone")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const confirm = useConfirm();
|
||||
@@ -801,22 +834,7 @@ function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Section title={t("settings.account.title")}>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Avatar
|
||||
src={me.avatar_url}
|
||||
fallback={me.display_name ?? me.email}
|
||||
className="w-12 h-12 rounded-full"
|
||||
/>
|
||||
<div className="min-w-0">
|
||||
<div className="font-semibold truncate">
|
||||
{me.display_name ?? me.email.split("@")[0]}
|
||||
</div>
|
||||
<div className="text-xs text-muted truncate">{me.email}</div>
|
||||
<div className="text-xs text-muted capitalize">{me.role}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<AccountHeader me={me} />
|
||||
|
||||
{!me.is_demo && <SignInMethods me={me} />}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user