feat(web): профиль с аватаром, панель в углу, настройки сервера, скрытые ID
Правки пользователя: - в «Профиль → Основной» сверху квадратный аватар слева, справа от него настройки имени, «О себе», состояния и эмодзи во всю высоту; смена аватара — клик по нему, форматы (JPEG, PNG, WebP, GIF до 8 МБ) — в подсказке; - убраны дубли: часовой пояс только в «Язык и регион», статус — только в мини-профиле панели; - панель пользователя переехала в нижний левый угол под сайдбар, подсказка о выходе из сервера — на её прежнее место; панель ужата под 240 px; - шестерёнка сервера открывает новый пункт настроек «Сервер» (список серверов, общее, участники, роли, опасная зона), а «Профиль → Сервер» оставлен только для ника и «Применить ко всем серверам»; - окно настроек получило статический размер 70% рабочей области со скроллом; - ID пользователей скрыты везде, кроме раздела «Инстанс» у администратора; - список друзей свёрнут под кнопкой «Друзья (N)» и раскрывается снизу вверх; - 249 тестов Vitest (было 239), check/lint/test/build зелёные.
This commit is contained in:
+35
-1
@@ -12,6 +12,8 @@ export const settingsSections = [
|
||||
'notifications',
|
||||
'voice',
|
||||
'language',
|
||||
/** Администрирование выбранного сервера. */
|
||||
'server',
|
||||
'instance',
|
||||
] as const;
|
||||
export type SettingsSection = (typeof settingsSections)[number];
|
||||
@@ -24,7 +26,7 @@ export type ProfileTab = (typeof profileTabs)[number];
|
||||
export interface SettingsTarget {
|
||||
section?: SettingsSection;
|
||||
tab?: ProfileTab;
|
||||
/** Сервер для вкладки «Профиль → Сервер». */
|
||||
/** Сервер для пункта «Сервер» и вкладки «Профиль → Сервер». */
|
||||
guildId?: string | null;
|
||||
}
|
||||
|
||||
@@ -44,13 +46,20 @@ interface UiState {
|
||||
settingsOpen: boolean;
|
||||
settingsSection: SettingsSection;
|
||||
settingsProfileTab: ProfileTab;
|
||||
/**
|
||||
* Развёрнут ли список друзей во вкладке «Друзья». Свёрнут по умолчанию, а
|
||||
* выбор пользователя переживает перезагрузку страницы.
|
||||
*/
|
||||
friendsListOpen: boolean;
|
||||
openSettings: (target?: SettingsTarget) => void;
|
||||
closeSettings: () => void;
|
||||
setSettingsSection: (section: SettingsSection) => void;
|
||||
setSettingsProfileTab: (tab: ProfileTab) => void;
|
||||
toggleFriendsList: () => void;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'glchat.theme';
|
||||
const FRIENDS_KEY = 'glchat.friends.expanded';
|
||||
|
||||
function initialTheme(): Theme {
|
||||
if (typeof window === 'undefined') {
|
||||
@@ -63,6 +72,14 @@ function initialTheme(): Theme {
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
/** Список друзей свёрнут, пока пользователь сам его не раскрыл. */
|
||||
function initialFriendsListOpen(): boolean {
|
||||
if (typeof window === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
return window.localStorage.getItem(FRIENDS_KEY) === 'true';
|
||||
}
|
||||
|
||||
export function applyTheme(theme: Theme): void {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
@@ -71,6 +88,8 @@ export function applyTheme(theme: Theme): void {
|
||||
}
|
||||
|
||||
export const useUiStore = create<UiState>((set, get) => ({
|
||||
friendsListOpen: initialFriendsListOpen(),
|
||||
|
||||
theme: initialTheme(),
|
||||
setTheme: (theme) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -101,6 +120,10 @@ export const useUiStore = create<UiState>((set, get) => ({
|
||||
settingsOpen: true,
|
||||
...(target?.section === undefined ? {} : { settingsSection: target.section }),
|
||||
...(target?.tab === undefined ? {} : { settingsProfileTab: target.tab }),
|
||||
// Профиль на сервере живёт во вкладке «Сервер» пункта «Профиль».
|
||||
...(target?.section === 'profile' && target.tab === 'server'
|
||||
? { settingsProfileTab: 'server' as ProfileTab }
|
||||
: {}),
|
||||
});
|
||||
},
|
||||
|
||||
@@ -109,4 +132,15 @@ export const useUiStore = create<UiState>((set, get) => ({
|
||||
setSettingsSection: (section) => set({ settingsSection: section }),
|
||||
|
||||
setSettingsProfileTab: (tab) => set({ settingsProfileTab: tab }),
|
||||
|
||||
toggleFriendsList: () => {
|
||||
const next = !get().friendsListOpen;
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem(FRIENDS_KEY, String(next));
|
||||
}
|
||||
set({ friendsListOpen: next });
|
||||
},
|
||||
}));
|
||||
|
||||
/** Ключ localStorage: нужен тестам и сбросу состояния. */
|
||||
export const FRIENDS_LIST_STORAGE_KEY = FRIENDS_KEY;
|
||||
|
||||
Reference in New Issue
Block a user