The four account panes now share a fixed frame, chart profiles open as list then detail, and membership lives in the homepage dialog instead of a separate page. Co-authored-by: Cursor <cursoragent@cursor.com>
76 lines
3.0 KiB
TypeScript
76 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import { UserAvatar } from "@/components/user-avatar";
|
|
import { beamAvatarPalettes, type BeamAvatarPatch } from "@/lib/beam-avatar";
|
|
import type { Account, Profile } from "@/lib/home-types";
|
|
|
|
export type ProfilePanelProps = {
|
|
readonly account: Account;
|
|
readonly profile: Profile;
|
|
readonly avatarSaving: boolean;
|
|
readonly avatarNotice: string;
|
|
readonly accountError: string;
|
|
readonly persistAvatar: (patch: BeamAvatarPatch) => void;
|
|
};
|
|
|
|
export function ProfilePanel({
|
|
account,
|
|
profile,
|
|
avatarSaving,
|
|
avatarNotice,
|
|
accountError,
|
|
persistAvatar,
|
|
}: ProfilePanelProps) {
|
|
return (
|
|
<>
|
|
{accountError && <p className="form-error" role="alert">{accountError}</p>}
|
|
{account.avatar && (
|
|
<section className="sheet-section avatar-section" aria-labelledby="avatar-section-title">
|
|
<div className="section-heading">
|
|
<b id="avatar-section-title">头像</b>
|
|
<small>Beam 形象由随机种子生成,刷新和换设备后保持一致</small>
|
|
</div>
|
|
<div className="avatar-editor">
|
|
<UserAvatar avatar={account.avatar} size={48} label="当前头像预览" />
|
|
<div className="avatar-editor-controls">
|
|
<div className="avatar-palette-list" role="radiogroup" aria-label="头像配色">
|
|
{beamAvatarPalettes.map((palette, index) => (
|
|
<button
|
|
key={palette.name}
|
|
className="avatar-palette"
|
|
type="button"
|
|
role="radio"
|
|
aria-checked={account.avatar?.palette === index}
|
|
aria-label={palette.name}
|
|
title={palette.name}
|
|
disabled={avatarSaving}
|
|
onClick={() => void persistAvatar({ action: "set_palette", palette: index })}
|
|
>
|
|
{palette.colors.map((color) => <span key={color} style={{ backgroundColor: color }} />)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<button
|
|
className="button-secondary avatar-randomize"
|
|
type="button"
|
|
disabled={avatarSaving}
|
|
onClick={() => void persistAvatar({ action: "randomize" })}
|
|
>
|
|
{avatarSaving ? "保存中" : "换一个形象"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{avatarNotice && <p className="form-success avatar-notice" role="status">{avatarNotice}</p>}
|
|
</section>
|
|
)}
|
|
<section className="sheet-section" aria-labelledby="account-info-title">
|
|
<div className="section-heading"><b id="account-info-title">账户信息</b><small>登录与账户识别信息</small></div>
|
|
<dl className="account-info-list">
|
|
<div><dt>昵称</dt><dd>{profile.name.trim() || "尚未设置"}<small>与“我的星盘”名称同步</small></dd></div>
|
|
<div><dt>登录邮箱</dt><dd>{account.user.email || "尚未读取邮箱"}</dd></div>
|
|
</dl>
|
|
</section>
|
|
</>
|
|
);
|
|
}
|