diff --git a/.gitignore b/.gitignore
index 404f860..096357a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -573,4 +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
+!frontend/src/css/lib
\ No newline at end of file
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 15466af..2e07646 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,17 +1,17 @@
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 { ElectronTitleBar } from "./pages/chat/ui/components/Electron";
+import { useAppState } from "./pages/chat/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 HomePage from "./pages/home/HomePage";
+import LoginPage from "./pages/auth/LoginPage";
+import RegisterPage from "./pages/auth/RegisterPage";
+import ChatPage from "./pages/chat/ChatPage";
+import DownloadAppPage from "./pages/download-app/DownloadAppPage";
+import NotFoundPage from "./pages/not-found/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";
+import { isElectron } from "./pages/chat/electron/electron";
+import { MINIMUM_WIDTH } from "./pages/chat/core/config";
+import useWindowSize from "./pages/chat/ui/hooks/useWindowSize";
export default function App() {
const { restoreUserFromStorage } = useAppState();
@@ -31,7 +31,7 @@ export default function App() {
- z} />
+ } />
} />
} />
} />
diff --git a/frontend/src/pages/app/auth/crypto.ts b/frontend/src/api/authApi.ts
similarity index 80%
rename from frontend/src/pages/app/auth/crypto.ts
rename to frontend/src/api/authApi.ts
index 1e16ff1..86fab4e 100644
--- a/frontend/src/pages/app/auth/crypto.ts
+++ b/frontend/src/api/authApi.ts
@@ -1,9 +1,26 @@
-import { API_BASE_URL } from "../core/config";
-import { getAuthHeaders } from "./api";
-import { generateX25519KeyPair } from "../utils/crypto/asymmetric";
-import { encryptBackupWithPassword, decryptBackupWithPassword, encodeBlob, decodeBlob } from "../utils/crypto/backup";
-import { b64, ub64 } from "../utils/utils";
-import type { BackupBlob, UploadPublicKeyRequest } from "../core/types";
+import type { Headers, UploadPublicKeyRequest, BackupBlob } from "../pages/chat/core/types";
+import { generateX25519KeyPair } from "../pages/chat/utils/crypto/asymmetric";
+import { encodeBlob, encryptBackupWithPassword, decryptBackupWithPassword, decodeBlob } from "../pages/chat/utils/crypto/backup";
+import { b64, ub64 } from "../pages/chat/utils/utils";
+import { API_BASE_URL } from "../pages/chat/core/config";
+
+/**
+ * Generates authentication headers for API requests
+ * @param {boolean} json - Whether to include JSON content type header
+ * @returns {Headers} Headers object with authentication and content type
+ */
+export function getAuthHeaders(token: string | null, json: boolean = true): Headers {
+ const headers: Headers = {};
+
+ if (json) {
+ headers["Content-Type"] = "application/json";
+ }
+
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+ return headers;
+}
let currentPublicKey: Uint8Array | null = null;
let currentPrivateKey: Uint8Array | null = null;
diff --git a/frontend/src/pages/app/api/dmApi.ts b/frontend/src/api/dmApi.ts
similarity index 92%
rename from frontend/src/pages/app/api/dmApi.ts
rename to frontend/src/api/dmApi.ts
index 84d6347..7c195af 100644
--- a/frontend/src/pages/app/api/dmApi.ts
+++ b/frontend/src/api/dmApi.ts
@@ -1,12 +1,12 @@
-import { API_BASE_URL } from "../core/config";
-import { getAuthHeaders } from "../auth/api";
-import { ecdhSharedSecret, deriveWrappingKey } from "../utils/crypto/asymmetric";
-import { importAesGcmKey, aesGcmEncrypt, aesGcmDecrypt } from "../utils/crypto/symmetric";
-import { randomBytes } from "../utils/crypto/kdf";
-import { getCurrentKeys } from "../auth/crypto";
-import { request } from "../core/websocket";
-import type { SendDMRequest, DmEnvelope, User, DMEditRequest, DmEncryptedJSON, BaseDmEnvelope } from "../core/types";
-import { b64, ub64 } from "../utils/utils";
+import { API_BASE_URL } from "../pages/chat/core/config";
+import { getAuthHeaders } from "./authApi";
+import { ecdhSharedSecret, deriveWrappingKey } from "../pages/chat/utils/crypto/asymmetric";
+import { importAesGcmKey, aesGcmEncrypt, aesGcmDecrypt } from "../pages/chat/utils/crypto/symmetric";
+import { randomBytes } from "../pages/chat/utils/crypto/kdf";
+import { getCurrentKeys } from "./authApi";
+import { request } from "../pages/chat/core/websocket";
+import type { SendDMRequest, DmEnvelope, User, DMEditRequest, DmEncryptedJSON, BaseDmEnvelope } from "../pages/chat/core/types";
+import { b64, ub64 } from "../pages/chat/utils/utils";
export async function decryptDm(envelope: DmEnvelope, senderPublicKeyB64: string): Promise {
const keys = getCurrentKeys();
diff --git a/frontend/src/pages/app/api/profileApi.ts b/frontend/src/api/profileApi.ts
similarity index 95%
rename from frontend/src/pages/app/api/profileApi.ts
rename to frontend/src/api/profileApi.ts
index a555821..85c382b 100644
--- a/frontend/src/pages/app/api/profileApi.ts
+++ b/frontend/src/api/profileApi.ts
@@ -1,6 +1,6 @@
-import { getAuthHeaders } from "../auth/api";
-import { API_BASE_URL } from "../core/config";
-import type { UserProfile } from "../core/types";
+import { getAuthHeaders } from "./authApi";
+import { API_BASE_URL } from "../pages/chat/core/config";
+import type { UserProfile } from "../pages/chat/core/types";
export interface ProfileData {
profile_picture?: string;
diff --git a/frontend/src/pages/app/resources/css/common/_animations.scss b/frontend/src/css/common/_animations.scss
similarity index 100%
rename from frontend/src/pages/app/resources/css/common/_animations.scss
rename to frontend/src/css/common/_animations.scss
diff --git a/frontend/src/pages/app/resources/css/common/_colors.scss b/frontend/src/css/common/_colors.scss
similarity index 100%
rename from frontend/src/pages/app/resources/css/common/_colors.scss
rename to frontend/src/css/common/_colors.scss
diff --git a/frontend/src/pages/app/resources/css/common/_components.scss b/frontend/src/css/common/_components.scss
similarity index 100%
rename from frontend/src/pages/app/resources/css/common/_components.scss
rename to frontend/src/css/common/_components.scss
diff --git a/frontend/src/pages/app/resources/css/common/_material.scss b/frontend/src/css/common/_material.scss
similarity index 100%
rename from frontend/src/pages/app/resources/css/common/_material.scss
rename to frontend/src/css/common/_material.scss
diff --git a/frontend/src/pages/app/resources/css/lib/fonts/material-symbols.scss b/frontend/src/css/lib/fonts/material-symbols.scss
similarity index 100%
rename from frontend/src/pages/app/resources/css/lib/fonts/material-symbols.scss
rename to frontend/src/css/lib/fonts/material-symbols.scss
diff --git a/frontend/src/pages/app/resources/css/lib/fonts/material-symbols.woff2 b/frontend/src/css/lib/fonts/material-symbols.woff2
similarity index 100%
rename from frontend/src/pages/app/resources/css/lib/fonts/material-symbols.woff2
rename to frontend/src/css/lib/fonts/material-symbols.woff2
diff --git a/frontend/src/pages/app/resources/css/lib/fonts/montserrat.scss b/frontend/src/css/lib/fonts/montserrat.scss
similarity index 100%
rename from frontend/src/pages/app/resources/css/lib/fonts/montserrat.scss
rename to frontend/src/css/lib/fonts/montserrat.scss
diff --git a/frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 b/frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2
similarity index 100%
rename from frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2
rename to frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2
diff --git a/frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 b/frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2
similarity index 100%
rename from frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2
rename to frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2
diff --git a/frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 b/frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2
similarity index 100%
rename from frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2
rename to frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2
diff --git a/frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 b/frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2
similarity index 100%
rename from frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2
rename to frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2
diff --git a/frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 b/frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2
similarity index 100%
rename from frontend/src/pages/app/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2
rename to frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2
diff --git a/frontend/src/pages/app/resources/css/style.scss b/frontend/src/css/style.scss
similarity index 78%
rename from frontend/src/pages/app/resources/css/style.scss
rename to frontend/src/css/style.scss
index e129c88..b490119 100644
--- a/frontend/src/pages/app/resources/css/style.scss
+++ b/frontend/src/css/style.scss
@@ -1,18 +1,7 @@
-@use "auth";
-@use "chat";
-@use "profile";
-@use "settings";
-@use "panelchat";
@use "common/animations";
@use "common/components";
@use "common/colors" as *;
@use "common/material" as *;
-@use "electron";
-@use "dialogs/reply";
-@use "download-app";
-@use "404" as not-found;
-@use "homepage";
-@use "reactions";
@use "lib/fonts/montserrat";
@use "lib/fonts/material-symbols";
diff --git a/frontend/src/pages/app/resources/images/default-avatar.png b/frontend/src/images/default-avatar.png
similarity index 100%
rename from frontend/src/pages/app/resources/images/default-avatar.png
rename to frontend/src/images/default-avatar.png
diff --git a/frontend/src/pages/app/resources/images/logo.png b/frontend/src/images/logo.png
similarity index 100%
rename from frontend/src/pages/app/resources/images/logo.png
rename to frontend/src/images/logo.png
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 2296930..c7f0e07 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -5,17 +5,16 @@
* @version 1.0.0
*/
-import './pages/app/resources/css/style.scss';
+import './css/style.scss';
import "mdui/mdui.css";
-import "./pages/app/utils/material";
-import "./pages/app/core/init";
-import "./pages/app/electron/electron";
+import "./pages/chat/utils/material";
+import "./pages/chat/core/init";
+import "./pages/chat/electron/electron";
import { createRoot } from 'react-dom/client';
import App from './App';
import { StrictMode } from 'react';
-// Initialize React app
createRoot(document.getElementById("root")!).render(
diff --git a/frontend/src/pages/ProtectedRoute.tsx b/frontend/src/pages/ProtectedRoute.tsx
index 40c4652..5036612 100644
--- a/frontend/src/pages/ProtectedRoute.tsx
+++ b/frontend/src/pages/ProtectedRoute.tsx
@@ -1,5 +1,5 @@
import { useEffect } from "react";
-import { useAppState } from "./app/ui/state";
+import { useAppState } from "./chat/ui/state";
import { useNavigate } from "react-router-dom";
interface ProtectedRouteProps {
diff --git a/frontend/src/pages/app/auth/api.ts b/frontend/src/pages/app/auth/api.ts
deleted file mode 100644
index 875404d..0000000
--- a/frontend/src/pages/app/auth/api.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Headers } from "../core/types";
-
-/**
- * Generates authentication headers for API requests
- * @param {boolean} json - Whether to include JSON content type header
- * @returns {Headers} Headers object with authentication and content type
- */
-export function getAuthHeaders(token: string | null, json: boolean = true): Headers {
- const headers: Headers = {};
-
- if (json) {
- headers["Content-Type"] = "application/json";
- }
-
- if (token) {
- headers['Authorization'] = `Bearer ${token}`;
- }
- return headers;
-}
\ No newline at end of file
diff --git a/frontend/src/pages/app/resources/css/_panelchat.scss b/frontend/src/pages/app/resources/css/_panelchat.scss
deleted file mode 100644
index 9c17533..0000000
--- a/frontend/src/pages/app/resources/css/_panelchat.scss
+++ /dev/null
@@ -1,150 +0,0 @@
-@use "common/colors" as *;
-@use "common/material" as *;
-
-
-// контейнер чата и панели с чатами
-.all-container {
- display: flex;
- flex-direction: row;
- width: 100%;
- height: 100%;
-}
-
-#profile {
- display: none;
- flex-direction: column;
- z-index: 2000;
- top: 0;
- left: 0;
- position: fixed;
- height: 100vh;
- width: 27%;
- background-color: $color-dark-surface-container;
- position: relative;
-
- .profileheader {
- display: flex;
- gap: 200px;
-
- p {
- color: white;
- }
-
- a {
- text-decoration: none;
- color: white;
- border: solid 2px $color-dark-on-surface-variant;
- padding: 5px;
- border-radius: 10px;
-
- &:hover {
- background-color: rgba(255, 255, 255, 0.241);
- }
- }
- }
-}
-
-#chat-list {
- display: flex;
- flex-direction: column;
- flex-grow: 0;
- width: 40%;
- background-color: $color-dark-surface-container;
- height: 100%;
- z-index: 1000;
- min-height: 0; // allow children to manage their own scrolling
-
- .chat-header-left {
- display: flex;
- color: white;
- font-size: 25px;
- background-color: $color-dark-surface-container;
- width: 100%;
- justify-content: center;
- align-items: center;
- padding: 16px;
- overflow: hidden;
-
- .product-name {
- flex-grow: 1;
- }
-
- .profile {
- font-size: 24px;
- display: flex;
- justify-content: start;
- flex-shrink: 0;
- flex-grow: 0;
-
- #closeprofile a {
- text-decoration: none;
- color: white;
- border: solid 2px $color-dark-on-surface-variant;
- padding: 5px;
- border-radius: 10px;
-
- &:hover {
- background-color: rgba(255, 255, 255, 0.241);
- }
- }
-
- img {
- $size: 45px;
- display: flex;
- border-radius: 50%;
- width: $size;
- height: $size;
- }
- }
- }
-
- .chat-tabs {
- margin-top: 5px;
- width: 100%;
- height: calc(100% - 80px);
- display: flex;
- flex-direction: column;
- min-height: 0; // prevent flex collapse when inner overflows
- --mdui-color-surface: $color-dark-surface-container;
- --mdui-color-surface-variant: transparent;
-
- img {
- width: 45px;
- height: 45px;
- border-radius: 20%;
- object-fit: cover;
- margin-right: 1rem;
- }
-
- mdui-tabs {
- height: 100%;
- display: flex;
- flex-direction: column;
- min-height: 0; // enable inner panel to scroll
- }
-
- mdui-tab-panel[active] {
- flex: 1;
- display: flex;
- flex-direction: column;
- min-height: 0; // critical to avoid collapsing
- overflow: hidden;
- }
-
- mdui-list {
- flex: 1;
- min-height: 0; // allow scroll area to size correctly
- overflow-y: auto;
- padding: 0;
- margin: 0;
- }
- }
-
- mdui-bottom-app-bar {
- position: relative;
- width: 100%;
- padding-left: 16px;
- padding-right: 16px;
- margin-top: auto;
- }
-}
\ No newline at end of file
diff --git a/frontend/src/pages/app/resources/css/_profile.scss b/frontend/src/pages/app/resources/css/_profile.scss
deleted file mode 100644
index f3f60a4..0000000
--- a/frontend/src/pages/app/resources/css/_profile.scss
+++ /dev/null
@@ -1,249 +0,0 @@
-@use "common/colors" as *;
-@use "common/material" as *;
-
-#profile-dialog .content {
- display: flex;
- flex-direction: column;
- gap: 24px;
- min-width: 400px;
-
- .header-top {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 16px;
- position: relative;
- padding-bottom: 16px;
- border-bottom: 1px solid $color-dark-outline;
-
- .profile-picture-container {
- position: relative;
- $size: 70px;
- width: $size;
- height: $size;
- flex-shrink: 0;
-
- #profile-picture {
- width: $size;
- height: $size;
- border-radius: 50%;
- object-fit: cover;
- }
-
- .upload-overlay {
- position: absolute;
- bottom: 0;
- right: 0;
- width: 28px;
- height: 28px;
- cursor: pointer;
- }
- }
-
- mdui-text-field {
- flex: 1;
- }
- }
-
- #profile-form {
- display: flex;
- flex-direction: column;
- gap: 16px;
-
- mdui-text-field {
- width: 100%;
- }
-
- .dialog-actions {
- display: flex;
- gap: 12px;
- padding-top: 16px;
- border-top: 1px solid $color-dark-outline;
-
- > * {
- flex: 1;
- }
- }
- }
-}
-
-// User profile dialog content styles
-#user-profile-dialog .content {
- display: flex;
- gap: 1.5rem;
-
- .profile-picture-section {
- flex-shrink: 0;
-
- .profile-picture {
- width: 80px;
- height: 80px;
- border-radius: 50%;
- object-fit: cover;
- border: 2px solid $color-dark-outline;
- }
- }
-
- .profile-info {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 1rem;
-
- .username-section {
- display: flex;
- align-items: center;
- gap: 0.75rem;
-
- .username {
- margin: 0;
- color: $color-dark-on-surface;
- font-size: 1.1rem;
- font-weight: 600;
- }
-
- .online-status {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- font-size: 0.85rem;
- padding: 0.25rem 0.5rem;
- border-radius: 12px;
- font-weight: 500;
-
- &.online {
- color: $success;
- background-color: rgba(76, 175, 80, 0.1);
-
- .online-indicator {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background-color: $success;
- }
- }
-
- &.offline {
- color: $color-dark-on-surface-variant;
- background-color: rgba(255, 255, 255, 0.05);
-
- .offline-indicator {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background-color: $color-dark-on-surface-variant;
- }
- }
- }
- }
-
- .bio-section {
- label {
- display: block;
- color: $color-dark-on-surface-variant;
- font-size: 0.85rem;
- font-weight: 500;
- margin-bottom: 0.5rem;
- }
-
- .bio-display {
- color: $color-dark-on-surface;
- font-size: 0.9rem;
- line-height: 1.4;
- padding: 0.75rem;
- background-color: $color-dark-surface;
- border-radius: 8px;
- border: 1px solid $color-dark-outline;
- min-height: 60px;
- }
-
- .bio-actions {
- display: flex;
- gap: 0.5rem;
- margin-top: 0.5rem;
- }
- }
-
- .profile-stats {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
-
- .stat {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0.5rem 0;
-
- .stat-label {
- color: $color-dark-on-surface-variant;
- font-size: 0.85rem;
- }
-
- .stat-value {
- color: $color-dark-on-surface;
- font-size: 0.85rem;
- font-weight: 500;
- }
- }
- }
-
- .profile-actions {
- display: flex;
- gap: 0.75rem;
- margin-top: 0.5rem;
-
- mdui-button {
- flex: 1;
- }
- }
- }
-}
-
-// Cropper Dialog Styles
-#cropper-dialog {
- .cropper-dialog-content {
- display: flex;
- flex-direction: column;
- gap: 16px;
- min-width: 500px;
- max-width: 600px;
- }
-
- .cropper-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-bottom: 16px;
- border-bottom: 1px solid $color-dark-outline;
-
- h3 {
- margin: 0;
- color: $color-dark-on-surface;
- }
- }
-
- .cropper-container {
- display: flex;
- justify-content: center;
- align-items: center;
- min-height: 400px;
- background: $color-dark-surface-container;
- border-radius: 8px;
- overflow: hidden;
-
- #cropper-area {
- width: 100%;
- height: 100%;
- min-height: 400px;
- }
- }
-
- .cropper-actions {
- display: flex;
- gap: 12px;
- justify-content: flex-end;
- padding-top: 16px;
- border-top: 1px solid $color-dark-outline;
- }
-}
\ No newline at end of file
diff --git a/frontend/src/pages/app/resources/css/_reactions.scss b/frontend/src/pages/app/resources/css/_reactions.scss
deleted file mode 100644
index f64402d..0000000
--- a/frontend/src/pages/app/resources/css/_reactions.scss
+++ /dev/null
@@ -1,389 +0,0 @@
-@use "common/material" as *;
-@use "sass:color";
-
-// Reaction styles
-.message-reactions {
- display: flex;
- flex-wrap: wrap;
- gap: 4px;
- margin-top: 8px;
- margin-left: 10px;
- margin-right: 10px;
- animation: messageReactionsFadeIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
-}
-
-.reaction-button {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 8px 12px;
- border: none;
- border-radius: 16px;
- background-color: $color-dark-surface-container;
- cursor: pointer;
- transition: transform 0.2s ease, background-color 0.2s ease;
- font-size: 1px;
- min-height: 28px;
- animation: reactionFadeIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
-
- &.removing {
- animation: reactionFadeOut 0.2s ease forwards;
- }
-
- &:hover {
- background-color: $color-dark-surface-container-high;
- transform: scale(1.05);
- }
-
- &.reacted {
- background-color: $color-dark-primary-container;
- border-color: $color-dark-primary;
- color: $color-dark-on-primary-container;
-
- &:hover {
- background-color: color.adjust($color-dark-primary-container, $lightness: 20%);
- }
- }
-}
-
-.reaction-emoji {
- font-size: 17px;
- line-height: 1;
-}
-
-.reaction-count {
- font-size: 12px;
- font-weight: 500;
- line-height: 1;
-}
-
-// Reaction bar styles (standalone)
-.reaction-bar {
- background: $color-dark-surface-container;
- border: 1px solid $color-dark-outline;
- border-radius: 24px;
- padding: 8px;
- opacity: 1;
- transition: all 0.15s ease;
- backdrop-filter: blur(8px);
- transform: translateY(0);
-
- &.closing {
- opacity: 0;
- transform: scale(0.8);
- }
-}
-
-// Emoji menu wrapper inside reaction bar
-.emoji-menu-wrapper {
- width: 320px;
- height: 400px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-// Context menu wrapper with animations
-.context-menu-wrapper {
- position: relative;
- display: block;
-
- // Animation states
- &.entering {
- opacity: 0;
- transform: scale(0.8);
- animation: contextMenuEnter 0.2s ease forwards;
- }
-
- &.entering-left {
- opacity: 0;
- transform: translateX(-20px) scale(0.8);
- animation: contextMenuEnterLeft 0.2s ease forwards;
- }
-
- &.entering-up {
- opacity: 0;
- transform: translateY(20px) scale(0.8);
- animation: contextMenuEnterUp 0.2s ease forwards;
- }
-
- &.entering-up-left {
- opacity: 0;
- transform: translateX(-20px) translateY(20px) scale(0.8);
- animation: contextMenuEnterUpLeft 0.2s ease forwards;
- }
-
- &.closing {
- opacity: 1;
- transform: scale(1);
- animation: contextMenuClose 0.2s ease forwards;
- }
-
- &.closing-left {
- opacity: 1;
- transform: translateX(0) scale(1);
- animation: contextMenuCloseLeft 0.2s ease forwards;
- }
-
- &.closing-up {
- opacity: 1;
- transform: translateY(0) scale(1);
- animation: contextMenuCloseUp 0.2s ease forwards;
- }
-
- &.closing-up-left {
- opacity: 1;
- transform: translateX(0) translateY(0) scale(1);
- animation: contextMenuCloseUpLeft 0.2s ease forwards;
- }
-}
-
-// Reaction bar inside context menu wrapper
-.context-menu-reaction-bar {
- display: flex;
- align-items: center;
- gap: 4px;
- padding: 8px 12px;
- background: $color-dark-surface-container;
- border: 1px solid $color-dark-outline;
- border-radius: 16px;
- position: absolute;
- bottom: 100%;
- justify-content: center;
- margin-bottom: 10px;
- transition: width 0.3s ease-out, height 0.3s ease-out;
-
- &.left {
- left: 0;
- transform: translateX(0);
- }
-
- &.right {
- right: 0;
- transform: translateX(0);
- }
-
- &.expanded {
- padding: 0;
- overflow: hidden;
- width: 320px;
- height: 400px;
- border-radius: 16px;
-
- // Default: expand downward from the reaction bar's bottom edge
- position: absolute;
- bottom: auto;
- top: 0;
- left: 0;
- transform: translateY(0);
-
- &.expand-upward {
- // Expand upward from the reaction bar's top edge
- bottom: 100%;
- top: auto;
- margin-bottom: 10px;
- margin-top: 0;
- transform: translateY(0);
- }
-
- .emoji-menu-wrapper {
- animation: emojiMenuEnter 0.5s ease;
- }
- }
-}
-
-@keyframes emojiMenuEnter {
- from {
- opacity: 0;
- }
-
- to {
- opacity: 1;
- }
-}
-
-.reaction-bar-content {
- display: flex;
- align-items: center;
- gap: 4px;
- transition: opacity 0.3s ease-out;
-
- &.faded {
- opacity: 0;
- }
-}
-
-.reaction-emoji-button {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- border: none;
- border-radius: 16px;
- background: transparent;
- cursor: pointer;
- transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
- font-size: 18px;
-
- &:hover {
- background: var(--mdui-color-surface-container-high);
- transform: scale(1.3);
- box-shadow: var(--mdui-elevation-1);
- }
-
- &:active {
- transform: scale(0.95);
- transition: transform 0.1s ease;
- }
-}
-
-.reaction-expand-button {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- border: 1px solid var(--mdui-color-outline);
- border-radius: 16px;
- background: var(--mdui-color-surface);
- cursor: pointer;
- transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
-
- &:hover {
- background: var(--mdui-color-surface-container-high);
- border-color: var(--mdui-color-primary);
- transform: scale(1.1);
- box-shadow: var(--mdui-elevation-1);
- }
-
- &:active {
- transform: scale(0.95);
- transition: transform 0.1s ease;
- }
-
- .material-symbols {
- font-size: 18px;
- color: var(--mdui-color-on-surface);
- transition: transform 0.2s ease;
- }
-
- &:hover .material-symbols {
- transform: rotate(90deg);
- }
-}
-
-// Animation for reactions appearing/disappearing
-@keyframes messageReactionsFadeIn {
- from {
- opacity: 0;
- transform: translateY(-10px);
- }
-
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-@keyframes reactionFadeIn {
- from {
- opacity: 0;
- transform: scale(0.8);
- }
-
- to {
- opacity: 1;
- transform: scale(1);
- }
-}
-
-@keyframes reactionFadeOut {
- from {
- opacity: 1;
- transform: scale(1);
- }
-
- to {
- opacity: 0;
- transform: scale(0.8);
- }
-}
-
-// Context menu wrapper animations
-@keyframes contextMenuEnter {
- to {
- opacity: 1;
- transform: scale(1);
- }
-}
-
-@keyframes contextMenuEnterLeft {
- to {
- opacity: 1;
- transform: translateX(0) scale(1);
- }
-}
-
-@keyframes contextMenuEnterUp {
- to {
- opacity: 1;
- transform: translateY(0) scale(1);
- }
-}
-
-@keyframes contextMenuEnterUpLeft {
- to {
- opacity: 1;
- transform: translateX(0) translateY(0) scale(1);
- }
-}
-
-@keyframes contextMenuClose {
- to {
- opacity: 0;
- transform: scale(0.8);
- }
-}
-
-@keyframes contextMenuCloseLeft {
- to {
- opacity: 0;
- transform: translateX(-20px) scale(0.8);
- }
-}
-
-@keyframes contextMenuCloseUp {
- to {
- opacity: 0;
- transform: translateY(20px) scale(0.8);
- }
-}
-
-@keyframes contextMenuCloseUpLeft {
- to {
- opacity: 0;
- transform: translateX(-20px) translateY(20px) scale(0.8);
- }
-}
-
-// 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;
- }
-}
diff --git a/frontend/src/pages/app/resources/css/_settings.scss b/frontend/src/pages/app/resources/css/_settings.scss
deleted file mode 100644
index 4607769..0000000
--- a/frontend/src/pages/app/resources/css/_settings.scss
+++ /dev/null
@@ -1,99 +0,0 @@
-@use "common/colors" as *;
-@use "common/material" as *;
-
-#settings-dialog {
- .fullscreen-wrapper {
- display: flex;
- align-items: center;
- justify-content: center;
- height: calc(100vh - (1.5rem * 2));
- width: 100%;
- position: relative;
-
- #settings-dialog-inner {
- max-width: 1200px;
- max-height: 1000px;
- width: 100%;
- height: 100%;
-
- .header {
- display: flex;
- flex-direction: row;
- gap: 10px;
- margin-bottom: 16px;
- }
-
- #settings-menu {
- display: flex;
- flex-direction: row;
- gap: 16px;
-
- mdui-list {
- max-width: 280px;
- padding-right: 16px;
- overflow-y: auto;
- }
-
- .screen {
- flex: 1;
- overflow-y: auto;
- position: relative;
-
- .settings-panel {
- display: flex;
- flex-direction: column;
- gap: 16px;
- opacity: 0;
- visibility: hidden;
- transform: translateY(20px);
- transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s ease;
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
-
- &.active {
- opacity: 1;
- visibility: visible;
- transform: translateY(0);
- position: relative;
- }
-
- h3 {
- margin: 0 0 16px 0;
- color: $color-dark-on-surface;
- }
-
- mdui-text-field,
- mdui-select,
- mdui-switch,
- mdui-button {
- margin-bottom: 8px;
- }
-
- mdui-switch {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px 0;
- border-bottom: 1px solid $color-dark-outline;
-
- &:last-child {
- border-bottom: none;
- }
- }
-
- p {
- margin: 8px 0;
- color: $color-dark-on-surface-variant;
- }
-
- mdui-linear-progress {
- margin: 16px 0;
- }
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/pages/app/resources/css/dialogs/_reply.scss b/frontend/src/pages/app/resources/css/dialogs/_reply.scss
deleted file mode 100644
index 420d389..0000000
--- a/frontend/src/pages/app/resources/css/dialogs/_reply.scss
+++ /dev/null
@@ -1,31 +0,0 @@
-@use "../common/material" as *;
-
-.reply-dialog .dialog-content {
- width: 300px;
- overflow-x:hidden;
-
- .reply-preview-dialog {
- margin-bottom: 1rem;
- padding: 16px;
- background-color: $color-dark-surface-container;
- border-radius: 16px;
-
- .reply-content {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
-
- .reply-username {
- font-weight: 600;
- color: $color-dark-on-surface;
- font-size: 0.85rem;
- }
-
- .reply-text {
- color: $color-dark-on-surface-variant;
- font-size: 0.9rem;
- line-height: 1.4;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/auth/LoginPage.tsx
similarity index 92%
rename from frontend/src/pages/LoginPage.tsx
rename to frontend/src/pages/auth/LoginPage.tsx
index 0fdb5b4..4bbb165 100644
--- a/frontend/src/pages/LoginPage.tsx
+++ b/frontend/src/pages/auth/LoginPage.tsx
@@ -1,16 +1,17 @@
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 { AlertsContainer, type Alert, type AlertType } from "../chat/ui/components/Alerts";
+import { AuthContainer, AuthHeader } from "../chat/ui/components/Auth";
+import type { ErrorResponse, LoginRequest, LoginResponse } from "../chat/core/types";
+import { ensureKeysOnLogin } from "../../api/authApi";
+import { API_BASE_URL } from "../chat/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 { useAppState } from "../chat/ui/state";
+import { MaterialTextField } from "../chat/ui/components/core/TextField";
+import { initialize, isSupported, startElectronReceiver, subscribe } from "../chat/utils/push-notifications";
+import { isElectron } from "../chat/electron/electron";
import { useNavigate } from "react-router-dom";
+import "./auth.scss";
export default function LoginPage() {
const [alerts, updateAlerts] = useImmer([]);
diff --git a/frontend/src/pages/RegisterPage.tsx b/frontend/src/pages/auth/RegisterPage.tsx
similarity index 93%
rename from frontend/src/pages/RegisterPage.tsx
rename to frontend/src/pages/auth/RegisterPage.tsx
index 2e437e1..ec2b906 100644
--- a/frontend/src/pages/RegisterPage.tsx
+++ b/frontend/src/pages/auth/RegisterPage.tsx
@@ -1,14 +1,15 @@
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 { AuthContainer, AuthHeader } from "../chat/ui/components/Auth";
+import { AlertsContainer, type Alert, type AlertType } from "../chat/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 type { ErrorResponse, RegisterRequest, LoginResponse } from "../chat/core/types";
+import { API_BASE_URL } from "../chat/core/config";
+import { useAppState } from "../chat/ui/state";
+import { MaterialTextField } from "../chat/ui/components/core/TextField";
+import { ensureKeysOnLogin } from "../../api/authApi";
import { useNavigate } from "react-router-dom";
+import "./auth.scss";
export default function RegisterPage() {
const [alerts, updateAlerts] = useImmer([]);
diff --git a/frontend/src/pages/app/resources/css/_auth.scss b/frontend/src/pages/auth/auth.scss
similarity index 93%
rename from frontend/src/pages/app/resources/css/_auth.scss
rename to frontend/src/pages/auth/auth.scss
index 59ad690..aea00ca 100644
--- a/frontend/src/pages/app/resources/css/_auth.scss
+++ b/frontend/src/pages/auth/auth.scss
@@ -1,5 +1,5 @@
-@use "common/colors" as *;
-@use "common/material" as *;
+@use "../../css/common/colors" as *;
+@use "../../css/common/material" as *;
.auth-container {
display: flex;
diff --git a/frontend/src/pages/ChatPage.tsx b/frontend/src/pages/chat/ChatPage.tsx
similarity index 61%
rename from frontend/src/pages/ChatPage.tsx
rename to frontend/src/pages/chat/ChatPage.tsx
index 0989a42..bbf547d 100644
--- a/frontend/src/pages/ChatPage.tsx
+++ b/frontend/src/pages/chat/ChatPage.tsx
@@ -1,5 +1,6 @@
-import { LeftPanel } from "./app/ui/components/chat/LeftPanel";
-import { RightPanel } from "./app/ui/components/chat/RightPanel";
+import { LeftPanel } from "./ui/components/chat/LeftPanel";
+import { RightPanel } from "./ui/components/chat/RightPanel";
+import "./chat.scss";
export default function ChatPage() {
return (
diff --git a/frontend/src/pages/app/resources/css/_chat.scss b/frontend/src/pages/chat/chat.scss
similarity index 59%
rename from frontend/src/pages/app/resources/css/_chat.scss
rename to frontend/src/pages/chat/chat.scss
index a333156..f17af69 100644
--- a/frontend/src/pages/app/resources/css/_chat.scss
+++ b/frontend/src/pages/chat/chat.scss
@@ -1,5 +1,5 @@
-@use "common/colors" as *;
-@use "common/material" as *;
+@use "../../css/common/colors" as *;
+@use "../../css/common/material" as *;
@use "sass:color";
#chat-interface {
@@ -982,3 +982,915 @@
overflow: visible;
}
}
+
+// Panel chat styles
+// контейнер чата и панели с чатами
+.all-container {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ height: 100%;
+}
+
+#profile {
+ display: none;
+ flex-direction: column;
+ z-index: 2000;
+ top: 0;
+ left: 0;
+ position: fixed;
+ height: 100vh;
+ width: 27%;
+ background-color: $color-dark-surface-container;
+ position: relative;
+
+ .profileheader {
+ display: flex;
+ gap: 200px;
+
+ p {
+ color: white;
+ }
+
+ a {
+ text-decoration: none;
+ color: white;
+ border: solid 2px $color-dark-on-surface-variant;
+ padding: 5px;
+ border-radius: 10px;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.241);
+ }
+ }
+ }
+}
+
+#chat-list {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 0;
+ width: 40%;
+ background-color: $color-dark-surface-container;
+ height: 100%;
+ z-index: 1000;
+ min-height: 0; // allow children to manage their own scrolling
+
+ .chat-header-left {
+ display: flex;
+ color: white;
+ font-size: 25px;
+ background-color: $color-dark-surface-container;
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+ padding: 16px;
+ overflow: hidden;
+
+ .product-name {
+ flex-grow: 1;
+ }
+
+ .profile {
+ font-size: 24px;
+ display: flex;
+ justify-content: start;
+ flex-shrink: 0;
+ flex-grow: 0;
+
+ #closeprofile a {
+ text-decoration: none;
+ color: white;
+ border: solid 2px $color-dark-on-surface-variant;
+ padding: 5px;
+ border-radius: 10px;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.241);
+ }
+ }
+
+ img {
+ $size: 45px;
+ display: flex;
+ border-radius: 50%;
+ width: $size;
+ height: $size;
+ }
+ }
+ }
+
+ .chat-tabs {
+ margin-top: 5px;
+ width: 100%;
+ height: calc(100% - 80px);
+ display: flex;
+ flex-direction: column;
+ min-height: 0; // prevent flex collapse when inner overflows
+ --mdui-color-surface: $color-dark-surface-container;
+ --mdui-color-surface-variant: transparent;
+
+ img {
+ width: 45px;
+ height: 45px;
+ border-radius: 20%;
+ object-fit: cover;
+ margin-right: 1rem;
+ }
+
+ mdui-tabs {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ min-height: 0; // enable inner panel to scroll
+ }
+
+ mdui-tab-panel[active] {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0; // critical to avoid collapsing
+ overflow: hidden;
+ }
+
+ mdui-list {
+ flex: 1;
+ min-height: 0; // allow scroll area to size correctly
+ overflow-y: auto;
+ padding: 0;
+ margin: 0;
+ }
+ }
+
+ mdui-bottom-app-bar {
+ position: relative;
+ width: 100%;
+ padding-left: 16px;
+ padding-right: 16px;
+ margin-top: auto;
+ }
+}
+
+// Profile styles
+#profile-dialog .content {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ min-width: 400px;
+
+ .header-top {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 16px;
+ position: relative;
+ padding-bottom: 16px;
+ border-bottom: 1px solid $color-dark-outline;
+
+ .profile-picture-container {
+ position: relative;
+ $size: 70px;
+ width: $size;
+ height: $size;
+ flex-shrink: 0;
+
+ #profile-picture {
+ width: $size;
+ height: $size;
+ border-radius: 50%;
+ object-fit: cover;
+ }
+
+ .upload-overlay {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ width: 28px;
+ height: 28px;
+ cursor: pointer;
+ }
+ }
+
+ mdui-text-field {
+ flex: 1;
+ }
+ }
+
+ #profile-form {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ mdui-text-field {
+ width: 100%;
+ }
+
+ .dialog-actions {
+ display: flex;
+ gap: 12px;
+ padding-top: 16px;
+ border-top: 1px solid $color-dark-outline;
+
+ > * {
+ flex: 1;
+ }
+ }
+ }
+}
+
+// User profile dialog content styles
+#user-profile-dialog .content {
+ display: flex;
+ gap: 1.5rem;
+
+ .profile-picture-section {
+ flex-shrink: 0;
+
+ .profile-picture {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ object-fit: cover;
+ border: 2px solid $color-dark-outline;
+ }
+ }
+
+ .profile-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ .username-section {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+
+ .username {
+ margin: 0;
+ color: $color-dark-on-surface;
+ font-size: 1.1rem;
+ font-weight: 600;
+ }
+
+ .online-status {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.85rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: 12px;
+ font-weight: 500;
+
+ &.online {
+ color: $success;
+ background-color: rgba(76, 175, 80, 0.1);
+
+ .online-indicator {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background-color: $success;
+ }
+ }
+
+ &.offline {
+ color: $color-dark-on-surface-variant;
+ background-color: rgba(255, 255, 255, 0.05);
+
+ .offline-indicator {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background-color: $color-dark-on-surface-variant;
+ }
+ }
+ }
+ }
+
+ .bio-section {
+ label {
+ display: block;
+ color: $color-dark-on-surface-variant;
+ font-size: 0.85rem;
+ font-weight: 500;
+ margin-bottom: 0.5rem;
+ }
+
+ .bio-display {
+ color: $color-dark-on-surface;
+ font-size: 0.9rem;
+ line-height: 1.4;
+ padding: 0.75rem;
+ background-color: $color-dark-surface;
+ border-radius: 8px;
+ border: 1px solid $color-dark-outline;
+ min-height: 60px;
+ }
+
+ .bio-actions {
+ display: flex;
+ gap: 0.5rem;
+ margin-top: 0.5rem;
+ }
+ }
+
+ .profile-stats {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+
+ .stat {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem 0;
+
+ .stat-label {
+ color: $color-dark-on-surface-variant;
+ font-size: 0.85rem;
+ }
+
+ .stat-value {
+ color: $color-dark-on-surface;
+ font-size: 0.85rem;
+ font-weight: 500;
+ }
+ }
+ }
+
+ .profile-actions {
+ display: flex;
+ gap: 0.75rem;
+ margin-top: 0.5rem;
+
+ mdui-button {
+ flex: 1;
+ }
+ }
+ }
+}
+
+// Cropper Dialog Styles
+#cropper-dialog {
+ .cropper-dialog-content {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ min-width: 500px;
+ max-width: 600px;
+ }
+
+ .cropper-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 16px;
+ border-bottom: 1px solid $color-dark-outline;
+
+ h3 {
+ margin: 0;
+ color: $color-dark-on-surface;
+ }
+ }
+
+ .cropper-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 400px;
+ background: $color-dark-surface-container;
+ border-radius: 8px;
+ overflow: hidden;
+
+ #cropper-area {
+ width: 100%;
+ height: 100%;
+ min-height: 400px;
+ }
+ }
+
+ .cropper-actions {
+ display: flex;
+ gap: 12px;
+ justify-content: flex-end;
+ padding-top: 16px;
+ border-top: 1px solid $color-dark-outline;
+ }
+}
+
+// Reaction styles
+.message-reactions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ margin-top: 8px;
+ margin-left: 10px;
+ margin-right: 10px;
+ animation: messageReactionsFadeIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+.reaction-button {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ border: none;
+ border-radius: 16px;
+ background-color: $color-dark-surface-container;
+ cursor: pointer;
+ transition: transform 0.2s ease, background-color 0.2s ease;
+ font-size: 1px;
+ min-height: 28px;
+ animation: reactionFadeIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+
+ &.removing {
+ animation: reactionFadeOut 0.2s ease forwards;
+ }
+
+ &:hover {
+ background-color: $color-dark-surface-container-high;
+ transform: scale(1.05);
+ }
+
+ &.reacted {
+ background-color: $color-dark-primary-container;
+ border-color: $color-dark-primary;
+ color: $color-dark-on-primary-container;
+
+ &:hover {
+ background-color: color.adjust($color-dark-primary-container, $lightness: 20%);
+ }
+ }
+}
+
+.reaction-emoji {
+ font-size: 17px;
+ line-height: 1;
+}
+
+.reaction-count {
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 1;
+}
+
+// Reaction bar styles (standalone)
+.reaction-bar {
+ background: $color-dark-surface-container;
+ border: 1px solid $color-dark-outline;
+ border-radius: 24px;
+ padding: 8px;
+ opacity: 1;
+ transition: all 0.15s ease;
+ backdrop-filter: blur(8px);
+ transform: translateY(0);
+
+ &.closing {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+}
+
+// Emoji menu wrapper inside reaction bar
+.emoji-menu-wrapper {
+ width: 320px;
+ height: 400px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+// Context menu wrapper with animations
+.context-menu-wrapper {
+ position: relative;
+ display: block;
+
+ // Animation states
+ &.entering {
+ opacity: 0;
+ transform: scale(0.8);
+ animation: contextMenuEnter 0.2s ease forwards;
+ }
+
+ &.entering-left {
+ opacity: 0;
+ transform: translateX(-20px) scale(0.8);
+ animation: contextMenuEnterLeft 0.2s ease forwards;
+ }
+
+ &.entering-up {
+ opacity: 0;
+ transform: translateY(20px) scale(0.8);
+ animation: contextMenuEnterUp 0.2s ease forwards;
+ }
+
+ &.entering-up-left {
+ opacity: 0;
+ transform: translateX(-20px) translateY(20px) scale(0.8);
+ animation: contextMenuEnterUpLeft 0.2s ease forwards;
+ }
+
+ &.closing {
+ opacity: 1;
+ transform: scale(1);
+ animation: contextMenuClose 0.2s ease forwards;
+ }
+
+ &.closing-left {
+ opacity: 1;
+ transform: translateX(0) scale(1);
+ animation: contextMenuCloseLeft 0.2s ease forwards;
+ }
+
+ &.closing-up {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ animation: contextMenuCloseUp 0.2s ease forwards;
+ }
+
+ &.closing-up-left {
+ opacity: 1;
+ transform: translateX(0) translateY(0) scale(1);
+ animation: contextMenuCloseUpLeft 0.2s ease forwards;
+ }
+}
+
+// Reaction bar inside context menu wrapper
+.context-menu-reaction-bar {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 8px 12px;
+ background: $color-dark-surface-container;
+ border: 1px solid $color-dark-outline;
+ border-radius: 16px;
+ position: absolute;
+ bottom: 100%;
+ justify-content: center;
+ margin-bottom: 10px;
+ transition: width 0.3s ease-out, height 0.3s ease-out;
+
+ &.left {
+ left: 0;
+ transform: translateX(0);
+ }
+
+ &.right {
+ right: 0;
+ transform: translateX(0);
+ }
+
+ &.expanded {
+ padding: 0;
+ overflow: hidden;
+ width: 320px;
+ height: 400px;
+ border-radius: 16px;
+
+ // Default: expand downward from the reaction bar's bottom edge
+ position: absolute;
+ bottom: auto;
+ top: 0;
+ left: 0;
+ transform: translateY(0);
+
+ &.expand-upward {
+ // Expand upward from the reaction bar's top edge
+ bottom: 100%;
+ top: auto;
+ margin-bottom: 10px;
+ margin-top: 0;
+ transform: translateY(0);
+ }
+
+ .emoji-menu-wrapper {
+ animation: emojiMenuEnter 0.5s ease;
+ }
+ }
+}
+
+@keyframes emojiMenuEnter {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+.reaction-bar-content {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ transition: opacity 0.3s ease-out;
+
+ &.faded {
+ opacity: 0;
+ }
+}
+
+.reaction-emoji-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border: none;
+ border-radius: 16px;
+ background: transparent;
+ cursor: pointer;
+ transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+ font-size: 18px;
+
+ &:hover {
+ background: var(--mdui-color-surface-container-high);
+ transform: scale(1.3);
+ box-shadow: var(--mdui-elevation-1);
+ }
+
+ &:active {
+ transform: scale(0.95);
+ transition: transform 0.1s ease;
+ }
+}
+
+.reaction-expand-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border: 1px solid var(--mdui-color-outline);
+ border-radius: 16px;
+ background: var(--mdui-color-surface);
+ cursor: pointer;
+ transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+
+ &:hover {
+ background: var(--mdui-color-surface-container-high);
+ border-color: var(--mdui-color-primary);
+ transform: scale(1.1);
+ box-shadow: var(--mdui-elevation-1);
+ }
+
+ &:active {
+ transform: scale(0.95);
+ transition: transform 0.1s ease;
+ }
+
+ .material-symbols {
+ font-size: 18px;
+ color: var(--mdui-color-on-surface);
+ transition: transform 0.2s ease;
+ }
+
+ &:hover .material-symbols {
+ transform: rotate(90deg);
+ }
+}
+
+// Animation for reactions appearing/disappearing
+@keyframes messageReactionsFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes reactionFadeIn {
+ from {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes reactionFadeOut {
+ from {
+ opacity: 1;
+ transform: scale(1);
+ }
+
+ to {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+}
+
+// Context menu wrapper animations
+@keyframes contextMenuEnter {
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes contextMenuEnterLeft {
+ to {
+ opacity: 1;
+ transform: translateX(0) scale(1);
+ }
+}
+
+@keyframes contextMenuEnterUp {
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+@keyframes contextMenuEnterUpLeft {
+ to {
+ opacity: 1;
+ transform: translateX(0) translateY(0) scale(1);
+ }
+}
+
+@keyframes contextMenuClose {
+ to {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+}
+
+@keyframes contextMenuCloseLeft {
+ to {
+ opacity: 0;
+ transform: translateX(-20px) scale(0.8);
+ }
+}
+
+@keyframes contextMenuCloseUp {
+ to {
+ opacity: 0;
+ transform: translateY(20px) scale(0.8);
+ }
+}
+
+@keyframes contextMenuCloseUpLeft {
+ to {
+ opacity: 0;
+ transform: translateX(-20px) translateY(20px) scale(0.8);
+ }
+}
+
+// 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;
+ }
+}
+
+// Settings styles
+#settings-dialog {
+ .fullscreen-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: calc(100vh - (1.5rem * 2));
+ width: 100%;
+ position: relative;
+
+ #settings-dialog-inner {
+ max-width: 1200px;
+ max-height: 1000px;
+ width: 100%;
+ height: 100%;
+
+ .header {
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+ margin-bottom: 16px;
+ }
+
+ #settings-menu {
+ display: flex;
+ flex-direction: row;
+ gap: 16px;
+
+ mdui-list {
+ max-width: 280px;
+ padding-right: 16px;
+ overflow-y: auto;
+ }
+
+ .screen {
+ flex: 1;
+ overflow-y: auto;
+ position: relative;
+
+ .settings-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ opacity: 0;
+ visibility: hidden;
+ transform: translateY(20px);
+ transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s ease;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+
+ &.active {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(0);
+ position: relative;
+ }
+
+ h3 {
+ margin: 0 0 16px 0;
+ color: $color-dark-on-surface;
+ }
+
+ mdui-text-field,
+ mdui-select,
+ mdui-switch,
+ mdui-button {
+ margin-bottom: 8px;
+ }
+
+ mdui-switch {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 0;
+ border-bottom: 1px solid $color-dark-outline;
+
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+
+ p {
+ margin: 8px 0;
+ color: $color-dark-on-surface-variant;
+ }
+
+ mdui-linear-progress {
+ margin: 16px 0;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// Reply dialog styles
+.reply-dialog .dialog-content {
+ width: 300px;
+ overflow-x:hidden;
+
+ .reply-preview-dialog {
+ margin-bottom: 1rem;
+ padding: 16px;
+ background-color: $color-dark-surface-container;
+ border-radius: 16px;
+
+ .reply-content {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+
+ .reply-username {
+ font-weight: 600;
+ color: $color-dark-on-surface;
+ font-size: 0.85rem;
+ }
+
+ .reply-text {
+ color: $color-dark-on-surface-variant;
+ font-size: 0.9rem;
+ line-height: 1.4;
+ }
+ }
+ }
+}
diff --git a/frontend/src/pages/app/core/config.ts b/frontend/src/pages/chat/core/config.ts
similarity index 100%
rename from frontend/src/pages/app/core/config.ts
rename to frontend/src/pages/chat/core/config.ts
diff --git a/frontend/src/pages/app/core/init.ts b/frontend/src/pages/chat/core/init.ts
similarity index 100%
rename from frontend/src/pages/app/core/init.ts
rename to frontend/src/pages/chat/core/init.ts
diff --git a/frontend/src/pages/app/core/types.d.ts b/frontend/src/pages/chat/core/types.d.ts
similarity index 100%
rename from frontend/src/pages/app/core/types.d.ts
rename to frontend/src/pages/chat/core/types.d.ts
diff --git a/frontend/src/pages/app/core/websocket.ts b/frontend/src/pages/chat/core/websocket.ts
similarity index 100%
rename from frontend/src/pages/app/core/websocket.ts
rename to frontend/src/pages/chat/core/websocket.ts
diff --git a/frontend/src/pages/app/resources/css/_electron.scss b/frontend/src/pages/chat/electron/electron.scss
similarity index 94%
rename from frontend/src/pages/app/resources/css/_electron.scss
rename to frontend/src/pages/chat/electron/electron.scss
index 69aa4ed..3d5f8e9 100644
--- a/frontend/src/pages/app/resources/css/_electron.scss
+++ b/frontend/src/pages/chat/electron/electron.scss
@@ -1,4 +1,4 @@
-@use "common/material" as *;
+@use "../../../css/common/material" as *;
#electron-title-bar {
display: none;
diff --git a/frontend/src/pages/app/electron/electron.ts b/frontend/src/pages/chat/electron/electron.ts
similarity index 94%
rename from frontend/src/pages/app/electron/electron.ts
rename to frontend/src/pages/chat/electron/electron.ts
index 0eb9ab1..c69cce2 100644
--- a/frontend/src/pages/app/electron/electron.ts
+++ b/frontend/src/pages/chat/electron/electron.ts
@@ -5,6 +5,8 @@
* @version 1.0.0
*/
+import "./electron.scss";
+
export const isElectron = import.meta.env.VITE_ELECTRON && window.electronInterface != undefined;
if (isElectron) {
diff --git a/frontend/src/pages/app/service-worker/service-worker.ts b/frontend/src/pages/chat/service-worker/service-worker.ts
similarity index 100%
rename from frontend/src/pages/app/service-worker/service-worker.ts
rename to frontend/src/pages/chat/service-worker/service-worker.ts
diff --git a/frontend/src/pages/app/ui/components/Alerts.tsx b/frontend/src/pages/chat/ui/components/Alerts.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/Alerts.tsx
rename to frontend/src/pages/chat/ui/components/Alerts.tsx
diff --git a/frontend/src/pages/app/ui/components/Auth.tsx b/frontend/src/pages/chat/ui/components/Auth.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/Auth.tsx
rename to frontend/src/pages/chat/ui/components/Auth.tsx
diff --git a/frontend/src/pages/app/ui/components/Electron.tsx b/frontend/src/pages/chat/ui/components/Electron.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/Electron.tsx
rename to frontend/src/pages/chat/ui/components/Electron.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/BottomAppBar.tsx b/frontend/src/pages/chat/ui/components/chat/BottomAppBar.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/BottomAppBar.tsx
rename to frontend/src/pages/chat/ui/components/chat/BottomAppBar.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/ChatHeader.tsx b/frontend/src/pages/chat/ui/components/chat/ChatHeader.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/ChatHeader.tsx
rename to frontend/src/pages/chat/ui/components/chat/ChatHeader.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/ChatInputWrapper.tsx b/frontend/src/pages/chat/ui/components/chat/ChatInputWrapper.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/ChatInputWrapper.tsx
rename to frontend/src/pages/chat/ui/components/chat/ChatInputWrapper.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/ChatMainHeader.tsx b/frontend/src/pages/chat/ui/components/chat/ChatMainHeader.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/ChatMainHeader.tsx
rename to frontend/src/pages/chat/ui/components/chat/ChatMainHeader.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/ChatMessages.tsx b/frontend/src/pages/chat/ui/components/chat/ChatMessages.tsx
similarity index 99%
rename from frontend/src/pages/app/ui/components/chat/ChatMessages.tsx
rename to frontend/src/pages/chat/ui/components/chat/ChatMessages.tsx
index efdf97c..43923a9 100644
--- a/frontend/src/pages/app/ui/components/chat/ChatMessages.tsx
+++ b/frontend/src/pages/chat/ui/components/chat/ChatMessages.tsx
@@ -4,7 +4,7 @@ import type { Message as MessageType } from "../../../core/types";
import type { UserProfile } from "../../../core/types";
import { UserProfileDialog } from "./UserProfileDialog";
import { MessageContextMenu, type ContextMenuState } from "./MessageContextMenu";
-import { fetchUserProfile } from "../../../api/profileApi";
+import { fetchUserProfile } from "../../../../../api/profileApi";
import { useEffect, useState, type ReactNode } from "react";
import { delay } from "../../../utils/utils";
import { MaterialDialog } from "../core/Dialog";
diff --git a/frontend/src/pages/app/ui/components/chat/ChatTabs.tsx b/frontend/src/pages/chat/ui/components/chat/ChatTabs.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/ChatTabs.tsx
rename to frontend/src/pages/chat/ui/components/chat/ChatTabs.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/DMUsersList.tsx b/frontend/src/pages/chat/ui/components/chat/DMUsersList.tsx
similarity index 96%
rename from frontend/src/pages/app/ui/components/chat/DMUsersList.tsx
rename to frontend/src/pages/chat/ui/components/chat/DMUsersList.tsx
index c09bc25..7d20b78 100644
--- a/frontend/src/pages/app/ui/components/chat/DMUsersList.tsx
+++ b/frontend/src/pages/chat/ui/components/chat/DMUsersList.tsx
@@ -1,8 +1,8 @@
import { useEffect } from "react";
import { useDM, type DMUser } from "../../hooks/useDM";
import { useAppState } from "../../state";
-import { fetchUserPublicKey } from "../../../api/dmApi";
-import defaultAvatar from "../../../resources/images/default-avatar.png";
+import { fetchUserPublicKey } from "../../../../../api/dmApi";
+import defaultAvatar from "../../../../../images/default-avatar.png";
export function DMUsersList() {
const { dmUsers, isLoadingUsers, loadUsers } = useDM();
diff --git a/frontend/src/pages/app/ui/components/chat/EmojiMenu.tsx b/frontend/src/pages/chat/ui/components/chat/EmojiMenu.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/EmojiMenu.tsx
rename to frontend/src/pages/chat/ui/components/chat/EmojiMenu.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/LeftPanel.tsx b/frontend/src/pages/chat/ui/components/chat/LeftPanel.tsx
similarity index 98%
rename from frontend/src/pages/app/ui/components/chat/LeftPanel.tsx
rename to frontend/src/pages/chat/ui/components/chat/LeftPanel.tsx
index 1377632..6c30c39 100644
--- a/frontend/src/pages/app/ui/components/chat/LeftPanel.tsx
+++ b/frontend/src/pages/chat/ui/components/chat/LeftPanel.tsx
@@ -1,6 +1,6 @@
import { PRODUCT_NAME } from "../../../core/config";
import { useAppState } from "../../state";
-import defaultAvatar from "../../../resources/images/default-avatar.png";
+import defaultAvatar from "../../../../../images/default-avatar.png";
import { useState, type FormEvent } from "react";
import { ProfileDialog } from "../profile/ProfileDialog";
import { SettingsDialog } from "../settings/SettingsDialog";
diff --git a/frontend/src/pages/app/ui/components/chat/Message.tsx b/frontend/src/pages/chat/ui/components/chat/Message.tsx
similarity index 99%
rename from frontend/src/pages/app/ui/components/chat/Message.tsx
rename to frontend/src/pages/chat/ui/components/chat/Message.tsx
index 5e88677..1894390 100644
--- a/frontend/src/pages/app/ui/components/chat/Message.tsx
+++ b/frontend/src/pages/chat/ui/components/chat/Message.tsx
@@ -1,14 +1,14 @@
import { formatTime, id } from "../../../utils/utils";
import type { Attachment, Message as MessageType } from "../../../core/types";
-import defaultAvatar from "../../../resources/images/default-avatar.png";
+import defaultAvatar from "../../../../../images/default-avatar.png";
import Quote from "../core/Quote";
import { parse } from "marked";
import DOMPurify from "dompurify";
import { useEffect, useState, useRef } from "react";
-import { getCurrentKeys } from "../../../auth/crypto";
+import { getCurrentKeys } from "../../../../../api/authApi";
import { ecdhSharedSecret, deriveWrappingKey } from "../../../utils/crypto/asymmetric";
import { importAesGcmKey, aesGcmDecrypt } from "../../../utils/crypto/symmetric";
-import { getAuthHeaders } from "../../../auth/api";
+import { getAuthHeaders } from "../../../../../api/authApi";
import { useAppState } from "../../state";
import { ub64 } from "../../../utils/utils";
import { useImmer } from "use-immer";
diff --git a/frontend/src/pages/app/ui/components/chat/MessageContextMenu.tsx b/frontend/src/pages/chat/ui/components/chat/MessageContextMenu.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/MessageContextMenu.tsx
rename to frontend/src/pages/chat/ui/components/chat/MessageContextMenu.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/MessagePanelRenderer.tsx b/frontend/src/pages/chat/ui/components/chat/MessagePanelRenderer.tsx
similarity index 99%
rename from frontend/src/pages/app/ui/components/chat/MessagePanelRenderer.tsx
rename to frontend/src/pages/chat/ui/components/chat/MessagePanelRenderer.tsx
index da64a2f..96795af 100644
--- a/frontend/src/pages/app/ui/components/chat/MessagePanelRenderer.tsx
+++ b/frontend/src/pages/chat/ui/components/chat/MessagePanelRenderer.tsx
@@ -5,7 +5,7 @@ import { ChatMessages } from "./ChatMessages";
import { ChatInputWrapper } from "./ChatInputWrapper";
import { setGlobalMessageHandler } from "../../../core/websocket";
import type { Message, WebSocketMessage } from "../../../core/types";
-import defaultAvatar from "../../../resources/images/default-avatar.png";
+import defaultAvatar from "../../../../../images/default-avatar.png";
import AnimatedOpacity from "../core/animations/AnimatedOpacity";
import type { DMPanel } from "../../panels/DMPanel";
diff --git a/frontend/src/pages/app/ui/components/chat/MessageReactions.tsx b/frontend/src/pages/chat/ui/components/chat/MessageReactions.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/MessageReactions.tsx
rename to frontend/src/pages/chat/ui/components/chat/MessageReactions.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/ReplyMessageDialog.tsx b/frontend/src/pages/chat/ui/components/chat/ReplyMessageDialog.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/ReplyMessageDialog.tsx
rename to frontend/src/pages/chat/ui/components/chat/ReplyMessageDialog.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/RightPanel.tsx b/frontend/src/pages/chat/ui/components/chat/RightPanel.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/RightPanel.tsx
rename to frontend/src/pages/chat/ui/components/chat/RightPanel.tsx
diff --git a/frontend/src/pages/app/ui/components/chat/UserProfileDialog.tsx b/frontend/src/pages/chat/ui/components/chat/UserProfileDialog.tsx
similarity index 97%
rename from frontend/src/pages/app/ui/components/chat/UserProfileDialog.tsx
rename to frontend/src/pages/chat/ui/components/chat/UserProfileDialog.tsx
index 1f3b8eb..ee77e28 100644
--- a/frontend/src/pages/app/ui/components/chat/UserProfileDialog.tsx
+++ b/frontend/src/pages/chat/ui/components/chat/UserProfileDialog.tsx
@@ -2,7 +2,7 @@ import type { DialogProps } from "../../../core/types";
import type { UserProfile } from "../../../core/types";
import { MaterialDialog } from "../core/Dialog";
import { formatTime } from "../../../utils/utils";
-import defaultAvatar from "../../../resources/images/default-avatar.png";
+import defaultAvatar from "../../../../../images/default-avatar.png";
interface UserProfileDialogProps extends DialogProps {
userProfile: UserProfile | null;
diff --git a/frontend/src/pages/app/ui/components/chat/emojiData.ts b/frontend/src/pages/chat/ui/components/chat/emojiData.ts
similarity index 100%
rename from frontend/src/pages/app/ui/components/chat/emojiData.ts
rename to frontend/src/pages/chat/ui/components/chat/emojiData.ts
diff --git a/frontend/src/pages/app/ui/components/core/Dialog.tsx b/frontend/src/pages/chat/ui/components/core/Dialog.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/core/Dialog.tsx
rename to frontend/src/pages/chat/ui/components/core/Dialog.tsx
diff --git a/frontend/src/pages/app/ui/components/core/Quote.tsx b/frontend/src/pages/chat/ui/components/core/Quote.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/core/Quote.tsx
rename to frontend/src/pages/chat/ui/components/core/Quote.tsx
diff --git a/frontend/src/pages/app/ui/components/core/RichTextArea.tsx b/frontend/src/pages/chat/ui/components/core/RichTextArea.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/core/RichTextArea.tsx
rename to frontend/src/pages/chat/ui/components/core/RichTextArea.tsx
diff --git a/frontend/src/pages/app/ui/components/core/TextField.tsx b/frontend/src/pages/chat/ui/components/core/TextField.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/core/TextField.tsx
rename to frontend/src/pages/chat/ui/components/core/TextField.tsx
diff --git a/frontend/src/pages/app/ui/components/core/animations/AnimatedHeight.tsx b/frontend/src/pages/chat/ui/components/core/animations/AnimatedHeight.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/core/animations/AnimatedHeight.tsx
rename to frontend/src/pages/chat/ui/components/core/animations/AnimatedHeight.tsx
diff --git a/frontend/src/pages/app/ui/components/core/animations/AnimatedOpacity.tsx b/frontend/src/pages/chat/ui/components/core/animations/AnimatedOpacity.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/core/animations/AnimatedOpacity.tsx
rename to frontend/src/pages/chat/ui/components/core/animations/AnimatedOpacity.tsx
diff --git a/frontend/src/pages/app/ui/components/core/animations/types.d.ts b/frontend/src/pages/chat/ui/components/core/animations/types.d.ts
similarity index 100%
rename from frontend/src/pages/app/ui/components/core/animations/types.d.ts
rename to frontend/src/pages/chat/ui/components/core/animations/types.d.ts
diff --git a/frontend/src/pages/app/ui/components/profile/CropperDialog.tsx b/frontend/src/pages/chat/ui/components/profile/CropperDialog.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/profile/CropperDialog.tsx
rename to frontend/src/pages/chat/ui/components/profile/CropperDialog.tsx
diff --git a/frontend/src/pages/app/ui/components/profile/ImageCropper.tsx b/frontend/src/pages/chat/ui/components/profile/ImageCropper.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/components/profile/ImageCropper.tsx
rename to frontend/src/pages/chat/ui/components/profile/ImageCropper.tsx
diff --git a/frontend/src/pages/app/ui/components/profile/ProfileDialog.tsx b/frontend/src/pages/chat/ui/components/profile/ProfileDialog.tsx
similarity index 99%
rename from frontend/src/pages/app/ui/components/profile/ProfileDialog.tsx
rename to frontend/src/pages/chat/ui/components/profile/ProfileDialog.tsx
index 1bb65ce..8a2b359 100644
--- a/frontend/src/pages/app/ui/components/profile/ProfileDialog.tsx
+++ b/frontend/src/pages/chat/ui/components/profile/ProfileDialog.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect, useRef, type FormEvent } from "react";
-import defaultAvatar from "../../../resources/images/default-avatar.png";
+import defaultAvatar from "../../../../../images/default-avatar.png";
import type { TextField } from "mdui/components/text-field";
import type { DialogProps } from "../../../core/types";
import { MaterialDialog } from "../core/Dialog";
diff --git a/frontend/src/pages/app/ui/components/settings/SettingsDialog.tsx b/frontend/src/pages/chat/ui/components/settings/SettingsDialog.tsx
similarity index 99%
rename from frontend/src/pages/app/ui/components/settings/SettingsDialog.tsx
rename to frontend/src/pages/chat/ui/components/settings/SettingsDialog.tsx
index 14b1ad5..9a1ead0 100644
--- a/frontend/src/pages/app/ui/components/settings/SettingsDialog.tsx
+++ b/frontend/src/pages/chat/ui/components/settings/SettingsDialog.tsx
@@ -6,7 +6,7 @@ import { initialize, isSupported, startElectronReceiver, stopElectronReceiver, s
import { isElectron } from "../../../electron/electron";
import { useAppState } from "../../state";
import type { Switch } from "mdui/components/switch";
-import { getAuthHeaders } from "../../../auth/api";
+import { getAuthHeaders } from "../../../../../api/authApi";
export function SettingsDialog({ isOpen, onOpenChange }: DialogProps) {
const [activePanel, setActivePanel] = useState("notifications-settings");
diff --git a/frontend/src/pages/app/ui/hooks/useCombinedRefs.ts b/frontend/src/pages/chat/ui/hooks/useCombinedRefs.ts
similarity index 100%
rename from frontend/src/pages/app/ui/hooks/useCombinedRefs.ts
rename to frontend/src/pages/chat/ui/hooks/useCombinedRefs.ts
diff --git a/frontend/src/pages/app/ui/hooks/useDM.ts b/frontend/src/pages/chat/ui/hooks/useDM.ts
similarity index 99%
rename from frontend/src/pages/app/ui/hooks/useDM.ts
rename to frontend/src/pages/chat/ui/hooks/useDM.ts
index 9e55858..53ae1bd 100644
--- a/frontend/src/pages/app/ui/hooks/useDM.ts
+++ b/frontend/src/pages/chat/ui/hooks/useDM.ts
@@ -6,7 +6,7 @@ import {
fetchDMHistory,
decryptDm,
sendDMViaWebSocket
-} from "../../api/dmApi";
+} from "../../../../api/dmApi";
import type { User, Message, DmEncryptedJSON } from "../../core/types";
import { websocket } from "../../core/websocket";
diff --git a/frontend/src/pages/app/ui/hooks/useProfile.ts b/frontend/src/pages/chat/ui/hooks/useProfile.ts
similarity index 98%
rename from frontend/src/pages/app/ui/hooks/useProfile.ts
rename to frontend/src/pages/chat/ui/hooks/useProfile.ts
index e256f78..9cad0a2 100644
--- a/frontend/src/pages/app/ui/hooks/useProfile.ts
+++ b/frontend/src/pages/chat/ui/hooks/useProfile.ts
@@ -1,6 +1,6 @@
import { useState, useCallback, useEffect } from "react";
import { useAppState } from "../state";
-import { loadProfile, updateProfile, uploadProfilePicture, type ProfileData } from "../../api/profileApi";
+import { loadProfile, updateProfile, uploadProfilePicture, type ProfileData } from "../../../../api/profileApi";
import { showSuccess, showError } from "../../utils/notification";
export default function useProfile() {
diff --git a/frontend/src/pages/app/ui/hooks/useWindowSize.ts b/frontend/src/pages/chat/ui/hooks/useWindowSize.ts
similarity index 100%
rename from frontend/src/pages/app/ui/hooks/useWindowSize.ts
rename to frontend/src/pages/chat/ui/hooks/useWindowSize.ts
diff --git a/frontend/src/pages/app/ui/panels/DMPanel.ts b/frontend/src/pages/chat/ui/panels/DMPanel.ts
similarity index 99%
rename from frontend/src/pages/app/ui/panels/DMPanel.ts
rename to frontend/src/pages/chat/ui/panels/DMPanel.ts
index b528138..eb6e8c0 100644
--- a/frontend/src/pages/app/ui/panels/DMPanel.ts
+++ b/frontend/src/pages/chat/ui/panels/DMPanel.ts
@@ -6,7 +6,7 @@ import {
sendDmWithFiles,
editDmEnvelope,
deleteDmEnvelope
-} from "../../api/dmApi";
+} from "../../../../api/dmApi";
import type { DmEncryptedJSON, DmEnvelope, DMWebSocketMessage, EncryptedMessageJson, Message } from "../../core/types";
import type { UserState } from "../state";
diff --git a/frontend/src/pages/app/ui/panels/MessagePanel.ts b/frontend/src/pages/chat/ui/panels/MessagePanel.ts
similarity index 100%
rename from frontend/src/pages/app/ui/panels/MessagePanel.ts
rename to frontend/src/pages/chat/ui/panels/MessagePanel.ts
diff --git a/frontend/src/pages/app/ui/panels/PublicChatPanel.ts b/frontend/src/pages/chat/ui/panels/PublicChatPanel.ts
similarity index 99%
rename from frontend/src/pages/app/ui/panels/PublicChatPanel.ts
rename to frontend/src/pages/chat/ui/panels/PublicChatPanel.ts
index 9d3d29a..38372cf 100644
--- a/frontend/src/pages/app/ui/panels/PublicChatPanel.ts
+++ b/frontend/src/pages/chat/ui/panels/PublicChatPanel.ts
@@ -1,6 +1,6 @@
import { MessagePanel } from "./MessagePanel";
import { API_BASE_URL } from "../../core/config";
-import { getAuthHeaders } from "../../auth/api";
+import { getAuthHeaders } from "../../../../api/authApi";
import { request } from "../../core/websocket";
import type { ChatWebSocketMessage, Message, SendMessageRequest, ReactionUpdateWebSocketMessage } from "../../core/types";
import type { UserState } from "../state";
diff --git a/frontend/src/pages/app/ui/screen/ChatScreen.tsx b/frontend/src/pages/chat/ui/screen/ChatScreen.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/screen/ChatScreen.tsx
rename to frontend/src/pages/chat/ui/screen/ChatScreen.tsx
diff --git a/frontend/src/pages/app/ui/screen/DownloadAppScreen.tsx b/frontend/src/pages/chat/ui/screen/DownloadAppScreen.tsx
similarity index 100%
rename from frontend/src/pages/app/ui/screen/DownloadAppScreen.tsx
rename to frontend/src/pages/chat/ui/screen/DownloadAppScreen.tsx
diff --git a/frontend/src/pages/app/ui/screen/LoginScreen.tsx b/frontend/src/pages/chat/ui/screen/LoginScreen.tsx
similarity index 99%
rename from frontend/src/pages/app/ui/screen/LoginScreen.tsx
rename to frontend/src/pages/chat/ui/screen/LoginScreen.tsx
index 168b0d8..c6e5656 100644
--- a/frontend/src/pages/app/ui/screen/LoginScreen.tsx
+++ b/frontend/src/pages/chat/ui/screen/LoginScreen.tsx
@@ -2,7 +2,7 @@ import { useImmer } from "use-immer";
import { AlertsContainer, type Alert, type AlertType } from "../components/Alerts";
import { AuthContainer, AuthHeader } from "../components/Auth";
import type { ErrorResponse, LoginRequest, LoginResponse } from "../../core/types";
-import { ensureKeysOnLogin } from "../../auth/crypto";
+import { ensureKeysOnLogin } from "../../../../api/authApi";
import { API_BASE_URL } from "../../core/config";
import { useRef } from "react";
import type { TextField } from "mdui/components/text-field";
diff --git a/frontend/src/pages/app/ui/screen/RegisterScreen.tsx b/frontend/src/pages/chat/ui/screen/RegisterScreen.tsx
similarity index 99%
rename from frontend/src/pages/app/ui/screen/RegisterScreen.tsx
rename to frontend/src/pages/chat/ui/screen/RegisterScreen.tsx
index ec16814..9fd2a6a 100644
--- a/frontend/src/pages/app/ui/screen/RegisterScreen.tsx
+++ b/frontend/src/pages/chat/ui/screen/RegisterScreen.tsx
@@ -9,7 +9,7 @@ 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";
+import { ensureKeysOnLogin } from "../../../../api/authApi";
export default function RegisterScreen() {
const [alerts, updateAlerts] = useImmer([]);
diff --git a/frontend/src/pages/app/ui/state.ts b/frontend/src/pages/chat/ui/state.ts
similarity index 99%
rename from frontend/src/pages/app/ui/state.ts
rename to frontend/src/pages/chat/ui/state.ts
index 14899e0..c9b9e79 100644
--- a/frontend/src/pages/app/ui/state.ts
+++ b/frontend/src/pages/chat/ui/state.ts
@@ -4,8 +4,8 @@ import { request } from "../core/websocket";
import { MessagePanel } from "./panels/MessagePanel";
import { PublicChatPanel } from "./panels/PublicChatPanel";
import { DMPanel, type DMPanelData } from "./panels/DMPanel";
-import { getAuthHeaders } from "../auth/api";
-import { restoreKeys } from "../auth/crypto";
+import { getAuthHeaders } from "../../../api/authApi";
+import { restoreKeys } from "../../../api/authApi";
import { API_BASE_URL } from "../core/config";
import { initialize, subscribe, startElectronReceiver, isSupported } from "../utils/push-notifications";
import { isElectron } from "../electron/electron";
diff --git a/frontend/src/pages/app/utils/crypto/asymmetric.ts b/frontend/src/pages/chat/utils/crypto/asymmetric.ts
similarity index 100%
rename from frontend/src/pages/app/utils/crypto/asymmetric.ts
rename to frontend/src/pages/chat/utils/crypto/asymmetric.ts
diff --git a/frontend/src/pages/app/utils/crypto/backup.ts b/frontend/src/pages/chat/utils/crypto/backup.ts
similarity index 100%
rename from frontend/src/pages/app/utils/crypto/backup.ts
rename to frontend/src/pages/chat/utils/crypto/backup.ts
diff --git a/frontend/src/pages/app/utils/crypto/kdf.ts b/frontend/src/pages/chat/utils/crypto/kdf.ts
similarity index 100%
rename from frontend/src/pages/app/utils/crypto/kdf.ts
rename to frontend/src/pages/chat/utils/crypto/kdf.ts
diff --git a/frontend/src/pages/app/utils/crypto/symmetric.ts b/frontend/src/pages/chat/utils/crypto/symmetric.ts
similarity index 100%
rename from frontend/src/pages/app/utils/crypto/symmetric.ts
rename to frontend/src/pages/chat/utils/crypto/symmetric.ts
diff --git a/frontend/src/pages/app/utils/material.ts b/frontend/src/pages/chat/utils/material.ts
similarity index 100%
rename from frontend/src/pages/app/utils/material.ts
rename to frontend/src/pages/chat/utils/material.ts
diff --git a/frontend/src/pages/app/utils/notification.ts b/frontend/src/pages/chat/utils/notification.ts
similarity index 100%
rename from frontend/src/pages/app/utils/notification.ts
rename to frontend/src/pages/chat/utils/notification.ts
diff --git a/frontend/src/pages/app/utils/push-notifications.ts b/frontend/src/pages/chat/utils/push-notifications.ts
similarity index 100%
rename from frontend/src/pages/app/utils/push-notifications.ts
rename to frontend/src/pages/chat/utils/push-notifications.ts
diff --git a/frontend/src/pages/app/utils/utils.ts b/frontend/src/pages/chat/utils/utils.ts
similarity index 100%
rename from frontend/src/pages/app/utils/utils.ts
rename to frontend/src/pages/chat/utils/utils.ts
diff --git a/frontend/src/pages/DownloadAppPage.tsx b/frontend/src/pages/download-app/DownloadAppPage.tsx
similarity index 97%
rename from frontend/src/pages/DownloadAppPage.tsx
rename to frontend/src/pages/download-app/DownloadAppPage.tsx
index 3e8ec2e..95f2cd3 100644
--- a/frontend/src/pages/DownloadAppPage.tsx
+++ b/frontend/src/pages/download-app/DownloadAppPage.tsx
@@ -1,3 +1,5 @@
+import "./download-app.scss";
+
export default function DownloadAppPage() {
return (
diff --git a/frontend/src/pages/app/resources/css/_download-app.scss b/frontend/src/pages/download-app/download-app.scss
similarity index 100%
rename from frontend/src/pages/app/resources/css/_download-app.scss
rename to frontend/src/pages/download-app/download-app.scss
diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/home/HomePage.tsx
similarity index 99%
rename from frontend/src/pages/HomePage.tsx
rename to frontend/src/pages/home/HomePage.tsx
index 76632df..19578ba 100644
--- a/frontend/src/pages/HomePage.tsx
+++ b/frontend/src/pages/home/HomePage.tsx
@@ -1,6 +1,7 @@
import { Navigate, useNavigate } from "react-router-dom";
-import { useAppState } from "./app/ui/state";
-import { isElectron } from "./app/electron/electron";
+import { useAppState } from "../chat/ui/state";
+import { isElectron } from "../chat/electron/electron";
+import "./home.scss";
function GitHubLink({ children }: { children: React.ReactNode }) {
return (
diff --git a/frontend/src/pages/app/resources/css/_homepage.scss b/frontend/src/pages/home/home.scss
similarity index 99%
rename from frontend/src/pages/app/resources/css/_homepage.scss
rename to frontend/src/pages/home/home.scss
index 15aee92..1dcb789 100644
--- a/frontend/src/pages/app/resources/css/_homepage.scss
+++ b/frontend/src/pages/home/home.scss
@@ -1,4 +1,4 @@
-@use "common/material" as *;
+@use "../../css/common/material" as *;
.homepage {
min-height: 100vh;
diff --git a/frontend/src/pages/NotFoundPage.tsx b/frontend/src/pages/not-found/NotFoundPage.tsx
similarity index 98%
rename from frontend/src/pages/NotFoundPage.tsx
rename to frontend/src/pages/not-found/NotFoundPage.tsx
index 907119f..1b06839 100644
--- a/frontend/src/pages/NotFoundPage.tsx
+++ b/frontend/src/pages/not-found/NotFoundPage.tsx
@@ -1,4 +1,5 @@
import { useNavigate } from "react-router-dom";
+import "./not-found.scss";
export default function NotFoundPage() {
const navigate = useNavigate();
diff --git a/frontend/src/pages/app/resources/css/_404.scss b/frontend/src/pages/not-found/not-found.scss
similarity index 100%
rename from frontend/src/pages/app/resources/css/_404.scss
rename to frontend/src/pages/not-found/not-found.scss