| import { useState, useEffect, useRef } from "react"; | |
| import { useParams, useNavigate } from "react-router-dom"; | |
| import "./RegisterPage.css"; | |
| interface RegistrationData { | |
| username: string; | |
| age: string; | |
| password: string; | |
| confirmPassword: string; | |
| } | |
| export function RegisterPage() { | |
| const { sessionId } = useParams<{ sessionId: string }>(); | |
| const navigate = useNavigate(); | |
| const [ws, setWs] = useState<WebSocket | null>(null); | |
| const [connected, setConnected] = useState(false); | |
| const [sessionValid, setSessionValid] = useState<boolean | null>(null); | |
| const [loading, setLoading] = useState(true); | |
| const [submitting, setSubmitting] = useState(false); | |
| const [formData, setFormData] = useState<RegistrationData>({ | |
| username: "", | |
| age: "", | |
| password: "", | |
| confirmPassword: "", | |
| }); | |
| const [errors, setErrors] = useState<Partial<RegistrationData>>({}); | |
| const [successMessage, setSuccessMessage] = useState(""); | |
| const [errorMessage, setErrorMessage] = useState(""); | |
| const wsRef = useRef<WebSocket | null>(null); | |
| useEffect(() => { | |
| if (!sessionId) { | |
| setSessionValid(false); | |
| setLoading(false); | |
| return; | |
| } | |
| const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; | |
| const wsUrl = `${protocol}//${window.location.host}/ws`; | |
| const websocket = new WebSocket(wsUrl); | |
| wsRef.current = websocket; | |
| websocket.onopen = () => { | |
| console.log("[WebSocket] Connected"); | |
| setConnected(true); | |
| websocket.send(JSON.stringify({ | |
| type: "user_connect", | |
| sessionId: sessionId, | |
| })); | |
| }; | |
| websocket.onmessage = (event) => { | |
| try { | |
| const message = JSON.parse(event.data); | |
| handleWebSocketMessage(message); | |
| } catch (error) { | |
| console.error("[WebSocket] Error parsing message:", error); | |
| } | |
| }; | |
| websocket.onerror = (error) => { | |
| console.error("[WebSocket] Error:", error); | |
| setErrorMessage("Connection error. Please try again."); | |
| setLoading(false); | |
| }; | |
| websocket.onclose = () => { | |
| console.log("[WebSocket] Disconnected"); | |
| setConnected(false); | |
| }; | |
| setWs(websocket); | |
| return () => { | |
| websocket.close(); | |
| }; | |
| }, [sessionId]); | |
| const handleWebSocketMessage = (message: any) => { | |
| switch (message.type) { | |
| case "connected": | |
| console.log("[WebSocket]", message.message); | |
| break; | |
| case "session_valid": | |
| setSessionValid(true); | |
| setLoading(false); | |
| break; | |
| case "session_invalid": | |
| setSessionValid(false); | |
| setLoading(false); | |
| setErrorMessage(message.message || "Session not found or expired"); | |
| break; | |
| case "registration_result": | |
| setSubmitting(false); | |
| if (message.success) { | |
| setSuccessMessage(message.message); | |
| setErrorMessage(""); | |
| setTimeout(() => { | |
| window.close(); | |
| }, 3000); | |
| } else { | |
| setErrorMessage(message.message); | |
| setSuccessMessage(""); | |
| } | |
| break; | |
| } | |
| }; | |
| const validateForm = (): boolean => { | |
| const newErrors: Partial<RegistrationData> = {}; | |
| if (!formData.username.trim()) { | |
| newErrors.username = "Username is required"; | |
| } else if (formData.username.trim().length < 3) { | |
| newErrors.username = "Username must be at least 3 characters"; | |
| } | |
| const ageNum = parseInt(formData.age); | |
| if (!formData.age) { | |
| newErrors.age = "Age is required"; | |
| } else if (isNaN(ageNum) || ageNum < 13 || ageNum > 100) { | |
| newErrors.age = "Age must be between 13 and 100"; | |
| } | |
| if (!formData.password) { | |
| newErrors.password = "Password is required"; | |
| } else if (formData.password.length < 6 || formData.password.length > 20) { | |
| newErrors.password = "Password must be 6-20 characters"; | |
| } | |
| if (!formData.confirmPassword) { | |
| newErrors.confirmPassword = "Please confirm your password"; | |
| } else if (formData.password !== formData.confirmPassword) { | |
| newErrors.confirmPassword = "Passwords do not match"; | |
| } | |
| setErrors(newErrors); | |
| return Object.keys(newErrors).length === 0; | |
| }; | |
| const handleSubmit = (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (!validateForm()) return; | |
| if (!ws || ws.readyState !== WebSocket.OPEN) { | |
| setErrorMessage("Connection lost. Please refresh the page."); | |
| return; | |
| } | |
| setSubmitting(true); | |
| setErrorMessage(""); | |
| setSuccessMessage(""); | |
| const submitData = { | |
| username: formData.username.trim(), | |
| age: parseInt(formData.age), | |
| password: formData.password, | |
| confirmPassword: formData.confirmPassword, | |
| }; | |
| ws.send(JSON.stringify({ | |
| type: "registration_submit", | |
| sessionId: sessionId, | |
| data: submitData, | |
| })); | |
| }; | |
| const handleInputChange = (field: keyof RegistrationData, value: string) => { | |
| setFormData(prev => ({ ...prev, [field]: value })); | |
| if (errors[field]) { | |
| setErrors(prev => ({ ...prev, [field]: "" })); | |
| } | |
| }; | |
| if (loading) { | |
| return ( | |
| <div className="register-page"> | |
| <div className="register-card"> | |
| <div className="loading-spinner"> | |
| <div className="spinner"></div> | |
| <p>Validating session...</p> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| if (sessionValid === false) { | |
| return ( | |
| <div className="register-page"> | |
| <div className="register-card error-card"> | |
| <div className="error-icon">โ</div> | |
| <h2>Invalid Session</h2> | |
| <p>{errorMessage || "This registration link is invalid or has expired."}</p> | |
| <button | |
| className="back-button" | |
| onClick={() => navigate("/")} | |
| > | |
| Go to Homepage | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| if (successMessage) { | |
| return ( | |
| <div className="register-page"> | |
| <div className="register-card success-card"> | |
| <div className="success-icon">โ </div> | |
| <h2>Registration Successful!</h2> | |
| <p>{successMessage}</p> | |
| <p className="success-subtitle">Your account has been activated automatically.</p> | |
| <div className="success-animation"> | |
| <div className="confetti"></div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="register-page"> | |
| <div className="register-card"> | |
| <div className="register-header"> | |
| <h1>๐ Account Registration</h1> | |
| <p>Join Yuki Botz and unlock amazing features!</p> | |
| {!connected && ( | |
| <div className="connection-status offline"> | |
| <span className="status-dot"></span> | |
| Reconnecting... | |
| </div> | |
| )} | |
| </div> | |
| <form onSubmit={handleSubmit} className="register-form"> | |
| <div className="form-group"> | |
| <label htmlFor="username"> | |
| <span className="label-icon">๐ค</span> | |
| Username | |
| </label> | |
| <input | |
| type="text" | |
| id="username" | |
| value={formData.username} | |
| onChange={(e) => handleInputChange("username", e.target.value)} | |
| placeholder="Enter your username" | |
| className={errors.username ? "error" : ""} | |
| disabled={submitting} | |
| /> | |
| {errors.username && <span className="error-text">{errors.username}</span>} | |
| </div> | |
| <div className="form-group"> | |
| <label htmlFor="age"> | |
| <span className="label-icon">๐</span> | |
| Age | |
| </label> | |
| <input | |
| type="number" | |
| id="age" | |
| value={formData.age} | |
| onChange={(e) => handleInputChange("age", e.target.value)} | |
| placeholder="Enter your age" | |
| className={errors.age ? "error" : ""} | |
| disabled={submitting} | |
| min="13" | |
| max="100" | |
| /> | |
| {errors.age && <span className="error-text">{errors.age}</span>} | |
| </div> | |
| <div className="password-section"> | |
| <div className="section-header"> | |
| <h3>๐ Password</h3> | |
| </div> | |
| <div className="bonus-badge"> | |
| <span className="badge-icon">๐</span> | |
| <span>Get bonus rewards with your account!</span> | |
| </div> | |
| <div className="rewards-list"> | |
| <div className="reward-item">๐ +100 Limit</div> | |
| <div className="reward-item">๐ช +10,000 Money</div> | |
| <div className="reward-item">โญ +50 EXP</div> | |
| <div className="reward-item">๐ฎ RPG Access</div> | |
| </div> | |
| <div className="form-group"> | |
| <label htmlFor="password">Password</label> | |
| <input | |
| type="password" | |
| id="password" | |
| value={formData.password} | |
| onChange={(e) => handleInputChange("password", e.target.value)} | |
| placeholder="6-20 characters" | |
| className={errors.password ? "error" : ""} | |
| disabled={submitting} | |
| /> | |
| {errors.password && <span className="error-text">{errors.password}</span>} | |
| </div> | |
| <div className="form-group"> | |
| <label htmlFor="confirmPassword">Confirm Password</label> | |
| <input | |
| type="password" | |
| id="confirmPassword" | |
| value={formData.confirmPassword} | |
| onChange={(e) => handleInputChange("confirmPassword", e.target.value)} | |
| placeholder="Re-enter password" | |
| className={errors.confirmPassword ? "error" : ""} | |
| disabled={submitting} | |
| /> | |
| {errors.confirmPassword && <span className="error-text">{errors.confirmPassword}</span>} | |
| </div> | |
| </div> | |
| {errorMessage && ( | |
| <div className="alert alert-error"> | |
| <span className="alert-icon">โ ๏ธ</span> | |
| {errorMessage} | |
| </div> | |
| )} | |
| <button | |
| type="submit" | |
| className="submit-button" | |
| disabled={submitting || !connected} | |
| > | |
| {submitting ? ( | |
| <> | |
| <span className="button-spinner"></span> | |
| Processing... | |
| </> | |
| ) : ( | |
| <> | |
| <span>๐</span> | |
| Complete Registration | |
| </> | |
| )} | |
| </button> | |
| </form> | |
| </div> | |
| </div> | |
| ); | |
| } |