// components/AuthPage.jsx /** * Authentication Page Component * * Handles user sign-in and sign-up forms, including input validation * and communication with the backend authentication API endpoints. */ // React, Hooks, and PropTypes are globally available const AuthPage = ({ onSignin }) => { // --- State --- const [isSignUp, setIsSignUp] = useState(false); // Toggle between Sign In/Sign Up const [formData, setFormData] = useState({ username: "", email: "", password: "", confirmPassword: "", // Only used for sign up validation full_name: "", // Only used for sign up }); const [errors, setErrors] = useState({}); // Stores validation errors const [loading, setLoading] = useState(false); // Tracks API request status const [message, setMessage] = useState(""); // Feedback message (success/error) const [touched, setTouched] = useState({}); // Tracks touched fields for validation UX // --- API Endpoints --- const SIGNIN_ENDPOINT = "/api/auth/signin.php"; const SIGNUP_ENDPOINT = "/api/auth/register.php"; // --- Input Handling --- const handleChange = (e) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); // Clear the specific error when the user types in the field if (touched[name] && errors[name]) { setErrors((prev) => ({ ...prev, [name]: "" })); } }; const handleBlur = (e) => { const { name, value } = e.target; // Mark field as touched setTouched((prev) => ({ ...prev, [name]: true })); // Validate the field on blur validateField(name, value); }; // --- Validation --- const validateField = (name, value) => { let error = ""; switch (name) { case "username": if (isSignUp) { if (!value.trim()) error = "Username is required"; else if (value.length < 3) error = "Username must be at least 3 characters"; else if (!/^[a-zA-Z0-9_]+$/.test(value)) error = "Username can only contain letters, numbers, underscores"; } break; case "full_name": if (isSignUp && !value.trim()) error = "Full name is required"; break; case "email": // Use username for signin, email for signup/common validation if (!isSignUp) { // Signin uses username field, but label is Email/Username if (!value.trim()) error = "Username or Email is required"; } else { // Signup uses email field if (!value.trim()) error = "Email is required"; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) error = "Invalid email format"; } break; case "password": if (!value) error = "Password is required"; else if (value.length < 6) error = "Password must be at least 6 characters"; // Stronger password check (optional but recommended) // else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) // error = "Password needs uppercase, lowercase, and number"; // Also validate confirmPassword if password changes during signup if (isSignUp && formData.confirmPassword && value !== formData.confirmPassword) { setErrors(prev => ({ ...prev, confirmPassword: 'Passwords do not match' })); } else if (isSignUp && formData.confirmPassword && value === formData.confirmPassword) { setErrors(prev => ({ ...prev, confirmPassword: '' })); // Clear error if they now match } break; case "confirmPassword": if (isSignUp) { if (!value) error = "Please confirm your password"; else if (value !== formData.password) error = "Passwords do not match"; } break; default: break; } setErrors((prev) => ({ ...prev, [name]: error })); return error === ""; // Return true if valid }; const validateForm = () => { // Validate all relevant fields let isValid = true; // Sign in uses 'email' field for username/email input const fieldsToValidate = isSignUp ? ["username", "email", "password", "confirmPassword", "full_name"] : ["email", "password"]; // Signin uses 'email' field for username/email fieldsToValidate.forEach((field) => { if (!validateField(field, formData[field])) { isValid = false; } }); // Mark all fields as touched to show errors on submit attempt const allTouched = fieldsToValidate.reduce((acc, field) => { acc[field] = true; return acc; }, {}); setTouched(allTouched); return isValid; }; // --- Form Submission --- const handleSubmit = async (e) => { e.preventDefault(); setMessage(""); // Clear previous messages if (!validateForm()) { setMessage("Please fix the errors in the form."); return; } setLoading(true); const endpoint = isSignUp ? SIGNUP_ENDPOINT : SIGNIN_ENDPOINT; // Prepare body based on signin/signup const body = isSignUp ? { // Signup sends all fields username: formData.username, email: formData.email, password: formData.password, full_name: formData.full_name, } : { // Signin sends 'email' (which holds username/email) and password email: formData.email, // Changed from username to email for signin API password: formData.password, }; console.log(`Submitting to ${endpoint} with body:`, body); try { const response = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); // Log raw response for debugging const responseText = await response.text(); console.log(`Response Status: ${response.status}`); console.log(`Raw Response: ${responseText}`); let data; try { data = JSON.parse(responseText); // Try parsing JSON } catch (parseError) { console.error("JSON Parsing Error:", parseError); throw new Error("Received invalid response from server."); } if (!response.ok) { // Handle specific backend validation errors if provided if (data.details) { setErrors((prev) => ({ ...prev, ...data.details })); } throw new Error(data.error || `Request failed with status ${response.status}`); } // --- Request Successful --- if (data.success) { if (isSignUp) { // Successful Sign Up setMessage("Registration successful! Please sign in."); setIsSignUp(false); // Switch to sign-in form // Clear form fields for sign-in, prefill email if desired setFormData({ username: "", email: formData.email, password: "", confirmPassword: "", full_name: "" }); setErrors({}); setTouched({}); } else { // Successful Sign In setMessage("Sign in successful! Redirecting..."); // Call the onSignin callback provided by App.jsx onSignin(data.user, data.token); // App.jsx will handle redirect via window.location.hash } } else { // API returned success: false throw new Error(data.error || "An unknown error occurred."); } } catch (error) { console.error("Authentication error:", error); setMessage(error.message); // Display error message to user setErrors(prev => ({ ...prev, form: error.message })); // General form error } finally { setLoading(false); } }; // --- Render --- return (

{isSignUp ? "Create Account" : "Sign In"}

{/* Display Feedback Messages */} {message && (
{message}
)} {/* Form */}
{/* Conditional Fields for Sign Up */} {isSignUp && ( <>
{errors.username && touched.username &&

{errors.username}

}
{errors.full_name && touched.full_name &&

{errors.full_name}

}
)} {/* Email (Sign Up) / Email or Username (Sign In) */}
{/* Adjust label based on mode */} {errors.email && touched.email &&

{errors.email}

}
{/* Password (Common Field) */}
{errors.password && touched.password &&

{errors.password}

}
{/* Confirm Password (Sign Up Only) */} {isSignUp && (
{errors.confirmPassword && touched.confirmPassword &&

{errors.confirmPassword}

}
)} {/* Submit Button */}
{/* Switch between Sign In / Sign Up */}

{isSignUp ? "Already have an account?" : "Don't have an account?"}{" "}

); }; // --- Prop Type Validation --- AuthPage.propTypes = { onSignin: PropTypes.func.isRequired, // Callback function after successful sign-in }; // Make AuthPage globally available window.AuthPage = AuthPage;