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
+215
-76
No files matched your search
+14
-2
@@ -1,5 +1,17 @@
|
||||
{
|
||||
"IMPRINT": {
|
||||
"TITLE": "Impressum",
|
||||
"LEGAL": "Angaben gemäß § 5 DDG",
|
||||
"CONTACT": "Kontakt"
|
||||
},
|
||||
"LOGIN": {
|
||||
"TITLE": "Login",
|
||||
"EMAIL": "Email",
|
||||
"PASSWORD": "Passwort",
|
||||
"SUBMIT": "Spiel betreten",
|
||||
"ERROR_EMAIL": "Gib eine gültige Email-Adresse ein mit '@' und mit '.'.",
|
||||
"ERROR_PASSWORD": "Das Passwort darf nicht leer sein."
|
||||
},
|
||||
"welcome": "Dein Game-Backlog unter Kontrolle",
|
||||
"login": "Anmelden",
|
||||
"imprint": "Impressum"
|
||||
"login": "Anmelden"
|
||||
}
|
||||
+14
-2
@@ -1,5 +1,17 @@
|
||||
{
|
||||
"IMPRINT": {
|
||||
"TITLE": "Imprint",
|
||||
"LEGAL": "Information according to § 5 TMG.",
|
||||
"CONTACT": "Contact"
|
||||
},
|
||||
"LOGIN": {
|
||||
"TITLE": "Login",
|
||||
"EMAIL": "Email",
|
||||
"PASSWORD": "Password",
|
||||
"SUBMIT": "Enter the game",
|
||||
"ERROR_EMAIL": "Enter a valid email address with '@' and with '.'.",
|
||||
"ERROR_PASSWORD": "The password must have at least one character."
|
||||
},
|
||||
"welcome": "Control your game backlog",
|
||||
"login": "Login",
|
||||
"imprint": "Imprint"
|
||||
"login": "Login"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||
</symbol>
|
||||
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||
</symbol>
|
||||
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||
</symbol>
|
||||
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||
</symbol>
|
||||
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.9 KiB |
+22
-46
@@ -1,42 +1,16 @@
|
||||
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
|
||||
import {BrowserRouter, Link, Route, Routes} from 'react-router-dom';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import { useState, useEffect } from 'react';
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
// Placeholder components to keep the routing structure simple
|
||||
function LandingPage() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="grow flex items-center justify-center p-4">
|
||||
<h1 className="text-3xl md:text-5xl font-bold text-slate-900 dark:text-white">
|
||||
{t('welcome')}
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LoginPage() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="grow flex items-center justify-center p-4">
|
||||
<h2 className="text-2xl text-slate-800 dark:text-slate-200">{t('login')}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ImprintPage() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="grow p-8">
|
||||
<h2 className="text-2xl text-slate-800 dark:text-slate-200">{t('imprint')}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import LandingPage from './pages/LandingPage';
|
||||
import LoginPage from './pages/LoginPage';
|
||||
import ImprintPage from './pages/ImprintPage';
|
||||
|
||||
function App() {
|
||||
const {t, i18n} = useTranslation();
|
||||
const [isDarkMode, setIsDarkMode] = useState(true);
|
||||
|
||||
// Apply dark mode class to the root HTML element for Tailwind
|
||||
// Sync React state with the HTML class required by Tailwind CSS
|
||||
useEffect(() => {
|
||||
if (isDarkMode) {
|
||||
document.documentElement.classList.add('dark');
|
||||
@@ -47,34 +21,36 @@ function App() {
|
||||
|
||||
const toggleLanguage = () => {
|
||||
if (i18n.language === 'de') {
|
||||
i18n.changeLanguage('en').then(r => console.log("Changed language to en"));
|
||||
i18n.changeLanguage('en').then(r => console.log("Changed language to 'en'"));
|
||||
} else {
|
||||
i18n.changeLanguage('de').then(r => console.log("Changed language to en"));
|
||||
i18n.changeLanguage('de').then(r => console.log("Changed language to 'de'"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div className="min-h-screen flex flex-col bg-slate-50 dark:bg-slate-950 transition-colors duration-300">
|
||||
<header className="p-4 flex justify-between items-center border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900">
|
||||
<Link to="/" className="font-bold text-blue-600 dark:text-blue-400 text-xl flex items-center">
|
||||
<i className="mdi mdi-controller mr-2"></i> To Be Played
|
||||
<div className="min-h-screen flex flex-col bg-slate-50 dark:bg-slate-950 transition-colors duration-300 font-sans">
|
||||
<header className="p-4 flex justify-between items-center border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 sticky top-0 z-50">
|
||||
<Link to="/" className="font-black text-blue-600 dark:text-blue-500 text-2xl flex items-center tracking-tight">
|
||||
<i className="mdi mdi-controller mr-2 text-3xl"></i> Game Log
|
||||
</Link>
|
||||
|
||||
<div className="flex gap-4 items-center">
|
||||
<button onClick={toggleLanguage} className="text-sm font-medium dark:text-slate-300">
|
||||
<div className="flex gap-5 items-center">
|
||||
<button onClick={toggleLanguage} className="text-sm font-bold text-slate-600 dark:text-slate-300 hover:text-blue-500 transition-colors">
|
||||
{i18n.language.toUpperCase()}
|
||||
</button>
|
||||
<button onClick={() => { setIsDarkMode(!isDarkMode); }} className="text-xl dark:text-slate-300">
|
||||
<button onClick={() => {
|
||||
setIsDarkMode(!isDarkMode);
|
||||
}} className="text-xl text-slate-600 dark:text-slate-300 hover:text-blue-500 transition-colors">
|
||||
<i className={isDarkMode ? "mdi mdi-weather-sunny" : "mdi mdi-weather-night"}></i>
|
||||
</button>
|
||||
<Link to="/login" className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors">
|
||||
<Link to="/login" className="bg-blue-600 hover:bg-blue-500 text-white px-5 py-2 rounded-lg font-bold transition-all shadow-md shadow-blue-500/20">
|
||||
{t('login')}
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="flex-grow flex flex-col">
|
||||
<main className="grow flex flex-col">
|
||||
<Routes>
|
||||
<Route path="/" element={<LandingPage/>}/>
|
||||
<Route path="/login" element={<LoginPage/>}/>
|
||||
@@ -82,9 +58,9 @@ function App() {
|
||||
</Routes>
|
||||
</main>
|
||||
|
||||
<footer className="p-4 text-center border-t border-slate-200 dark:border-slate-800 text-slate-500 text-sm bg-white dark:bg-slate-900">
|
||||
<Link to="/impressum" className="hover:text-blue-500 transition-colors">
|
||||
{t('imprint')}
|
||||
<footer className="p-6 text-center border-t border-slate-200 dark:border-slate-800 text-slate-500 dark:text-slate-400 text-sm bg-white dark:bg-slate-900">
|
||||
<Link to="/impressum" className="hover:text-blue-500 font-semibold transition-colors">
|
||||
{t('IMPRINT.TITLE')}
|
||||
</Link>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="77" height="47" fill="none" aria-labelledby="vite-logo-title" viewBox="0 0 77 47"><title id="vite-logo-title">Vite</title><style>.parenthesis{fill:#000}@media (prefers-color-scheme:dark){.parenthesis{fill:#fff}}</style><path fill="#9135ff" d="M40.151 45.71c-.663.844-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.493c-.92 0-1.457-1.04-.92-1.788l7.479-10.471c1.07-1.498 0-3.578-1.842-3.578H15.443c-.92 0-1.456-1.04-.92-1.788l9.696-13.576c.213-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.472c-1.07 1.497 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.087.89 1.83L40.153 45.712z"/><mask id="a" width="48" height="47" x="14" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M40.047 45.71c-.663.843-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.389c-.92 0-1.457-1.04-.92-1.788l7.479-10.472c1.07-1.497 0-3.578-1.842-3.578H15.34c-.92 0-1.456-1.04-.92-1.788l9.696-13.575c.213-.297.556-.474.92-.474H53.93c.92 0 1.456 1.04.92 1.788L47.37 13.03c-1.07 1.498 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.088.89 1.831L40.049 45.712z"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#eee6ff" rx="5.508" ry="14.704" transform="rotate(269.814 20.96 11.29)scale(-1 1)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#eee6ff" rx="10.399" ry="29.851" transform="rotate(89.814 -16.902 -8.275)scale(1 -1)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#8900ff" rx="5.508" ry="30.487" transform="rotate(89.814 -19.197 -7.127)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.928 4.177)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.738 5.52)scale(1 -1)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#eee6ff" rx="14.072" ry="22.078" transform="rotate(93.35 31.245 55.578)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx="14.592" cy="9.743" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(39.51 14.592 9.743)"/></g><g filter="url(#k)"><ellipse cx="61.728" cy="-5.321" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 61.728 -5.32)"/></g><g filter="url(#l)"><ellipse cx="55.618" cy="7.104" fill="#00c2ff" rx="5.971" ry="9.665" transform="rotate(37.892 55.618 7.104)"/></g><g filter="url(#m)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#n)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#o)"><ellipse cx="49.857" cy="30.678" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 49.857 30.678)"/></g><g filter="url(#p)"><ellipse cx="52.623" cy="33.171" fill="#00c2ff" rx="5.971" ry="15.297" transform="rotate(37.892 52.623 33.17)"/></g></g><path d="M6.919 0c-9.198 13.166-9.252 33.575 0 46.789h6.215c-9.25-13.214-9.196-33.623 0-46.789zm62.424 0h-6.215c9.198 13.166 9.252 33.575 0 46.789h6.215c9.25-13.214 9.196-33.623 0-46.789" class="parenthesis"/><defs><filter id="b" width="60.045" height="41.654" x="-5.564" y="16.92" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-40.407" y="-6.762" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-35.435" y="2.801" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-30.84" y="20.8" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-29.307" y="21.949" color-interpolation-filters="sRGB" filterUnits="usLine truncated
|
||||
|
Before Width: | Height: | Size: 8.5 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 496 B |
@@ -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