mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-23 03:25:07 +03:00
Implement verification checkmark
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import useCombinedRefs from '@/core/hooks/useCombinedRefs';
|
||||
import { id } from '@/utils/utils';
|
||||
|
||||
interface AutoResizeInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
autoresizing?: true;
|
||||
placeholderMinWidth?: boolean;
|
||||
onAutosize?: (width: number) => void;
|
||||
}
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
autoresizing?: false;
|
||||
placeholderMinWidth?: false;
|
||||
onAutosize?: undefined;
|
||||
}
|
||||
|
||||
export function Input({
|
||||
autoresizing = false,
|
||||
placeholderMinWidth = false,
|
||||
onAutosize,
|
||||
style: inputStyle,
|
||||
...inputProps
|
||||
}: AutoResizeInputProps | InputProps) {
|
||||
const [inputWidth, setInputWidth] = useState(0);
|
||||
|
||||
const sizerRef = useRef<HTMLDivElement>(null);
|
||||
const placeholderSizerRef = useRef<HTMLDivElement>(null);
|
||||
const [inputRef, inputElement] = useCombinedRefs<HTMLInputElement>();
|
||||
|
||||
const sizerStyle: React.CSSProperties = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
visibility: 'hidden',
|
||||
height: 0,
|
||||
overflow: 'scroll',
|
||||
whiteSpace: 'pre',
|
||||
};
|
||||
|
||||
const copyStyles = useCallback((styles: CSSStyleDeclaration, node: HTMLElement) => {
|
||||
node.style.fontSize = styles.fontSize;
|
||||
node.style.fontFamily = styles.fontFamily;
|
||||
node.style.fontWeight = styles.fontWeight;
|
||||
node.style.fontStyle = styles.fontStyle;
|
||||
node.style.letterSpacing = styles.letterSpacing;
|
||||
node.style.textTransform = styles.textTransform;
|
||||
}, []);
|
||||
|
||||
const updateInputWidth = useCallback(() => {
|
||||
if (!sizerRef.current || typeof sizerRef.current.scrollWidth === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
let newInputWidth: number;
|
||||
|
||||
if (inputProps.placeholder && (!inputProps.value || (inputProps.value && placeholderMinWidth))) {
|
||||
const sizerWidth = sizerRef.current.scrollWidth;
|
||||
const placeholderWidth = placeholderSizerRef.current?.scrollWidth || 0;
|
||||
newInputWidth = Math.max(sizerWidth, placeholderWidth) + 2;
|
||||
} else {
|
||||
newInputWidth = sizerRef.current.scrollWidth + 2;
|
||||
}
|
||||
|
||||
|
||||
if (newInputWidth !== inputWidth) {
|
||||
setInputWidth(newInputWidth);
|
||||
onAutosize?.(newInputWidth);
|
||||
}
|
||||
}, [inputProps.placeholder, inputProps.value, inputProps.type, placeholderMinWidth, inputWidth, onAutosize]);
|
||||
|
||||
const copyInputStyles = useCallback(() => {
|
||||
if (!inputElement.current || !window.getComputedStyle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputStyles = window.getComputedStyle(inputElement.current);
|
||||
if (!inputStyles) {
|
||||
return;
|
||||
}
|
||||
|
||||
copyStyles(inputStyles, sizerRef.current!);
|
||||
if (placeholderSizerRef.current) {
|
||||
copyStyles(inputStyles, placeholderSizerRef.current);
|
||||
}
|
||||
}, [inputElement]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoresizing) {
|
||||
copyInputStyles();
|
||||
updateInputWidth();
|
||||
}
|
||||
}, [autoresizing, copyInputStyles, updateInputWidth]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoresizing) {
|
||||
updateInputWidth();
|
||||
}
|
||||
}, [inputProps.value, inputProps.placeholder, autoresizing, updateInputWidth]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
{...inputProps}
|
||||
ref={inputRef}
|
||||
style={{
|
||||
boxSizing: 'content-box',
|
||||
width: autoresizing ? `${inputWidth}px` : undefined,
|
||||
...inputStyle,
|
||||
}}
|
||||
/>
|
||||
{autoresizing && createPortal(
|
||||
<>
|
||||
<div ref={sizerRef} style={sizerStyle}>
|
||||
{inputProps.defaultValue || inputProps.value || ''}
|
||||
</div>
|
||||
{inputProps.placeholder && (
|
||||
<div ref={placeholderSizerRef} style={sizerStyle}>
|
||||
{inputProps.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</>,
|
||||
id("root")
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
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} />
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { checkUserSimilarity } from "@/core/api/profileApi";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
|
||||
interface StatusBadgeProps {
|
||||
verified: boolean;
|
||||
userId?: number;
|
||||
size?: "small" | "medium" | "large";
|
||||
}
|
||||
|
||||
export function StatusBadge({ verified, userId, size = "small" }: StatusBadgeProps) {
|
||||
const [isSimilarToVerified, setIsSimilarToVerified] = useState(false);
|
||||
const { user } = useAppState();
|
||||
|
||||
const className = `status-badge ${size}`;
|
||||
|
||||
// Check similarity for unverified users
|
||||
useEffect(() => {
|
||||
if (!verified && userId && user.authToken) {
|
||||
checkUserSimilarity(userId, user.authToken)
|
||||
.then(result => {
|
||||
setIsSimilarToVerified(result?.isSimilar || false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error checking similarity:', error);
|
||||
setIsSimilarToVerified(false);
|
||||
});
|
||||
} else {
|
||||
setIsSimilarToVerified(false);
|
||||
}
|
||||
}, [verified, userId, user.authToken]);
|
||||
|
||||
if (verified) {
|
||||
return (
|
||||
<span className={`${className} verified`} title="Подтверждённый аккаунт">
|
||||
<mdui-icon name="verified--filled" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (isSimilarToVerified) {
|
||||
return (
|
||||
<span className={`${className} warning`} title="Похож на подтверждённый аккаунт">
|
||||
<mdui-icon name="warning" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Don't show anything if not verified and not similar
|
||||
return null;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { TextField } from "mdui/components/text-field";
|
||||
|
||||
type TextFieldProps = React.ComponentPropsWithoutRef<"mdui-text-field">
|
||||
|
||||
export function MaterialTextField(props: TextFieldProps & { ref?: React.Ref<TextField> }) {
|
||||
return <mdui-text-field
|
||||
autocomplete="off"
|
||||
{...(props as TextFieldProps & { ref?: React.Ref<HTMLElement> })} />
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useState } from "react";
|
||||
import { verifyUser } from "@/core/api/profileApi";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
|
||||
interface VerifyButtonProps {
|
||||
userId: number;
|
||||
verified: boolean;
|
||||
onVerificationChange?: (verified: boolean) => void;
|
||||
}
|
||||
|
||||
export function VerifyButton({ userId, verified, onVerificationChange }: VerifyButtonProps) {
|
||||
const [isVerifying, setIsVerifying] = useState(false);
|
||||
const { user } = useAppState();
|
||||
|
||||
// Only show for owner
|
||||
if (user.currentUser?.id !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function handleVerifyToggle() {
|
||||
if (!user.authToken || isVerifying) return;
|
||||
|
||||
setIsVerifying(true);
|
||||
try {
|
||||
const result = await verifyUser(userId, user.authToken);
|
||||
if (result) {
|
||||
onVerificationChange?.(result.verified);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error toggling verification:', error);
|
||||
} finally {
|
||||
setIsVerifying(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<mdui-button
|
||||
variant="filled"
|
||||
loading={isVerifying}
|
||||
onClick={handleVerifyToggle}
|
||||
title={verified ? "Снять подтверждение" : "Подтвердить аккаунт"}
|
||||
>
|
||||
{verified ? "Отменить подтверждение" : "Подтвердить"}
|
||||
</mdui-button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user