From a24270b37ae47b099a518dfd4a19ff6cf8dd80fa Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Thu, 2 Oct 2025 18:18:08 +0300 Subject: [PATCH 1/5] Restructure code --- .gitignore | 4 +++- frontend/src/{ui => }/App.tsx | 18 +++++++++--------- frontend/src/main.tsx | 8 ++++---- frontend/src/{ => pages/app}/api/dmApi.ts | 0 .../src/{ => pages/app}/api/profileApi.ts | 0 frontend/src/{ => pages/app}/auth/api.ts | 0 frontend/src/{ => pages/app}/auth/crypto.ts | 0 frontend/src/{ => pages/app}/core/config.ts | 0 frontend/src/{ => pages/app}/core/init.ts | 0 frontend/src/{ => pages/app}/core/types.d.ts | 0 .../src/{ => pages/app}/core/websocket.ts | 0 .../src/{ => pages/app}/electron/electron.ts | 0 .../{ => pages/app}/resources/css/_auth.scss | 0 .../{ => pages/app}/resources/css/_chat.scss | 0 .../app}/resources/css/_download-app.scss | 0 .../app}/resources/css/_electron.scss | 0 .../app}/resources/css/_panelchat.scss | 0 .../app}/resources/css/_profile.scss | 0 .../app}/resources/css/_settings.scss | 0 .../resources/css/common/_animations.scss | 0 .../app}/resources/css/common/_colors.scss | 0 .../resources/css/common/_components.scss | 0 .../app}/resources/css/common/_material.scss | 0 .../app}/resources/css/dialogs/_reply.scss | 0 .../css/lib/fonts/material-symbols.scss | 0 .../css/lib/fonts/material-symbols.woff2 | Bin .../resources/css/lib/fonts/montserrat.scss | 0 .../JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 | Bin .../JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 | Bin .../JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 | Bin .../JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 | Bin .../JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 | Bin .../{ => pages/app}/resources/css/style.scss | 0 .../app}/resources/images/default-avatar.png | Bin .../{ => pages/app}/resources/images/logo.png | Bin .../app}/service-worker/service-worker.ts | 0 .../{ => pages/app}/ui/components/Alerts.tsx | 0 .../{ => pages/app}/ui/components/Auth.tsx | 0 .../app}/ui/components/Electron.tsx | 0 .../app}/ui/components/chat/BottomAppBar.tsx | 0 .../app}/ui/components/chat/ChatHeader.tsx | 0 .../ui/components/chat/ChatInputWrapper.tsx | 0 .../ui/components/chat/ChatMainHeader.tsx | 0 .../app}/ui/components/chat/ChatMessages.tsx | 0 .../app}/ui/components/chat/ChatTabs.tsx | 0 .../app}/ui/components/chat/DMUsersList.tsx | 0 .../app}/ui/components/chat/LeftPanel.tsx | 0 .../app}/ui/components/chat/Message.tsx | 0 .../ui/components/chat/MessageContextMenu.tsx | 0 .../components/chat/MessagePanelRenderer.tsx | 0 .../ui/components/chat/ReplyMessageDialog.tsx | 0 .../app}/ui/components/chat/RightPanel.tsx | 0 .../ui/components/chat/UserProfileDialog.tsx | 0 .../app}/ui/components/core/Dialog.tsx | 0 .../app}/ui/components/core/Quote.tsx | 0 .../app}/ui/components/core/RichTextArea.tsx | 0 .../app}/ui/components/core/TextField.tsx | 0 .../core/animations/AnimatedHeight.tsx | 0 .../core/animations/AnimatedOpacity.tsx | 0 .../ui/components/core/animations/types.d.ts | 0 .../ui/components/profile/CropperDialog.tsx | 0 .../ui/components/profile/ImageCropper.tsx | 0 .../ui/components/profile/ProfileDialog.tsx | 0 .../ui/components/settings/SettingsDialog.tsx | 0 .../src/{ => pages/app}/ui/hooks/useChat.ts | 0 .../app}/ui/hooks/useCombinedRefs.ts | 0 .../src/{ => pages/app}/ui/hooks/useDM.ts | 0 .../{ => pages/app}/ui/hooks/useProfile.ts | 0 .../{ => pages/app}/ui/hooks/useWindowSize.ts | 0 .../src/{ => pages/app}/ui/panels/DMPanel.ts | 0 .../{ => pages/app}/ui/panels/MessagePanel.ts | 0 .../app}/ui/panels/PublicChatPanel.ts | 0 .../{ => pages/app}/ui/screen/ChatScreen.tsx | 0 .../app}/ui/screen/DownloadAppScreen.tsx | 0 .../{ => pages/app}/ui/screen/LoginScreen.tsx | 0 .../app}/ui/screen/RegisterScreen.tsx | 0 frontend/src/{ => pages/app}/ui/state.ts | 0 .../app}/utils/crypto/asymmetric.ts | 2 +- .../{ => pages/app}/utils/crypto/backup.ts | 0 .../src/{ => pages/app}/utils/crypto/kdf.ts | 0 .../{ => pages/app}/utils/crypto/symmetric.ts | 0 .../src/{ => pages/app}/utils/material.ts | 0 .../src/{ => pages/app}/utils/notification.ts | 0 .../app}/utils/push-notifications.ts | 0 frontend/src/{ => pages/app}/utils/utils.ts | 0 85 files changed, 17 insertions(+), 15 deletions(-) rename frontend/src/{ui => }/App.tsx (61%) rename frontend/src/{ => pages/app}/api/dmApi.ts (100%) rename frontend/src/{ => pages/app}/api/profileApi.ts (100%) rename frontend/src/{ => pages/app}/auth/api.ts (100%) rename frontend/src/{ => pages/app}/auth/crypto.ts (100%) rename frontend/src/{ => pages/app}/core/config.ts (100%) rename frontend/src/{ => pages/app}/core/init.ts (100%) rename frontend/src/{ => pages/app}/core/types.d.ts (100%) rename frontend/src/{ => pages/app}/core/websocket.ts (100%) rename frontend/src/{ => pages/app}/electron/electron.ts (100%) rename frontend/src/{ => pages/app}/resources/css/_auth.scss (100%) rename frontend/src/{ => pages/app}/resources/css/_chat.scss (100%) rename frontend/src/{ => pages/app}/resources/css/_download-app.scss (100%) rename frontend/src/{ => pages/app}/resources/css/_electron.scss (100%) rename frontend/src/{ => pages/app}/resources/css/_panelchat.scss (100%) rename frontend/src/{ => pages/app}/resources/css/_profile.scss (100%) rename frontend/src/{ => pages/app}/resources/css/_settings.scss (100%) rename frontend/src/{ => pages/app}/resources/css/common/_animations.scss (100%) rename frontend/src/{ => pages/app}/resources/css/common/_colors.scss (100%) rename frontend/src/{ => pages/app}/resources/css/common/_components.scss (100%) rename frontend/src/{ => pages/app}/resources/css/common/_material.scss (100%) rename frontend/src/{ => pages/app}/resources/css/dialogs/_reply.scss (100%) rename frontend/src/{ => pages/app}/resources/css/lib/fonts/material-symbols.scss (100%) rename frontend/src/{ => pages/app}/resources/css/lib/fonts/material-symbols.woff2 (100%) rename frontend/src/{ => pages/app}/resources/css/lib/fonts/montserrat.scss (100%) rename frontend/src/{ => pages/app}/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 (100%) rename frontend/src/{ => pages/app}/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 (100%) rename frontend/src/{ => pages/app}/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 (100%) rename frontend/src/{ => pages/app}/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 (100%) rename frontend/src/{ => pages/app}/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 (100%) rename frontend/src/{ => pages/app}/resources/css/style.scss (100%) rename frontend/src/{ => pages/app}/resources/images/default-avatar.png (100%) rename frontend/src/{ => pages/app}/resources/images/logo.png (100%) rename frontend/src/{ => pages/app}/service-worker/service-worker.ts (100%) rename frontend/src/{ => pages/app}/ui/components/Alerts.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/Auth.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/Electron.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/BottomAppBar.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/ChatHeader.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/ChatInputWrapper.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/ChatMainHeader.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/ChatMessages.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/ChatTabs.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/DMUsersList.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/LeftPanel.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/Message.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/MessageContextMenu.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/MessagePanelRenderer.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/ReplyMessageDialog.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/RightPanel.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/chat/UserProfileDialog.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/core/Dialog.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/core/Quote.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/core/RichTextArea.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/core/TextField.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/core/animations/AnimatedHeight.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/core/animations/AnimatedOpacity.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/core/animations/types.d.ts (100%) rename frontend/src/{ => pages/app}/ui/components/profile/CropperDialog.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/profile/ImageCropper.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/profile/ProfileDialog.tsx (100%) rename frontend/src/{ => pages/app}/ui/components/settings/SettingsDialog.tsx (100%) rename frontend/src/{ => pages/app}/ui/hooks/useChat.ts (100%) rename frontend/src/{ => pages/app}/ui/hooks/useCombinedRefs.ts (100%) rename frontend/src/{ => pages/app}/ui/hooks/useDM.ts (100%) rename frontend/src/{ => pages/app}/ui/hooks/useProfile.ts (100%) rename frontend/src/{ => pages/app}/ui/hooks/useWindowSize.ts (100%) rename frontend/src/{ => pages/app}/ui/panels/DMPanel.ts (100%) rename frontend/src/{ => pages/app}/ui/panels/MessagePanel.ts (100%) rename frontend/src/{ => pages/app}/ui/panels/PublicChatPanel.ts (100%) rename frontend/src/{ => pages/app}/ui/screen/ChatScreen.tsx (100%) rename frontend/src/{ => pages/app}/ui/screen/DownloadAppScreen.tsx (100%) rename frontend/src/{ => pages/app}/ui/screen/LoginScreen.tsx (100%) rename frontend/src/{ => pages/app}/ui/screen/RegisterScreen.tsx (100%) rename frontend/src/{ => pages/app}/ui/state.ts (100%) rename frontend/src/{ => pages/app}/utils/crypto/asymmetric.ts (93%) rename frontend/src/{ => pages/app}/utils/crypto/backup.ts (100%) rename frontend/src/{ => pages/app}/utils/crypto/kdf.ts (100%) rename frontend/src/{ => pages/app}/utils/crypto/symmetric.ts (100%) rename frontend/src/{ => pages/app}/utils/material.ts (100%) rename frontend/src/{ => pages/app}/utils/notification.ts (100%) rename frontend/src/{ => pages/app}/utils/push-notifications.ts (100%) rename frontend/src/{ => pages/app}/utils/utils.ts (100%) diff --git a/.gitignore b/.gitignore index decede0..404f860 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,8 @@ .LSOverride # Icon must end with two \r -Icon +Icon + # Thumbnails ._* @@ -572,3 +573,4 @@ package-lock.json backend/alembic/** !backend/alembic/env.py !backend/alembic/script.py.mako +!frontend/src/pages/app/resources/css/lib \ No newline at end of file diff --git a/frontend/src/ui/App.tsx b/frontend/src/App.tsx similarity index 61% rename from frontend/src/ui/App.tsx rename to frontend/src/App.tsx index 2712a2e..d403a77 100644 --- a/frontend/src/ui/App.tsx +++ b/frontend/src/App.tsx @@ -1,12 +1,12 @@ -import { MINIMUM_WIDTH } from "../core/config"; -import { isElectron } from "../electron/electron"; -import { ElectronTitleBar } from "./components/Electron"; -import useWindowSize from "./hooks/useWindowSize"; -import ChatScreen from "./screen/ChatScreen"; -import DownloadAppScreen from "./screen/DownloadAppScreen"; -import LoginScreen from "./screen/LoginScreen"; -import RegisterScreen from "./screen/RegisterScreen"; -import { useAppState } from "./state"; +import { MINIMUM_WIDTH } from "./pages/app/core/config"; +import { isElectron } from "./pages/app/electron/electron"; +import { ElectronTitleBar } from "./pages/app/ui/components/Electron"; +import useWindowSize from "./pages/app/ui/hooks/useWindowSize"; +import ChatScreen from "./pages/app/ui/screen/ChatScreen"; +import DownloadAppScreen from "./pages/app/ui/screen/DownloadAppScreen"; +import LoginScreen from "./pages/app/ui/screen/LoginScreen"; +import RegisterScreen from "./pages/app/ui/screen/RegisterScreen"; +import { useAppState } from "./pages/app/ui/state"; import { useEffect } from "react"; export default function App() { diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index b923780..572bfd4 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -8,11 +8,11 @@ import './resources/css/style.scss'; import "mdui/mdui.css"; -import "./utils/material"; -import "./core/init"; -import "./electron/electron"; +import "./pages/app/utils/material"; +import "./pages/app/core/init"; +import "./pages/app/electron/electron"; import { createRoot } from 'react-dom/client'; -import App from './ui/App'; +import App from './App'; import { StrictMode } from 'react'; // Initialize React app diff --git a/frontend/src/api/dmApi.ts b/frontend/src/pages/app/api/dmApi.ts similarity index 100% rename from frontend/src/api/dmApi.ts rename to frontend/src/pages/app/api/dmApi.ts diff --git a/frontend/src/api/profileApi.ts b/frontend/src/pages/app/api/profileApi.ts similarity index 100% rename from frontend/src/api/profileApi.ts rename to frontend/src/pages/app/api/profileApi.ts diff --git a/frontend/src/auth/api.ts b/frontend/src/pages/app/auth/api.ts similarity index 100% rename from frontend/src/auth/api.ts rename to frontend/src/pages/app/auth/api.ts diff --git a/frontend/src/auth/crypto.ts b/frontend/src/pages/app/auth/crypto.ts similarity index 100% rename from frontend/src/auth/crypto.ts rename to frontend/src/pages/app/auth/crypto.ts diff --git a/frontend/src/core/config.ts b/frontend/src/pages/app/core/config.ts similarity index 100% rename from frontend/src/core/config.ts rename to frontend/src/pages/app/core/config.ts diff --git a/frontend/src/core/init.ts b/frontend/src/pages/app/core/init.ts similarity index 100% rename from frontend/src/core/init.ts rename to frontend/src/pages/app/core/init.ts diff --git a/frontend/src/core/types.d.ts b/frontend/src/pages/app/core/types.d.ts similarity index 100% rename from frontend/src/core/types.d.ts rename to frontend/src/pages/app/core/types.d.ts diff --git a/frontend/src/core/websocket.ts b/frontend/src/pages/app/core/websocket.ts similarity index 100% rename from frontend/src/core/websocket.ts rename to frontend/src/pages/app/core/websocket.ts diff --git a/frontend/src/electron/electron.ts b/frontend/src/pages/app/electron/electron.ts similarity index 100% rename from frontend/src/electron/electron.ts rename to frontend/src/pages/app/electron/electron.ts diff --git a/frontend/src/resources/css/_auth.scss b/frontend/src/pages/app/resources/css/_auth.scss similarity index 100% rename from frontend/src/resources/css/_auth.scss rename to frontend/src/pages/app/resources/css/_auth.scss diff --git a/frontend/src/resources/css/_chat.scss b/frontend/src/pages/app/resources/css/_chat.scss similarity index 100% rename from frontend/src/resources/css/_chat.scss rename to frontend/src/pages/app/resources/css/_chat.scss diff --git a/frontend/src/resources/css/_download-app.scss b/frontend/src/pages/app/resources/css/_download-app.scss similarity index 100% rename from frontend/src/resources/css/_download-app.scss rename to frontend/src/pages/app/resources/css/_download-app.scss diff --git a/frontend/src/resources/css/_electron.scss b/frontend/src/pages/app/resources/css/_electron.scss similarity index 100% rename from frontend/src/resources/css/_electron.scss rename to frontend/src/pages/app/resources/css/_electron.scss diff --git a/frontend/src/resources/css/_panelchat.scss b/frontend/src/pages/app/resources/css/_panelchat.scss similarity index 100% rename from frontend/src/resources/css/_panelchat.scss rename to frontend/src/pages/app/resources/css/_panelchat.scss diff --git a/frontend/src/resources/css/_profile.scss b/frontend/src/pages/app/resources/css/_profile.scss similarity index 100% rename from frontend/src/resources/css/_profile.scss rename to frontend/src/pages/app/resources/css/_profile.scss diff --git a/frontend/src/resources/css/_settings.scss b/frontend/src/pages/app/resources/css/_settings.scss similarity index 100% rename from frontend/src/resources/css/_settings.scss rename to frontend/src/pages/app/resources/css/_settings.scss diff --git a/frontend/src/resources/css/common/_animations.scss b/frontend/src/pages/app/resources/css/common/_animations.scss similarity index 100% rename from frontend/src/resources/css/common/_animations.scss rename to frontend/src/pages/app/resources/css/common/_animations.scss diff --git a/frontend/src/resources/css/common/_colors.scss b/frontend/src/pages/app/resources/css/common/_colors.scss similarity index 100% rename from frontend/src/resources/css/common/_colors.scss rename to frontend/src/pages/app/resources/css/common/_colors.scss diff --git a/frontend/src/resources/css/common/_components.scss b/frontend/src/pages/app/resources/css/common/_components.scss similarity index 100% rename from frontend/src/resources/css/common/_components.scss rename to frontend/src/pages/app/resources/css/common/_components.scss diff --git a/frontend/src/resources/css/common/_material.scss b/frontend/src/pages/app/resources/css/common/_material.scss similarity index 100% rename from frontend/src/resources/css/common/_material.scss rename to frontend/src/pages/app/resources/css/common/_material.scss diff --git a/frontend/src/resources/css/dialogs/_reply.scss b/frontend/src/pages/app/resources/css/dialogs/_reply.scss similarity index 100% rename from frontend/src/resources/css/dialogs/_reply.scss rename to frontend/src/pages/app/resources/css/dialogs/_reply.scss diff --git a/frontend/src/resources/css/lib/fonts/material-symbols.scss b/frontend/src/pages/app/resources/css/lib/fonts/material-symbols.scss similarity index 100% rename from frontend/src/resources/css/lib/fonts/material-symbols.scss rename to frontend/src/pages/app/resources/css/lib/fonts/material-symbols.scss diff --git a/frontend/src/resources/css/lib/fonts/material-symbols.woff2 b/frontend/src/pages/app/resources/css/lib/fonts/material-symbols.woff2 similarity index 100% rename from frontend/src/resources/css/lib/fonts/material-symbols.woff2 rename to frontend/src/pages/app/resources/css/lib/fonts/material-symbols.woff2 diff --git a/frontend/src/resources/css/lib/fonts/montserrat.scss b/frontend/src/pages/app/resources/css/lib/fonts/montserrat.scss similarity index 100% rename from frontend/src/resources/css/lib/fonts/montserrat.scss rename to frontend/src/pages/app/resources/css/lib/fonts/montserrat.scss diff --git a/frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 b/frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 similarity index 100% rename from frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 rename to frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 diff --git a/frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 b/frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 similarity index 100% rename from frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 rename to frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 diff --git a/frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 b/frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 similarity index 100% rename from frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 rename to frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 diff --git a/frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 b/frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 similarity index 100% rename from frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 rename to frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 diff --git a/frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 b/frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 similarity index 100% rename from frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 rename to frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 diff --git a/frontend/src/resources/css/style.scss b/frontend/src/pages/app/resources/css/style.scss similarity index 100% rename from frontend/src/resources/css/style.scss rename to frontend/src/pages/app/resources/css/style.scss diff --git a/frontend/src/resources/images/default-avatar.png b/frontend/src/pages/app/resources/images/default-avatar.png similarity index 100% rename from frontend/src/resources/images/default-avatar.png rename to frontend/src/pages/app/resources/images/default-avatar.png diff --git a/frontend/src/resources/images/logo.png b/frontend/src/pages/app/resources/images/logo.png similarity index 100% rename from frontend/src/resources/images/logo.png rename to frontend/src/pages/app/resources/images/logo.png diff --git a/frontend/src/service-worker/service-worker.ts b/frontend/src/pages/app/service-worker/service-worker.ts similarity index 100% rename from frontend/src/service-worker/service-worker.ts rename to frontend/src/pages/app/service-worker/service-worker.ts diff --git a/frontend/src/ui/components/Alerts.tsx b/frontend/src/pages/app/ui/components/Alerts.tsx similarity index 100% rename from frontend/src/ui/components/Alerts.tsx rename to frontend/src/pages/app/ui/components/Alerts.tsx diff --git a/frontend/src/ui/components/Auth.tsx b/frontend/src/pages/app/ui/components/Auth.tsx similarity index 100% rename from frontend/src/ui/components/Auth.tsx rename to frontend/src/pages/app/ui/components/Auth.tsx diff --git a/frontend/src/ui/components/Electron.tsx b/frontend/src/pages/app/ui/components/Electron.tsx similarity index 100% rename from frontend/src/ui/components/Electron.tsx rename to frontend/src/pages/app/ui/components/Electron.tsx diff --git a/frontend/src/ui/components/chat/BottomAppBar.tsx b/frontend/src/pages/app/ui/components/chat/BottomAppBar.tsx similarity index 100% rename from frontend/src/ui/components/chat/BottomAppBar.tsx rename to frontend/src/pages/app/ui/components/chat/BottomAppBar.tsx diff --git a/frontend/src/ui/components/chat/ChatHeader.tsx b/frontend/src/pages/app/ui/components/chat/ChatHeader.tsx similarity index 100% rename from frontend/src/ui/components/chat/ChatHeader.tsx rename to frontend/src/pages/app/ui/components/chat/ChatHeader.tsx diff --git a/frontend/src/ui/components/chat/ChatInputWrapper.tsx b/frontend/src/pages/app/ui/components/chat/ChatInputWrapper.tsx similarity index 100% rename from frontend/src/ui/components/chat/ChatInputWrapper.tsx rename to frontend/src/pages/app/ui/components/chat/ChatInputWrapper.tsx diff --git a/frontend/src/ui/components/chat/ChatMainHeader.tsx b/frontend/src/pages/app/ui/components/chat/ChatMainHeader.tsx similarity index 100% rename from frontend/src/ui/components/chat/ChatMainHeader.tsx rename to frontend/src/pages/app/ui/components/chat/ChatMainHeader.tsx diff --git a/frontend/src/ui/components/chat/ChatMessages.tsx b/frontend/src/pages/app/ui/components/chat/ChatMessages.tsx similarity index 100% rename from frontend/src/ui/components/chat/ChatMessages.tsx rename to frontend/src/pages/app/ui/components/chat/ChatMessages.tsx diff --git a/frontend/src/ui/components/chat/ChatTabs.tsx b/frontend/src/pages/app/ui/components/chat/ChatTabs.tsx similarity index 100% rename from frontend/src/ui/components/chat/ChatTabs.tsx rename to frontend/src/pages/app/ui/components/chat/ChatTabs.tsx diff --git a/frontend/src/ui/components/chat/DMUsersList.tsx b/frontend/src/pages/app/ui/components/chat/DMUsersList.tsx similarity index 100% rename from frontend/src/ui/components/chat/DMUsersList.tsx rename to frontend/src/pages/app/ui/components/chat/DMUsersList.tsx diff --git a/frontend/src/ui/components/chat/LeftPanel.tsx b/frontend/src/pages/app/ui/components/chat/LeftPanel.tsx similarity index 100% rename from frontend/src/ui/components/chat/LeftPanel.tsx rename to frontend/src/pages/app/ui/components/chat/LeftPanel.tsx diff --git a/frontend/src/ui/components/chat/Message.tsx b/frontend/src/pages/app/ui/components/chat/Message.tsx similarity index 100% rename from frontend/src/ui/components/chat/Message.tsx rename to frontend/src/pages/app/ui/components/chat/Message.tsx diff --git a/frontend/src/ui/components/chat/MessageContextMenu.tsx b/frontend/src/pages/app/ui/components/chat/MessageContextMenu.tsx similarity index 100% rename from frontend/src/ui/components/chat/MessageContextMenu.tsx rename to frontend/src/pages/app/ui/components/chat/MessageContextMenu.tsx diff --git a/frontend/src/ui/components/chat/MessagePanelRenderer.tsx b/frontend/src/pages/app/ui/components/chat/MessagePanelRenderer.tsx similarity index 100% rename from frontend/src/ui/components/chat/MessagePanelRenderer.tsx rename to frontend/src/pages/app/ui/components/chat/MessagePanelRenderer.tsx diff --git a/frontend/src/ui/components/chat/ReplyMessageDialog.tsx b/frontend/src/pages/app/ui/components/chat/ReplyMessageDialog.tsx similarity index 100% rename from frontend/src/ui/components/chat/ReplyMessageDialog.tsx rename to frontend/src/pages/app/ui/components/chat/ReplyMessageDialog.tsx diff --git a/frontend/src/ui/components/chat/RightPanel.tsx b/frontend/src/pages/app/ui/components/chat/RightPanel.tsx similarity index 100% rename from frontend/src/ui/components/chat/RightPanel.tsx rename to frontend/src/pages/app/ui/components/chat/RightPanel.tsx diff --git a/frontend/src/ui/components/chat/UserProfileDialog.tsx b/frontend/src/pages/app/ui/components/chat/UserProfileDialog.tsx similarity index 100% rename from frontend/src/ui/components/chat/UserProfileDialog.tsx rename to frontend/src/pages/app/ui/components/chat/UserProfileDialog.tsx diff --git a/frontend/src/ui/components/core/Dialog.tsx b/frontend/src/pages/app/ui/components/core/Dialog.tsx similarity index 100% rename from frontend/src/ui/components/core/Dialog.tsx rename to frontend/src/pages/app/ui/components/core/Dialog.tsx diff --git a/frontend/src/ui/components/core/Quote.tsx b/frontend/src/pages/app/ui/components/core/Quote.tsx similarity index 100% rename from frontend/src/ui/components/core/Quote.tsx rename to frontend/src/pages/app/ui/components/core/Quote.tsx diff --git a/frontend/src/ui/components/core/RichTextArea.tsx b/frontend/src/pages/app/ui/components/core/RichTextArea.tsx similarity index 100% rename from frontend/src/ui/components/core/RichTextArea.tsx rename to frontend/src/pages/app/ui/components/core/RichTextArea.tsx diff --git a/frontend/src/ui/components/core/TextField.tsx b/frontend/src/pages/app/ui/components/core/TextField.tsx similarity index 100% rename from frontend/src/ui/components/core/TextField.tsx rename to frontend/src/pages/app/ui/components/core/TextField.tsx diff --git a/frontend/src/ui/components/core/animations/AnimatedHeight.tsx b/frontend/src/pages/app/ui/components/core/animations/AnimatedHeight.tsx similarity index 100% rename from frontend/src/ui/components/core/animations/AnimatedHeight.tsx rename to frontend/src/pages/app/ui/components/core/animations/AnimatedHeight.tsx diff --git a/frontend/src/ui/components/core/animations/AnimatedOpacity.tsx b/frontend/src/pages/app/ui/components/core/animations/AnimatedOpacity.tsx similarity index 100% rename from frontend/src/ui/components/core/animations/AnimatedOpacity.tsx rename to frontend/src/pages/app/ui/components/core/animations/AnimatedOpacity.tsx diff --git a/frontend/src/ui/components/core/animations/types.d.ts b/frontend/src/pages/app/ui/components/core/animations/types.d.ts similarity index 100% rename from frontend/src/ui/components/core/animations/types.d.ts rename to frontend/src/pages/app/ui/components/core/animations/types.d.ts diff --git a/frontend/src/ui/components/profile/CropperDialog.tsx b/frontend/src/pages/app/ui/components/profile/CropperDialog.tsx similarity index 100% rename from frontend/src/ui/components/profile/CropperDialog.tsx rename to frontend/src/pages/app/ui/components/profile/CropperDialog.tsx diff --git a/frontend/src/ui/components/profile/ImageCropper.tsx b/frontend/src/pages/app/ui/components/profile/ImageCropper.tsx similarity index 100% rename from frontend/src/ui/components/profile/ImageCropper.tsx rename to frontend/src/pages/app/ui/components/profile/ImageCropper.tsx diff --git a/frontend/src/ui/components/profile/ProfileDialog.tsx b/frontend/src/pages/app/ui/components/profile/ProfileDialog.tsx similarity index 100% rename from frontend/src/ui/components/profile/ProfileDialog.tsx rename to frontend/src/pages/app/ui/components/profile/ProfileDialog.tsx diff --git a/frontend/src/ui/components/settings/SettingsDialog.tsx b/frontend/src/pages/app/ui/components/settings/SettingsDialog.tsx similarity index 100% rename from frontend/src/ui/components/settings/SettingsDialog.tsx rename to frontend/src/pages/app/ui/components/settings/SettingsDialog.tsx diff --git a/frontend/src/ui/hooks/useChat.ts b/frontend/src/pages/app/ui/hooks/useChat.ts similarity index 100% rename from frontend/src/ui/hooks/useChat.ts rename to frontend/src/pages/app/ui/hooks/useChat.ts diff --git a/frontend/src/ui/hooks/useCombinedRefs.ts b/frontend/src/pages/app/ui/hooks/useCombinedRefs.ts similarity index 100% rename from frontend/src/ui/hooks/useCombinedRefs.ts rename to frontend/src/pages/app/ui/hooks/useCombinedRefs.ts diff --git a/frontend/src/ui/hooks/useDM.ts b/frontend/src/pages/app/ui/hooks/useDM.ts similarity index 100% rename from frontend/src/ui/hooks/useDM.ts rename to frontend/src/pages/app/ui/hooks/useDM.ts diff --git a/frontend/src/ui/hooks/useProfile.ts b/frontend/src/pages/app/ui/hooks/useProfile.ts similarity index 100% rename from frontend/src/ui/hooks/useProfile.ts rename to frontend/src/pages/app/ui/hooks/useProfile.ts diff --git a/frontend/src/ui/hooks/useWindowSize.ts b/frontend/src/pages/app/ui/hooks/useWindowSize.ts similarity index 100% rename from frontend/src/ui/hooks/useWindowSize.ts rename to frontend/src/pages/app/ui/hooks/useWindowSize.ts diff --git a/frontend/src/ui/panels/DMPanel.ts b/frontend/src/pages/app/ui/panels/DMPanel.ts similarity index 100% rename from frontend/src/ui/panels/DMPanel.ts rename to frontend/src/pages/app/ui/panels/DMPanel.ts diff --git a/frontend/src/ui/panels/MessagePanel.ts b/frontend/src/pages/app/ui/panels/MessagePanel.ts similarity index 100% rename from frontend/src/ui/panels/MessagePanel.ts rename to frontend/src/pages/app/ui/panels/MessagePanel.ts diff --git a/frontend/src/ui/panels/PublicChatPanel.ts b/frontend/src/pages/app/ui/panels/PublicChatPanel.ts similarity index 100% rename from frontend/src/ui/panels/PublicChatPanel.ts rename to frontend/src/pages/app/ui/panels/PublicChatPanel.ts diff --git a/frontend/src/ui/screen/ChatScreen.tsx b/frontend/src/pages/app/ui/screen/ChatScreen.tsx similarity index 100% rename from frontend/src/ui/screen/ChatScreen.tsx rename to frontend/src/pages/app/ui/screen/ChatScreen.tsx diff --git a/frontend/src/ui/screen/DownloadAppScreen.tsx b/frontend/src/pages/app/ui/screen/DownloadAppScreen.tsx similarity index 100% rename from frontend/src/ui/screen/DownloadAppScreen.tsx rename to frontend/src/pages/app/ui/screen/DownloadAppScreen.tsx diff --git a/frontend/src/ui/screen/LoginScreen.tsx b/frontend/src/pages/app/ui/screen/LoginScreen.tsx similarity index 100% rename from frontend/src/ui/screen/LoginScreen.tsx rename to frontend/src/pages/app/ui/screen/LoginScreen.tsx diff --git a/frontend/src/ui/screen/RegisterScreen.tsx b/frontend/src/pages/app/ui/screen/RegisterScreen.tsx similarity index 100% rename from frontend/src/ui/screen/RegisterScreen.tsx rename to frontend/src/pages/app/ui/screen/RegisterScreen.tsx diff --git a/frontend/src/ui/state.ts b/frontend/src/pages/app/ui/state.ts similarity index 100% rename from frontend/src/ui/state.ts rename to frontend/src/pages/app/ui/state.ts diff --git a/frontend/src/utils/crypto/asymmetric.ts b/frontend/src/pages/app/utils/crypto/asymmetric.ts similarity index 93% rename from frontend/src/utils/crypto/asymmetric.ts rename to frontend/src/pages/app/utils/crypto/asymmetric.ts index 09b8dfa..72bf39f 100644 --- a/frontend/src/utils/crypto/asymmetric.ts +++ b/frontend/src/pages/app/utils/crypto/asymmetric.ts @@ -1,5 +1,5 @@ import nacl from "tweetnacl"; -import { hkdfExtractAndExpand } from "../crypto/kdf"; +import { hkdfExtractAndExpand } from "./kdf"; export interface X25519KeyPair { publicKey: Uint8Array; diff --git a/frontend/src/utils/crypto/backup.ts b/frontend/src/pages/app/utils/crypto/backup.ts similarity index 100% rename from frontend/src/utils/crypto/backup.ts rename to frontend/src/pages/app/utils/crypto/backup.ts diff --git a/frontend/src/utils/crypto/kdf.ts b/frontend/src/pages/app/utils/crypto/kdf.ts similarity index 100% rename from frontend/src/utils/crypto/kdf.ts rename to frontend/src/pages/app/utils/crypto/kdf.ts diff --git a/frontend/src/utils/crypto/symmetric.ts b/frontend/src/pages/app/utils/crypto/symmetric.ts similarity index 100% rename from frontend/src/utils/crypto/symmetric.ts rename to frontend/src/pages/app/utils/crypto/symmetric.ts diff --git a/frontend/src/utils/material.ts b/frontend/src/pages/app/utils/material.ts similarity index 100% rename from frontend/src/utils/material.ts rename to frontend/src/pages/app/utils/material.ts diff --git a/frontend/src/utils/notification.ts b/frontend/src/pages/app/utils/notification.ts similarity index 100% rename from frontend/src/utils/notification.ts rename to frontend/src/pages/app/utils/notification.ts diff --git a/frontend/src/utils/push-notifications.ts b/frontend/src/pages/app/utils/push-notifications.ts similarity index 100% rename from frontend/src/utils/push-notifications.ts rename to frontend/src/pages/app/utils/push-notifications.ts diff --git a/frontend/src/utils/utils.ts b/frontend/src/pages/app/utils/utils.ts similarity index 100% rename from frontend/src/utils/utils.ts rename to frontend/src/pages/app/utils/utils.ts From 2995bc96acfca7ec33b5bbdafe4b3098bfeb4e92 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Thu, 2 Oct 2025 18:24:11 +0300 Subject: [PATCH 2/5] Use proper Vite syntax --- .../src/pages/app/utils/push-notifications.ts | 3 +- frontend/vite.config.ts | 46 +------------------ 2 files changed, 4 insertions(+), 45 deletions(-) diff --git a/frontend/src/pages/app/utils/push-notifications.ts b/frontend/src/pages/app/utils/push-notifications.ts index eb8cf5d..2696d30 100644 --- a/frontend/src/pages/app/utils/push-notifications.ts +++ b/frontend/src/pages/app/utils/push-notifications.ts @@ -2,6 +2,7 @@ import { API_BASE_URL } from "../core/config"; import { isElectron } from "../electron/electron"; import { websocket } from "../core/websocket"; import type { NewMessageWebSocketMessage, WebSocketMessage } from "../core/types"; +import serviceWorker from "../service-worker/service-worker.ts?worker&url"; export interface PushSubscriptionData { endpoint: string; @@ -152,7 +153,7 @@ export async function initialize(): Promise { } try { - registration = await navigator.serviceWorker.register("/assets/serviceWorker.js"); + registration = await navigator.serviceWorker.register(serviceWorker, { type: "module" }); console.log("Service Worker registered successfully"); const permission = await Notification.requestPermission(); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1386a83..66e9e17 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -5,8 +5,6 @@ import electron from "vite-plugin-electron/simple"; import react from "@vitejs/plugin-react"; import { resolve } from "path"; -const serviceWorkerPath = resolve(__dirname, "src/service-worker/service-worker.ts"); - const plugins: PluginOption[] = [ react(), createHtmlPlugin({ @@ -20,31 +18,7 @@ const plugins: PluginOption[] = [ minifyCSS: true, minifyJS: true } - }), - { - name: 'service-worker-redirect', - configureServer(server) { - server.middlewares.use(async (req, res, next) => { - if (req.url === '/assets/serviceWorker.js') { - try { - const traspiled = await server.transformRequest(serviceWorkerPath); - - res.writeHead(200, { - 'Content-Type': "application/javascript" - }); - res.end(traspiled!.code); - } catch (e) { - try { - res.writeHead(500); - res.end(); - } catch (e) {} - } - } else { - next(); - } - }); - } - } + }) ] if (process.env.VITE_ELECTRON) { @@ -107,22 +81,6 @@ export default defineConfig({ cssMinify: true, assetsInlineLimit: 0, outDir: process.env.VITE_ELECTRON ? "build/electron/dist" : "build/normal/dist", - chunkSizeWarningLimit: 1024, - rollupOptions: { - input: { - main: resolve(__dirname, "index.html"), - serviceWorker: serviceWorkerPath - }, - output: { - entryFileNames: (chunkInfo) => { - // Проверяем имя чанка - if (chunkInfo.name === 'serviceWorker') { - return 'assets/serviceWorker.js'; // Указываем фиксированное имя для этого скрипта - } - // Для остальных файлов используем стандартное именование с хэшем - return 'assets/[name]-[hash].js'; - } - } - } + chunkSizeWarningLimit: 1024 } }); \ No newline at end of file From aa1fb8c5188a60025f26b577ee68ad90ea3608b5 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Thu, 2 Oct 2025 18:35:29 +0300 Subject: [PATCH 3/5] Migrate to React Router --- frontend/src/App.tsx | 67 ++++---- frontend/src/main.tsx | 2 +- frontend/src/pages/ChatPage.tsx | 13 ++ frontend/src/pages/DownloadAppPage.tsx | 25 +++ frontend/src/pages/LoginPage.tsx | 142 +++++++++++++++++ frontend/src/pages/NotFoundPage.tsx | 36 +++++ frontend/src/pages/ProtectedRoute.tsx | 21 +++ frontend/src/pages/RegisterPage.tsx | 147 ++++++++++++++++++ .../src/pages/app/resources/css/_404.scss | 84 ++++++++++ .../src/pages/app/resources/css/style.scss | 1 + .../src/pages/app/ui/screen/LoginScreen.tsx | 7 +- .../pages/app/ui/screen/RegisterScreen.tsx | 7 +- frontend/src/pages/app/ui/state.ts | 15 +- package.json | 1 + 14 files changed, 514 insertions(+), 54 deletions(-) create mode 100644 frontend/src/pages/ChatPage.tsx create mode 100644 frontend/src/pages/DownloadAppPage.tsx create mode 100644 frontend/src/pages/LoginPage.tsx create mode 100644 frontend/src/pages/NotFoundPage.tsx create mode 100644 frontend/src/pages/ProtectedRoute.tsx create mode 100644 frontend/src/pages/RegisterPage.tsx create mode 100644 frontend/src/pages/app/resources/css/_404.scss diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d403a77..275b2e0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,50 +1,47 @@ -import { MINIMUM_WIDTH } from "./pages/app/core/config"; -import { isElectron } from "./pages/app/electron/electron"; +import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import { ElectronTitleBar } from "./pages/app/ui/components/Electron"; -import useWindowSize from "./pages/app/ui/hooks/useWindowSize"; -import ChatScreen from "./pages/app/ui/screen/ChatScreen"; -import DownloadAppScreen from "./pages/app/ui/screen/DownloadAppScreen"; -import LoginScreen from "./pages/app/ui/screen/LoginScreen"; -import RegisterScreen from "./pages/app/ui/screen/RegisterScreen"; import { useAppState } from "./pages/app/ui/state"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; +import LoginPage from "./pages/LoginPage"; +import RegisterPage from "./pages/RegisterPage"; +import ChatPage from "./pages/ChatPage"; +import NotFoundPage from "./pages/NotFoundPage"; +import ProtectedRoute from "./pages/ProtectedRoute"; +import { isElectron } from "./pages/app/electron/electron"; +import { MINIMUM_WIDTH } from "./pages/app/core/config"; +import useWindowSize from "./pages/app/ui/hooks/useWindowSize"; export default function App() { - const { currentPage, restoreUserFromStorage } = useAppState(); + const { restoreUserFromStorage } = useAppState(); const { width } = useWindowSize(); + const [authReady, setAuthReady] = useState(false); // Restore user from localStorage on app initialization useEffect(() => { - restoreUserFromStorage(); + restoreUserFromStorage().finally(() => { + setAuthReady(true); + }); }, [restoreUserFromStorage]); - if (!isElectron && width < MINIMUM_WIDTH) { - return - } - - let page = ; - - switch (currentPage) { - case "login": { - page = - break; - } - case "register": { - page = - break; - } - case "chat": { - page = - break; - } - } - - return ( - <> + return authReady && ( + + {!isElectron && width < MINIMUM_WIDTH && }
- {page} + + } /> + } /> + } /> + + + + + } /> + + } /> +
- +
) } \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 572bfd4..2296930 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,7 +5,7 @@ * @version 1.0.0 */ -import './resources/css/style.scss'; +import './pages/app/resources/css/style.scss'; import "mdui/mdui.css"; import "./pages/app/utils/material"; diff --git a/frontend/src/pages/ChatPage.tsx b/frontend/src/pages/ChatPage.tsx new file mode 100644 index 0000000..0989a42 --- /dev/null +++ b/frontend/src/pages/ChatPage.tsx @@ -0,0 +1,13 @@ +import { LeftPanel } from "./app/ui/components/chat/LeftPanel"; +import { RightPanel } from "./app/ui/components/chat/RightPanel"; + +export default function ChatPage() { + return ( +
+
+ + +
+
+ ); +} diff --git a/frontend/src/pages/DownloadAppPage.tsx b/frontend/src/pages/DownloadAppPage.tsx new file mode 100644 index 0000000..3e8ec2e --- /dev/null +++ b/frontend/src/pages/DownloadAppPage.tsx @@ -0,0 +1,25 @@ +export default function DownloadAppPage() { + return ( +
+
+

Чтобы пользоваться мессенджером, скачайте приложение

+

+ Этот сайт не предназначен для работы на маленьких экранах, поэтому + вам нужно скачать приложение мессенджера. +

+ + + Скачать на GitHub + + +

+ Если возникнут сложности или есть вопросы, нажмите кнопку! +

+ + + Написать в поддержку + +
+
+ ) +} diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx new file mode 100644 index 0000000..0fdb5b4 --- /dev/null +++ b/frontend/src/pages/LoginPage.tsx @@ -0,0 +1,142 @@ +import { useImmer } from "use-immer"; +import { AlertsContainer, type Alert, type AlertType } from "./app/ui/components/Alerts"; +import { AuthContainer, AuthHeader } from "./app/ui/components/Auth"; +import type { ErrorResponse, LoginRequest, LoginResponse } from "./app/core/types"; +import { ensureKeysOnLogin } from "./app/auth/crypto"; +import { API_BASE_URL } from "./app/core/config"; +import { useRef } from "react"; +import type { TextField } from "mdui/components/text-field"; +import { useAppState } from "./app/ui/state"; +import { MaterialTextField } from "./app/ui/components/core/TextField"; +import { initialize, isSupported, startElectronReceiver, subscribe } from "./app/utils/push-notifications"; +import { isElectron } from "./app/electron/electron"; +import { useNavigate } from "react-router-dom"; + +export default function LoginPage() { + const [alerts, updateAlerts] = useImmer([]); + const setUser = useAppState(state => state.setUser); + const navigate = useNavigate(); + + function showAlert(type: AlertType, message: string) { + updateAlerts((alerts) => { alerts.push({type: type, message: message}) }); + } + + const usernameElement = useRef(null); + const passwordElement = useRef(null); + + return ( + + +
+ + +
{ + e.preventDefault(); + + const username = usernameElement.current!.value.trim(); + const password = passwordElement.current!.value.trim(); + + if (!username || !password) { + showAlert("danger", "Пожалуйста, заполните все поля"); + return; + } + + try { + const request: LoginRequest = { + username: username, + password: password + } + + const response = await fetch(`${API_BASE_URL}/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request) + }); + + if (response.ok) { + const data: LoginResponse = await response.json(); + // Store the JWT token first + setUser(data.token, data.user); + + // Setup keys with the token we just received + try { + await ensureKeysOnLogin(password, data.token); + } catch (e) { + console.error("Key setup failed:", e); + } + + navigate("/chat"); + + // Initialize notifications + try { + if (isSupported()) { + const initialized = await initialize(); + if (initialized) { + await subscribe(data.token); + + // For Electron, start the notification receiver + if (isElectron) { + await startElectronReceiver(); + } + + console.log("Notifications enabled"); + } else { + console.log("Notification permission denied"); + } + } else { + console.log("Notifications not supported"); + } + } catch (e) { + console.error("Notification setup failed:", e); + } + } else { + const data: ErrorResponse = await response.json(); + showAlert("danger", data.message || "Неверное имя пользователя или пароль"); + } + } catch (error) { + showAlert("danger", "Ошибка соединения с сервером"); + } + }}> + + + + + Войти + + + +
+
+ ) +} diff --git a/frontend/src/pages/NotFoundPage.tsx b/frontend/src/pages/NotFoundPage.tsx new file mode 100644 index 0000000..907119f --- /dev/null +++ b/frontend/src/pages/NotFoundPage.tsx @@ -0,0 +1,36 @@ +import { useNavigate } from "react-router-dom"; + +export default function NotFoundPage() { + const navigate = useNavigate(); + + return ( +
+
+
+
404
+

Страница не найдена

+

+ К сожалению, запрашиваемая страница не существует или была перемещена. +

+
+ navigate("/")} + > + На главную + + navigate(-1)} + > + Назад + +
+
+
+ +
+
+
+ ); +} diff --git a/frontend/src/pages/ProtectedRoute.tsx b/frontend/src/pages/ProtectedRoute.tsx new file mode 100644 index 0000000..40c4652 --- /dev/null +++ b/frontend/src/pages/ProtectedRoute.tsx @@ -0,0 +1,21 @@ +import { useEffect } from "react"; +import { useAppState } from "./app/ui/state"; +import { useNavigate } from "react-router-dom"; + +interface ProtectedRouteProps { + children: React.ReactNode; +} + +export default function ProtectedRoute({ children }: ProtectedRouteProps) { + const { user } = useAppState(); + const navigate = useNavigate(); + + useEffect(() => { + if (!user.authToken) { + navigate("/login"); + return; + } + }, [user.authToken, user.currentUser, navigate]); + + return <>{children}; +} diff --git a/frontend/src/pages/RegisterPage.tsx b/frontend/src/pages/RegisterPage.tsx new file mode 100644 index 0000000..2e437e1 --- /dev/null +++ b/frontend/src/pages/RegisterPage.tsx @@ -0,0 +1,147 @@ +import { useImmer } from "use-immer"; +import { AuthContainer, AuthHeader } from "./app/ui/components/Auth"; +import { AlertsContainer, type Alert, type AlertType } from "./app/ui/components/Alerts"; +import { useRef } from "react"; +import { TextField } from "mdui/components/text-field"; +import type { ErrorResponse, RegisterRequest, LoginResponse } from "./app/core/types"; +import { API_BASE_URL } from "./app/core/config"; +import { useAppState } from "./app/ui/state"; +import { MaterialTextField } from "./app/ui/components/core/TextField"; +import { ensureKeysOnLogin } from "./app/auth/crypto"; +import { useNavigate } from "react-router-dom"; + +export default function RegisterPage() { + const [alerts, updateAlerts] = useImmer([]); + const setUser = useAppState(state => state.setUser); + const navigate = useNavigate(); + + function showAlert(type: AlertType, message: string) { + updateAlerts((alerts) => { alerts.push({type: type, message: message}) }); + } + + const usernameElement = useRef(null); + const passwordElement = useRef(null); + const confirmPasswordElement = useRef(null); + + return ( + + +
+ + +
{ + e.preventDefault(); + + const username = usernameElement.current!.value.trim(); + const password = passwordElement.current!.value.trim(); + const confirmPassword = confirmPasswordElement.current!.value.trim(); + + if (!username || !password || !confirmPassword) { + showAlert("danger", "Пожалуйста, заполните все поля"); + return; + } + + if (password !== confirmPassword) { + showAlert("danger", "Пароли не совпадают"); + return; + } + + if (username.length < 3 || username.length > 20) { + showAlert("danger", "Имя пользователя должно быть от 3 до 20 символов"); + return; + } + + if (password.length < 5 || password.length > 50) { + showAlert("danger", "Пароль должен быть от 5 до 50 символов"); + return; + } + + try { + const request: RegisterRequest = { + username: username, + password: password, + confirm_password: confirmPassword + } + + const response = await fetch(`${API_BASE_URL}/register`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request) + }); + + if (response.ok) { + const data: LoginResponse = await response.json(); + // Store the JWT token first + setUser(data.token, data.user); + + // Setup keys with the token we just received + try { + await ensureKeysOnLogin(password, data.token); + } catch (e) { + console.error("Key setup failed:", e); + } + + navigate("/chat"); + } else { + const data: ErrorResponse = await response.json(); + showAlert("danger", data.message || "Ошибка при регистрации"); + } + } catch (error) { + showAlert("danger", "Ошибка соединения с сервером"); + } + }}> + + + + + Зарегистрироваться + + +
+

+ Уже есть аккаунт? + navigate("/login")}> + Войдите + +

+
+
+
+ ) +} diff --git a/frontend/src/pages/app/resources/css/_404.scss b/frontend/src/pages/app/resources/css/_404.scss new file mode 100644 index 0000000..d91252f --- /dev/null +++ b/frontend/src/pages/app/resources/css/_404.scss @@ -0,0 +1,84 @@ +.not-found-page { + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 2rem; +} + +.not-found-container { + display: flex; + align-items: center; + gap: 3rem; + max-width: 800px; + width: 100%; + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + padding: 3rem; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); +} + +.not-found-content { + flex: 1; +} + +.error-code { + font-size: 6rem; + font-weight: 900; + color: #667eea; + line-height: 1; + margin-bottom: 1rem; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); +} + +.not-found-content h1 { + font-size: 2.5rem; + font-weight: 700; + color: #2d3748; + margin-bottom: 1rem; + line-height: 1.2; +} + +.not-found-content p { + font-size: 1.1rem; + color: #718096; + margin-bottom: 2rem; + line-height: 1.6; +} + +.not-found-actions { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.not-found-illustration { + display: flex; + align-items: center; + justify-content: center; + color: #667eea; + opacity: 0.7; +} + +@media (max-width: 768px) { + .not-found-container { + flex-direction: column; + text-align: center; + gap: 2rem; + padding: 2rem; + } + + .error-code { + font-size: 4rem; + } + + .not-found-content h1 { + font-size: 2rem; + } + + .not-found-actions { + justify-content: center; + } +} diff --git a/frontend/src/pages/app/resources/css/style.scss b/frontend/src/pages/app/resources/css/style.scss index 4cfe673..3e06041 100644 --- a/frontend/src/pages/app/resources/css/style.scss +++ b/frontend/src/pages/app/resources/css/style.scss @@ -10,6 +10,7 @@ @use "electron"; @use "dialogs/reply"; @use "download-app"; +@use "404" as not-found; @use "lib/fonts/montserrat"; @use "lib/fonts/material-symbols"; diff --git a/frontend/src/pages/app/ui/screen/LoginScreen.tsx b/frontend/src/pages/app/ui/screen/LoginScreen.tsx index 06df52f..168b0d8 100644 --- a/frontend/src/pages/app/ui/screen/LoginScreen.tsx +++ b/frontend/src/pages/app/ui/screen/LoginScreen.tsx @@ -7,14 +7,15 @@ import { API_BASE_URL } from "../../core/config"; import { useRef } from "react"; import type { TextField } from "mdui/components/text-field"; import { useAppState } from "../state"; +import { useNavigate } from "react-router-dom"; import { MaterialTextField } from "../components/core/TextField"; import { initialize, isSupported, startElectronReceiver, subscribe } from "../../utils/push-notifications"; import { isElectron } from "../../electron/electron"; export default function LoginScreen() { const [alerts, updateAlerts] = useImmer([]); - const setCurrentPage = useAppState(state => state.setCurrentPage); const setUser = useAppState(state => state.setUser); + const navigate = useNavigate(); function showAlert(type: AlertType, message: string) { updateAlerts((alerts) => { alerts.push({type: type, message: message}) }); @@ -67,7 +68,7 @@ export default function LoginScreen() { console.error("Key setup failed:", e); } - setCurrentPage("chat"); + navigate("/chat"); // Initialize notifications try { @@ -130,7 +131,7 @@ export default function LoginScreen() { setCurrentPage("register")}> + onClick={() => navigate("/register")}> Зарегистрируйтесь

diff --git a/frontend/src/pages/app/ui/screen/RegisterScreen.tsx b/frontend/src/pages/app/ui/screen/RegisterScreen.tsx index a93efd3..ec16814 100644 --- a/frontend/src/pages/app/ui/screen/RegisterScreen.tsx +++ b/frontend/src/pages/app/ui/screen/RegisterScreen.tsx @@ -7,13 +7,14 @@ import { TextField } from "mdui/components/text-field"; import type { ErrorResponse, RegisterRequest, LoginResponse } from "../../core/types"; import { API_BASE_URL } from "../../core/config"; import { useAppState } from "../state"; +import { useNavigate } from "react-router-dom"; import { MaterialTextField } from "../components/core/TextField"; import { ensureKeysOnLogin } from "../../auth/crypto"; export default function RegisterScreen() { const [alerts, updateAlerts] = useImmer([]); - const setCurrentPage = useAppState(state => state.setCurrentPage); const setUser = useAppState(state => state.setUser); + const navigate = useNavigate(); function showAlert(type: AlertType, message: string) { updateAlerts((alerts) => { alerts.push({type: type, message: message}) }); @@ -83,7 +84,7 @@ export default function RegisterScreen() { console.error("Key setup failed:", e); } - setCurrentPage("chat"); + navigate("/chat"); } else { const data: ErrorResponse = await response.json(); showAlert("danger", data.message || "Ошибка при регистрации"); @@ -136,7 +137,7 @@ export default function RegisterScreen() { href="#" id="login-link" className="link" - onClick={() => setCurrentPage("login")}> + onClick={() => navigate("/login")}> Войдите

diff --git a/frontend/src/pages/app/ui/state.ts b/frontend/src/pages/app/ui/state.ts index bf31eb4..c5db87e 100644 --- a/frontend/src/pages/app/ui/state.ts +++ b/frontend/src/pages/app/ui/state.ts @@ -10,7 +10,6 @@ import { API_BASE_URL } from "../core/config"; import { initialize, subscribe, startElectronReceiver, isSupported } from "../utils/push-notifications"; import { isElectron } from "../electron/electron"; -type Page = "login" | "register" | "chat" export type ChatTabs = "chats" | "channels" | "contacts" | "dms" interface ActiveDM { @@ -37,9 +36,6 @@ export interface UserState { } interface AppState { - currentPage: Page; - setCurrentPage: (page: Page) => void; - // Chat state chat: ChatState; addMessage: (message: Message) => void; @@ -60,13 +56,10 @@ interface AppState { user: UserState; setUser: (token: string, user: User) => void; logout: () => void; - restoreUserFromStorage: () => void; + restoreUserFromStorage: () => Promise; } export const useAppState = create((set, get) => ({ - currentPage: "login", // default page - setCurrentPage: (page: Page) => set({ currentPage: page }), - // Chat state chat: { messages: [], @@ -191,8 +184,7 @@ export const useAppState = create((set, get) => ({ user: { currentUser: null, authToken: null - }, - currentPage: "login" + } })); }, restoreUserFromStorage: async () => { @@ -212,8 +204,7 @@ export const useAppState = create((set, get) => ({ user: { currentUser: user, authToken: token - }, - currentPage: "chat" + } })); try { diff --git a/package.json b/package.json index 7a1dd91..ab204c5 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "mdui": "^2.1.4", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-router-dom": "^7.9.3", "tweetnacl": "^1.0.3", "use-immer": "^0.11.0", "zustand": "^5.0.8" From 10c2aaa6caa92f3e818ee6e1803aa008bcda8ef1 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Thu, 2 Oct 2025 19:36:39 +0300 Subject: [PATCH 4/5] Implement the new homepage --- frontend/src/App.tsx | 5 +- frontend/src/pages/HomePage.tsx | 267 +++++++ .../pages/app/resources/css/_homepage.scss | 657 ++++++++++++++++++ .../src/pages/app/resources/css/style.scss | 1 + 4 files changed, 929 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/HomePage.tsx create mode 100644 frontend/src/pages/app/resources/css/_homepage.scss diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 275b2e0..8177b51 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,9 +2,11 @@ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import { ElectronTitleBar } from "./pages/app/ui/components/Electron"; import { useAppState } from "./pages/app/ui/state"; import { useEffect, useState } from "react"; +import HomePage from "./pages/HomePage"; import LoginPage from "./pages/LoginPage"; import RegisterPage from "./pages/RegisterPage"; import ChatPage from "./pages/ChatPage"; +import DownloadAppPage from "./pages/DownloadAppPage"; import NotFoundPage from "./pages/NotFoundPage"; import ProtectedRoute from "./pages/ProtectedRoute"; import { isElectron } from "./pages/app/electron/electron"; @@ -29,9 +31,10 @@ export default function App() {
- } /> + } /> } /> } /> + } /> diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx new file mode 100644 index 0000000..4ab16a4 --- /dev/null +++ b/frontend/src/pages/HomePage.tsx @@ -0,0 +1,267 @@ +import { useNavigate } from "react-router-dom"; +import { useAppState } from "./app/ui/state"; + +function GitHubLink({ children }: { children: React.ReactNode }) { + return ( + {children} + ); +} + +function SupportLink({ children }: { children: React.ReactNode }) { + return ( + {children} + ); +} + +export default function HomePage() { + const navigate = useNavigate(); + const { user } = useAppState(); + + const handleGetStarted = () => { + if (user.authToken && user.currentUser) { + navigate("/chat"); + } else { + navigate("/login"); + } + }; + + return ( +
+
+
+
+
+

FromChat

+ 100% открытый мессенджер +
+ +
+
+
+ +
+
+
+
+

+ Безопасный мессенджер с открытым исходным кодом +

+

+ FromChat — это полностью открытый мессенджер с end-to-end шифрованием, + поддержкой файлов и уведомлений. Создан для тех, кто ценит приватность и свободу. +

+
+ + {user.authToken ? "Перейти в чат" : "Начать общение"} + + navigate("/register")} + > + Зарегистрироваться + +
+
+
+
+
+
+
Общий чат
+
+
+
+
+
А
+
+
Привет! Как дела?
+
14:30
+
+
+
+
+
Всё отлично! А у тебя как?
+
14:32
+
+
+
+
Б
+
+
Отправляю файл 📎
+
14:35
+
+
+
+
+
+
+
+
+ +
+
+

Возможности

+
+
+
+ +
+

End-to-End Шифрование

+

+ Ваши личные сообщения защищены современным шифрованием X25519 + AES-GCM. + Только вы и получатель можете прочитать сообщения. +

+
+ +
+
+ +
+

100% открытый код

+

+ Весь исходный код доступен на GitHub. Вы можете проверить безопасность, + внести изменения или развернуть свой сервер. +

+
+ +
+
+ +
+

Обмен Файлами

+

+ Отправляйте файлы до 4 ГБ. Файлы в личных сообщениях шифруются. + В общем чате шифрования нет, так как ваши сообщения могут читать все пользователи FromChat. +

+
+ +
+
+ +
+

Уведомления

+

+ Получайте push-уведомления в браузере и настольном приложении. + Никогда не пропустите важное сообщение. +

+
+ +
+
+ +
+

Редактирование

+

+ Редактируйте и удаляйте свои сообщения. Отвечайте на сообщения + для лучшего контекста общения. +

+
+ +
+
+ +
+

Кроссплатформенность

+

+ Работает в браузере и как настольное приложение для Windows, + macOS и Linux. Единый интерфейс везде. +

+
+
+
+
+ +
+
+
+

Скачайте приложение

+

+ Для лучшего опыта используйте настольное приложение с поддержкой + уведомлений и автономной работы. +

+
+ + + + Скачать для ПК + + + navigate("/login")}> + + Веб-версия + +
+
+
+
+ +
+
+
+

Готовы начать общение?

+

+ Присоединяйтесь к FromChat и общайтесь безопасно с друзьями и коллегами. +

+
+ navigate("/register")} + > + Создать аккаунт + + navigate("/login")} + > + Войти + +
+
+
+
+
+ +
+
+
+
+

Ссылки

+ GitHub + Поддержка +
+
+

Лицензия

+

GPL-3.0

+
+
+
+

© 2025 FromChat. Сделано программистом denis0001-dev с ❤️ для свободы общения.

+
+
+
+
+ ); +} diff --git a/frontend/src/pages/app/resources/css/_homepage.scss b/frontend/src/pages/app/resources/css/_homepage.scss new file mode 100644 index 0000000..15aee92 --- /dev/null +++ b/frontend/src/pages/app/resources/css/_homepage.scss @@ -0,0 +1,657 @@ +@use "common/material" as *; + +.homepage { + min-height: 100vh; + background: linear-gradient(135deg, $color-dark-background 0%, $color-dark-surface-container 50%, $color-dark-primary-container 100%); + color: $color-dark-on-background; + font-family: 'Montserrat', sans-serif; + position: relative; + + &::before { + content: ''; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: + radial-gradient(circle at 20% 80%, rgba($color-dark-primary, 0.3) 0%, transparent 50%), + radial-gradient(circle at 80% 20%, rgba($color-dark-tertiary, 0.3) 0%, transparent 50%), + radial-gradient(circle at 40% 40%, rgba($color-dark-secondary, 0.2) 0%, transparent 50%); + pointer-events: none; + z-index: 0; + } + + // Cascaded styles for all child elements + * { + position: relative; + z-index: 1; + } + + // Container styles + .container { + max-width: 1200px; + margin: 0 auto; + padding: 0 2rem; + } + + // Header styles + .homepage-header { + padding: 1rem 0; + background: rgba($color-dark-surface-container, 0.8); + backdrop-filter: blur(20px); + border-bottom: 1px solid rgba($color-dark-primary, 0.3); + box-shadow: 0 4px 20px rgba($color-dark-primary, 0.1); + position: sticky; + top: 0; + z-index: 1000; + transition: all 0.3s ease; + + .header-content { + display: flex; + justify-content: space-between; + align-items: center; + + .logo { + h1 { + font-size: 2rem; + font-weight: 700; + margin: 0; + background: linear-gradient(45deg, $color-dark-primary, $color-dark-tertiary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 0 20px rgba($color-dark-primary, 0.5); + } + + .tagline { + font-size: 0.9rem; + } + } + + .header-nav { + display: flex; + align-items: center; + gap: 10px; + + a { + display: flex; + } + } + } + } + + // Hero section + .hero { + padding: 4rem 0; + display: flex; + align-items: center; + min-height: 80vh; + margin-top: 0; + + .hero-content { + flex: 1; + max-width: 600px; + + .hero-title { + font-size: 3.5rem; + font-weight: 800; + line-height: 1.1; + margin-bottom: 1.5rem; + background: linear-gradient(45deg, $color-dark-primary, $color-dark-tertiary, $color-dark-secondary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 0 30px rgba($color-dark-primary, 0.5); + animation: neonGlow 3s ease-in-out infinite alternate; + } + + .hero-description { + font-size: 1.25rem; + line-height: 1.6; + margin-bottom: 2.5rem; + opacity: 0.9; + } + + .hero-actions { + display: flex; + gap: 1rem; + flex-wrap: wrap; + } + } + + .hero-visual { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + padding: 2rem; + + .chat-preview { + perspective: 1000px; + + .chat-window { + background: rgba($color-dark-surface-container, 0.95); + border-radius: 20px; + padding: 1.5rem; + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.5), + 0 0 20px rgba($color-dark-primary, 0.3), + inset 0 1px 0 rgba($color-dark-primary, 0.2); + transform: rotateY(-5deg) rotateX(5deg); + color: $color-dark-on-surface; + max-width: 400px; + width: 100%; + border: 1px solid rgba($color-dark-primary, 0.3); + + .chat-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 1rem; + border-bottom: 1px solid rgba($color-dark-primary, 0.3); + margin-bottom: 1rem; + + .chat-title { + font-weight: 600; + font-size: 1.1rem; + } + + .online-indicator { + color: $color-dark-primary; + font-size: 0.8rem; + text-shadow: 0 0 10px rgba($color-dark-primary, 0.8); + animation: pulse 2s ease-in-out infinite; + } + } + + .chat-messages { + display: flex; + flex-direction: column; + gap: 1rem; + + .message { + display: flex; + gap: 0.75rem; + align-items: flex-start; + + &.sent { + flex-direction: row-reverse; + + .message-content { + background: linear-gradient(135deg, $color-dark-primary, $color-dark-primary-container); + color: $color-dark-on-primary; + box-shadow: 0 0 15px rgba($color-dark-primary, 0.3); + } + } + + &.received { + .message-content { + background: rgba($color-dark-surface-variant, 0.8); + color: $color-dark-on-surface-variant; + border: 1px solid rgba($color-dark-outline-variant, 0.3); + } + } + + .message-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: linear-gradient(135deg, $color-dark-primary, $color-dark-tertiary); + color: $color-dark-on-primary; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 0.8rem; + flex-shrink: 0; + box-shadow: 0 0 10px rgba($color-dark-primary, 0.4); + } + + .message-content { + max-width: 70%; + padding: 0.75rem 1rem; + border-radius: 18px; + position: relative; + + .message-text { + font-size: 0.9rem; + line-height: 1.4; + } + + .message-time { + font-size: 0.75rem; + opacity: 0.7; + margin-top: 0.25rem; + } + } + } + } + } + } + } + } + + // Features section + .features { + padding: 6rem 0; + background: rgba($color-dark-surface-container, 0.3); + backdrop-filter: blur(20px); + border-top: 1px solid rgba($color-dark-primary, 0.2); + border-bottom: 1px solid rgba($color-dark-primary, 0.2); + + .section-title { + text-align: center; + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 3rem; + background: linear-gradient(45deg, $color-dark-primary, $color-dark-tertiary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 0 20px rgba($color-dark-primary, 0.5); + } + + .features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 2rem; + + .feature-card { + background: rgba($color-dark-surface-container, 0.6); + backdrop-filter: blur(20px); + border-radius: 20px; + padding: 2rem; + border: 1px solid rgba($color-dark-primary, 0.3); + transition: all 0.3s ease; + position: relative; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba($color-dark-primary, 0.1), rgba($color-dark-tertiary, 0.1)); + opacity: 0; + transition: opacity 0.3s ease; + z-index: 0; + } + + > * { + position: relative; + z-index: 1; + } + + &:hover { + transform: translateY(-5px); + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.3), + 0 0 30px rgba($color-dark-primary, 0.2); + border-color: rgba($color-dark-primary, 0.5); + + &::before { + opacity: 1; + } + } + + .feature-icon { + width: 60px; + height: 60px; + background: linear-gradient(135deg, rgba($color-dark-primary, 0.3), rgba($color-dark-tertiary, 0.3)); + border-radius: 15px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 1.5rem; + border: 1px solid rgba($color-dark-primary, 0.4); + box-shadow: 0 0 15px rgba($color-dark-primary, 0.2); + user-select: none; + + mdui-icon { + font-size: 1.5rem; + color: $color-dark-primary; + text-shadow: 0 0 10px rgba($color-dark-primary, 0.8); + } + } + + h4 { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 1rem; + color: $color-dark-on-surface; + text-shadow: 0 0 10px rgba($color-dark-primary, 0.3); + } + + p { + line-height: 1.6; + opacity: 0.9; + color: $color-dark-on-surface-variant; + } + } + } + } + + // Download section + .download { + padding: 6rem 0; + + .download-content { + text-align: center; + max-width: 600px; + margin: 0 auto; + + h3 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 1.5rem; + background: linear-gradient(45deg, $color-dark-primary, $color-dark-tertiary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 0 20px rgba($color-dark-primary, 0.5); + } + + p { + font-size: 1.25rem; + line-height: 1.6; + margin-bottom: 2.5rem; + opacity: 0.9; + } + + .download-buttons { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; + } + } + } + + // CTA section + .cta { + padding: 6rem 0; + background: rgba($color-dark-surface-container, 0.3); + backdrop-filter: blur(20px); + border-top: 1px solid rgba($color-dark-primary, 0.2); + + .cta-content { + text-align: center; + max-width: 600px; + margin: 0 auto; + + h3 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 1.5rem; + background: linear-gradient(45deg, $color-dark-primary, $color-dark-tertiary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 0 20px rgba($color-dark-primary, 0.5); + } + + p { + font-size: 1.25rem; + line-height: 1.6; + margin-bottom: 2.5rem; + opacity: 0.9; + } + + .cta-actions { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; + } + } + } + + // Footer + .homepage-footer { + background: rgba($color-dark-surface-container, 0.8); + backdrop-filter: blur(20px); + padding: 3rem 0 1rem; + border-top: 1px solid rgba($color-dark-primary, 0.3); + box-shadow: 0 -4px 20px rgba($color-dark-primary, 0.1); + + .footer-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 2rem; + margin-bottom: 2rem; + + .footer-section { + h4 { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 1rem; + color: $color-dark-on-surface; + text-shadow: 0 0 10px rgba($color-dark-primary, 0.3); + } + + p { + opacity: 0.8; + line-height: 1.6; + color: $color-dark-on-surface-variant; + } + + a { + color: $color-dark-on-surface; + text-decoration: none; + opacity: 0.8; + display: block; + margin-bottom: 0.5rem; + transition: all 0.3s ease; + padding: 0.25rem 0; + border-radius: 4px; + + &:hover { + opacity: 1; + color: $color-dark-primary; + text-shadow: 0 0 10px rgba($color-dark-primary, 0.5); + background: rgba($color-dark-primary, 0.1); + } + } + } + } + + .footer-bottom { + text-align: center; + padding-top: 2rem; + border-top: 1px solid rgba($color-dark-primary, 0.3); + + p { + opacity: 0.7; + margin: 0; + color: $color-dark-on-surface-variant; + } + } + } +} + +// Keyframes for animations +@keyframes neonGlow { + 0% { + text-shadow: 0 0 20px rgba($color-dark-primary, 0.5), 0 0 40px rgba($color-dark-primary, 0.3); + } + 100% { + text-shadow: 0 0 30px rgba($color-dark-primary, 0.8), 0 0 60px rgba($color-dark-primary, 0.5); + } +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +// Responsive Design +@media (min-width: 1024px) { + .homepage { + .homepage-header { + position: fixed; + top: 1rem; + left: 50%; + transform: translateX(-50%); + width: calc(100% - 2rem); + max-width: 1200px; + border-radius: 20px; + border: 1px solid rgba($color-dark-primary, 0.3); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.3), + 0 0 20px rgba($color-dark-primary, 0.2); + backdrop-filter: blur(30px); + background: rgba($color-dark-surface-container, 0.9); + } + + .hero { + margin-top: 6rem; + } + } +} + +@media (max-width: 1023px) { + .homepage { + .homepage-header { + position: sticky; + top: 0; + border-radius: 0; + margin: 0; + width: 100%; + transform: none; + } + } +} + +@media (max-width: 768px) { + .homepage { + .container { + padding: 0 1rem; + } + + .homepage-header { + .header-content { + flex-direction: column; + gap: 1rem; + } + } + + .hero { + flex-direction: column; + text-align: center; + padding: 2rem 0; + + .hero-content { + .hero-title { + font-size: 2.5rem; + } + + .hero-description { + font-size: 1.1rem; + } + } + + .hero-visual { + padding: 1rem; + + .chat-preview { + .chat-window { + transform: none; + max-width: 100%; + } + } + } + } + + .features { + .features-grid { + grid-template-columns: 1fr; + + .feature-card { + padding: 1.5rem; + } + } + + .section-title { + font-size: 2rem; + } + } + + .download { + .download-content { + h3 { + font-size: 2rem; + } + + .download-buttons { + flex-direction: column; + align-items: center; + } + } + } + + .cta { + .cta-content { + h3 { + font-size: 2rem; + } + + .cta-actions { + flex-direction: column; + align-items: center; + } + } + } + + .homepage-footer { + .footer-content { + grid-template-columns: 1fr; + text-align: center; + } + } + } +} + +@media (max-width: 480px) { + .homepage { + .hero { + .hero-content { + .hero-title { + font-size: 2rem; + } + + .hero-description { + font-size: 1rem; + } + } + } + + .features { + .section-title { + font-size: 1.75rem; + } + } + + .download { + .download-content { + h3 { + font-size: 1.75rem; + } + } + } + + .cta { + .cta-content { + h3 { + font-size: 1.75rem; + } + } + } + } +} \ No newline at end of file diff --git a/frontend/src/pages/app/resources/css/style.scss b/frontend/src/pages/app/resources/css/style.scss index 3e06041..225262c 100644 --- a/frontend/src/pages/app/resources/css/style.scss +++ b/frontend/src/pages/app/resources/css/style.scss @@ -11,6 +11,7 @@ @use "dialogs/reply"; @use "download-app"; @use "404" as not-found; +@use "homepage"; @use "lib/fonts/montserrat"; @use "lib/fonts/material-symbols"; From 1f6a8ec0b6aef9a6d1f82008ad73dea7a1c604d5 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Thu, 2 Oct 2025 20:18:37 +0300 Subject: [PATCH 5/5] Use the same design for the app --- .../src/pages/app/resources/css/_chat.scss | 193 +++++++++++++++--- 1 file changed, 162 insertions(+), 31 deletions(-) diff --git a/frontend/src/pages/app/resources/css/_chat.scss b/frontend/src/pages/app/resources/css/_chat.scss index 19bb6ab..cf26163 100644 --- a/frontend/src/pages/app/resources/css/_chat.scss +++ b/frontend/src/pages/app/resources/css/_chat.scss @@ -1,8 +1,26 @@ @use "common/colors" as *; @use "common/material" as *; +@use "sass:color"; #chat-interface { height: 100%; + background: linear-gradient(135deg, $color-dark-background 0%, $color-dark-surface-container 70%, rgba($color-dark-primary-container, 0.3) 100%); + position: relative; + + &::before { + content: ''; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: + radial-gradient(circle at 20% 80%, rgba($color-dark-primary, 0.15) 0%, transparent 50%), + radial-gradient(circle at 80% 20%, rgba($color-dark-tertiary, 0.15) 0%, transparent 50%), + radial-gradient(circle at 40% 40%, rgba($color-dark-secondary, 0.1) 0%, transparent 50%); + pointer-events: none; + z-index: 0; + } .header { display: flex; @@ -69,10 +87,13 @@ .chat-header { padding: 16px; - background: $color-dark-surface-container; + background: rgba($color-dark-surface-container, 0.8); + backdrop-filter: blur(20px); display: flex; align-items: center; - box-shadow: black 0 0 20px; + box-shadow: 0 4px 20px rgba($color-dark-primary, 0.1); + position: relative; + z-index: 1; .chat-header-avatar { width: 45px; @@ -148,8 +169,10 @@ .chat-messages { flex: 1; - padding: 1rem; + padding: 10px 20px; overflow-y: auto; + position: relative; + z-index: 1; &::-webkit-scrollbar { width: 7px; @@ -337,9 +360,47 @@ } &.received .message-inner { - background-color: $color-dark-surface-container; + background: $color-dark-surface-container; + color: $color-dark-on-surface; border-top-left-radius: 5px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border: 1px solid rgba($color-dark-outline-variant, 0.4); + position: relative; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba($color-dark-primary, 0.05), rgba($color-dark-tertiary, 0.03)); + pointer-events: none; + z-index: 0; + } + + > * { + position: relative; + z-index: 1; + } + } + + &.received .message-time { + color: $color-dark-on-surface-variant; + font-weight: 500; + + .message-status-indicator { + .success-icon { + color: $color-dark-on-surface-variant; + filter: brightness(1.1); + } + + .error-icon { + color: #ff6b6b; + filter: brightness(1.1); + } + } } &.sent { @@ -347,13 +408,47 @@ flex-direction: row-reverse; .message-inner { - background-color: $color-dark-primary-container; - color: $color-dark-on-primary-container; + background: linear-gradient(135deg, color.adjust($color-dark-primary, $lightness: 8%), color.adjust($color-dark-primary-container, $lightness: 5%)); + color: $color-dark-on-primary; border-top-right-radius: 5px; + box-shadow: 0 0 20px rgba($color-dark-primary, 0.4); + border: 1px solid rgba($color-dark-primary, 0.5); + position: relative; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.08)); + pointer-events: none; + z-index: 0; + } + + > * { + position: relative; + z-index: 1; + } } .message-time { - color: $color-dark-on-primary-container; + color: $color-dark-on-primary; + font-weight: 500; + + .message-status-indicator { + .success-icon { + color: $color-dark-on-primary; + filter: brightness(1.2); + } + + .error-icon { + color: #ff6b6b; + filter: brightness(1.2); + } + } } } } @@ -401,30 +496,15 @@ .chat-input-wrapper { position: relative; - margin: 0 20px 20px 20px; - - &::before { - $height: 20px; - - content: ''; - position: absolute; - top: -$height; - left: 0; - right: 0; - width: 100%; - height: $height; - background: linear-gradient( - 0deg, - $color-dark-surface, - rgba(255, 255, 255, 0), - ); - } + margin: 0 10px 10px 10px; .input-group { display: flex; - background-color: $color-dark-surface-container; + background: $color-dark-surface-container; border-radius: 30px; flex-direction: column; + border: 1px solid rgba($color-dark-outline-variant, 0.4); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); .contextual-preview { padding: 12px 16px 0 16px; @@ -467,11 +547,17 @@ outline: none; caret-color: $color-dark-primary; color: $color-dark-on-surface; + background: transparent; resize: none; font: inherit; font-size: 13pt; height: 100%; width: 100%; + + &::placeholder { + color: $color-dark-on-surface-variant; + opacity: 0.7; + } } .buttons { @@ -485,17 +571,21 @@ width: 50px; height: 50px; border-radius: 50%; - background-color: $color-dark-primary; + background: linear-gradient(135deg, color.adjust($color-dark-primary, $lightness: 8%), color.adjust($color-dark-primary-container, $lightness: 5%)); color: $color-dark-on-primary; - border: none; + border: 1px solid rgba($color-dark-primary, 0.5); cursor: pointer; display: flex; align-items: center; justify-content: center; - transition: background-color 0.25s ease; + transition: all 0.25s ease; align-self: flex-end; + box-shadow: 0 0 20px rgba($color-dark-primary, 0.4); - @include hoverStateLayer($background: $color-dark-primary); + &:hover { + transform: translateY(-2px); + box-shadow: 0 0 30px rgba($color-dark-primary, 0.6); + } } } } @@ -505,6 +595,47 @@ } } +// ChatHeader component styles +.chat-header-left { + .product-name { + font-size: 1.8rem; + font-weight: 700; + background: linear-gradient(45deg, $color-dark-primary, $color-dark-tertiary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 0 20px rgba($color-dark-primary, 0.5); + } + + .profile { + a { + display: flex; + align-items: center; + text-decoration: none; + transition: transform 0.3s ease; + + &:hover { + transform: scale(1.05); + } + + img { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; + border: 2px solid rgba($color-dark-primary, 0.4); + box-shadow: 0 0 15px rgba($color-dark-primary, 0.3); + transition: all 0.3s ease; + + &:hover { + box-shadow: 0 0 25px rgba($color-dark-primary, 0.5); + border-color: rgba($color-dark-primary, 0.6); + } + } + } + } +} + .message-profile-pic { img { width: 40px;