Add nickname to user account settings

Introduces a nickname field to user accounts, updates the backend user interface, and extends the settings UI to allow users to view and edit their nickname. Localization strings and state management have been updated accordingly to support this new field.
This commit is contained in:
Pas 2025-11-17 10:49:20 -07:00
parent 5842af7029
commit 067b6e43bc
7 changed files with 69 additions and 21 deletions

View file

@ -947,6 +947,8 @@
},
"account": {
"accountDetails": {
"nicknameLabel": "Nickname",
"nicknamePlaceholder": "Enter your nickname",
"deviceNameLabel": "Device name",
"deviceNamePlaceholder": "Personal phone",
"editProfile": "Edit",
@ -1144,6 +1146,7 @@
"backendVersion": "Backend version",
"hostname": "Hostname",
"insecure": "Insecure",
"nickname": "Nickname",
"notLoggedIn": "You are not logged in",
"secure": "Secure",
"title": "App stats",

View file

@ -8,9 +8,8 @@ import { ProgressMediaItem } from "@/stores/progress";
export interface UserResponse {
id: string;
namespace: string;
name: string;
roles: string[];
createdAt: string;
nickname: string;
permissions: string[];
profile: {
colorA: string;
colorB: string;
@ -24,6 +23,7 @@ export interface UserEdit {
colorB: string;
icon: string;
};
nickname?: string;
}
export interface BookmarkResponse {

View file

@ -101,6 +101,7 @@ export function useAuthData() {
sessionId: loginResponse.session.id,
deviceName: session.device,
profile: user.profile,
nickname: user.nickname,
seed,
};
setAccount(account);

View file

@ -42,6 +42,7 @@ export function useSettingsState(
appLanguage: string,
subtitleStyling: SubtitleStyling,
deviceName: string,
nickname: string,
proxyUrls: string[] | null,
backendUrl: string | null,
febboxKey: string | null,
@ -110,6 +111,8 @@ export function useSettingsState(
resetDeviceName,
deviceNameChanged,
] = useDerived(deviceName);
const [nicknameState, setNicknameState, resetNickname, nicknameChanged] =
useDerived(nickname);
const [profileState, setProfileState, resetProfile, profileChanged] =
useDerived(profile);
const [
@ -263,6 +266,7 @@ export function useSettingsState(
resetFebboxKey();
resetRealDebridKey();
resetDeviceName();
resetNickname();
resetProfile();
resetEnableThumbnails();
resetEnableAutoplay();
@ -295,6 +299,7 @@ export function useSettingsState(
appLanguageChanged ||
subStylingChanged ||
deviceNameChanged ||
nicknameChanged ||
backendUrlChanged ||
proxyUrlsChanged ||
febboxKeyChanged ||
@ -348,6 +353,11 @@ export function useSettingsState(
set: setDeviceNameState,
changed: deviceNameChanged,
},
nickname: {
state: nicknameState,
set: setNicknameState,
changed: nicknameChanged,
},
proxyUrls: {
state: proxyUrlsState,
set: setProxyUrls,

View file

@ -114,6 +114,8 @@ export function AccountSettings(props: {
account: AccountWithToken;
deviceName: string;
setDeviceName: (s: string) => void;
nickname: string;
setNickname: (s: string) => void;
colorA: string;
setColorA: (s: string) => void;
colorB: string;
@ -136,6 +138,8 @@ export function AccountSettings(props: {
<AccountEditPart
deviceName={props.deviceName}
setDeviceName={props.setDeviceName}
nickname={props.nickname}
setNickname={props.setNickname}
colorA={props.colorA}
setColorA={props.setColorA}
colorB={props.colorB}
@ -481,6 +485,7 @@ export function SettingsPage() {
);
const account = useAuthStore((s) => s.account);
const setAccount = useAuthStore((s) => s.setAccount);
const updateProfile = useAuthStore((s) => s.setAccountProfile);
const updateDeviceName = useAuthStore((s) => s.updateDeviceName);
const decryptedName = useMemo(() => {
@ -513,6 +518,7 @@ export function SettingsPage() {
appLanguage,
subStyling,
decryptedName,
account?.nickname || "",
proxySet,
backendUrlSetting,
febboxKey,
@ -646,6 +652,14 @@ export function SettingsPage() {
});
updateDeviceName(newDeviceName);
}
if (state.nickname.changed) {
await editUser(backendUrl, account, {
nickname: state.nickname.state,
});
// Update the account in the store
const updatedAccount = { ...account, nickname: state.nickname.state };
setAccount(updatedAccount);
}
if (state.profile.changed) {
await editUser(backendUrl, account, {
profile: state.profile.state,
@ -720,6 +734,7 @@ export function SettingsPage() {
setProxySet,
updateDeviceName,
updateProfile,
setAccount,
logout,
setBackendUrl,
setProxyTmdb,
@ -754,6 +769,8 @@ export function SettingsPage() {
account={user.account}
deviceName={state.deviceName.state}
setDeviceName={state.deviceName.set}
nickname={state.nickname.state}
setNickname={state.nickname.set}
colorA={state.profile.state.colorA}
setColorA={(v) => {
state.profile.set((s) =>

View file

@ -13,6 +13,8 @@ import { ProfileEditModal } from "@/pages/parts/settings/ProfileEditModal";
export function AccountEditPart(props: {
deviceName: string;
setDeviceName: (s: string) => void;
nickname: string;
setNickname: (s: string) => void;
colorA: string;
setColorA: (s: string) => void;
colorB: string;
@ -59,24 +61,38 @@ export function AccountEditPart(props: {
/>
</div>
<div>
<div className="space-y-8 max-w-xs">
<AuthInputBox
label={
t("settings.account.accountDetails.deviceNameLabel") ??
undefined
}
placeholder={
t("settings.account.accountDetails.deviceNamePlaceholder") ??
undefined
}
value={props.deviceName}
onChange={(value) => props.setDeviceName(value)}
/>
<div className="flex space-x-3">
<Button className="logout-button" theme="danger" onClick={logout}>
{t("settings.account.accountDetails.logoutButton")}
</Button>
<div className="flex flex-col md:flex-row md:gap-4 gap-4">
<div className="w-full">
<AuthInputBox
label={t("settings.account.accountDetails.nicknameLabel")}
placeholder={t(
"settings.account.accountDetails.nicknamePlaceholder",
)}
value={props.nickname}
onChange={(value) => props.setNickname(value)}
className="w-full"
/>
</div>
<div className="w-full">
<AuthInputBox
label={
t("settings.account.accountDetails.deviceNameLabel") ??
undefined
}
placeholder={
t("settings.account.accountDetails.deviceNamePlaceholder") ??
undefined
}
value={props.deviceName}
onChange={(value) => props.setDeviceName(value)}
className="w-full"
/>
</div>
</div>
<div className="flex space-x-3 mt-4">
<Button className="logout-button" theme="danger" onClick={logout}>
{t("settings.account.accountDetails.logoutButton")}
</Button>
</div>
</div>
</div>

View file

@ -8,6 +8,7 @@ export interface Account {
colorB: string;
icon: string;
};
nickname: string;
}
export type AccountWithToken = Account & {
@ -25,7 +26,7 @@ interface AuthStore {
removeAccount(): void;
setAccount(acc: AccountWithToken): void;
updateDeviceName(deviceName: string): void;
updateAccount(acc: Account): void;
updateAccount(acc: Partial<Account>): void;
setAccountProfile(acc: Account["profile"]): void;
setBackendUrl(url: null | string): void;
setProxySet(urls: null | string[]): void;