mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Restructure CSS
This commit is contained in:
@@ -9,8 +9,8 @@ import ChatPage from "./pages/chat/ChatPage";
|
|||||||
import DownloadAppPage from "./pages/download-app/DownloadAppPage";
|
import DownloadAppPage from "./pages/download-app/DownloadAppPage";
|
||||||
import NotFoundPage from "./pages/not-found/NotFoundPage";
|
import NotFoundPage from "./pages/not-found/NotFoundPage";
|
||||||
import ProtectedRoute from "./pages/ProtectedRoute";
|
import ProtectedRoute from "./pages/ProtectedRoute";
|
||||||
import { isElectron } from "./pages/chat/electron/electron";
|
import { isElectron } from "./core/electron/electron";
|
||||||
import { MINIMUM_WIDTH } from "./pages/chat/core/config";
|
import { MINIMUM_WIDTH } from "./core/config";
|
||||||
import useWindowSize from "./pages/chat/ui/hooks/useWindowSize";
|
import useWindowSize from "./pages/chat/ui/hooks/useWindowSize";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { Headers, UploadPublicKeyRequest, BackupBlob } from "../pages/chat/core/types";
|
import type { Headers, UploadPublicKeyRequest, BackupBlob } from "../types";
|
||||||
import { generateX25519KeyPair } from "../utils/crypto/asymmetric";
|
import { generateX25519KeyPair } from "../../utils/crypto/asymmetric";
|
||||||
import { encodeBlob, encryptBackupWithPassword, decryptBackupWithPassword, decodeBlob } from "../utils/crypto/backup";
|
import { encodeBlob, encryptBackupWithPassword, decryptBackupWithPassword, decodeBlob } from "../../utils/crypto/backup";
|
||||||
import { b64, ub64 } from "../utils/utils";
|
import { b64, ub64 } from "../../utils/utils";
|
||||||
import { API_BASE_URL } from "../pages/chat/core/config";
|
import { API_BASE_URL } from "../config";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates authentication headers for API requests
|
* Generates authentication headers for API requests
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { API_BASE_URL } from "../pages/chat/core/config";
|
import { API_BASE_URL } from "../config";
|
||||||
import { getAuthHeaders } from "./authApi";
|
import { getAuthHeaders } from "./authApi";
|
||||||
import { ecdhSharedSecret, deriveWrappingKey } from "../utils/crypto/asymmetric";
|
import { ecdhSharedSecret, deriveWrappingKey } from "../../utils/crypto/asymmetric";
|
||||||
import { importAesGcmKey, aesGcmEncrypt, aesGcmDecrypt } from "../utils/crypto/symmetric";
|
import { importAesGcmKey, aesGcmEncrypt, aesGcmDecrypt } from "../../utils/crypto/symmetric";
|
||||||
import { randomBytes } from "../utils/crypto/kdf";
|
import { randomBytes } from "../../utils/crypto/kdf";
|
||||||
import { getCurrentKeys } from "./authApi";
|
import { getCurrentKeys } from "./authApi";
|
||||||
import { request } from "../pages/chat/core/websocket";
|
import { request } from "../websocket";
|
||||||
import type { SendDMRequest, DmEnvelope, User, DMEditRequest, DmEncryptedJSON, BaseDmEnvelope } from "../pages/chat/core/types";
|
import type { SendDMRequest, DmEnvelope, User, DMEditRequest, DmEncryptedJSON, BaseDmEnvelope } from "../types";
|
||||||
import { b64, ub64 } from "../utils/utils";
|
import { b64, ub64 } from "../../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 "./authApi";
|
import { getAuthHeaders } from "./authApi";
|
||||||
import { API_BASE_URL } from "../pages/chat/core/config";
|
import { API_BASE_URL } from "../config";
|
||||||
import type { UserProfile } from "../pages/chat/core/types";
|
import type { UserProfile } from "../types";
|
||||||
|
|
||||||
export interface ProfileData {
|
export interface ProfileData {
|
||||||
profile_picture?: string;
|
profile_picture?: string;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
@use "../../../css/common/material" as *;
|
@use "../../css/material" as *;
|
||||||
|
|
||||||
#electron-title-bar {
|
#electron-title-bar {
|
||||||
display: none;
|
display: none;
|
||||||
+5
-5
@@ -1,8 +1,8 @@
|
|||||||
import { API_BASE_URL } from "./core/config";
|
import { API_BASE_URL } from "../config";
|
||||||
import { isElectron } from "./electron/electron";
|
import { isElectron } from "../electron/electron";
|
||||||
import { websocket } from "./core/websocket";
|
import { websocket } from "../websocket";
|
||||||
import type { NewMessageWebSocketMessage, WebSocketMessage } from "./core/types";
|
import type { NewMessageWebSocketMessage, WebSocketMessage } from "../types";
|
||||||
import serviceWorker from "../../../service-worker?worker&url";
|
import serviceWorker from "./service-worker?worker&url";
|
||||||
|
|
||||||
export interface PushSubscriptionData {
|
export interface PushSubscriptionData {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import { API_WS_BASE_URL } from "./config";
|
import { API_WS_BASE_URL } from "./config";
|
||||||
import type { WebSocketMessage } from "./types";
|
import type { WebSocketMessage } from "./types";
|
||||||
import { delay } from "../../../utils/utils";
|
import { delay } from "../utils/utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new WebSocket connection to the chat server
|
* Creates a new WebSocket connection to the chat server
|
||||||
@@ -30,77 +30,6 @@ button, input {
|
|||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu {
|
|
||||||
position: relative;
|
|
||||||
background-color: $color-dark-surface-container;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
z-index: 1000;
|
|
||||||
display: block;
|
|
||||||
min-width: 150px;
|
|
||||||
max-width: 200px;
|
|
||||||
white-space: nowrap;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
.context-menu-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: $color-dark-on-surface;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-symbols {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.pos-top-left {
|
|
||||||
transform-origin: top right;
|
|
||||||
}
|
|
||||||
&.pos-top-right {
|
|
||||||
transform-origin: top left;
|
|
||||||
}
|
|
||||||
&.pos-bottom-left {
|
|
||||||
transform-origin: bottom right;
|
|
||||||
}
|
|
||||||
&.pos-bottom-right {
|
|
||||||
transform-origin: bottom left;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.open {
|
|
||||||
animation: context-menu-open 0.25s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.faded {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes context-menu-open {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dialog content styles
|
// Dialog content styles
|
||||||
.dialog-content {
|
.dialog-content {
|
||||||
h3 {
|
h3 {
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
@use "common/animations";
|
@use "animations";
|
||||||
@use "common/components";
|
@use "components";
|
||||||
@use "common/colors" as *;
|
@use "colors" as *;
|
||||||
@use "common/material" as *;
|
@use "material" as *;
|
||||||
|
|
||||||
@use "lib/fonts/montserrat";
|
@use "fonts/montserrat";
|
||||||
@use "lib/fonts/material-symbols";
|
@use "fonts/material-symbols";
|
||||||
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import './css/style.scss';
|
|||||||
import "mdui/mdui.css";
|
import "mdui/mdui.css";
|
||||||
|
|
||||||
import "./utils/material";
|
import "./utils/material";
|
||||||
import "./pages/chat/core/init";
|
import "./core/init";
|
||||||
import "./pages/chat/electron/electron";
|
import "./core/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';
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { useImmer } from "use-immer";
|
import { useImmer } from "use-immer";
|
||||||
import { AlertsContainer, type Alert, type AlertType } from "../chat/ui/components/Alerts";
|
import { AlertsContainer, type Alert, type AlertType } from "../chat/ui/components/Alerts";
|
||||||
import { AuthContainer, AuthHeader } from "../chat/ui/components/Auth";
|
import { AuthContainer, AuthHeader } from "../chat/ui/components/Auth";
|
||||||
import type { ErrorResponse, LoginRequest, LoginResponse } from "../chat/core/types";
|
import type { ErrorResponse, LoginRequest, LoginResponse } from "../../core/types";
|
||||||
import { ensureKeysOnLogin } from "../../api/authApi";
|
import { ensureKeysOnLogin } from "../../core/api/authApi";
|
||||||
import { API_BASE_URL } from "../chat/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";
|
||||||
import { useAppState } from "../chat/ui/state";
|
import { useAppState } from "../chat/ui/state";
|
||||||
import { MaterialTextField } from "../chat/ui/components/core/TextField";
|
import { MaterialTextField } from "../chat/ui/components/core/TextField";
|
||||||
import { initialize, isSupported, startElectronReceiver, subscribe } from "../chat/push-notifications";
|
import { initialize, isSupported, startElectronReceiver, subscribe } from "../../core/push-notifications/push-notifications";
|
||||||
import { isElectron } from "../chat/electron/electron";
|
import { isElectron } from "../../core/electron/electron";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import "./auth.scss";
|
import "./auth.scss";
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { AuthContainer, AuthHeader } from "../chat/ui/components/Auth";
|
|||||||
import { AlertsContainer, type Alert, type AlertType } from "../chat/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 "../chat/core/types";
|
import type { ErrorResponse, RegisterRequest, LoginResponse } from "../../core/types";
|
||||||
import { API_BASE_URL } from "../chat/core/config";
|
import { API_BASE_URL } from "../../core/config";
|
||||||
import { useAppState } from "../chat/ui/state";
|
import { useAppState } from "../chat/ui/state";
|
||||||
import { MaterialTextField } from "../chat/ui/components/core/TextField";
|
import { MaterialTextField } from "../chat/ui/components/core/TextField";
|
||||||
import { ensureKeysOnLogin } from "../../api/authApi";
|
import { ensureKeysOnLogin } from "../../core/api/authApi";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import "./auth.scss";
|
import "./auth.scss";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../css/common/colors" as *;
|
@use "../../css/colors" as *;
|
||||||
@use "../../css/common/material" as *;
|
@use "../../css/material" as *;
|
||||||
|
|
||||||
.auth-container {
|
.auth-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/common/colors" as *;
|
@use "../../../css/colors" as *;
|
||||||
@use "../../../css/common/material" as *;
|
@use "../../../css/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
// Animation for reactions appearing/disappearing
|
// Animation for reactions appearing/disappearing
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/common/colors" as *;
|
@use "../../../css/colors" as *;
|
||||||
@use "../../../css/common/material" as *;
|
@use "../../../css/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
.chat-input-wrapper {
|
.chat-input-wrapper {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/common/colors" as *;
|
@use "../../../css/colors" as *;
|
||||||
@use "../../../css/common/material" as *;
|
@use "../../../css/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
// Reaction bar styles (standalone)
|
// Reaction bar styles (standalone)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/common/colors" as *;
|
@use "../../../css/colors" as *;
|
||||||
@use "../../../css/common/material" as *;
|
@use "../../../css/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
#chat-interface {
|
#chat-interface {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/common/colors" as *;
|
@use "../../../css/colors" as *;
|
||||||
@use "../../../css/common/material" as *;
|
@use "../../../css/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/common/colors" as *;
|
@use "../../../css/colors" as *;
|
||||||
@use "../../../css/common/material" as *;
|
@use "../../../css/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
// Reaction styles
|
// Reaction styles
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/common/colors" as *;
|
@use "../../../css/colors" as *;
|
||||||
@use "../../../css/common/material" as *;
|
@use "../../../css/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
.quote.contextual-content > .quote-inner {
|
.quote.contextual-content > .quote-inner {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/common/colors" as *;
|
@use "../../../css/colors" as *;
|
||||||
@use "../../../css/common/material" as *;
|
@use "../../../css/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
// Profile styles
|
// Profile styles
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/common/colors" as *;
|
@use "../../../css/colors" as *;
|
||||||
@use "../../../css/common/material" as *;
|
@use "../../../css/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
.chat-main {
|
.chat-main {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/common/colors" as *;
|
@use "../../../css/colors" as *;
|
||||||
@use "../../../css/common/material" as *;
|
@use "../../../css/material" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
// Settings styles
|
// Settings styles
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PRODUCT_NAME } from "../../core/config";
|
import { PRODUCT_NAME } from "../../../../core/config";
|
||||||
import { isElectron } from "../../electron/electron";
|
import { isElectron } from "../../../../core/electron/electron";
|
||||||
|
|
||||||
export function ElectronTitleBar() {
|
export function ElectronTitleBar() {
|
||||||
return isElectron && (
|
return isElectron && (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PRODUCT_NAME } from "../../../core/config";
|
import { PRODUCT_NAME } from "../../../../../core/config";
|
||||||
import useProfile from "../../hooks/useProfile";
|
import useProfile from "../../hooks/useProfile";
|
||||||
import defaultAvatar from "../../../resources/images/default-avatar.png";
|
import defaultAvatar from "../../../resources/images/default-avatar.png";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { MaterialDialog } from "../core/Dialog";
|
import { MaterialDialog } from "../core/Dialog";
|
||||||
import { RichTextArea } from "../core/RichTextArea";
|
import { RichTextArea } from "../core/RichTextArea";
|
||||||
import type { Message } from "../../../core/types";
|
import type { Message } from "../../../../../core/types";
|
||||||
import Quote from "../core/Quote";
|
import Quote from "../core/Quote";
|
||||||
import AnimatedHeight from "../core/animations/AnimatedHeight";
|
import AnimatedHeight from "../core/animations/AnimatedHeight";
|
||||||
import { useImmer } from "use-immer";
|
import { useImmer } from "use-immer";
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Message } from "./Message";
|
import { Message } from "./Message";
|
||||||
import { useAppState } from "../../state";
|
import { useAppState } from "../../state";
|
||||||
import type { Message as MessageType } from "../../../core/types";
|
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 "../../../../../core/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";
|
||||||
import { request } from "../../../core/websocket";
|
import { request } from "../../../../../core/websocket";
|
||||||
import type { AddReactionRequest, AddDmReactionRequest } from "../../../core/types";
|
import type { AddReactionRequest, AddDmReactionRequest } from "../../../../../core/types";
|
||||||
|
|
||||||
interface ChatMessagesProps {
|
interface ChatMessagesProps {
|
||||||
messages?: MessageType[];
|
messages?: MessageType[];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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 "../../../../../core/api/dmApi";
|
||||||
import defaultAvatar from "../../../../../images/default-avatar.png";
|
import defaultAvatar from "../../../../../images/default-avatar.png";
|
||||||
|
|
||||||
export function DMUsersList() {
|
export function DMUsersList() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect, useRef, useCallback } from "react";
|
import { useState, useEffect, useRef, useCallback } from "react";
|
||||||
import { EMOJI_CATEGORIES, getRecentEmojis, addRecentEmoji } from "./emojiData";
|
import { EMOJI_CATEGORIES, getRecentEmojis, addRecentEmoji } from "./emojiData";
|
||||||
import type { Size2D } from "../../../core/types";
|
import type { Size2D } from "../../../../../core/types";
|
||||||
|
|
||||||
interface BaseEmojiMenuProps {
|
interface BaseEmojiMenuProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PRODUCT_NAME } from "../../../core/config";
|
import { PRODUCT_NAME } from "../../../../../core/config";
|
||||||
import { useAppState } from "../../state";
|
import { useAppState } from "../../state";
|
||||||
import defaultAvatar from "../../../../../images/default-avatar.png";
|
import defaultAvatar from "../../../../../images/default-avatar.png";
|
||||||
import { useState, type FormEvent } from "react";
|
import { useState, type FormEvent } from "react";
|
||||||
|
|||||||
@@ -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 "../../../../../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 "../../../../../api/authApi";
|
import { getCurrentKeys } from "../../../../../core/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 "../../../../../api/authApi";
|
import { getAuthHeaders } from "../../../../../core/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,5 +1,5 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import type { Message, Size2D } from "../../../core/types";
|
import type { Message, Size2D } from "../../../../../core/types";
|
||||||
import { EmojiMenu } from "./EmojiMenu";
|
import { EmojiMenu } from "./EmojiMenu";
|
||||||
|
|
||||||
interface MessageContextMenuProps {
|
interface MessageContextMenuProps {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { useAppState } from "../../state";
|
|||||||
import { MessagePanel, type MessagePanelState } from "../../panels/MessagePanel";
|
import { MessagePanel, type MessagePanelState } from "../../panels/MessagePanel";
|
||||||
import { ChatMessages } from "./ChatMessages";
|
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 "../../../../../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,6 +1,6 @@
|
|||||||
import { useAppState } from "../../state";
|
import { useAppState } from "../../state";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import type { Reaction } from "../../../core/types";
|
import type { Reaction } from "../../../../../core/types";
|
||||||
|
|
||||||
interface MessageReactionsProps {
|
interface MessageReactionsProps {
|
||||||
reactions?: Reaction[];
|
reactions?: Reaction[];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import type { Message } from "../../../core/types";
|
import type { Message } from "../../../../../core/types";
|
||||||
import { MaterialDialog } from "../core/Dialog";
|
import { MaterialDialog } from "../core/Dialog";
|
||||||
import { MaterialTextField } from "../core/TextField";
|
import { MaterialTextField } from "../core/TextField";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { DialogProps } from "../../../core/types";
|
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 "../../../../../images/default-avatar.png";
|
import defaultAvatar from "../../../../../images/default-avatar.png";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import type { Size2D, Rect } from "../../../core/types";
|
import type { Size2D, Rect } from "../../../../../core/types";
|
||||||
|
|
||||||
interface ImageCropperProps {
|
interface ImageCropperProps {
|
||||||
onCrop: (croppedImageData: string) => void;
|
onCrop: (croppedImageData: string) => void;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect, useRef, type FormEvent } from "react";
|
import { useState, useEffect, useRef, type FormEvent } from "react";
|
||||||
import defaultAvatar from "../../../../../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";
|
||||||
import useProfile from "../../hooks/useProfile";
|
import useProfile from "../../hooks/useProfile";
|
||||||
import { ImageCropper } from "./ImageCropper";
|
import { ImageCropper } from "./ImageCropper";
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { PRODUCT_NAME, API_BASE_URL } from "../../../core/config";
|
import { PRODUCT_NAME, API_BASE_URL } from "../../../../../core/config";
|
||||||
import type { DialogProps } from "../../../core/types";
|
import type { DialogProps } from "../../../../../core/types";
|
||||||
import { MaterialDialog } from "../core/Dialog";
|
import { MaterialDialog } from "../core/Dialog";
|
||||||
import { initialize, isSupported, startElectronReceiver, stopElectronReceiver, subscribe, unsubscribe } from "../../../push-notifications";
|
import { initialize, isSupported, startElectronReceiver, stopElectronReceiver, subscribe, unsubscribe } from "../../../../../core/push-notifications/push-notifications";
|
||||||
import { isElectron } from "../../../electron/electron";
|
import { isElectron } from "../../../../../core/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 "../../../../../api/authApi";
|
import { getAuthHeaders } from "../../../../../core/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");
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import {
|
|||||||
fetchDMHistory,
|
fetchDMHistory,
|
||||||
decryptDm,
|
decryptDm,
|
||||||
sendDMViaWebSocket
|
sendDMViaWebSocket
|
||||||
} from "../../../../api/dmApi";
|
} from "../../../../core/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";
|
||||||
|
|
||||||
export interface DMUser extends User {
|
export interface DMUser extends User {
|
||||||
lastMessage?: string;
|
lastMessage?: string;
|
||||||
|
|||||||
@@ -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 "../../../../core/api/profileApi";
|
||||||
import { showSuccess, showError } from "../../../../utils/notification";
|
import { showSuccess, showError } from "../../../../utils/notification";
|
||||||
|
|
||||||
export default function useProfile() {
|
export default function useProfile() {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
sendDmWithFiles,
|
sendDmWithFiles,
|
||||||
editDmEnvelope,
|
editDmEnvelope,
|
||||||
deleteDmEnvelope
|
deleteDmEnvelope
|
||||||
} from "../../../../api/dmApi";
|
} from "../../../../core/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";
|
||||||
|
|
||||||
export interface DMPanelData {
|
export interface DMPanelData {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Message, WebSocketMessage } from "../../core/types";
|
import type { Message, WebSocketMessage } from "../../../../core/types";
|
||||||
import type { UserState } from "../state";
|
import type { UserState } from "../state";
|
||||||
|
|
||||||
export interface MessagePanelState {
|
export interface MessagePanelState {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
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 "../../../../api/authApi";
|
import { getAuthHeaders } from "../../../../core/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";
|
||||||
|
|
||||||
export class PublicChatPanel extends MessagePanel {
|
export class PublicChatPanel extends MessagePanel {
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { LeftPanel } from "../components/chat/LeftPanel";
|
|
||||||
import { RightPanel } from "../components/chat/RightPanel";
|
|
||||||
|
|
||||||
export default function ChatScreen() {
|
|
||||||
return (
|
|
||||||
<div id="chat-interface">
|
|
||||||
<div className="all-container">
|
|
||||||
<LeftPanel />
|
|
||||||
<RightPanel />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
export default function DownloadAppScreen() {
|
|
||||||
return (
|
|
||||||
<div className="download-app-screen">
|
|
||||||
<div className="inner">
|
|
||||||
<h1>Чтобы пользоваться мессенджером, скачайте приложение</h1>
|
|
||||||
<p>
|
|
||||||
Этот сайт <b>не предназначен</b> для работы на маленьких экранах, поэтому
|
|
||||||
вам нужно скачать приложение мессенджера.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<a href="https://github.com/denis0001-dev/FromChat-android/releases/latest">
|
|
||||||
<mdui-button>Скачать на GitHub</mdui-button>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Если возникнут сложности или есть вопросы, нажмите кнопку!
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<a href="https://t.me/denis0001-dev">
|
|
||||||
<mdui-button>Написать в поддержку</mdui-button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
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 "../../../../api/authApi";
|
|
||||||
import { API_BASE_URL } from "../../core/config";
|
|
||||||
import { useRef } from "react";
|
|
||||||
import type { TextField } from "mdui/components/text-field";
|
|
||||||
import { useAppState } from "../state";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { MaterialTextField } from "../components/core/TextField";
|
|
||||||
import { initialize, isSupported, startElectronReceiver, subscribe } from "../../push-notifications";
|
|
||||||
import { isElectron } from "../../electron/electron";
|
|
||||||
|
|
||||||
export default function LoginScreen() {
|
|
||||||
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
|
||||||
const setUser = useAppState(state => state.setUser);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
function showAlert(type: AlertType, message: string) {
|
|
||||||
updateAlerts((alerts) => { alerts.push({type: type, message: message}) });
|
|
||||||
}
|
|
||||||
|
|
||||||
const usernameElement = useRef<TextField>(null);
|
|
||||||
const passwordElement = useRef<TextField>(null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthContainer>
|
|
||||||
<AuthHeader icon="login" title="Добро пожаловать!" subtitle="Войдите в свой аккаунт" />
|
|
||||||
<div className="auth-body">
|
|
||||||
<AlertsContainer alerts={alerts} />
|
|
||||||
|
|
||||||
<form
|
|
||||||
onSubmit={async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const username = usernameElement.current!.value.trim();
|
|
||||||
const password = passwordElement.current!.value.trim();
|
|
||||||
|
|
||||||
if (!username || !password) {
|
|
||||||
showAlert("danger", "Пожалуйста, заполните все поля");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const request: LoginRequest = {
|
|
||||||
username: username,
|
|
||||||
password: password
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(request)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data: LoginResponse = await response.json();
|
|
||||||
// Store the JWT token first
|
|
||||||
setUser(data.token, data.user);
|
|
||||||
|
|
||||||
// Setup keys with the token we just received
|
|
||||||
try {
|
|
||||||
await ensureKeysOnLogin(password, data.token);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Key setup failed:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
navigate("/chat");
|
|
||||||
|
|
||||||
// Initialize notifications
|
|
||||||
try {
|
|
||||||
if (isSupported()) {
|
|
||||||
const initialized = await initialize();
|
|
||||||
if (initialized) {
|
|
||||||
await subscribe(data.token);
|
|
||||||
|
|
||||||
// For Electron, start the notification receiver
|
|
||||||
if (isElectron) {
|
|
||||||
await startElectronReceiver();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Notifications enabled");
|
|
||||||
} else {
|
|
||||||
console.log("Notification permission denied");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("Notifications not supported");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Notification setup failed:", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const data: ErrorResponse = await response.json();
|
|
||||||
showAlert("danger", data.message || "Неверное имя пользователя или пароль");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showAlert("danger", "Ошибка соединения с сервером");
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<MaterialTextField
|
|
||||||
label="Имя пользователя"
|
|
||||||
id="login-username"
|
|
||||||
name="username"
|
|
||||||
variant="outlined"
|
|
||||||
icon="person--filled"
|
|
||||||
autocomplete="username"
|
|
||||||
required
|
|
||||||
ref={usernameElement} />
|
|
||||||
|
|
||||||
<MaterialTextField
|
|
||||||
label="Пароль"
|
|
||||||
id="login-password"
|
|
||||||
name="password"
|
|
||||||
variant="outlined"
|
|
||||||
type="password"
|
|
||||||
toggle-password
|
|
||||||
icon="password--filled"
|
|
||||||
autocomplete="current-password"
|
|
||||||
required
|
|
||||||
ref={passwordElement} />
|
|
||||||
|
|
||||||
<mdui-button type="submit">Войти</mdui-button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div className="text-center">
|
|
||||||
<p>
|
|
||||||
Ещё нет аккаунта?
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="link"
|
|
||||||
onClick={() => navigate("/register")}>
|
|
||||||
Зарегистрируйтесь
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AuthContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
import { useImmer } from "use-immer";
|
|
||||||
// import { showLogin } from "../../navigation";
|
|
||||||
import { AuthContainer, AuthHeader } from "../components/Auth";
|
|
||||||
import { AlertsContainer, type Alert, type AlertType } from "../components/Alerts";
|
|
||||||
import { useRef } from "react";
|
|
||||||
import { TextField } from "mdui/components/text-field";
|
|
||||||
import type { ErrorResponse, RegisterRequest, LoginResponse } from "../../core/types";
|
|
||||||
import { API_BASE_URL } from "../../core/config";
|
|
||||||
import { useAppState } from "../state";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { MaterialTextField } from "../components/core/TextField";
|
|
||||||
import { ensureKeysOnLogin } from "../../../../api/authApi";
|
|
||||||
|
|
||||||
export default function RegisterScreen() {
|
|
||||||
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
|
|
||||||
const setUser = useAppState(state => state.setUser);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
function showAlert(type: AlertType, message: string) {
|
|
||||||
updateAlerts((alerts) => { alerts.push({type: type, message: message}) });
|
|
||||||
}
|
|
||||||
|
|
||||||
const usernameElement = useRef<TextField>(null);
|
|
||||||
const passwordElement = useRef<TextField>(null);
|
|
||||||
const confirmPasswordElement = useRef<TextField>(null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthContainer>
|
|
||||||
<AuthHeader icon="person_add" title="Регистрация" subtitle="Создайте новый аккаунт" />
|
|
||||||
<div className="auth-body">
|
|
||||||
<AlertsContainer alerts={alerts} />
|
|
||||||
|
|
||||||
<form onSubmit={async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const username = usernameElement.current!.value.trim();
|
|
||||||
const password = passwordElement.current!.value.trim();
|
|
||||||
const confirmPassword = confirmPasswordElement.current!.value.trim();
|
|
||||||
|
|
||||||
if (!username || !password || !confirmPassword) {
|
|
||||||
showAlert("danger", "Пожалуйста, заполните все поля");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password !== confirmPassword) {
|
|
||||||
showAlert("danger", "Пароли не совпадают");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (username.length < 3 || username.length > 20) {
|
|
||||||
showAlert("danger", "Имя пользователя должно быть от 3 до 20 символов");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password.length < 5 || password.length > 50) {
|
|
||||||
showAlert("danger", "Пароль должен быть от 5 до 50 символов");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const request: RegisterRequest = {
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
confirm_password: confirmPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/register`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(request)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data: LoginResponse = await response.json();
|
|
||||||
// Store the JWT token first
|
|
||||||
setUser(data.token, data.user);
|
|
||||||
|
|
||||||
// Setup keys with the token we just received
|
|
||||||
try {
|
|
||||||
await ensureKeysOnLogin(password, data.token);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Key setup failed:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
navigate("/chat");
|
|
||||||
} else {
|
|
||||||
const data: ErrorResponse = await response.json();
|
|
||||||
showAlert("danger", data.message || "Ошибка при регистрации");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showAlert("danger", "Ошибка соединения с сервером");
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<MaterialTextField
|
|
||||||
label="Имя пользователя"
|
|
||||||
id="register-username"
|
|
||||||
name="username"
|
|
||||||
variant="outlined"
|
|
||||||
icon="person--filled"
|
|
||||||
autocomplete="username"
|
|
||||||
maxlength={20}
|
|
||||||
counter
|
|
||||||
required
|
|
||||||
ref={usernameElement} />
|
|
||||||
<MaterialTextField
|
|
||||||
label="Пароль"
|
|
||||||
id="register-password"
|
|
||||||
name="password"
|
|
||||||
variant="outlined"
|
|
||||||
type="password"
|
|
||||||
toggle-password
|
|
||||||
icon="password--filled"
|
|
||||||
autocomplete="new-password"
|
|
||||||
required
|
|
||||||
ref={passwordElement} />
|
|
||||||
<MaterialTextField
|
|
||||||
label="Подтвердите пароль"
|
|
||||||
id="register-confirm-password"
|
|
||||||
name="confirm_password"
|
|
||||||
variant="outlined"
|
|
||||||
type="password"
|
|
||||||
toggle-password
|
|
||||||
icon="password--filled"
|
|
||||||
autocomplete="new-password"
|
|
||||||
required
|
|
||||||
ref={confirmPasswordElement} />
|
|
||||||
|
|
||||||
<mdui-button type="submit">Зарегистрироваться</mdui-button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div className="text-center">
|
|
||||||
<p>
|
|
||||||
Уже есть аккаунт?
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
id="login-link"
|
|
||||||
className="link"
|
|
||||||
onClick={() => navigate("/login")}>
|
|
||||||
Войдите
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AuthContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import type { Message, User } from "../core/types";
|
import type { Message, User } from "../../../core/types";
|
||||||
import { request } from "../core/websocket";
|
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 "../../../api/authApi";
|
import { getAuthHeaders } from "../../../core/api/authApi";
|
||||||
import { restoreKeys } from "../../../api/authApi";
|
import { restoreKeys } from "../../../core/api/authApi";
|
||||||
import { API_BASE_URL } from "../core/config";
|
import { API_BASE_URL } from "../../../core/config";
|
||||||
import { initialize, subscribe, startElectronReceiver, isSupported } from "../push-notifications";
|
import { initialize, subscribe, startElectronReceiver, isSupported } from "../../../core/push-notifications/push-notifications";
|
||||||
import { isElectron } from "../electron/electron";
|
import { isElectron } from "../../../core/electron/electron";
|
||||||
|
|
||||||
export type ChatTabs = "chats" | "channels" | "contacts" | "dms"
|
export type ChatTabs = "chats" | "channels" | "contacts" | "dms"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Navigate, useNavigate } from "react-router-dom";
|
import { Navigate, useNavigate } from "react-router-dom";
|
||||||
import { useAppState } from "../chat/ui/state";
|
import { useAppState } from "../chat/ui/state";
|
||||||
import { isElectron } from "../chat/electron/electron";
|
import { isElectron } from "../../core/electron/electron";
|
||||||
import "./home.scss";
|
import "./home.scss";
|
||||||
|
|
||||||
function GitHubLink({ children }: { children: React.ReactNode }) {
|
function GitHubLink({ children }: { children: React.ReactNode }) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@use "../../css/common/material" as *;
|
@use "../../css/material" as *;
|
||||||
|
|
||||||
.homepage {
|
.homepage {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|||||||
Reference in New Issue
Block a user