mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Change the structure
This commit is contained in:
+1
-1
@@ -573,4 +573,4 @@ package-lock.json
|
|||||||
backend/alembic/**
|
backend/alembic/**
|
||||||
!backend/alembic/env.py
|
!backend/alembic/env.py
|
||||||
!backend/alembic/script.py.mako
|
!backend/alembic/script.py.mako
|
||||||
!frontend/src/pages/app/resources/css/lib
|
!frontend/src/css/lib
|
||||||
+12
-12
@@ -1,17 +1,17 @@
|
|||||||
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
||||||
import { ElectronTitleBar } from "./pages/app/ui/components/Electron";
|
import { ElectronTitleBar } from "./pages/chat/ui/components/Electron";
|
||||||
import { useAppState } from "./pages/app/ui/state";
|
import { useAppState } from "./pages/chat/ui/state";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import HomePage from "./pages/HomePage";
|
import HomePage from "./pages/home/HomePage";
|
||||||
import LoginPage from "./pages/LoginPage";
|
import LoginPage from "./pages/auth/LoginPage";
|
||||||
import RegisterPage from "./pages/RegisterPage";
|
import RegisterPage from "./pages/auth/RegisterPage";
|
||||||
import ChatPage from "./pages/ChatPage";
|
import ChatPage from "./pages/chat/ChatPage";
|
||||||
import DownloadAppPage from "./pages/DownloadAppPage";
|
import DownloadAppPage from "./pages/download-app/DownloadAppPage";
|
||||||
import NotFoundPage from "./pages/NotFoundPage";
|
import NotFoundPage from "./pages/not-found/NotFoundPage";
|
||||||
import ProtectedRoute from "./pages/ProtectedRoute";
|
import ProtectedRoute from "./pages/ProtectedRoute";
|
||||||
import { isElectron } from "./pages/app/electron/electron";
|
import { isElectron } from "./pages/chat/electron/electron";
|
||||||
import { MINIMUM_WIDTH } from "./pages/app/core/config";
|
import { MINIMUM_WIDTH } from "./pages/chat/core/config";
|
||||||
import useWindowSize from "./pages/app/ui/hooks/useWindowSize";
|
import useWindowSize from "./pages/chat/ui/hooks/useWindowSize";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { restoreUserFromStorage } = useAppState();
|
const { restoreUserFromStorage } = useAppState();
|
||||||
@@ -31,7 +31,7 @@ export default function App() {
|
|||||||
<ElectronTitleBar />
|
<ElectronTitleBar />
|
||||||
<div id="main-wrapper">
|
<div id="main-wrapper">
|
||||||
<Routes>
|
<Routes>
|
||||||
z<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route path="/register" element={<RegisterPage />} />
|
<Route path="/register" element={<RegisterPage />} />
|
||||||
<Route path="/download-app" element={<DownloadAppPage />} />
|
<Route path="/download-app" element={<DownloadAppPage />} />
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
import { API_BASE_URL } from "../core/config";
|
import type { Headers, UploadPublicKeyRequest, BackupBlob } from "../pages/chat/core/types";
|
||||||
import { getAuthHeaders } from "./api";
|
import { generateX25519KeyPair } from "../pages/chat/utils/crypto/asymmetric";
|
||||||
import { generateX25519KeyPair } from "../utils/crypto/asymmetric";
|
import { encodeBlob, encryptBackupWithPassword, decryptBackupWithPassword, decodeBlob } from "../pages/chat/utils/crypto/backup";
|
||||||
import { encryptBackupWithPassword, decryptBackupWithPassword, encodeBlob, decodeBlob } from "../utils/crypto/backup";
|
import { b64, ub64 } from "../pages/chat/utils/utils";
|
||||||
import { b64, ub64 } from "../utils/utils";
|
import { API_BASE_URL } from "../pages/chat/core/config";
|
||||||
import type { BackupBlob, UploadPublicKeyRequest } 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;
|
||||||
|
}
|
||||||
|
|
||||||
let currentPublicKey: Uint8Array | null = null;
|
let currentPublicKey: Uint8Array | null = null;
|
||||||
let currentPrivateKey: Uint8Array | null = null;
|
let currentPrivateKey: Uint8Array | null = null;
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { API_BASE_URL } from "../core/config";
|
import { API_BASE_URL } from "../pages/chat/core/config";
|
||||||
import { getAuthHeaders } from "../auth/api";
|
import { getAuthHeaders } from "./authApi";
|
||||||
import { ecdhSharedSecret, deriveWrappingKey } from "../utils/crypto/asymmetric";
|
import { ecdhSharedSecret, deriveWrappingKey } from "../pages/chat/utils/crypto/asymmetric";
|
||||||
import { importAesGcmKey, aesGcmEncrypt, aesGcmDecrypt } from "../utils/crypto/symmetric";
|
import { importAesGcmKey, aesGcmEncrypt, aesGcmDecrypt } from "../pages/chat/utils/crypto/symmetric";
|
||||||
import { randomBytes } from "../utils/crypto/kdf";
|
import { randomBytes } from "../pages/chat/utils/crypto/kdf";
|
||||||
import { getCurrentKeys } from "../auth/crypto";
|
import { getCurrentKeys } from "./authApi";
|
||||||
import { request } from "../core/websocket";
|
import { request } from "../pages/chat/core/websocket";
|
||||||
import type { SendDMRequest, DmEnvelope, User, DMEditRequest, DmEncryptedJSON, BaseDmEnvelope } from "../core/types";
|
import type { SendDMRequest, DmEnvelope, User, DMEditRequest, DmEncryptedJSON, BaseDmEnvelope } from "../pages/chat/core/types";
|
||||||
import { b64, ub64 } from "../utils/utils";
|
import { b64, ub64 } from "../pages/chat/utils/utils";
|
||||||
|
|
||||||
export async function decryptDm(envelope: DmEnvelope, senderPublicKeyB64: string): Promise<string> {
|
export async function decryptDm(envelope: DmEnvelope, senderPublicKeyB64: string): Promise<string> {
|
||||||
const keys = getCurrentKeys();
|
const keys = getCurrentKeys();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getAuthHeaders } from "../auth/api";
|
import { getAuthHeaders } from "./authApi";
|
||||||
import { API_BASE_URL } from "../core/config";
|
import { API_BASE_URL } from "../pages/chat/core/config";
|
||||||
import type { UserProfile } from "../core/types";
|
import type { UserProfile } from "../pages/chat/core/types";
|
||||||
|
|
||||||
export interface ProfileData {
|
export interface ProfileData {
|
||||||
profile_picture?: string;
|
profile_picture?: string;
|
||||||
@@ -1,18 +1,7 @@
|
|||||||
@use "auth";
|
|
||||||
@use "chat";
|
|
||||||
@use "profile";
|
|
||||||
@use "settings";
|
|
||||||
@use "panelchat";
|
|
||||||
@use "common/animations";
|
@use "common/animations";
|
||||||
@use "common/components";
|
@use "common/components";
|
||||||
@use "common/colors" as *;
|
@use "common/colors" as *;
|
||||||
@use "common/material" 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/montserrat";
|
||||||
@use "lib/fonts/material-symbols";
|
@use "lib/fonts/material-symbols";
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
@@ -5,17 +5,16 @@
|
|||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import './pages/app/resources/css/style.scss';
|
import './css/style.scss';
|
||||||
import "mdui/mdui.css";
|
import "mdui/mdui.css";
|
||||||
|
|
||||||
import "./pages/app/utils/material";
|
import "./pages/chat/utils/material";
|
||||||
import "./pages/app/core/init";
|
import "./pages/chat/core/init";
|
||||||
import "./pages/app/electron/electron";
|
import "./pages/chat/electron/electron";
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
|
|
||||||
// Initialize React app
|
|
||||||
createRoot(document.getElementById("root")!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<App />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useAppState } from "./app/ui/state";
|
import { useAppState } from "./chat/ui/state";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
interface ProtectedRouteProps {
|
interface ProtectedRouteProps {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
import { useImmer } from "use-immer";
|
import { useImmer } from "use-immer";
|
||||||
import { AlertsContainer, type Alert, type AlertType } from "./app/ui/components/Alerts";
|
import { AlertsContainer, type Alert, type AlertType } from "../chat/ui/components/Alerts";
|
||||||
import { AuthContainer, AuthHeader } from "./app/ui/components/Auth";
|
import { AuthContainer, AuthHeader } from "../chat/ui/components/Auth";
|
||||||
import type { ErrorResponse, LoginRequest, LoginResponse } from "./app/core/types";
|
import type { ErrorResponse, LoginRequest, LoginResponse } from "../chat/core/types";
|
||||||
import { ensureKeysOnLogin } from "./app/auth/crypto";
|
import { ensureKeysOnLogin } from "../../api/authApi";
|
||||||
import { API_BASE_URL } from "./app/core/config";
|
import { API_BASE_URL } from "../chat/core/config";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import type { TextField } from "mdui/components/text-field";
|
import type { TextField } from "mdui/components/text-field";
|
||||||
import { useAppState } from "./app/ui/state";
|
import { useAppState } from "../chat/ui/state";
|
||||||
import { MaterialTextField } from "./app/ui/components/core/TextField";
|
import { MaterialTextField } from "../chat/ui/components/core/TextField";
|
||||||
import { initialize, isSupported, startElectronReceiver, subscribe } from "./app/utils/push-notifications";
|
import { initialize, isSupported, startElectronReceiver, subscribe } from "../chat/utils/push-notifications";
|
||||||
import { isElectron } from "./app/electron/electron";
|
import { isElectron } from "../chat/electron/electron";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import "./auth.scss";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import { useImmer } from "use-immer";
|
import { useImmer } from "use-immer";
|
||||||
import { AuthContainer, AuthHeader } from "./app/ui/components/Auth";
|
import { AuthContainer, AuthHeader } from "../chat/ui/components/Auth";
|
||||||
import { AlertsContainer, type Alert, type AlertType } from "./app/ui/components/Alerts";
|
import { AlertsContainer, type Alert, type AlertType } from "../chat/ui/components/Alerts";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { TextField } from "mdui/components/text-field";
|
import { TextField } from "mdui/components/text-field";
|
||||||
import type { ErrorResponse, RegisterRequest, LoginResponse } from "./app/core/types";
|
import type { ErrorResponse, RegisterRequest, LoginResponse } from "../chat/core/types";
|
||||||
import { API_BASE_URL } from "./app/core/config";
|
import { API_BASE_URL } from "../chat/core/config";
|
||||||
import { useAppState } from "./app/ui/state";
|
import { useAppState } from "../chat/ui/state";
|
||||||
import { MaterialTextField } from "./app/ui/components/core/TextField";
|
import { MaterialTextField } from "../chat/ui/components/core/TextField";
|
||||||
import { ensureKeysOnLogin } from "./app/auth/crypto";
|
import { ensureKeysOnLogin } from "../../api/authApi";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import "./auth.scss";
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "common/colors" as *;
|
@use "../../css/common/colors" as *;
|
||||||
@use "common/material" as *;
|
@use "../../css/common/material" as *;
|
||||||
|
|
||||||
.auth-container {
|
.auth-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { LeftPanel } from "./app/ui/components/chat/LeftPanel";
|
import { LeftPanel } from "./ui/components/chat/LeftPanel";
|
||||||
import { RightPanel } from "./app/ui/components/chat/RightPanel";
|
import { RightPanel } from "./ui/components/chat/RightPanel";
|
||||||
|
import "./chat.scss";
|
||||||
|
|
||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
return (
|
return (
|
||||||
+914
-2
@@ -1,5 +1,5 @@
|
|||||||
@use "common/colors" as *;
|
@use "../../css/common/colors" as *;
|
||||||
@use "common/material" as *;
|
@use "../../css/common/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
#chat-interface {
|
#chat-interface {
|
||||||
@@ -982,3 +982,915 @@
|
|||||||
overflow: visible;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
@use "common/material" as *;
|
@use "../../../css/common/material" as *;
|
||||||
|
|
||||||
#electron-title-bar {
|
#electron-title-bar {
|
||||||
display: none;
|
display: none;
|
||||||
+2
@@ -5,6 +5,8 @@
|
|||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./electron.scss";
|
||||||
|
|
||||||
export const isElectron = import.meta.env.VITE_ELECTRON && window.electronInterface != undefined;
|
export const isElectron = import.meta.env.VITE_ELECTRON && window.electronInterface != undefined;
|
||||||
|
|
||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import type { Message as MessageType } from "../../../core/types";
|
|||||||
import type { UserProfile } from "../../../core/types";
|
import type { UserProfile } from "../../../core/types";
|
||||||
import { UserProfileDialog } from "./UserProfileDialog";
|
import { UserProfileDialog } from "./UserProfileDialog";
|
||||||
import { MessageContextMenu, type ContextMenuState } from "./MessageContextMenu";
|
import { MessageContextMenu, type ContextMenuState } from "./MessageContextMenu";
|
||||||
import { fetchUserProfile } from "../../../api/profileApi";
|
import { fetchUserProfile } from "../../../../../api/profileApi";
|
||||||
import { useEffect, useState, type ReactNode } from "react";
|
import { useEffect, useState, type ReactNode } from "react";
|
||||||
import { delay } from "../../../utils/utils";
|
import { delay } from "../../../utils/utils";
|
||||||
import { MaterialDialog } from "../core/Dialog";
|
import { MaterialDialog } from "../core/Dialog";
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useDM, type DMUser } from "../../hooks/useDM";
|
import { useDM, type DMUser } from "../../hooks/useDM";
|
||||||
import { useAppState } from "../../state";
|
import { useAppState } from "../../state";
|
||||||
import { fetchUserPublicKey } from "../../../api/dmApi";
|
import { fetchUserPublicKey } from "../../../../../api/dmApi";
|
||||||
import defaultAvatar from "../../../resources/images/default-avatar.png";
|
import defaultAvatar from "../../../../../images/default-avatar.png";
|
||||||
|
|
||||||
export function DMUsersList() {
|
export function DMUsersList() {
|
||||||
const { dmUsers, isLoadingUsers, loadUsers } = useDM();
|
const { dmUsers, isLoadingUsers, loadUsers } = useDM();
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { PRODUCT_NAME } from "../../../core/config";
|
import { PRODUCT_NAME } from "../../../core/config";
|
||||||
import { useAppState } from "../../state";
|
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 { useState, type FormEvent } from "react";
|
||||||
import { ProfileDialog } from "../profile/ProfileDialog";
|
import { ProfileDialog } from "../profile/ProfileDialog";
|
||||||
import { SettingsDialog } from "../settings/SettingsDialog";
|
import { SettingsDialog } from "../settings/SettingsDialog";
|
||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
import { formatTime, id } from "../../../utils/utils";
|
import { formatTime, id } from "../../../utils/utils";
|
||||||
import type { Attachment, Message as MessageType } from "../../../core/types";
|
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 Quote from "../core/Quote";
|
||||||
import { parse } from "marked";
|
import { parse } from "marked";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { getCurrentKeys } from "../../../auth/crypto";
|
import { getCurrentKeys } from "../../../../../api/authApi";
|
||||||
import { ecdhSharedSecret, deriveWrappingKey } from "../../../utils/crypto/asymmetric";
|
import { ecdhSharedSecret, deriveWrappingKey } from "../../../utils/crypto/asymmetric";
|
||||||
import { importAesGcmKey, aesGcmDecrypt } from "../../../utils/crypto/symmetric";
|
import { importAesGcmKey, aesGcmDecrypt } from "../../../utils/crypto/symmetric";
|
||||||
import { getAuthHeaders } from "../../../auth/api";
|
import { getAuthHeaders } from "../../../../../api/authApi";
|
||||||
import { useAppState } from "../../state";
|
import { useAppState } from "../../state";
|
||||||
import { ub64 } from "../../../utils/utils";
|
import { ub64 } from "../../../utils/utils";
|
||||||
import { useImmer } from "use-immer";
|
import { useImmer } from "use-immer";
|
||||||
+1
-1
@@ -5,7 +5,7 @@ import { ChatMessages } from "./ChatMessages";
|
|||||||
import { ChatInputWrapper } from "./ChatInputWrapper";
|
import { ChatInputWrapper } from "./ChatInputWrapper";
|
||||||
import { setGlobalMessageHandler } from "../../../core/websocket";
|
import { setGlobalMessageHandler } from "../../../core/websocket";
|
||||||
import type { Message, WebSocketMessage } from "../../../core/types";
|
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 AnimatedOpacity from "../core/animations/AnimatedOpacity";
|
||||||
import type { DMPanel } from "../../panels/DMPanel";
|
import type { DMPanel } from "../../panels/DMPanel";
|
||||||
|
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import type { DialogProps } from "../../../core/types";
|
|||||||
import type { UserProfile } from "../../../core/types";
|
import type { UserProfile } from "../../../core/types";
|
||||||
import { MaterialDialog } from "../core/Dialog";
|
import { MaterialDialog } from "../core/Dialog";
|
||||||
import { formatTime } from "../../../utils/utils";
|
import { formatTime } from "../../../utils/utils";
|
||||||
import defaultAvatar from "../../../resources/images/default-avatar.png";
|
import defaultAvatar from "../../../../../images/default-avatar.png";
|
||||||
|
|
||||||
interface UserProfileDialogProps extends DialogProps {
|
interface UserProfileDialogProps extends DialogProps {
|
||||||
userProfile: UserProfile | null;
|
userProfile: UserProfile | null;
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useRef, type FormEvent } from "react";
|
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 { TextField } from "mdui/components/text-field";
|
||||||
import type { DialogProps } from "../../../core/types";
|
import type { DialogProps } from "../../../core/types";
|
||||||
import { MaterialDialog } from "../core/Dialog";
|
import { MaterialDialog } from "../core/Dialog";
|
||||||
+1
-1
@@ -6,7 +6,7 @@ import { initialize, isSupported, startElectronReceiver, stopElectronReceiver, s
|
|||||||
import { isElectron } from "../../../electron/electron";
|
import { isElectron } from "../../../electron/electron";
|
||||||
import { useAppState } from "../../state";
|
import { useAppState } from "../../state";
|
||||||
import type { Switch } from "mdui/components/switch";
|
import type { Switch } from "mdui/components/switch";
|
||||||
import { getAuthHeaders } from "../../../auth/api";
|
import { getAuthHeaders } from "../../../../../api/authApi";
|
||||||
|
|
||||||
export function SettingsDialog({ isOpen, onOpenChange }: DialogProps) {
|
export function SettingsDialog({ isOpen, onOpenChange }: DialogProps) {
|
||||||
const [activePanel, setActivePanel] = useState("notifications-settings");
|
const [activePanel, setActivePanel] = useState("notifications-settings");
|
||||||
+1
-1
@@ -6,7 +6,7 @@ import {
|
|||||||
fetchDMHistory,
|
fetchDMHistory,
|
||||||
decryptDm,
|
decryptDm,
|
||||||
sendDMViaWebSocket
|
sendDMViaWebSocket
|
||||||
} from "../../api/dmApi";
|
} from "../../../../api/dmApi";
|
||||||
import type { User, Message, DmEncryptedJSON } from "../../core/types";
|
import type { User, Message, DmEncryptedJSON } from "../../core/types";
|
||||||
import { websocket } from "../../core/websocket";
|
import { websocket } from "../../core/websocket";
|
||||||
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useCallback, useEffect } from "react";
|
import { useState, useCallback, useEffect } from "react";
|
||||||
import { useAppState } from "../state";
|
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";
|
import { showSuccess, showError } from "../../utils/notification";
|
||||||
|
|
||||||
export default function useProfile() {
|
export default function useProfile() {
|
||||||
+1
-1
@@ -6,7 +6,7 @@ import {
|
|||||||
sendDmWithFiles,
|
sendDmWithFiles,
|
||||||
editDmEnvelope,
|
editDmEnvelope,
|
||||||
deleteDmEnvelope
|
deleteDmEnvelope
|
||||||
} from "../../api/dmApi";
|
} from "../../../../api/dmApi";
|
||||||
import type { DmEncryptedJSON, DmEnvelope, DMWebSocketMessage, EncryptedMessageJson, Message } from "../../core/types";
|
import type { DmEncryptedJSON, DmEnvelope, DMWebSocketMessage, EncryptedMessageJson, Message } from "../../core/types";
|
||||||
import type { UserState } from "../state";
|
import type { UserState } from "../state";
|
||||||
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { MessagePanel } from "./MessagePanel";
|
import { MessagePanel } from "./MessagePanel";
|
||||||
import { API_BASE_URL } from "../../core/config";
|
import { API_BASE_URL } from "../../core/config";
|
||||||
import { getAuthHeaders } from "../../auth/api";
|
import { getAuthHeaders } from "../../../../api/authApi";
|
||||||
import { request } from "../../core/websocket";
|
import { request } from "../../core/websocket";
|
||||||
import type { ChatWebSocketMessage, Message, SendMessageRequest, ReactionUpdateWebSocketMessage } from "../../core/types";
|
import type { ChatWebSocketMessage, Message, SendMessageRequest, ReactionUpdateWebSocketMessage } from "../../core/types";
|
||||||
import type { UserState } from "../state";
|
import type { UserState } from "../state";
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import { useImmer } from "use-immer";
|
|||||||
import { AlertsContainer, type Alert, type AlertType } from "../components/Alerts";
|
import { AlertsContainer, type Alert, type AlertType } from "../components/Alerts";
|
||||||
import { AuthContainer, AuthHeader } from "../components/Auth";
|
import { AuthContainer, AuthHeader } from "../components/Auth";
|
||||||
import type { ErrorResponse, LoginRequest, LoginResponse } from "../../core/types";
|
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 { API_BASE_URL } from "../../core/config";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import type { TextField } from "mdui/components/text-field";
|
import type { TextField } from "mdui/components/text-field";
|
||||||
+1
-1
@@ -9,7 +9,7 @@ import { API_BASE_URL } from "../../core/config";
|
|||||||
import { useAppState } from "../state";
|
import { useAppState } from "../state";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { MaterialTextField } from "../components/core/TextField";
|
import { MaterialTextField } from "../components/core/TextField";
|
||||||
import { ensureKeysOnLogin } from "../../auth/crypto";
|
import { ensureKeysOnLogin } from "../../../../api/authApi";
|
||||||
|
|
||||||
export default function RegisterScreen() {
|
export default function RegisterScreen() {
|
||||||
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
||||||
@@ -4,8 +4,8 @@ import { request } from "../core/websocket";
|
|||||||
import { MessagePanel } from "./panels/MessagePanel";
|
import { MessagePanel } from "./panels/MessagePanel";
|
||||||
import { PublicChatPanel } from "./panels/PublicChatPanel";
|
import { PublicChatPanel } from "./panels/PublicChatPanel";
|
||||||
import { DMPanel, type DMPanelData } from "./panels/DMPanel";
|
import { DMPanel, type DMPanelData } from "./panels/DMPanel";
|
||||||
import { getAuthHeaders } from "../auth/api";
|
import { getAuthHeaders } from "../../../api/authApi";
|
||||||
import { restoreKeys } from "../auth/crypto";
|
import { restoreKeys } from "../../../api/authApi";
|
||||||
import { API_BASE_URL } from "../core/config";
|
import { API_BASE_URL } from "../core/config";
|
||||||
import { initialize, subscribe, startElectronReceiver, isSupported } from "../utils/push-notifications";
|
import { initialize, subscribe, startElectronReceiver, isSupported } from "../utils/push-notifications";
|
||||||
import { isElectron } from "../electron/electron";
|
import { isElectron } from "../electron/electron";
|
||||||
+2
@@ -1,3 +1,5 @@
|
|||||||
|
import "./download-app.scss";
|
||||||
|
|
||||||
export default function DownloadAppPage() {
|
export default function DownloadAppPage() {
|
||||||
return (
|
return (
|
||||||
<div className="download-app-screen">
|
<div className="download-app-screen">
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Navigate, useNavigate } from "react-router-dom";
|
import { Navigate, useNavigate } from "react-router-dom";
|
||||||
import { useAppState } from "./app/ui/state";
|
import { useAppState } from "../chat/ui/state";
|
||||||
import { isElectron } from "./app/electron/electron";
|
import { isElectron } from "../chat/electron/electron";
|
||||||
|
import "./home.scss";
|
||||||
|
|
||||||
function GitHubLink({ children }: { children: React.ReactNode }) {
|
function GitHubLink({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
@use "common/material" as *;
|
@use "../../css/common/material" as *;
|
||||||
|
|
||||||
.homepage {
|
.homepage {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import "./not-found.scss";
|
||||||
|
|
||||||
export default function NotFoundPage() {
|
export default function NotFoundPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
Reference in New Issue
Block a user