From ff37cca46430ed714015647469f88ce06781457a Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Sun, 29 Jun 2025 01:28:39 -0700 Subject: scaffold register,login,and auth endpoints --- frontend/src/pages/Home.tsx | 119 +++++++++++++++++++++ frontend/src/pages/Landing.tsx | 154 +++++++++++++++++++++++++++ frontend/src/pages/Login.tsx | 207 ++++++++++++++++++++++++++++++++++++ frontend/src/pages/Register.tsx | 230 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 710 insertions(+) create mode 100644 frontend/src/pages/Home.tsx create mode 100644 frontend/src/pages/Landing.tsx create mode 100644 frontend/src/pages/Login.tsx create mode 100644 frontend/src/pages/Register.tsx (limited to 'frontend/src/pages') diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx new file mode 100644 index 0000000..1d87c8a --- /dev/null +++ b/frontend/src/pages/Home.tsx @@ -0,0 +1,119 @@ +import { Link, useNavigate } from 'react-router'; +import { useAuth } from '../contexts/AuthContext'; + +const Home = () => { + const { user, isLoading, logout } = useAuth(); + const navigate = useNavigate(); + + const handleLogout = async () => { + try { + await logout(); + navigate('/'); + } catch (error) { + console.error('Logout failed:', error); + alert('Network error during logout. Please try again.'); + } + }; + + if (isLoading) { + return ( +
+
+
+

Loading dashboard...

+
+
+ ); + } + + if (!user) { + return ( +
+
+
+

Session Expired

+

Please sign in to access your dashboard.

+
+ + Sign In + + + Back to Home + +
+
+
+
+ ); + } + + return ( +
+ {/* Navigation */} + + + {/* Main Content */} +
+ {/* Header */} +
+

Dashboard

+

Track your rhythm game progress and performance

+
+ + {/* Coming Soon Card */} +
+
+ + + +
+

Dashboard Coming Soon

+

+ We're working hard to bring you an amazing dashboard experience. Track your scores, + analyze your performance, and compete with friends - all coming soon! +

+
+
+

+ User ID: {user.id} +

+
+
+

+ Email: {user.email} +

+
+
+
+
+
+ ); +}; + +export default Home; diff --git a/frontend/src/pages/Landing.tsx b/frontend/src/pages/Landing.tsx new file mode 100644 index 0000000..7cd0f43 --- /dev/null +++ b/frontend/src/pages/Landing.tsx @@ -0,0 +1,154 @@ +import { Link } from 'react-router'; + +const Landing = () => { + return ( +
+ {/* Navigation */} + + + {/* Hero Section with Banner */} +
+ {/* Background Image */} +
+ + {/* Overlay */} +
+ + {/* Content */} +
+
+

+ Welcome to Mirage! +

+

+ Looks like you're not logged in. If you've got an account, Login! +

+
+
+
+ + {/* Introduction Section */} +
+
+
+

+ I'm New Around Here, What is this? +

+

+ Mirage is a Rhythm Game Score Tracker. That means we... +

+
+
+
+ + {/* Track Your Scores Section */} +
+
+
+

+ Track Your Scores. +

+

+ Mirage supports a bunch of your favourite games, and integrates with many existing services to + make sure no score is lost to the void. Furthermore, it's backed by an Open-Source API, so your + scores are always available! +

+
+
+
+ + {/* Analyse Your Scores Section */} +
+
+
+

+ Analyse Your Scores. +

+

+ Mirage analyses your scores for you, breaking them down into all the statistics you'll ever need. + No more spreadsheets! +

+
+
+
+ + {/* Provide Cool Features Section */} +
+
+
+

+ Provide Cool Features. +

+

+ Mirage implements the features rhythm gamers already talk about. Break your scores down + into sessions, showcase your best metrics on your profile, study your progress on folders - it's all + there, and done for you! +

+
+
+
+ + {/* Call to Action Section */} +
+
+
+

+ Ready to Start Tracking? +

+
+ + Create Account + + + Log In + +
+
+
+
+ + {/* Footer */} + +
+ ); +}; + +export default Landing; diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx new file mode 100644 index 0000000..5bbdefc --- /dev/null +++ b/frontend/src/pages/Login.tsx @@ -0,0 +1,207 @@ +import { useState } from "react"; +import { Link, useNavigate } from "react-router"; +import { useAuth } from "../contexts/AuthContext"; + +const Login = () => { + const [formData, setFormData] = useState({ + username: "", + password: "", + }); + const [errors, setErrors] = useState>({}); + const [isLoading, setIsLoading] = useState(false); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ + ...prev, + [name]: value, + })); + // Clear error when user starts typing + if (errors[name]) { + setErrors((prev) => ({ + ...prev, + [name]: "", + })); + } + }; + + const validateForm = () => { + const newErrors: Record = {}; + + if (!formData.username.trim()) { + newErrors.username = "Username is required"; + } else if (formData.username.length < 3) { + newErrors.username = "Username must be at least 3 characters"; + } + + if (!formData.password) { + newErrors.password = "Password is required"; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const { login } = useAuth(); + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!validateForm()) return; + + setIsLoading(true); + try { + const result = await login(formData.username, formData.password); + + if (!result.success) { + setErrors({ + general: result.error || "Login failed. Please try again.", + }); + return; + } + + // Redirect to home page on successful login + navigate("/home"); + } catch (error) { + console.error("Login failed:", error); + setErrors({ general: "Network error. Please try again." }); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+ {/* Header */} +
+ +
+ M +
+ Mirage + +

