Public Access
Add pages, update App layout and i18n
Introduce LandingPage, LoginPage and ImprintPage components and replace the previous inline placeholders in App.jsx. Update App layout and header (branding, language toggle, dark mode toggle, route wiring) and use new i18n keys (IMPRINT and LOGIN) in en/de locales. Add client-side validation and UI for the login form. Remove unused assets/icons (public/icons.svg, src/assets/hero.png, vite.svg, vue.svg). Small logging/message tweaks and various Tailwind styling refinements.
This commit is contained in:
1 parent
c8f1d66cd2
commit
d9c3f40c4a
10 files changed
+220
-81
No files matched your search
@@ -0,0 +1,28 @@
|
||||
import {useTranslation} from 'react-i18next';
|
||||
|
||||
export default function ImprintPage() {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="grow flex justify-center p-4 md:p-8">
|
||||
<div className="w-full max-w-3xl bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-8 shadow-sm">
|
||||
<h1 className="text-3xl font-black text-slate-900 dark:text-white mb-6">
|
||||
{t('IMPRINT.TITLE')}
|
||||
</h1>
|
||||
<div className="text-slate-600 dark:text-slate-400 space-y-4">
|
||||
<p>{t('IMPRINT.LEGAL')}</p>
|
||||
<p>
|
||||
<strong>Andreas Dahm</strong><br/>
|
||||
Bgm.-Germeier-Str. 2<br/>
|
||||
85586 Poing<br/>
|
||||
Deutschland
|
||||
</p>
|
||||
<h2 className="text-xl font-bold text-slate-900 dark:text-white mt-8">
|
||||
{t('IMPRINT.CONTACT')}
|
||||
</h2>
|
||||
<p>Email: andreas.dahm@gmail.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
export default function LandingPage() {
|
||||
const {t} = useTranslation();
|
||||
const mockGames = [1, 2, 3];
|
||||
|
||||
return (
|
||||
<div className="grow flex flex-col items-center p-4 md:p-8">
|
||||
<section className="text-center my-12 md:my-20">
|
||||
<h1 className="text-4xl md:text-6xl font-black text-slate-900 dark:text-white mb-6 tracking-tight">
|
||||
{t('welcome')}
|
||||
</h1>
|
||||
<p className="text-lg text-slate-600 dark:text-slate-400 max-w-2xl mx-auto mb-8">
|
||||
Organize your pile of shame. Track, manage, and conquer your gaming backlog like never before.
|
||||
</p>
|
||||
<Link to="/login" className="bg-blue-600 hover:bg-blue-500 text-white px-8 py-3 rounded-xl font-bold text-lg transition-all shadow-lg shadow-blue-500/30">
|
||||
Get Started
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
<div className="w-full max-w-6xl grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
|
||||
{mockGames.map((n) => {
|
||||
return (
|
||||
<div key={n} className="border border-slate-200 dark:border-slate-800 rounded-2xl bg-white dark:bg-slate-900 overflow-hidden shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="h-48 bg-slate-200 dark:bg-slate-800 relative">
|
||||
<img src={`https://picsum.photos/500/300?random=${n}`} alt={`Game ${n}`} className="w-full h-full object-cover opacity-90 hover:opacity-100 transition-opacity"/>
|
||||
<div className="absolute top-3 right-3 bg-slate-900/90 text-white text-xs font-bold px-2 py-1 rounded">
|
||||
BACKLOG
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<h3 className="font-bold text-xl dark:text-white mb-1">Epic Title {n}</h3>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400 mb-4">RPG • ~50h playtime</p>
|
||||
<button className="w-full bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 py-2 rounded-lg font-semibold hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors">
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {useState} from 'react';
|
||||
|
||||
export default function LoginPage() {
|
||||
const {t} = useTranslation();
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [emailError, setEmailError] = useState('');
|
||||
const [passwordError, setPasswordError] = useState('');
|
||||
|
||||
const handleLogin = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
setEmailError('');
|
||||
setPasswordError('');
|
||||
|
||||
let hasError = false;
|
||||
|
||||
if (!email.includes('@') && !email.includes('.')) {
|
||||
setEmailError(t('LOGIN.ERROR_EMAIL'));
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="grow flex items-center justify-center p-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">
|
||||
<h2 className="text-3xl font-black text-slate-900 dark:text-white mb-8 text-center">
|
||||
{t('LOGIN.TITLE')}
|
||||
</h2>
|
||||
|
||||
<form className="flex flex-col gap-5" onSubmit={handleLogin} noValidate>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2">
|
||||
{t('LOGIN.EMAIL')}
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value);
|
||||
}}
|
||||
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>
|
||||
<label className="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2">
|
||||
{t('LOGIN.PASSWORD')}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
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>
|
||||
<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')}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user