Create new Material components, redesign the chat UI, add security settings

This commit is contained in:
2025-11-01 14:55:36 +03:00
Unverified
parent 5f49b45eed
commit fc11d06570
40 changed files with 917 additions and 618 deletions
+1 -2
View File
@@ -3,7 +3,7 @@ import { generateX25519KeyPair } from "@/utils/crypto/asymmetric";
import { encodeBlob, encryptBackupWithPassword, decryptBackupWithPassword, decodeBlob } from "@/utils/crypto/backup";
import { b64, ub64 } from "@/utils/utils";
import { API_BASE_URL } from "@/core/config";
import { importPassword, hkdfExtractAndExpand } from "@/utils/crypto/kdf";
import { hkdfExtractAndExpand } from "@/utils/crypto/kdf";
/**
* Generates authentication headers for API requests
@@ -151,7 +151,6 @@ export function getAuthToken(): string | null {
* Uses PBKDF2 (via WebCrypto) + HKDF to produce a stable 32-byte key, then base64.
*/
export async function deriveAuthSecret(username: string, password: string): Promise<string> {
const key = await importPassword(password);
// Use per-user salt derived from username; in future we can fetch a server-provided salt
const salt = new TextEncoder().encode(`fromchat.user:${username}`);
// Derive 32 bytes using HKDF; PBKDF2 already used within importPassword
+1
View File
@@ -3,6 +3,7 @@ import { getAuthHeaders } from "@/core/api/authApi";
export interface DeviceInfo {
session_id: string;
device_name?: string;
device_type?: string;
os_name?: string;
os_version?: string;
@@ -1,9 +0,0 @@
import type { TextField } from "mdui/components/text-field";
interface TextFieldProps extends React.ComponentPropsWithoutRef<"mdui-text-field"> {
ref?: React.Ref<TextField>
}
export function MaterialTextField({ ref, ...props }: TextFieldProps) {
return <mdui-text-field autocomplete="off" ref={ref as React.Ref<HTMLElement>} {...props} />
}
+3 -2
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from "react";
import "./css/searchBar.scss";
import { MaterialIcon } from "@/utils/material";
interface SearchBarProps {
placeholder: string;
@@ -57,9 +58,9 @@ export default function SearchBar({
function renderIcon(icon: string | React.ReactNode | undefined, defaultIcon?: string) {
if (icon === null) return null;
if (!icon) {
return defaultIcon ? <mdui-icon name={defaultIcon}></mdui-icon> : null;
return defaultIcon ? <MaterialIcon name={defaultIcon} /> : null;
} else if (typeof icon === 'string') {
return <mdui-icon name={icon}></mdui-icon>;
return <MaterialIcon name={icon} />;
} else {
return icon;
}
+3 -2
View File
@@ -1,6 +1,7 @@
import { useState, useEffect } from "react";
import { checkUserSimilarity } from "@/core/api/profileApi";
import { useAppState } from "@/pages/chat/state";
import { MaterialIcon } from "@/utils/material";
interface StatusBadgeProps {
verified: boolean;
@@ -33,7 +34,7 @@ export function StatusBadge({ verified, userId, size = "small" }: StatusBadgePro
if (verified) {
return (
<span className={`${className} verified`} title="Подтверждённый аккаунт">
<mdui-icon name="verified--filled" />
<MaterialIcon name="verified--filled" />
</span>
);
}
@@ -41,7 +42,7 @@ export function StatusBadge({ verified, userId, size = "small" }: StatusBadgePro
if (isSimilarToVerified) {
return (
<span className={`${className} warning`} title="Похож на подтверждённый аккаунт">
<mdui-icon name="warning" />
<MaterialIcon name="warning--filled" />
</span>
);
}
@@ -1,6 +1,7 @@
import { useState } from "react";
import { verifyUser } from "@/core/api/profileApi";
import { useAppState } from "@/pages/chat/state";
import { MaterialButton } from "@/utils/material";
interface VerifyButtonProps {
userId: number;
@@ -34,13 +35,13 @@ export function VerifyButton({ userId, verified, onVerificationChange }: VerifyB
}
return (
<mdui-button
<MaterialButton
variant="filled"
loading={isVerifying}
onClick={handleVerifyToggle}
title={verified ? "Снять подтверждение" : "Подтвердить аккаунт"}
>
{verified ? "Отменить подтверждение" : "Подтвердить"}
</mdui-button>
</MaterialButton>
);
}
+8 -1
View File
@@ -630,4 +630,11 @@ export interface StopDmTypingRequest extends WebSocketMessage {
data: {
recipientId: number;
};
}
}
// -------------
// Utility types
// -------------
export type Override<TBase, TExt> = Omit<TBase, keyof TExt> & TExt;