Implement basic layout of website

This commit is contained in:
Aelita4 2023-11-11 23:28:42 +01:00
parent eb5283882b
commit edf97f63dd
Signed by: Aelita4
GPG Key ID: E44490C2025906C1
6 changed files with 375 additions and 14 deletions

View File

@ -1,4 +1,5 @@
--- ---
import { getHighestWeightedLanguage, getLocales } from '../lib/lang/langDriver';
interface Props { interface Props {
loggedIn: string; loggedIn: string;
} }
@ -10,20 +11,34 @@ interface NavElement {
dropdowns?: Array<NavElement>; dropdowns?: Array<NavElement>;
} }
const lang = await getLocales(getHighestWeightedLanguage(Astro.request.headers.get('accept-language')), 'navbar');
const listOfElements: Array<NavElement> = [{ const listOfElements: Array<NavElement> = [{
title: "home", title: lang["Link_home"],
type: "simple",
url: "#"
}, {
title: "login",
type: "simple", type: "simple",
url: "/" url: "/"
}, { }, {
title: "dropdown", title: lang["Link_login"],
type: "simple",
url: "/login"
}, {
title: lang["Link_register"],
type: "simple",
url: "/register"
},{
title: lang["Link_dropdown"],
type: "dropdown", type: "dropdown",
url: "about:blank", url: "about:blank",
dropdowns: [{ dropdowns: [{
title: "chuj", title: "drop1",
type: "simple",
url: "aaa"
}, {
title: "drop2",
type: "simple",
url: "aaa"
}, {
title: "drop3",
type: "simple", type: "simple",
url: "aaa" url: "aaa"
}] }]
@ -38,8 +53,8 @@ const { loggedIn } = Astro.props;
element.type === "dropdown" ? element.type === "dropdown" ?
<div class="nav-item nav-item-dropdown"> <div class="nav-item nav-item-dropdown">
<li><a href={element.url} class="nav-url">{element.title}</a></li> <li><a href={element.url} class="nav-url">{element.title}</a></li>
<div> <div class="nav-item-dropdown-div">
<ul>{element.dropdowns?.map(drop => <li class="nav-item-dropdown-content"><a href={drop.url} class="nav-url">{drop.title}</a></li>)}</ul> <ul>{element.dropdowns?.map(drop => <li class="nav-item-dropdown-content"><a href={drop.url} class="nav-url">{drop.title}</a></li><br />)}</ul>
</div> </div>
</div> : // else </div> : // else
<li class="nav-item"><a href={element.url} class="nav-url">{element.title}</a></li> <li class="nav-item"><a href={element.url} class="nav-url">{element.title}</a></li>
@ -66,21 +81,37 @@ nav ul {
} }
.nav-item:not(:first-child) { .nav-item:not(:first-child) {
margin-left: 20px; margin-left: 50px;
color: green; /* color: green; */
} }
.nav-item-dropdown { .nav-item-dropdown {
color: red; color: red;
font-size: xx-large; font-size: xx-large;
position: relative;
display: inline-block;
} }
.nav-item-dropdown-content { .nav-item-dropdown-div {
display: none; display: none;
position: absolute;
background-color: green;
}
.nav-item-dropdown-div ul {
display: flex;
flex-direction: column;
}
.nav-item-dropdown:hover .nav-item-dropdown-div {
display: inline-block;
/* position: relative; */
/* padding-top: 100px; */
} }
.nav-item-dropdown:hover .nav-item-dropdown-content { .nav-item-dropdown:hover .nav-item-dropdown-content {
display: block; z-index: 1;
padding-top: 5px;
} }
.nav-url { .nav-url {

120
src/pages/game/index.astro Normal file
View File

@ -0,0 +1,120 @@
---
import Layout from '../../layouts/Layout.astro';
import NavBar from '../../components/NavBar.astro';
import { getUserResources, updateUserResources } from '../../lib/users';
import { getHighestWeightedLanguage, getLocales } from '../../lib/lang/langDriver';
String.prototype.format = function() {
return [...arguments].reduce((p,c) => p.replace(/{}/,c), this);
};
const loggedToken = Astro.cookies.get('sessionToken')?.value ?? null;
const username = Astro.cookies.get('username')?.value ?? "";
if(loggedToken === null || username === "") return Astro.redirect('/');
const resources = await getUserResources(username);
await updateUserResources(username, {
coal: resources.coal * 2,
iron: resources.iron * 3,
gold: resources.gold * 4,
});
const langResources = await getLocales(getHighestWeightedLanguage(Astro.request.headers.get('accept-language')), 'resources');
const langGame = await getLocales(getHighestWeightedLanguage(Astro.request.headers.get('accept-language')), 'game');
// console.log(resources);
---
<Layout title="chujów sto">
<NavBar loggedIn="true" />
<a href="/logout" style="color: pink;">{langGame['Link_logout']}</a>
<h1>{langGame['Header_user'].format(username)}</h1>
<ul>
<li>{langResources['Label_coal']}: <span id="coal">{resources.coal * 2}</span></li>
<li>{langResources['Label_iron']}: <span id="iron">{resources.iron * 3}</span></li>
<li>{langResources['Label_gold']}: <span id="gold">{resources.gold * 4}</span></li>
</ul>
<ul>
<li><a href="#">{langGame['Link_build'].format('iron mine')}</a></li>
<li><a href="#">{langGame['Link_build'].format('gold mine')}</a></li>
</ul>
</Layout>
<style>
* {
color: white;
}
main {
margin: auto;
padding: 1rem;
width: 800px;
max-width: calc(100% - 2rem);
color: white;
font-size: 20px;
line-height: 1.6;
}
.astro-a {
position: absolute;
top: -32px;
left: 50%;
transform: translatex(-50%);
width: 220px;
height: auto;
z-index: -1;
}
h1 {
font-size: 4rem;
font-weight: 700;
line-height: 1;
text-align: center;
margin-bottom: 1em;
}
.text-gradient {
background-image: var(--accent-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 400%;
background-position: 0%;
}
.instructions {
margin-bottom: 2rem;
border: 1px solid rgba(var(--accent-light), 25%);
background: linear-gradient(rgba(var(--accent-dark), 66%), rgba(var(--accent-dark), 33%));
padding: 1.5rem;
border-radius: 8px;
}
.instructions code {
font-size: 0.8em;
font-weight: bold;
background: rgba(var(--accent-light), 12%);
color: rgb(var(--accent-light));
border-radius: 4px;
padding: 0.3em 0.4em;
}
.instructions strong {
color: rgb(var(--accent-light));
}
.link-card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
gap: 2rem;
padding: 0;
}
</style>
<script>
setInterval(() => {
const coal = document.querySelector('#coal');
const iron = document.querySelector('#iron');
const gold = document.querySelector('#gold');
if(!coal || !iron || !gold) return;
coal.innerHTML = String(parseInt(coal?.innerHTML ?? "0") + 1);
iron.innerHTML = String(parseInt(iron?.innerHTML ?? "0") + 2);
gold.innerHTML = String(parseInt(gold?.innerHTML ?? "0") + 3);
}, 1_000)
</script>

View File

@ -1,7 +1,12 @@
--- ---
import Layout from '../layouts/Layout.astro'; import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro'; // import Card from '../components/Card.astro';
import NavBar from '../components/NavBar.astro'; import NavBar from '../components/NavBar.astro';
const loggedToken = Astro.cookies.get('sessionToken')?.value ?? null;
if(loggedToken === null) return Astro.redirect('/login');
--- ---
<Layout title="Welcome to Astro."> <Layout title="Welcome to Astro.">

76
src/pages/login.astro Normal file
View File

@ -0,0 +1,76 @@
---
import Layout from '../layouts/Layout.astro';
import NavBar from '../components/NavBar.astro';
import { getUserByNickOrEmail } from '../lib/users';
import { compare } from 'bcrypt';
let error = "";
if(Astro.request.method === "POST") {
const data = await Astro.request.formData();
const username = data.get("username") as string | "";
const password = data.get("password") as string | "";
if(username === "") {
error = "username is required";
Astro.redirect("/login");
}
if(password === "") {
error = "password is required";
Astro.redirect("/login");
}
const user = await getUserByNickOrEmail(username as string);
if(user !== null && await compare(password as string, user.password)) {
const sessionTime = import.meta.env.SESSION_TIME_MINUTES * 60;
const res = await fetch(`${Astro.url.origin}/api/auth/generateAccessToken`, {
method: 'POST',
body: JSON.stringify({
username,
createdFrom: 'loginForm',
duration: sessionTime
}),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + import.meta.env.MASTER_ACCESSTOKEN
}
});
const token = (await res.json()).accessToken;
Astro.cookies.set("sessionToken", token, {
path: "/",
maxAge: sessionTime,
sameSite: "lax",
secure: true
});
Astro.cookies.set("username", username, {
path: "/",
maxAge: sessionTime,
sameSite: "lax",
secure: true
});
return Astro.redirect("/game");
} else {
error = "invalid username or password";
return Astro.redirect("/login");
}
}
---
<Layout title="Login">
<NavBar loggedIn="false" />
<form method="POST">
<input type="text" name="username" placeholder="username" /><br />
<input type="password" name="password" placeholder="password" /><br />
<input type="submit" value="login" />
{ error !== "" ? <p style="color: red;">{error}</p> : "" }
</form>
</Layout>

7
src/pages/logout.astro Normal file
View File

@ -0,0 +1,7 @@
---
if(Astro.cookies.has('sessionToken')) {
Astro.cookies.delete('sessionToken');
}
return Astro.redirect('/');
---

122
src/pages/register.astro Normal file
View File

@ -0,0 +1,122 @@
---
import Layout from '../layouts/Layout.astro';
import NavBar from '../components/NavBar.astro';
import { createUser } from '../lib/users';
import type User from '../types/User';
import bcrypt from 'bcrypt';
let error = "";
if(Astro.request.method === "POST") {
const data = await Astro.request.formData();
const username = data.get("username") as string | "";
const email = data.get("email") as string | "";
const password = data.get("password") as string | "";
const password2 = data.get("password2") as string | "";
if(username === "") {
error = "username is required";
Astro.redirect("/register");
}
if(email === "") {
error = "email is required";
Astro.redirect("/register");
}
if(password === "") {
error = "password is required";
Astro.redirect("/register");
}
if(password2 === "") {
error = "password2 is required";
Astro.redirect("/register");
}
if(username.length < 3 || username.length > 20) {
error = "username must be between 3 and 20 characters long";
Astro.redirect("/register");
}
if(email.match(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/) === null) {
error = "email is invalid";
Astro.redirect("/register");
}
if(password.length < 8 || password.length > 50) {
error = "password must be between 8 and 50 characters long";
Astro.redirect("/register");
}
if(password !== password2) {
error = "passwords must match";
Astro.redirect("/register");
}
if(error === "") {
const user: User = {
username,
email,
password: await bcrypt.hash(password, 10),
lastLogin: new Date(),
createdAt: new Date(),
updatedAt: new Date(),
resources: {
coal: 1,
iron: 2,
gold: 3
}
}
await createUser(user);
const sessionTime = import.meta.env.SESSION_TIME_MINUTES * 60;
const res = await fetch(`${Astro.url.origin}/api/auth/generateAccessToken`, {
method: 'POST',
body: JSON.stringify({
username,
createdFrom: 'loginForm',
duration: sessionTime
}),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + import.meta.env.MASTER_ACCESSTOKEN
}
});
const token = (await res.json()).accessToken;
Astro.cookies.set("sessionToken", token, {
path: "/",
maxAge: sessionTime,
sameSite: "lax",
secure: true
});
Astro.cookies.set("username", username, {
path: "/",
maxAge: sessionTime,
sameSite: "lax",
secure: true
});
return Astro.redirect("/game");
}
}
---
<Layout title="Register">
<NavBar loggedIn="false" />
<form method="POST">
<input type="text" name="username" placeholder="username" /><br />
<input type="email" name="email" placeholder="email" /><br />
<input type="password" name="password" placeholder="password" /><br />
<input type="password" name="password2" placeholder="password2" /><br />
<input type="submit" value="register" />
{ error !== "" ? <p style="color: red;">{error}</p> : "" }
</form>
</Layout>