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