import type { AppBskyActorDefs } from '@atcute/bluesky';
import { clsx } from 'clsx';
import { useModerationOpts } from '#/state/moderation/moderation-opts';
import { useProfilesQuery } from '#/state/queries/profile';
import { type SessionAccount, useSession } from '#/state/session';
import { accountProfileView } from '#/state/session/account-profile';
import * as css from '#/components/AccountList.css';
import { Text } from '#/components/Text';
import * as ProfileCard from '#/components/web/ProfileCard';
import CheckIcon from '#/icons/central/Checkmark2_round_outlined_radius1_stroke2.svg';
import ChevronIcon from '#/icons/central/ChevronRight_round_outlined_radius1_stroke2.svg';
import PlusIcon from '#/icons/central/PlusLarge_round_outlined_radius1_stroke2.svg';
import { m } from '#/paraglide/messages';
export function AccountList({
onSelectAccount,
onSelectOther,
otherLabel,
pendingDid,
}: {
onSelectAccount: (account: SessionAccount) => void;
onSelectOther?: () => void;
otherLabel?: string;
pendingDid: string | null;
}) {
const { currentAccount, accounts } = useSession();
const { data: profiles } = useProfilesQuery({
dids: accounts.map((acc) => acc.did),
});
return (
// pointer-events locked while an account resumes so a second pick can't race the first.
<div className={css.container} style={pendingDid ? { pointerEvents: 'none' } : undefined}>
{accounts.map((account) => (
<AccountItem
key={account.did}
account={account}
isCurrentAccount={account.did === currentAccount?.did}
isPendingAccount={account.did === pendingDid}
onSelect={onSelectAccount}
profile={profiles?.profiles.find((p) => p.did === account.did)}
/>
))}
{onSelectOther && (
<button
aria-label={m['components.accountList.a11y.signInOther']()}
className={clsx(css.row, css.rowInteractive)}
onClick={onSelectOther}
type="button"
>
<span className={css.addAvatar}>
<PlusIcon className={css.addIcon} />
</span>
<Text className={css.addLabel} size="md" weight="medium">
{otherLabel ?? m['components.accountList.other']()}
</Text>
<ChevronIcon className={css.chevron} />
</button>
)}
</div>
);
}
function AccountItem({
account,
isCurrentAccount,
isPendingAccount,
onSelect,
profile,
}: {
account: SessionAccount;
isCurrentAccount: boolean;
isPendingAccount: boolean;
onSelect: (account: SessionAccount) => void;
profile?: AppBskyActorDefs.ProfileViewDetailed;
}) {
const moderationOpts = useModerationOpts();
if (!moderationOpts) {
return (
<div className={css.row}>
<ProfileCard.AvatarPlaceholder />
<ProfileCard.NameAndHandlePlaceholder />
</div>
);
}
const profileView = profile ?? accountProfileView(account);
// every row is one of the viewer's own accounts, so moderate each as its own viewer. this dialog also
// opens signed out, where an absent viewer would otherwise let `!no-unauthenticated` blank the row out.
const rowModerationOpts = { ...moderationOpts, viewerDid: account.did };
return (
<button
aria-label={
isCurrentAccount
? m['components.accountList.a11y.continueAs']({ handle: account.handle })
: m['components.accountList.a11y.signInAs']({ handle: account.handle })
}
className={clsx(css.row, css.rowInteractive, isPendingAccount && css.rowActive)}
onClick={() => onSelect(account)}
type="button"
>
<ProfileCard.Avatar
disabledPreview
hideLiveBadge
moderationOpts={rowModerationOpts}
profile={profileView}
/>
<ProfileCard.NameAndHandle moderationOpts={rowModerationOpts} profile={profileView} />
{isCurrentAccount ? (
<span className={css.check}>
<CheckIcon className={css.checkIcon} />
</span>
) : (
<ChevronIcon className={css.chevron} />
)}
</button>
);
}