0.1.38 gateway
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useQuery } from '../lib/hooks';
|
||||
import { initials, num, presence, recent, when } from '../lib/format';
|
||||
import { Banner, Empty, Loading, Note, PageHead, Tag, Tiles } from '../components/ui';
|
||||
import type { KnownClient } from '../api/types';
|
||||
|
||||
/* A directory, and only a directory. Everything you can *do* to a person lives on their own
|
||||
page: this list used to render a full seventeen-control settings editor for every account
|
||||
at once, which meant the page grew with the household and an operator scrolled past four
|
||||
other people's preferences to reach the one they came for. */
|
||||
|
||||
interface Account {
|
||||
id: string;
|
||||
username: string;
|
||||
lastSeen: string;
|
||||
devices: KnownClient[] | null;
|
||||
recommendations?: { prompted?: boolean; completed?: boolean };
|
||||
}
|
||||
|
||||
interface AccountsResponse {
|
||||
accounts: Account[] | null;
|
||||
}
|
||||
|
||||
export function AccountsPage() {
|
||||
const { data, error, loading } = useQuery<AccountsResponse>('/admin/api/accounts', {
|
||||
pollMs: 60_000,
|
||||
});
|
||||
const accounts = data?.accounts ?? [];
|
||||
const devices = accounts.flatMap((account) => account.devices ?? []);
|
||||
const completed = accounts.filter((account) => account.recommendations?.completed).length;
|
||||
const queued = accounts.filter(
|
||||
(account) => account.recommendations?.prompted && !account.recommendations?.completed,
|
||||
).length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title="Memby users" intro="Who uses Memby, and the devices they are signed in on." />
|
||||
<Banner message={error} />
|
||||
|
||||
<Note tone="info">
|
||||
This is the Memby user list, not the Emby user directory. A person appears here only after
|
||||
signing in to the Memby app. Removing access signs their Memby devices out and does not delete
|
||||
or change their Emby account.
|
||||
</Note>
|
||||
|
||||
{loading ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<>
|
||||
<Tiles
|
||||
tiles={[
|
||||
{ label: 'Memby users', value: num(accounts.length), icon: 'people', tone: 'note' },
|
||||
{ label: 'signed-in devices', value: num(devices.length), icon: 'tv', tone: 'info' },
|
||||
{
|
||||
label: 'active in the last quarter hour',
|
||||
value: num(devices.filter((device) => recent(device.lastSeen)).length),
|
||||
icon: 'pulse',
|
||||
tone: 'ok',
|
||||
},
|
||||
{ label: 'recommendation setups completed', value: num(completed), icon: 'check', tone: 'ok' },
|
||||
{ label: 'setup prompts queued', value: num(queued), icon: 'sparkle', tone: 'note' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<section className="card flush">
|
||||
{accounts.length === 0 ? (
|
||||
<Empty>
|
||||
No one has signed in to Memby yet. Emby-only accounts are intentionally not listed here.
|
||||
</Empty>
|
||||
) : (
|
||||
accounts.map((account) => {
|
||||
const list = account.devices ?? [];
|
||||
const active = list.filter((device) => recent(device.lastSeen)).length;
|
||||
const state = account.recommendations?.completed
|
||||
? { label: 'personalised', tone: 'ok' as const }
|
||||
: account.recommendations?.prompted
|
||||
? { label: 'prompt queued', tone: 'warn' as const }
|
||||
: { label: 'not invited', tone: undefined };
|
||||
const seen = presence(account.lastSeen);
|
||||
return (
|
||||
<Link className="list-row" key={account.id} to={`/admin/accounts/${encodeURIComponent(account.id)}`}>
|
||||
<span className="list-main">
|
||||
<span className="avatar">{initials(account.username)}</span>
|
||||
<span>
|
||||
<span className="list-title">
|
||||
{account.username || 'Unnamed user'}
|
||||
<span className="dot-state" data-tone={seen.tone} title={seen.label} />
|
||||
</span>
|
||||
<span className="list-meta">
|
||||
{num(list.length)} device{list.length === 1 ? '' : 's'}
|
||||
{active ? ` · ${active} active now` : ''} · last seen {when(account.lastSeen)}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span className="list-actions">
|
||||
<Tag tone={state.tone}>{state.label}</Tag>
|
||||
<span className="crumb">Manage</span>
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/** The account page reads the same list to find the person it is about. */
|
||||
export type { Account, AccountsResponse };
|
||||
Reference in New Issue
Block a user