+ Sign in to your account +

+

+ Or{" "} + + create a new account + +

+
+ + {/* Form */} +
+ {errors.general && ( +
+
+
+ + + +
+
+

{errors.general}

+
+
+
+ )} + +
+
+ + + {errors.username && ( +

{errors.username}

+ )} +
+ +
+ + + {errors.password && ( +

{errors.password}

+ )} +
+ + +
+
+
+
+ ); +}; + +export default Login; diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx new file mode 100644 index 0000000..1f8dde8 --- /dev/null +++ b/frontend/src/pages/Register.tsx @@ -0,0 +1,230 @@ +import { useState } from 'react'; +import { Link, useNavigate } from 'react-router'; +import { useAuth } from '../contexts/AuthContext'; + +const Register = () => { + const [formData, setFormData] = useState({ + username: '', + email: '', + password: '', + confirmPassword: '' + }); + const [errors, setErrors] = useState>({}); + const [isLoading, setIsLoading] = useState(false); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); + if (errors[name]) { + setErrors(prev => ({ + ...prev, + [name]: '' + })); + } + }; + + const validateForm = () => { + const newErrors: Record = {}; + + if (!formData.username.trim()) { + newErrors.username = 'Username is required'; + } else if (formData.username.length < 3) { + newErrors.username = 'Username must be at least 3 characters'; + } + + if (!formData.email.trim()) { + newErrors.email = 'Email is required'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { + newErrors.email = 'Please enter a valid email address'; + } + + if (!formData.password) { + newErrors.password = 'Password is required'; + } else if (formData.password.length < 6) { + newErrors.password = 'Password must be at least 6 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 { register } = useAuth(); + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!validateForm()) return; + + setIsLoading(true); + try { + const result = await register({ + username: formData.username, + email: formData.email, + password: formData.password, + }); + + if (!result.success) { + setErrors({ general: result.error || 'Registration failed. Please try again.' }); + return; + } + + // Redirect to home page on successful registration + navigate('/home'); + } catch (error) { + console.error('Registration failed:', error); + setErrors({ general: 'Network error. Please try again.' }); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+ {/* Header */} +
+ +
+ M +
+ Mirage + +

+ Create your account +

+

+ Already have an account?{' '} + + Sign in here + +

+
+ + {/* Form */} +
+ {errors.general && ( +
+
+
+ + + +
+
+

{errors.general}

+
+
+
+ )} + +
+
+ + + {errors.username && ( +

{errors.username}

+ )} +
+ +
+ + + {errors.email && ( +

{errors.email}

+ )} +
+ +
+ + + {errors.password && ( +

{errors.password}

+ )} +
+ +
+ + + {errors.confirmPassword && ( +

{errors.confirmPassword}

+ )} +
+ + +
+
+
+
+ ); +}; + +export default Register; -- cgit v1.2.3