Public Access
Call backend now with answer
This commit is contained in:
1 parent
1bb549c4df
commit
eab7a9edcf
3 files changed
+99
-50
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
# For local development (npm run dev)
|
||||||
|
VITE_API_URL=http://localhost:8080
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# For the docker image on the server
|
||||||
|
# Uses the variable AUTH_HOST from your docker structure [cite: 3]
|
||||||
|
VITE_API_URL=https://api.andreas-dahm.eu
|
||||||
+94
-50
@@ -6,86 +6,130 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [emailError, setEmailError] = useState('');
|
const [feedback, setFeedback] = useState({message: '', isError: false});
|
||||||
const [passwordError, setPasswordError] = useState('');
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const handleLogin = (e) => {
|
/**
|
||||||
|
* Handles the form submission and API call to the backend.
|
||||||
|
* * @param {Event} e - The form submission event
|
||||||
|
*/
|
||||||
|
const handleLogin = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
setFeedback({message: '', isError: false});
|
||||||
|
|
||||||
setEmailError('');
|
try {
|
||||||
setPasswordError('');
|
// Build the API URL using the Vite environment variable
|
||||||
|
const apiUrl = `${import.meta.env.VITE_API_URL}/api/auth/login`;
|
||||||
|
|
||||||
let hasError = false;
|
const response = await fetch(apiUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({email, password}),
|
||||||
|
});
|
||||||
|
|
||||||
if (!email.includes('@') && !email.includes('.')) {
|
// Parse the JSON response (matches our Spring Boot LoginResponse record)
|
||||||
setEmailError(t('LOGIN.ERROR_EMAIL'));
|
const data = await response.json();
|
||||||
hasError = true;
|
|
||||||
|
if (response.ok) {
|
||||||
|
// HTTP 200 OK
|
||||||
|
setFeedback({
|
||||||
|
message: `Success: ${data.message} - Welcome ${data.email}!`,
|
||||||
|
isError: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Save JWT token or trigger routing here in the future
|
||||||
|
} else {
|
||||||
|
// HTTP 401 Unauthorized or other errors
|
||||||
|
setFeedback({
|
||||||
|
message: `Error: ${data.message || 'Login failed'}`,
|
||||||
|
isError: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Network errors (e.g., backend is not running or CORS issue)
|
||||||
|
console.error('Login API Error:', error);
|
||||||
|
setFeedback({
|
||||||
|
message: 'Network error: Could not connect to the authentication service.',
|
||||||
|
isError: true
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.trim() === '') {
|
|
||||||
setPasswordError(t('LOGIN.ERROR_PASSWORD'));
|
|
||||||
hasError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop execution if form inputs are invalid
|
|
||||||
if (hasError) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proceed with API authentication
|
|
||||||
console.log('Login attempt with:', email);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grow flex items-center justify-center p-4">
|
<div className="flex min-h-screen items-center justify-center bg-slate-50 dark:bg-slate-900 px-4">
|
||||||
<div className="w-full max-w-md bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-8 shadow-xl">
|
<div className="w-full max-w-md bg-white dark:bg-slate-800 rounded-xl shadow-lg p-8">
|
||||||
<h2 className="text-3xl font-black text-slate-900 dark:text-white mb-8 text-center">
|
|
||||||
|
<h2 className="text-2xl font-bold text-slate-900 dark:text-white mb-6 text-center">
|
||||||
{t('LOGIN.TITLE')}
|
{t('LOGIN.TITLE')}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form className="flex flex-col gap-5" onSubmit={handleLogin} noValidate>
|
<form onSubmit={handleLogin} className="space-y-4" noValidate>
|
||||||
|
|
||||||
|
{/* Email Field */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2">
|
<label
|
||||||
|
htmlFor="email"
|
||||||
|
className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"
|
||||||
|
>
|
||||||
{t('LOGIN.EMAIL')}
|
{t('LOGIN.EMAIL')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="email"
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => {
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
setEmail(e.target.value);
|
className="w-full px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-slate-50 dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||||
}}
|
required
|
||||||
className={`w-full bg-slate-50 dark:bg-slate-950 border ${emailError ? 'border-red-500' : 'border-slate-200 dark:border-slate-800'} rounded-lg px-4 py-3 text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500`}
|
|
||||||
placeholder="player@tobeplayed.com"
|
|
||||||
/>
|
/>
|
||||||
{emailError && (
|
|
||||||
<p className="text-red-500 text-sm mt-1 font-medium">
|
|
||||||
{emailError}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Password Field */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2">
|
<label
|
||||||
|
htmlFor="password"
|
||||||
|
className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"
|
||||||
|
>
|
||||||
{t('LOGIN.PASSWORD')}
|
{t('LOGIN.PASSWORD')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="password"
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => {
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
setPassword(e.target.value);
|
className="w-full px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-slate-50 dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||||
}}
|
required
|
||||||
className={`w-full bg-slate-50 dark:bg-slate-950 border ${passwordError ? 'border-red-500' : 'border-slate-200 dark:border-slate-800'} rounded-lg px-4 py-3 text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500`}
|
|
||||||
placeholder="••••••••"
|
|
||||||
/>
|
/>
|
||||||
{passwordError && (
|
|
||||||
<p className="text-red-500 text-sm mt-1 font-medium">
|
|
||||||
{passwordError}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" className="mt-2 w-full bg-blue-600 hover:bg-blue-500 text-white font-bold py-3 rounded-lg transition-colors shadow-md shadow-blue-500/20">
|
|
||||||
{t('LOGIN.SUBMIT')}
|
{/* Submit Button */}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading}
|
||||||
|
className="w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold rounded-lg transition-colors duration-200"
|
||||||
|
>
|
||||||
|
{isLoading ? '...' : (t('LOGIN.SUBMIT'))}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{/* Feedback Message Area */}
|
||||||
|
{feedback.message && (
|
||||||
|
<div
|
||||||
|
className={`mt-6 p-4 rounded-lg text-sm text-center ${
|
||||||
|
feedback.isError
|
||||||
|
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||||
|
: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{feedback.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user