Add "Download app" screen for too small screens, translate strings

# Please enter the commit message for your changes. Lines starting
This commit is contained in:
grabic21
2025-09-13 17:17:43 +04:00
committed by denis0001-dev
Unverified
parent 002a59d3c8
commit a7275f58e1
10 changed files with 88 additions and 15 deletions
+2
View File
@@ -28,3 +28,5 @@ export const API_WS_BASE_URL = `${location.host || BASE_DOMAIN}/api`;
* @constant * @constant
*/ */
export const PRODUCT_NAME = "FromChat"; export const PRODUCT_NAME = "FromChat";
export const MINIMUM_WIDTH = 800;
@@ -0,0 +1,8 @@
.download-app-screen {
display: flex;
justify-content: center;
align-items: center;
min-width: 100vw;
min-height: 100vh;
padding: 32px;
}
+1
View File
@@ -9,6 +9,7 @@
@use "common/material" as *; @use "common/material" as *;
@use "electron"; @use "electron";
@use "dialogs/reply"; @use "dialogs/reply";
@use "download-app";
@use "lib/fonts/montserrat"; @use "lib/fonts/montserrat";
@use "lib/fonts/material-symbols"; @use "lib/fonts/material-symbols";
+8
View File
@@ -1,5 +1,8 @@
import { MINIMUM_WIDTH } from "../core/config";
import { ElectronTitleBar } from "./components/Electron"; import { ElectronTitleBar } from "./components/Electron";
import useWindowSize from "./hooks/useWindowSize";
import ChatScreen from "./screen/ChatScreen"; import ChatScreen from "./screen/ChatScreen";
import DownloadAppScreen from "./screen/DownloadAppScreen";
import LoginScreen from "./screen/LoginScreen"; import LoginScreen from "./screen/LoginScreen";
import RegisterScreen from "./screen/RegisterScreen"; import RegisterScreen from "./screen/RegisterScreen";
import { useAppState } from "./state"; import { useAppState } from "./state";
@@ -7,12 +10,17 @@ import { useEffect } from "react";
export default function App() { export default function App() {
const { currentPage, restoreUserFromStorage } = useAppState(); const { currentPage, restoreUserFromStorage } = useAppState();
const { width } = useWindowSize();
// Restore user from localStorage on app initialization // Restore user from localStorage on app initialization
useEffect(() => { useEffect(() => {
restoreUserFromStorage(); restoreUserFromStorage();
}, [restoreUserFromStorage]); }, [restoreUserFromStorage]);
if (width < MINIMUM_WIDTH) {
return <DownloadAppScreen />
}
let page = <LoginScreen />; let page = <LoginScreen />;
switch (currentPage) { switch (currentPage) {
@@ -176,17 +176,17 @@ export function MessageContextMenu({
> >
<div className="context-menu-item" onClick={() => handleAction("reply")}> <div className="context-menu-item" onClick={() => handleAction("reply")}>
<span className="material-symbols">reply</span> <span className="material-symbols">reply</span>
Reply Ответить
</div> </div>
{isAuthor && ( {isAuthor && (
<> <>
<div className="context-menu-item" onClick={() => handleAction("edit")}> <div className="context-menu-item" onClick={() => handleAction("edit")}>
<span className="material-symbols">edit</span> <span className="material-symbols">edit</span>
Edit Редактировать
</div> </div>
<div className="context-menu-item" onClick={() => handleAction("delete")}> <div className="context-menu-item" onClick={() => handleAction("delete")}>
<span className="material-symbols">delete</span> <span className="material-symbols">delete</span>
Delete Удалить
</div> </div>
</> </>
)} )}
@@ -41,8 +41,8 @@ export function MessagePanelRenderer({ panel, isChatSwitching }: MessagePanelRen
// Cleanup function // Cleanup function
return () => { return () => {
if (panel && (panel as any).onStateChange) { if (panel && panel.onStateChange) {
(panel as any).onStateChange = null; panel.onStateChange = null;
} }
}; };
}, [panel]); }, [panel]);
@@ -72,10 +72,10 @@ export function MessagePanelRenderer({ panel, isChatSwitching }: MessagePanelRen
<img src={defaultAvatar} alt="Avatar" className="chat-header-avatar" /> <img src={defaultAvatar} alt="Avatar" className="chat-header-avatar" />
<div className="chat-header-info"> <div className="chat-header-info">
<div className="info-chat"> <div className="info-chat">
<h4 id="chat-name">Select a chat</h4> <h4 id="chat-name">Выбор чата</h4>
<p> <p>
<span className="online-status"></span> <span className="online-status"></span>
Choose a chat to start messaging Выберите чат, чтобы начать переписку
</p> </p>
</div> </div>
</div> </div>
@@ -88,7 +88,7 @@ export function MessagePanelRenderer({ panel, isChatSwitching }: MessagePanelRen
height: "100%", height: "100%",
color: "var(--mdui-color-on-surface-variant)" color: "var(--mdui-color-on-surface-variant)"
}}> }}>
Select a chat from the sidebar to start messaging Выберите чат на боковой панели, чтобы начать переписку
</div> </div>
</div> </div>
</div> </div>
@@ -128,7 +128,7 @@ export function MessagePanelRenderer({ panel, isChatSwitching }: MessagePanelRen
height: "100%", height: "100%",
color: "var(--mdui-color-on-surface-variant)" color: "var(--mdui-color-on-surface-variant)"
}}> }}>
Loading messages... Загрузка сообщений...
</div> </div>
</div> </div>
): ( ): (
@@ -36,7 +36,7 @@ export function ReplyMessageDialog({ isOpen, onOpenChange, replyToMessage, onSen
return ( return (
<MaterialDialog open={isOpen} onOpenChange={onOpenChange} close-on-overlay-click close-on-esc className="reply-dialog"> <MaterialDialog open={isOpen} onOpenChange={onOpenChange} close-on-overlay-click close-on-esc className="reply-dialog">
<div className="dialog-content"> <div className="dialog-content">
<h3>Reply to Message</h3> <h3>Ответить на сообщение</h3>
<div className="reply-preview-dialog"> <div className="reply-preview-dialog">
<div className="reply-content"> <div className="reply-content">
<span className="reply-username">{replyToMessage.username}</span> <span className="reply-username">{replyToMessage.username}</span>
@@ -28,24 +28,24 @@ export function UserProfileDialog({ isOpen, onOpenChange, userProfile }: UserPro
<div className={`online-status ${userProfile.online ? "online" : "offline"}`}> <div className={`online-status ${userProfile.online ? "online" : "offline"}`}>
{userProfile.online ? ( {userProfile.online ? (
<> <>
<span className="online-indicator"></span> Online <span className="online-indicator"></span> Онлайн
</> </>
) : ( ) : (
<> <>
<span className="offline-indicator"></span> Last seen {formatTime(userProfile.last_seen)} <span className="offline-indicator"></span> Последний заход {formatTime(userProfile.last_seen)}
</> </>
)} )}
</div> </div>
</div> </div>
<div className="bio-section"> <div className="bio-section">
<label>Bio:</label> <label>О себе:</label>
<div className="bio-display"> <div className="bio-display">
{userProfile.bio || "No bio available."} {userProfile.bio || "No bio available."}
</div> </div>
</div> </div>
<div className="profile-stats"> <div className="profile-stats">
<div className="stat"> <div className="stat">
<span className="stat-label">Member since:</span> <span className="stat-label">Зарегистрирован:</span>
<span className="stat-value member-since">{formatTime(userProfile.created_at)}</span> <span className="stat-value member-since">{formatTime(userProfile.created_at)}</span>
</div> </div>
<div className="stat"> <div className="stat">
+29
View File
@@ -0,0 +1,29 @@
import { useEffect, useState } from "react";
export interface WindowSize {
width: number;
height: number;
}
export default function useWindowSize(): WindowSize {
const [width, setWidth] = useState(innerWidth);
const [height, setHeight] = useState(innerHeight);
useEffect(() => {
function listener() {
setWidth(innerWidth);
setHeight(innerHeight);
}
addEventListener("resize", listener);
return () => {
removeEventListener("resize", listener);
}
});
return {
width: width,
height: height
}
}
@@ -0,0 +1,25 @@
export default function DownloadAppScreen() {
return (
<div className="download-app-screen">
<div className="inner">
<h1>Чтобы пользоваться мессенджером, скачайте приложение</h1>
<p>
Этот сайт <b>не предназначен</b> для работы на маленьких экранах, поэтому
вам нужно скачать приложение мессенджера.
</p>
<a href="https://github.com/denis0001-dev/FromChat-android/releases/latest">
<mdui-button>Скачать на GitHub</mdui-button>
</a>
<p>
Если возникнут сложности или есть вопросы, нажмите кнопку!
</p>
<a href="https://t.me/denis0001-dev">
<mdui-button>Написать в поддержку</mdui-button>
</a>
</div>
</div>
)
}