mirror of
https://github.com/p-stream/p-stream.git
synced 2026-05-11 06:20:53 +00:00
Added error handling for device name decryption in Avatar, LinksDropdown, Settings, and DeviceListPart components. If decryption fails, a fallback 'Unknown device' message is shown using a new translation key. This improves user experience by preventing crashes or blank fields when device name decryption fails.
107 lines
2.8 KiB
TypeScript
107 lines
2.8 KiB
TypeScript
import classNames from "classnames";
|
|
import { useMemo } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { base64ToBuffer, decryptData } from "@/backend/accounts/crypto";
|
|
import { Icon, Icons } from "@/components/Icon";
|
|
import { UserIcon } from "@/components/UserIcon";
|
|
import { AccountProfile } from "@/pages/parts/auth/AccountCreatePart";
|
|
import { useAuthStore } from "@/stores/auth";
|
|
|
|
export interface AvatarProps {
|
|
profile: AccountProfile["profile"];
|
|
sizeClass?: string;
|
|
iconClass?: string;
|
|
bottom?: React.ReactNode;
|
|
}
|
|
|
|
export function Avatar(props: AvatarProps) {
|
|
return (
|
|
<div className="relative inline-block">
|
|
<div
|
|
className={classNames(
|
|
props.sizeClass,
|
|
"rounded-full overflow-hidden flex items-center justify-center text-white",
|
|
)}
|
|
style={{
|
|
background: `linear-gradient(to bottom right, ${props.profile.colorA}, ${props.profile.colorB})`,
|
|
}}
|
|
>
|
|
<UserIcon
|
|
className={props.iconClass}
|
|
icon={props.profile.icon as any}
|
|
/>
|
|
</div>
|
|
{props.bottom ? (
|
|
<div className="absolute bottom-0 left-1/2 transform translate-y-1/2 -translate-x-1/2">
|
|
{props.bottom}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function UserAvatar(props: {
|
|
sizeClass?: string;
|
|
iconClass?: string;
|
|
bottom?: React.ReactNode;
|
|
withName?: boolean;
|
|
}) {
|
|
const auth = useAuthStore();
|
|
|
|
const bufferSeed = useMemo(
|
|
() =>
|
|
auth.account && auth.account.seed
|
|
? base64ToBuffer(auth.account.seed)
|
|
: null,
|
|
[auth],
|
|
);
|
|
const { t } = useTranslation();
|
|
|
|
if (!auth.account || auth.account === null) return null;
|
|
|
|
const deviceName = bufferSeed
|
|
? (() => {
|
|
try {
|
|
return decryptData(auth.account.deviceName, bufferSeed);
|
|
} catch (error) {
|
|
console.warn(
|
|
"Failed to decrypt device name in Avatar, using fallback:",
|
|
error,
|
|
);
|
|
return t("settings.account.devices.unknownDevice");
|
|
}
|
|
})()
|
|
: "...";
|
|
|
|
return (
|
|
<>
|
|
<Avatar
|
|
profile={auth.account.profile}
|
|
sizeClass={
|
|
props.sizeClass ?? "w-[1.5rem] h-[1.5rem] ssm:w-[2rem] ssm:h-[2rem]"
|
|
}
|
|
iconClass={props.iconClass}
|
|
bottom={props.bottom}
|
|
/>
|
|
{props.withName && bufferSeed ? (
|
|
<span className="hidden md:inline-block">
|
|
{deviceName.length >= 20
|
|
? `${deviceName.slice(0, 20 - 1)}…`
|
|
: deviceName}
|
|
</span>
|
|
) : null}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function NoUserAvatar(props: { iconClass?: string }) {
|
|
return (
|
|
<div className="relative inline-block p-1 text-type-dimmed">
|
|
<Icon
|
|
className={props.iconClass ?? "text-base ssm:text-xl"}
|
|
icon={Icons.MENU}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|