mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Improve mobile support
This commit is contained in:
+7
-28
@@ -1,10 +1,7 @@
|
|||||||
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||||
import { ElectronTitleBar } from "./Electron";
|
import { ElectronTitleBar } from "./Electron";
|
||||||
import { useAppState } from "./pages/chat/state";
|
import { useAppState } from "./pages/chat/state";
|
||||||
import { useEffect, useState, lazy, Suspense } from "react";
|
import { useEffect, useState, lazy } from "react";
|
||||||
import { isElectron } from "./core/electron/electron";
|
|
||||||
import { MINIMUM_WIDTH } from "./core/config";
|
|
||||||
import useWindowSize from "./core/hooks/useWindowSize";
|
|
||||||
import ProtectedRoute from "./pages/ProtectedRoute";
|
import ProtectedRoute from "./pages/ProtectedRoute";
|
||||||
import NotFoundPage from "./pages/not-found/NotFoundPage";
|
import NotFoundPage from "./pages/not-found/NotFoundPage";
|
||||||
import DownloadAppPage from "./pages/download-app/DownloadAppPage";
|
import DownloadAppPage from "./pages/download-app/DownloadAppPage";
|
||||||
@@ -17,7 +14,6 @@ const ChatPage = lazy(() => import("./pages/chat/ui/ChatPage"));
|
|||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { restoreUserFromStorage } = useAppState();
|
const { restoreUserFromStorage } = useAppState();
|
||||||
const { width } = useWindowSize();
|
|
||||||
const [authReady, setAuthReady] = useState(false);
|
const [authReady, setAuthReady] = useState(false);
|
||||||
|
|
||||||
// Restore user from localStorage on app initialization
|
// Restore user from localStorage on app initialization
|
||||||
@@ -29,28 +25,13 @@ export default function App() {
|
|||||||
|
|
||||||
return authReady && (
|
return authReady && (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
{!isElectron && width < MINIMUM_WIDTH && <Navigate to="/download-app" replace />}
|
|
||||||
<ElectronTitleBar />
|
<ElectronTitleBar />
|
||||||
<div id="main-wrapper">
|
<div id="main-wrapper">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={
|
<Route path="/" element={<HomePage />} />
|
||||||
<Suspense>
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<HomePage />
|
<Route path="/register" element={<RegisterPage />} />
|
||||||
</Suspense>
|
<Route path="/download-app" element={<DownloadAppPage />} />
|
||||||
} />
|
|
||||||
<Route path="/login" element={
|
|
||||||
<Suspense>
|
|
||||||
<LoginPage />
|
|
||||||
</Suspense>
|
|
||||||
} />
|
|
||||||
<Route path="/register" element={
|
|
||||||
<Suspense>
|
|
||||||
<RegisterPage />
|
|
||||||
</Suspense>
|
|
||||||
} />
|
|
||||||
<Route path="/download-app" element={
|
|
||||||
<DownloadAppPage />
|
|
||||||
} />
|
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<Route path="chat" element={
|
<Route path="chat" element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
@@ -58,9 +39,7 @@ export default function App() {
|
|||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
} />
|
} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="*" element={
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
<NotFoundPage />
|
|
||||||
} />
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Navigate } from "react-router-dom";
|
||||||
|
import { MINIMUM_WIDTH } from "../config";
|
||||||
|
import useWindowSize from "./useWindowSize";
|
||||||
|
|
||||||
|
export default function useDownloadAppScreen() {
|
||||||
|
const { width } = useWindowSize();
|
||||||
|
const isMobile = width < MINIMUM_WIDTH;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isMobile,
|
||||||
|
navigate: isMobile ? <Navigate to="/download-app" replace /> : null
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,11 +12,14 @@ import { initialize, isSupported, startElectronReceiver, subscribe } from "@/cor
|
|||||||
import { isElectron } from "@/core/electron/electron";
|
import { isElectron } from "@/core/electron/electron";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import "./auth.scss";
|
import "./auth.scss";
|
||||||
|
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
||||||
const setUser = useAppState(state => state.setUser);
|
const setUser = useAppState(state => state.setUser);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { navigate: navigateDownloadApp } = useDownloadAppScreen();
|
||||||
|
if (navigateDownloadApp) return navigateDownloadApp;
|
||||||
|
|
||||||
function showAlert(type: AlertType, message: string) {
|
function showAlert(type: AlertType, message: string) {
|
||||||
updateAlerts((alerts) => { alerts.push({type: type, message: message}) });
|
updateAlerts((alerts) => { alerts.push({type: type, message: message}) });
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ import { MaterialTextField } from "@/core/components/TextField";
|
|||||||
import { ensureKeysOnLogin } from "@/core/api/authApi";
|
import { ensureKeysOnLogin } from "@/core/api/authApi";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import "./auth.scss";
|
import "./auth.scss";
|
||||||
|
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
||||||
const setUser = useAppState(state => state.setUser);
|
const setUser = useAppState(state => state.setUser);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { navigate: navigateDownloadApp } = useDownloadAppScreen();
|
||||||
|
if (navigateDownloadApp) return navigateDownloadApp;
|
||||||
|
|
||||||
function showAlert(type: AlertType, message: string) {
|
function showAlert(type: AlertType, message: string) {
|
||||||
updateAlerts((alerts) => { alerts.push({type: type, message: message}) });
|
updateAlerts((alerts) => { alerts.push({type: type, message: message}) });
|
||||||
|
|||||||
@@ -84,7 +84,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context-menu {
|
.context-menu {
|
||||||
position: fixed;
|
|
||||||
background: $color-dark-surface;
|
background: $color-dark-surface;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
@@ -269,24 +268,3 @@
|
|||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mobile responsive
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.reaction-bar {
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reaction-emoji-button,
|
|
||||||
.reaction-expand-button {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reaction-emoji-button {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reaction-expand-button .material-symbols {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { LeftPanel } from "./left/LeftPanel";
|
import { LeftPanel } from "./left/LeftPanel";
|
||||||
import { RightPanel } from "./right/RightPanel";
|
import { RightPanel } from "./right/RightPanel";
|
||||||
import "@/pages/chat/css/chat.scss";
|
import "@/pages/chat/css/chat.scss";
|
||||||
|
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
||||||
|
|
||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
|
const { navigate: navigateDownloadApp } = useDownloadAppScreen();
|
||||||
|
if (navigateDownloadApp) return navigateDownloadApp;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="chat-interface">
|
<div id="chat-interface">
|
||||||
<div className="all-container">
|
<div className="all-container">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Navigate, useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useAppState } from "@/pages/chat/state";
|
import { useAppState } from "@/pages/chat/state";
|
||||||
import { isElectron } from "@/core/electron/electron";
|
|
||||||
import "./home.scss";
|
import "./home.scss";
|
||||||
|
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
||||||
|
|
||||||
function GitHubLink({ children }: { children: React.ReactNode }) {
|
function GitHubLink({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
@@ -18,18 +18,24 @@ function SupportLink({ children }: { children: React.ReactNode }) {
|
|||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user } = useAppState();
|
const { user } = useAppState();
|
||||||
|
const { isMobile } = useDownloadAppScreen();
|
||||||
|
const isLoggedIn = user.authToken && user.currentUser;
|
||||||
|
|
||||||
const handleGetStarted = () => {
|
function handleGetStarted() {
|
||||||
if (user.authToken && user.currentUser) {
|
if (isMobile) {
|
||||||
|
navigate("/download-app");
|
||||||
|
} else if (isLoggedIn) {
|
||||||
navigate("/chat");
|
navigate("/chat");
|
||||||
} else {
|
} else {
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isElectron) {
|
const openBtn = (
|
||||||
return <Navigate to="/chat" />;
|
<mdui-button variant="filled" onClick={handleGetStarted}>
|
||||||
}
|
{isMobile ? "Скачать приложение" : isLoggedIn ? "Перейти в чат" : "Войти"}
|
||||||
|
</mdui-button>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="homepage">
|
<div className="homepage">
|
||||||
@@ -48,15 +54,7 @@ export default function HomePage() {
|
|||||||
<mdui-button variant="text">Поддержка</mdui-button>
|
<mdui-button variant="text">Поддержка</mdui-button>
|
||||||
</SupportLink>
|
</SupportLink>
|
||||||
|
|
||||||
{user.authToken ? (
|
{openBtn}
|
||||||
<mdui-button variant="filled" onClick={() => navigate("/chat")}>
|
|
||||||
Перейти в чат
|
|
||||||
</mdui-button>
|
|
||||||
) : (
|
|
||||||
<mdui-button variant="filled" onClick={() => navigate("/login")}>
|
|
||||||
Войти
|
|
||||||
</mdui-button>
|
|
||||||
)}
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,18 +72,13 @@ export default function HomePage() {
|
|||||||
поддержкой файлов и уведомлений. Создан для тех, кто ценит приватность и свободу.
|
поддержкой файлов и уведомлений. Создан для тех, кто ценит приватность и свободу.
|
||||||
</p>
|
</p>
|
||||||
<div className="hero-actions">
|
<div className="hero-actions">
|
||||||
<mdui-button
|
{openBtn}
|
||||||
variant="filled"
|
{!isMobile && <mdui-button
|
||||||
onClick={handleGetStarted}
|
|
||||||
>
|
|
||||||
{user.authToken ? "Перейти в чат" : "Начать общение"}
|
|
||||||
</mdui-button>
|
|
||||||
<mdui-button
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={() => navigate("/register")}
|
onClick={() => navigate("/register")}
|
||||||
>
|
>
|
||||||
Зарегистрироваться
|
Зарегистрироваться
|
||||||
</mdui-button>
|
</mdui-button>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="hero-visual">
|
<div className="hero-visual">
|
||||||
@@ -129,7 +122,7 @@ export default function HomePage() {
|
|||||||
<div className="features-grid">
|
<div className="features-grid">
|
||||||
<div className="feature-card">
|
<div className="feature-card">
|
||||||
<div className="feature-icon">
|
<div className="feature-icon">
|
||||||
<mdui-icon name="security"></mdui-icon>
|
<mdui-icon name="security" />
|
||||||
</div>
|
</div>
|
||||||
<h4>End-to-End Шифрование</h4>
|
<h4>End-to-End Шифрование</h4>
|
||||||
<p>
|
<p>
|
||||||
@@ -140,7 +133,7 @@ export default function HomePage() {
|
|||||||
|
|
||||||
<div className="feature-card">
|
<div className="feature-card">
|
||||||
<div className="feature-icon">
|
<div className="feature-icon">
|
||||||
<mdui-icon name="code"></mdui-icon>
|
<mdui-icon name="code" />
|
||||||
</div>
|
</div>
|
||||||
<h4>100% открытый код</h4>
|
<h4>100% открытый код</h4>
|
||||||
<p>
|
<p>
|
||||||
@@ -151,7 +144,7 @@ export default function HomePage() {
|
|||||||
|
|
||||||
<div className="feature-card">
|
<div className="feature-card">
|
||||||
<div className="feature-icon">
|
<div className="feature-icon">
|
||||||
<mdui-icon name="attach_file"></mdui-icon>
|
<mdui-icon name="attach_file" />
|
||||||
</div>
|
</div>
|
||||||
<h4>Обмен Файлами</h4>
|
<h4>Обмен Файлами</h4>
|
||||||
<p>
|
<p>
|
||||||
@@ -162,7 +155,7 @@ export default function HomePage() {
|
|||||||
|
|
||||||
<div className="feature-card">
|
<div className="feature-card">
|
||||||
<div className="feature-icon">
|
<div className="feature-icon">
|
||||||
<mdui-icon name="notifications"></mdui-icon>
|
<mdui-icon name="notifications" />
|
||||||
</div>
|
</div>
|
||||||
<h4>Уведомления</h4>
|
<h4>Уведомления</h4>
|
||||||
<p>
|
<p>
|
||||||
@@ -173,7 +166,7 @@ export default function HomePage() {
|
|||||||
|
|
||||||
<div className="feature-card">
|
<div className="feature-card">
|
||||||
<div className="feature-icon">
|
<div className="feature-icon">
|
||||||
<mdui-icon name="edit"></mdui-icon>
|
<mdui-icon name="edit" />
|
||||||
</div>
|
</div>
|
||||||
<h4>Редактирование</h4>
|
<h4>Редактирование</h4>
|
||||||
<p>
|
<p>
|
||||||
@@ -184,7 +177,7 @@ export default function HomePage() {
|
|||||||
|
|
||||||
<div className="feature-card">
|
<div className="feature-card">
|
||||||
<div className="feature-icon">
|
<div className="feature-icon">
|
||||||
<mdui-icon name="computer"></mdui-icon>
|
<mdui-icon name="computer" />
|
||||||
</div>
|
</div>
|
||||||
<h4>Кроссплатформенность</h4>
|
<h4>Кроссплатформенность</h4>
|
||||||
<p>
|
<p>
|
||||||
@@ -205,20 +198,28 @@ export default function HomePage() {
|
|||||||
уведомлений и автономной работы.
|
уведомлений и автономной работы.
|
||||||
</p>
|
</p>
|
||||||
<div className="download-buttons">
|
<div className="download-buttons">
|
||||||
|
{!isMobile ? (
|
||||||
|
<>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/Toolbox-io/FromChat/actions/workflows/build.yml"
|
href="https://github.com/Toolbox-io/FromChat/actions/workflows/build.yml"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<mdui-button variant="filled">
|
<mdui-button variant="filled">
|
||||||
<mdui-icon name="download" slot="icon"></mdui-icon>
|
<mdui-icon name="download" slot="icon" />
|
||||||
Скачать для ПК
|
Скачать для ПК
|
||||||
</mdui-button>
|
</mdui-button>
|
||||||
</a>
|
</a>
|
||||||
<mdui-button variant="outlined" onClick={() => navigate("/login")}>
|
<mdui-button variant="outlined" onClick={() => navigate("/login")}>
|
||||||
<mdui-icon name="language" slot="icon"></mdui-icon>
|
<mdui-icon name="language" slot="icon" />
|
||||||
Веб-версия
|
Веб-версия
|
||||||
</mdui-button>
|
</mdui-button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<mdui-button variant="filled" onClick={() => navigate("/download-app")}>
|
||||||
|
Скачать приложение
|
||||||
|
</mdui-button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -232,18 +233,24 @@ export default function HomePage() {
|
|||||||
Присоединяйтесь к FromChat и общайтесь безопасно с друзьями и коллегами.
|
Присоединяйтесь к FromChat и общайтесь безопасно с друзьями и коллегами.
|
||||||
</p>
|
</p>
|
||||||
<div className="cta-actions">
|
<div className="cta-actions">
|
||||||
|
{isMobile ? (
|
||||||
|
<mdui-button variant="filled" onClick={() => navigate("/download-app")}>
|
||||||
|
Скачать приложение
|
||||||
|
</mdui-button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<mdui-button
|
<mdui-button
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onClick={() => navigate("/register")}
|
onClick={() => navigate("/register")}>
|
||||||
>
|
|
||||||
Создать аккаунт
|
Создать аккаунт
|
||||||
</mdui-button>
|
</mdui-button>
|
||||||
<mdui-button
|
<mdui-button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={() => navigate("/login")}
|
onClick={() => navigate("/login")}>
|
||||||
>
|
|
||||||
Войти
|
Войти
|
||||||
</mdui-button>
|
</mdui-button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user