Implement logic

This commit is contained in:
2025-08-30 12:08:25 +03:00
Unverified
parent 138e0420cf
commit ebd075136b
14 changed files with 585 additions and 43 deletions
@@ -1,7 +1,15 @@
import { useDialog } from "../../contexts/DialogContext";
export function BottomAppBar() {
const { openSettings } = useDialog();
const handleSettingsClick = () => {
openSettings();
};
return (
<mdui-bottom-app-bar>
<mdui-button-icon icon="settings--filled" id="settings-open"></mdui-button-icon>
<mdui-button-icon icon="settings--filled" id="settings-open" onClick={handleSettingsClick}></mdui-button-icon>
<mdui-button-icon icon="group_add--filled"></mdui-button-icon>
<div style={{ flexGrow: 1 }}></div>
<mdui-fab icon="edit--filled"></mdui-fab>
@@ -1,11 +1,18 @@
import { PRODUCT_NAME } from "../../../core/config";
import { useDialog } from "../../contexts/DialogContext";
export function ChatHeader() {
const { openProfile } = useDialog();
const handleProfileClick = () => {
openProfile();
};
return (
<header className="chat-header-left">
<div className="product-name">{PRODUCT_NAME}</div>
<div className="profile">
<a href="#" id="profile-open">
<a href="#" id="profile-open" onClick={handleProfileClick}>
<img src="./src/resources/images/default-avatar.png" alt="" id="preview1" />
</a>
</div>
@@ -1,9 +1,31 @@
import { useState } from "react";
import { useChat } from "../../hooks/useChat";
export function ChatInputWrapper() {
const [message, setMessage] = useState("");
const { sendMessage } = useChat();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (message.trim()) {
await sendMessage(message);
setMessage("");
}
};
return (
<div className="chat-input-wrapper">
<div className="chat-input">
<form className="input-group" id="message-form">
<input type="text" className="message-input" id="message-input" placeholder="Напишите сообщение..." autoComplete="off" />
<form className="input-group" id="message-form" onSubmit={handleSubmit}>
<input
type="text"
className="message-input"
id="message-input"
placeholder="Напишите сообщение..."
autoComplete="off"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type="submit" className="send-btn">
<span className="material-symbols filled">send</span>
</button>
@@ -1,16 +1,27 @@
import { useChat } from "../../hooks/useChat";
import { useState } from "react";
export function ChatMainHeader() {
const { currentChat } = useChat();
const [isCollapsed, setIsCollapsed] = useState(false);
const handleCollapse = () => {
setIsCollapsed(!isCollapsed);
// TODO: Implement chat collapse animation
};
return (
<div className="chat-header">
<img src="./src/resources/images/default-avatar.png" alt="Avatar" className="chat-header-avatar" />
<div className="chat-header-info">
<div className="info-chat">
<h4 id="chat-name">Общий чат</h4>
<h4 id="chat-name">{currentChat}</h4>
<p>
<span className="online-status"></span>
Онлайн
</p>
</div>
<a href="#" id="hide-chat">Свернуть чат</a>
<a href="#" id="hide-chat" onClick={handleCollapse}>Свернуть чат</a>
</div>
</div>
);
@@ -1,6 +1,34 @@
import { useChat } from "../../hooks/useChat";
import { Message } from "./Message";
import { useAppState } from "../../state";
import type { Message as MessageType } from "../../../core/types";
export function ChatMessages() {
const { messages } = useChat();
const { user } = useAppState();
const handleProfileClick = (username: string) => {
// TODO: Show user profile dialog
console.log("Show profile for:", username);
};
const handleContextMenu = (e: React.MouseEvent, message: MessageType) => {
e.preventDefault();
// TODO: Show context menu
console.log("Show context menu for message:", message.id);
};
return (
<div className="chat-messages" id="chat-messages">
{messages.map((message) => (
<Message
key={message.id}
message={message}
isAuthor={message.username === user.currentUser?.username}
onProfileClick={handleProfileClick}
onContextMenu={handleContextMenu}
/>
))}
</div>
);
}
+35 -7
View File
@@ -1,18 +1,46 @@
import { useChat } from "../../hooks/useChat";
export function ChatTabs() {
const { activeTab, setActiveTab, setCurrentChat } = useChat();
const handleChatClick = (chatName: string) => {
setCurrentChat(chatName);
};
return (
<div className="chat-tabs">
<mdui-tabs value="chats" full-width>
<mdui-tab value="chats">Чаты</mdui-tab>
<mdui-tab value="channels">Каналы</mdui-tab>
<mdui-tab value="contacts">Контакты</mdui-tab>
<mdui-tab value="dms">ЛС</mdui-tab>
<mdui-tabs value={activeTab} full-width onChange={(e: any) => setActiveTab(e.detail.value)}>
<mdui-tab value="chats">
Чаты
</mdui-tab>
<mdui-tab value="channels">
Каналы
</mdui-tab>
<mdui-tab value="contacts">
Контакты
</mdui-tab>
<mdui-tab value="dms">
ЛС
</mdui-tab>
<mdui-tab-panel slot="panel" value="chats">
<mdui-list>
<mdui-list-item headline="Общий чат" description="Вы: Последнее сообщение" id="chat-list-chat-1">
<mdui-list-item
headline="Общий чат"
description="Вы: Последнее сообщение"
id="chat-list-chat-1"
onClick={() => handleChatClick("Общий чат")}
style={{ cursor: "pointer" }}
>
<img src="./src/resources/images/default-avatar.png" alt="" slot="icon" />
</mdui-list-item>
<mdui-list-item headline="Общий чат 2" description="Вы: Последнее сообщение" id="chat-list-chat-2">
<mdui-list-item
headline="Общий чат 2"
description="Вы: Последнее сообщение"
id="chat-list-chat-2"
onClick={() => handleChatClick("Общий чат 2")}
style={{ cursor: "pointer" }}
>
<img src="./src/resources/images/default-avatar.png" alt="" slot="icon" />
</mdui-list-item>
</mdui-list>
@@ -0,0 +1,70 @@
import { formatTime } from "../../../utils/utils";
import type { Message as MessageType } from "../../../core/types";
import defaultAvatar from "../../../resources/images/default-avatar.png";
interface MessageProps {
message: MessageType;
isAuthor: boolean;
onProfileClick: (username: string) => void;
onContextMenu: (e: React.MouseEvent, message: MessageType) => void;
}
export function Message({ message, isAuthor, onProfileClick, onContextMenu }: MessageProps) {
return (
<div
className={`message ${isAuthor ? "sent" : "received"}`}
data-id={message.id}
onContextMenu={(e) => onContextMenu(e, message)}
>
<div className="message-inner">
{/* Add profile picture for received messages */}
{!isAuthor && (
<div className="message-profile-pic">
<img
src={message.profile_picture || defaultAvatar}
alt={message.username}
onClick={() => onProfileClick(message.username)}
style={{ cursor: "pointer" }}
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = defaultAvatar;
}}
/>
</div>
)}
{!isAuthor && (
<div
className="message-username"
onClick={() => onProfileClick(message.username)}
style={{ cursor: "pointer" }}> {/* TODO extract to SCSS */}
{message.username}
</div>
)}
{/* Add reply preview if this is a reply */}
{message.reply_to && (
<div className="message-reply">
<div className="reply-content">
<span className="reply-username">{message.reply_to.username}</span>
<span className="reply-text">{message.reply_to.content}</span>
</div>
</div>
)}
<div className="message-content">
{message.content}
</div>
<div className="message-time">
{formatTime(message.timestamp)}
{message.is_edited ? " (edited)" : undefined}
{isAuthor && message.is_read ? (
<span className="material-symbols outlined"></span>
) : undefined}
</div>
</div>
</div>
);
}
@@ -1,6 +1,22 @@
import { useState } from "react";
import { useAppState } from "../../state";
import { useDialog } from "../../contexts/DialogContext";
export function ProfileDialog() {
const [username, setUsername] = useState("user123");
const [description, setDescription] = useState("");
const { user } = useAppState();
const { isProfileOpen, closeProfile } = useDialog();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// TODO: Implement profile update logic
console.log("Profile update:", { username, description });
closeProfile();
};
return (
<mdui-dialog id="profile-dialog" close-on-overlay-click close-on-esc>
<mdui-dialog id="profile-dialog" close-on-overlay-click close-on-esc open={isProfileOpen}>
<div className="content">
<div className="header-top">
<div className="profile-picture-container">
@@ -8,21 +24,29 @@ export function ProfileDialog() {
<mdui-button-icon icon="camera_alt--filled" id="upload-pfp-btn" className="upload-overlay" variant="filled"></mdui-button-icon>
<input type="file" id="pfp-file-input" accept="image/*" style={{ display: "none" }} />
</div>
<mdui-text-field id="username-field" label="Имя пользователя" variant="outlined" value="user123" autocomplete="username"></mdui-text-field>
<mdui-text-field
id="username-field"
label="Имя пользователя"
variant="outlined"
value={username}
onChange={(e: any) => setUsername(e.target.value)}
autocomplete="username">
</mdui-text-field>
</div>
<form id="profile-form">
<form id="profile-form" onSubmit={handleSubmit}>
<mdui-text-field
id="description-field"
label="О себе"
variant="outlined"
// multiline={true}
rows={3}
value={description}
onChange={(e: any) => setDescription(e.target.value)}
placeholder="Расскажите о себе..."
autocomplete="none"></mdui-text-field>
autocomplete="none">
</mdui-text-field>
<div className="dialog-actions">
<mdui-button type="submit" id="profile-submit">Сохранить изменения</mdui-button>
<mdui-button id="profile-dialog-close" variant="outlined">Закрыть</mdui-button>
<mdui-button id="profile-dialog-close" variant="outlined" onClick={closeProfile}>Закрыть</mdui-button>
</div>
</form>
</div>
@@ -1,24 +1,91 @@
import { useState } from "react";
import { useDialog } from "../../contexts/DialogContext";
import { PRODUCT_NAME } from "../../../core/config";
export function SettingsDialog() {
const { isSettingsOpen, closeSettings } = useDialog();
const [activePanel, setActivePanel] = useState("notifications-settings");
const handlePanelChange = (panelId: string) => {
setActivePanel(panelId);
};
return (
<mdui-dialog id="settings-dialog" close-on-overlay-click close-on-esc fullscreen>
<mdui-dialog id="settings-dialog" close-on-overlay-click close-on-esc fullscreen open={isSettingsOpen}>
<div className="fullscreen-wrapper">
<div id="settings-dialog-inner">
<div className="header">
<mdui-button-icon icon="close" id="settings-close"></mdui-button-icon>
<mdui-button-icon icon="close" id="settings-close" onClick={closeSettings}></mdui-button-icon>
<mdui-top-app-bar-title>Настройки</mdui-top-app-bar-title>
</div>
<div id="settings-menu">
<mdui-list>
<mdui-list-item icon="notifications--filled" rounded active>Уведомления</mdui-list-item>
<mdui-list-item icon="palette--filled" rounded>Внешний вид</mdui-list-item>
<mdui-list-item icon="security--filled" rounded>Безопасность</mdui-list-item>
<mdui-list-item icon="language--filled" rounded>Язык</mdui-list-item>
<mdui-list-item icon="storage--filled" rounded>Хранилище</mdui-list-item>
<mdui-list-item icon="help--filled" rounded>Помощь</mdui-list-item>
<mdui-list-item icon="info--filled" rounded>О приложении</mdui-list-item>
<mdui-list-item
icon="notifications--filled"
rounded
active={activePanel === "notifications-settings"}
onClick={() => handlePanelChange("notifications-settings")}
style={{ cursor: "pointer" }}
>
Уведомления
</mdui-list-item>
<mdui-list-item
icon="palette--filled"
rounded
active={activePanel === "appearance-settings"}
onClick={() => handlePanelChange("appearance-settings")}
style={{ cursor: "pointer" }}
>
Внешний вид
</mdui-list-item>
<mdui-list-item
icon="security--filled"
rounded
active={activePanel === "security-settings"}
onClick={() => handlePanelChange("security-settings")}
style={{ cursor: "pointer" }}
>
Безопасность
</mdui-list-item>
<mdui-list-item
icon="language--filled"
rounded
active={activePanel === "language-settings"}
onClick={() => handlePanelChange("language-settings")}
style={{ cursor: "pointer" }}
>
Язык
</mdui-list-item>
<mdui-list-item
icon="storage--filled"
rounded
active={activePanel === "storage-settings"}
onClick={() => handlePanelChange("storage-settings")}
style={{ cursor: "pointer" }}
>
Хранилище
</mdui-list-item>
<mdui-list-item
icon="help--filled"
rounded
active={activePanel === "help-settings"}
onClick={() => handlePanelChange("help-settings")}
style={{ cursor: "pointer" }}
>
Помощь
</mdui-list-item>
<mdui-list-item
icon="info--filled"
rounded
active={activePanel === "about-settings"}
onClick={() => handlePanelChange("about-settings")}
style={{ cursor: "pointer" }}
>
О приложении
</mdui-list-item>
</mdui-list>
<div className="screen">
<div id="notifications-settings" className="settings-panel active">
<div id="notifications-settings" className={`settings-panel ${activePanel === "notifications-settings" ? "active" : ""}`}>
<h3>Уведомления</h3>
<mdui-switch checked>Новые сообщения</mdui-switch>
<mdui-switch checked>Звуковые уведомления</mdui-switch>
@@ -26,7 +93,7 @@ export function SettingsDialog() {
<mdui-switch checked>Email уведомления</mdui-switch>
</div>
<div id="appearance-settings" className="settings-panel">
<div id="appearance-settings" className={`settings-panel ${activePanel === "appearance-settings" ? "active" : ""}`}>
<h3>Внешний вид</h3>
<mdui-select label="Тема" variant="outlined">
<mdui-menu-item value="dark">Тёмная</mdui-menu-item>
@@ -40,14 +107,14 @@ export function SettingsDialog() {
</mdui-select>
</div>
<div id="security-settings" className="settings-panel">
<div id="security-settings" className={`settings-panel ${activePanel === "security-settings" ? "active" : ""}`}>
<h3>Безопасность</h3>
<mdui-button variant="outlined">Изменить пароль</mdui-button>
<mdui-button variant="outlined">Двухфакторная аутентификация</mdui-button>
<mdui-switch>Автоматический выход</mdui-switch>
</div>
<div id="language-settings" className="settings-panel">
<div id="language-settings" className={`settings-panel ${activePanel === "language-settings" ? "active" : ""}`}>
<h3>Язык</h3>
<mdui-select label="Выберите язык" variant="outlined">
<mdui-menu-item value="ru">Русский</mdui-menu-item>
@@ -56,24 +123,24 @@ export function SettingsDialog() {
</mdui-select>
</div>
<div id="storage-settings" className="settings-panel">
<div id="storage-settings" className={`settings-panel ${activePanel === "storage-settings" ? "active" : ""}`}>
<h3>Хранилище</h3>
<p>Использовано: 2.5 ГБ из 10 ГБ</p>
<mdui-linear-progress value={25}></mdui-linear-progress>
<mdui-button variant="outlined">Очистить кэш</mdui-button>
</div>
<div id="help-settings" className="settings-panel">
<div id="help-settings" className={`settings-panel ${activePanel === "help-settings" ? "active" : ""}`}>
<h3>Помощь</h3>
<mdui-button variant="outlined">Руководство пользователя</mdui-button>
<mdui-button variant="outlined">Связаться с поддержкой</mdui-button>
<mdui-button variant="outlined">FAQ</mdui-button>
</div>
<div id="about-settings" className="settings-panel">
<div id="about-settings" className={`settings-panel ${activePanel === "about-settings" ? "active" : ""}`}>
<h3>О приложении</h3>
<p>Версия: 1.0.0</p>
<p>© 2025 <span className="product-name">Loading...</span>. Все права защищены.</p>
<p>© 2025 <span className="product-name">{PRODUCT_NAME}</span>. Все права защищены.</p>
<mdui-button variant="outlined">Политика конфиденциальности</mdui-button>
<mdui-button variant="outlined">Условия использования</mdui-button>
</div>