import { useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { motion, type Transition, type Variants } from "motion/react"; import { useImmer } from "use-immer"; import type { RegisterRequest } from "@/core/types"; import { useUserStore } from "@/state/user"; import { MaterialButton, MaterialIconButton } from "@/utils/material"; import api from "@/core/api"; import { AuthTextField, type AuthTextFieldHandle } from "./AuthTextField"; import type { Alert, AlertType } from "./Auth"; import { AuthHeader, AlertsContainer } from "./Auth"; import styles from "./auth.module.scss"; const registerFieldVariants: Variants = { initial: { opacity: 0, y: 10 }, animate: { opacity: 1, y: 0 } }; const registerFieldTransition: Transition = { duration: 0.3, ease: "easeInOut" }; const registerButtonVariants: Variants = { initial: { opacity: 0, y: 10 }, animate: { opacity: 1, y: 0 } }; const registerButtonTransition: Transition = { duration: 0.3, delay: 0.6, ease: "easeInOut" }; interface RegisterFormProps { onSwitchMode: () => void; } export function RegisterForm({ onSwitchMode }: RegisterFormProps) { const [isLoading, setIsLoading] = useState(false); const [alerts, updateAlerts] = useImmer([]); const setUser = useUserStore(state => state.setUser); const navigate = useNavigate(); function showAlert(type: AlertType, message: string) { updateAlerts((alerts) => { alerts.push({type: type, message: message}) }); } const displayNameElement = useRef(null); const usernameElement = useRef(null); const passwordElement = useRef(null); const confirmPasswordElement = useRef(null); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (isLoading) return; const displayName = displayNameElement.current!.value.trim(); const username = usernameElement.current!.value.trim(); const password = passwordElement.current!.value.trim(); const confirmPassword = confirmPasswordElement.current!.value.trim(); if (!displayName || !username || !password || !confirmPassword) { showAlert("danger", "Пожалуйста, заполните все поля"); return; } if (password !== confirmPassword) { showAlert("danger", "Пароли не совпадают"); return; } if (displayName.length < 1 || displayName.length > 64) { showAlert("danger", "Отображаемое имя должно быть от 1 до 64 символов"); return; } if (username.length < 3 || username.length > 20) { showAlert("danger", "Имя пользователя должно быть от 3 до 20 символов"); return; } if (!/^[a-zA-Z0-9_-]+$/.test(username)) { showAlert("danger", "Имя пользователя может содержать только английские буквы, цифры, дефисы и подчеркивания"); return; } if (password.length < 5 || password.length > 50) { showAlert("danger", "Пароль должен быть от 5 до 50 символов"); return; } setIsLoading(true); try { const derived = await api.user.auth.deriveAuthSecret(username, password); const request: RegisterRequest = { display_name: displayName, username: username, password: derived, confirm_password: derived } try { const data = await api.user.auth.register(request); setUser(data.token, data.user); try { await api.user.auth.ensureKeysOnLogin(password, data.token); // Initialize Signal Protocol after keys are set up (non-blocking) if (data.user?.id) { // Run Signal Protocol initialization in background to avoid blocking navigation (async () => { try { const { SignalProtocolService } = await import("@/utils/crypto/signalProtocol"); const { uploadPreKeyBundle, uploadAllPreKeys } = await import("@/core/api/crypto/prekeys"); const { uploadAllSessionsToServer, initializeSessionSync } = await import("@/utils/crypto/sessionSync"); const signalService = new SignalProtocolService(data.user!.id.toString()); await signalService.initialize(); // Initialize session sync (enables automatic upload of new sessions) initializeSessionSync(data.user!.id.toString(), password, data.token); // Upload base bundle with one prekey (for backward compatibility) const bundle = await signalService.getPreKeyBundle(); await uploadPreKeyBundle(bundle, data.token); // Upload all prekeys for server-side rotation const baseBundle = await signalService.getBaseBundle(); const prekeys = await signalService.getAllPreKeys(); await uploadAllPreKeys(baseBundle, prekeys, data.token); // Upload all current sessions to server (backup - will be empty on registration) await uploadAllSessionsToServer(data.user!.id.toString(), password, data.token); console.log(`Uploaded ${prekeys.length} prekeys to server`); } catch (e) { console.error("Key setup failed:", e); } })(); } } catch (e) { console.error("Key setup failed:", e); } navigate("/chat"); } catch (error: any) { showAlert("danger", error.message || "Ошибка при регистрации"); } } catch (error: any) { showAlert("danger", error.message || "Ошибка соединения с сервером"); } finally { setIsLoading(false); } } return ( <>
{isLoading ? "Регистрация..." : "Зарегистрироваться"}
); }