Dashboard greeting names who is signed in
It greets the account by its full name where it has one, and otherwise by the name it signs in with. The line beneath says who is signed in. The account is looked up by its local part and matched on the whole address, so a same-named account on another domain can't answer. The username is kept from the JMAP session and cleared on sign-out.
This commit is contained in:
@@ -4,8 +4,10 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '@/stores/authStore';
|
||||
import { getAccountId, jmapQueryAndGet } from '@/services/jmap/client';
|
||||
import inbuxaMark from '@/assets/inbuxa-mark.png';
|
||||
|
||||
function partOfDay(hour: number): 'morning' | 'afternoon' | 'evening' {
|
||||
@@ -14,13 +16,40 @@ function partOfDay(hour: number): 'morning' | 'afternoon' | 'evening' {
|
||||
return 'evening';
|
||||
}
|
||||
|
||||
/** The dashboard's hello: whose server this is, and a nod to the time of day. */
|
||||
/**
|
||||
* The name to greet: the account's full name where it has one, otherwise the
|
||||
* name it signs in with. Looked up by the local part and matched on the whole
|
||||
* address, so an account of the same name on another domain can't answer.
|
||||
*/
|
||||
function useFullName(username: string | null): string | null {
|
||||
const [fullName, setFullName] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (!username) return;
|
||||
let live = true;
|
||||
const localPart = username.split('@')[0];
|
||||
jmapQueryAndGet('x:Account', getAccountId('x:Account'), { filter: { name: localPart } }, ['description', 'emailAddress'])
|
||||
.then((responses) => {
|
||||
const list = (responses[1]?.[1] as { list?: { description?: string | null; emailAddress?: string }[] }).list ?? [];
|
||||
const own = list.find((a) => a.emailAddress?.toLowerCase() === username.toLowerCase());
|
||||
const name = own?.description?.trim();
|
||||
if (live && name) setFullName(name);
|
||||
})
|
||||
.catch(() => {
|
||||
/* the sign-in name stands */
|
||||
});
|
||||
return () => {
|
||||
live = false;
|
||||
};
|
||||
}, [username]);
|
||||
return fullName;
|
||||
}
|
||||
|
||||
/** The dashboard's hello: to whoever is signed in, with a nod to the time of day. */
|
||||
export function Greeting() {
|
||||
const { t } = useTranslation();
|
||||
const accounts = useAuthStore((s) => s.accounts);
|
||||
const activeAccountId = useAuthStore((s) => s.activeAccountId);
|
||||
const full = (activeAccountId && accounts[activeAccountId]?.name) || '';
|
||||
const name = full.split('@')[0];
|
||||
const username = useAuthStore((s) => s.username);
|
||||
const fullName = useFullName(username);
|
||||
const name = fullName ?? username?.split('@')[0] ?? '';
|
||||
const part = partOfDay(new Date().getHours());
|
||||
const hello =
|
||||
part === 'morning'
|
||||
@@ -36,7 +65,13 @@ export function Greeting() {
|
||||
{hello}
|
||||
{name && `, ${name}`}
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="truncate text-sm text-muted-foreground">
|
||||
{username && (
|
||||
<>
|
||||
{t('greeting.signedInAs', 'Signed in as {{username}}', { username })}
|
||||
{' · '}
|
||||
</>
|
||||
)}
|
||||
{t('greeting.subtitle', "Here's how your mail server is doing.")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user