'use client'; import './NexusAuth.css'; import { useState, useEffect } from 'react'; import NexusAuthApi from '@lib/Nexus_Auth_API'; import SplashScreen from '@components/SplashScreen'; import { useToast } from '@lib/ToastContext'; import { CheckCircleIcon } from '@heroicons/react/20/solid'; const SignupForm = ({ onSignup }) => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [email, setEmail] = useState(''); const [usernameAvailable, setUsernameAvailable] = useState(null); // null for initial state const [doesUsernameContainInvalidChars, setDoesUsernameContainInvalidChars] = useState(false); const [doesUsernameExceedMinLength, setDoesUsernameExceedMinLength] = useState(false); const [passwordValid, setPasswordValid] = useState(false); // Initially invalid const [formValid, setFormValid] = useState(false); const [debounceTimeout, setDebounceTimeout] = useState(null); // Store timeout ID const minUsernameLength = 3; const validatePassword = (password) => { return password.length >= 8; }; const handleUsernameChange = (e) => { const newUsername = e.target.value; setUsername(newUsername); // Reset username availability while typing setUsernameAvailable(null); // Clear any existing debounce timeout if (debounceTimeout) { clearTimeout(debounceTimeout); } // Check for invalid characters const invalidChars = /[^a-zA-Z0-9_]/g; if (invalidChars.test(newUsername)) { setDoesUsernameContainInvalidChars(true); setTimeout(() => { setDoesUsernameContainInvalidChars(false); }, 2000); // Show error for 2 seconds } // Basic sanitization to prevent SQL injection const sanitizedUsername = newUsername.replace(invalidChars, ''); if (sanitizedUsername.length < minUsernameLength) { setDoesUsernameExceedMinLength(false); return; } else { setDoesUsernameExceedMinLength(true);} if (sanitizedUsername.trim().length > 0) { // Set a new timeout to check availability const newTimeout = setTimeout(async () => { try { const response = await NexusAuthApi.isUsernameAvailable(sanitizedUsername); setUsernameAvailable(response?.is_available === true); } catch (error) { console.error('Error checking username availability:', error); setUsernameAvailable(null); // Fallback state } }, 1000); // 1-second debounce delay setDebounceTimeout(newTimeout); } else { setUsernameAvailable(null); // Reset availability check when input is empty } // Set sanitized username setUsername(sanitizedUsername); }; const handlePasswordChange = (e) => { const newPassword = e.target.value; setPassword(newPassword); setPasswordValid(validatePassword(newPassword)); }; const handleConfirmPasswordChange = (e) => { setConfirmPassword(e.target.value); }; const handleSubmit = (e) => { e.preventDefault(); // Set email to null if it's empty const emailValue = email.trim() === '' ? null : email; if (password === confirmPassword && passwordValid) { onSignup({ username, password, email: emailValue }); } }; useEffect(() => { setFormValid( usernameAvailable === true && password === confirmPassword && passwordValid && username.length >= minUsernameLength && !doesUsernameContainInvalidChars ); }, [username, password, confirmPassword, usernameAvailable, passwordValid]); return (
); }; const LoginForm = ({ onLogin }) => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const handleSubmit = (e) => { e.preventDefault(); onLogin({ username, password }); }; return ( ); }; export const NexusAuthWrapper = ({ children }) => { const [isLoggedIn, setIsLoggedIn] = useState(false); const [isSignup, setIsSignup] = useState(false); const [isLoading, setIsLoading] = useState(true); const toast = useToast(); useEffect(() => { const validateUserSession = async () => { const storedUsername = localStorage.getItem("me"); const storedToken = localStorage.getItem("s_tkn"); const storedUserID = localStorage.getItem("u_id"); if (storedUsername && storedToken && storedUserID) { try { // Validate the token with the NexusAuthApi const response = await NexusAuthApi.validateToken(storedUserID, storedToken); if (response.data && response.data.user_id) { // Token is valid; response contains user details console.log("User is already logged in."); toast.info("Welcome back, " + response.data.username + "!"); setIsLoggedIn(true); // Optionally, update localStorage with new details if needed localStorage.setItem("me", response.data.username); localStorage.setItem("s_tkn", response.data.access_token); localStorage.setItem("u_id", response.data.user_id); localStorage.setItem("a_l", response.data.access_level); } else if (response.status === 401) { // Token is invalid; clear local storage console.info("Token validation failed with status 401:"); clearLocalStorage(); } else { // Handle other errors (e.g., network issues) console.debug("Token validation failed due to an unexpected error:", response.data); toast.error("Unable to validate token. Please check your connection."); } } catch (error) { // Handle other errors (e.g., network issues) console.debug("Token validation failed due to an unexpected error:", error); toast.error("Unable to validate token. Please check your connection."); } } setIsLoading(false); }; const clearLocalStorage = () => { localStorage.removeItem("me"); localStorage.removeItem("s_tkn"); localStorage.removeItem("u_id"); localStorage.removeItem("a_l"); setIsLoggedIn(false); toast.error("Session expired. Please login again."); }; validateUserSession(); }, []); const handleSignup = async (data) => { setIsLoading(true); try { const response = await NexusAuthApi.signup(data.username, data.password, data.email); console.log("Signup successful:", response); setIsLoading(false); toast.success('Signup successful. Please login to continue'); setIsSignup(false); } catch (error) { setIsLoading(false); console.debug("Signup failed:", error); toast.error("Signup failed"); } }; const handleLogin = async (data) => { setIsLoading(true); try { const response = await NexusAuthApi.login(data.username, data.password); console.log("Login successful:", response); toast.success('Login successful.'); // Save username and token to localStorage localStorage.setItem("me", response.username); localStorage.setItem("s_tkn", response.access_token); localStorage.setItem("u_id", response.user_id); localStorage.setItem("a_l", response.access_level); setIsLoggedIn(true); setIsLoading(false); } catch (error) { setIsLoading(false); console.debug("Login failed:", error); toast.error("Login failed"); } }; if (isLoading) { return