Iniziata la frontend (con Preact) della pagina di creazione delle partite

main
Antonio De Lucreziis 2 years ago
parent d8f1a9b4d2
commit a6c06dc831

@ -1,12 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login &bull; Lupus Lite</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Crea Partita &bull; Lupus Lite</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=IM+Fell+English:ital@0;1&family=Lato:ital,wght@0,300;0,400;0,900;1,300;1,400;1,900&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0" rel="stylesheet" />
<link rel="stylesheet" href="/src/main.scss" />
</head>
<body>
<h1>Crea Partita | Lupus Lite</h1>
<main></main>
<script type="module" src="/src/crea-partita/main.jsx"></script>
</body>
</html>

@ -14,17 +14,22 @@
</head>
<body>
<main>
<h1>Lupus Lite</h1>
<header>
<h1><a href="/">Lupus Lite</a></h1>
<div id="show-user" class="center hidden">(Accesso eseguito come <b id="show-username"></b>)</div>
</header>
<hr>
<p>
Ottieni un link di accesso ad un partita oppure accedi e creane una nuova.
</p>
<hr>
<div class="column">
<a href="/login" class="button">Registrati</a>
<a href="/login" class="button">Accedi</a>
<a href="/crea-partita" class="button">Crea Partita</a>
<a class="button if-not-logged" href="/login">Accedi</a>
<a class="button if-not-logged" href="/register">Registrati</a>
<a class="button if-logged" href="/crea-partita">Crea Partita</a>
<button id="btn-logout" class="button if-logged">Logout</button>
</div>
</main>
<script type="module" src="/src/home/index.js"></script>
</body>
</html>

@ -14,7 +14,10 @@
</head>
<body>
<main>
<h1>Lupus Lite</h1>
<header>
<h1><a href="/">Lupus Lite</a></h1>
<h2>Login</h2>
</header>
<form action="/api/login" method="post">
<h2 class="fill-row">Accedi</h2>
<p class="fill-row">
@ -26,19 +29,6 @@
<input type="password" name="password" id="login-password">
<button class="fill-row" type="submit">Accedi</button>
</form>
<form action="/api/register" method="post">
<h2 class="fill-row">Registrati</h2>
<p class="fill-row">
Crea un account per accedere alla pagina utente
</p>
<label for="register-username">Username</label>
<input type="text" name="username" id="register-username">
<label for="register-password">Password</label>
<input type="password" name="password" id="register-password">
<label for="register-password">Ripeti Password</label>
<input type="password" name="password2" id="register-password2">
<button class="fill-row" type="submit">Registrati</button>
</form>
</main>
</body>
</html>

File diff suppressed because it is too large Load Diff

@ -10,6 +10,7 @@
"author": "aziis98",
"license": "MIT",
"devDependencies": {
"@preact/preset-vite": "^2.2.0",
"sass": "^1.52.3",
"vite": "^2.9.12"
}

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lupus Lite</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IM+Fell+English:ital@0;1&family=Lato:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/src/main.scss">
</head>
<body>
<main>
<header>
<h1><a href="/">Lupus Lite</a></h1>
<h2>Login</h2>
</header>
<form action="/api/register" method="post">
<h2 class="fill-row">Registrati</h2>
<p class="fill-row">
Crea un account per accedere alla pagina utente
</p>
<label for="register-username">Username</label>
<input type="text" name="username" id="register-username">
<label for="register-password">Password</label>
<input type="password" name="password" id="register-password">
<label for="register-password">Ripeti Password</label>
<input type="password" name="password2" id="register-password2">
<button class="fill-row" type="submit">Registrati</button>
</form>
</main>
</body>
</html>

@ -0,0 +1,100 @@
import { render } from 'preact'
import { useState, useEffect } from 'preact/hooks'
const InputNumero = ({ name, id, value, setValue }) => (
<div class="compound-input">
<input
class="grow"
type="number"
name={name}
id={id}
value={value}
onInput={e => setValue(e.target.value)}
/>
<button type="button" class="square" onClick={() => setValue(value => value + 1)}>
<span class="material-symbols-outlined">add</span>
</button>
<button type="button" class="square" onClick={() => setValue(value => value - 1)}>
<span class="material-symbols-outlined">remove</span>
</button>
</div>
)
const App = () => {
const [user, setUser] = useState(null)
useEffect(() => {
fetch('/api/user')
.then(res => {
if (res.ok) {
return res.json()
}
})
.then(user => setUser(user))
})
const [numeroGiocatori, setNumeroGiocatori] = useState(10)
const [numLupi, setNumLupi] = useState(2)
const [numFattucchiere, setNumFattucchiere] = useState(1)
return (
<>
<header>
<h1>
<a href="/">Lupus Lite</a>
</h1>
<h2>Crea Partita</h2>
{user && (
<div>
(Accesso eseguito come <b>{user.username}</b>)
</div>
)}
</header>
<hr />
<form action="/api/crea-partita" method="post">
<h2 className="fill-row">Nuova Partita</h2>
<p class="fill-row">
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Esse ipsam quisquam
laborum nemo at dignissimos excepturi sapiente incidunt enim fuga?
</p>
<label for="numero-giocatori">Numero Giocatori</label>
<InputNumero
name="numero-giocatori"
id="numero-giocatori"
value={numeroGiocatori}
setValue={setNumeroGiocatori}
/>
<label for="numero-lupi">Numero Lupi</label>
<InputNumero
name="numero-lupi"
id="numero-lupi"
value={numLupi}
setValue={setNumLupi}
/>
<label for="numero-fattucchiere">Numero Fattucchiere</label>
<InputNumero
name="numero-fattucchiere"
id="numero-fattucchiere"
value={numFattucchiere}
setValue={setNumFattucchiere}
/>
<label for="numero-lupi">Numero Lupi</label>
<InputNumero
name="numero-lupi"
id="numero-lupi"
value={numLupi}
setValue={setNumLupi}
/>
<label for="numero-lupi">Numero Lupi</label>
<InputNumero
name="numero-lupi"
id="numero-lupi"
value={numLupi}
setValue={setNumLupi}
/>
<button class="fill-row">Crea Partita</button>
</form>
</>
)
}
render(<App />, document.querySelector('main'))

@ -0,0 +1,45 @@
console.log('Home has just come lines of JS')
const $showUser = document.getElementById('show-user')
const $showUsername = document.getElementById('show-username')
//
// Load user if logged
//
let user = null
const req = await fetch('/api/user')
if (req.ok) {
user = await req.json()
$showUser.classList.toggle('hidden', false)
$showUsername.textContent = user.username
}
const isLogged = !!user
//
// Logged / Not Logged Pages
//
document.querySelectorAll('.if-not-logged').forEach($el => {
$el.classList.toggle('hidden', isLogged)
})
document.querySelectorAll('.if-logged').forEach($el => {
$el.classList.toggle('hidden', !isLogged)
})
//
// Logout Button
//
const $btnLogout = document.getElementById('btn-logout')
$btnLogout.addEventListener('click', () => {
fetch('/api/logout', { method: 'POST' }).then(res => {
if (res.ok) {
location.href = '/'
}
})
})

@ -42,7 +42,7 @@ body {
main {
margin: 0 auto;
padding: 1rem;
padding: 2rem 1rem 1rem;
max-width: 80ch;
@ -50,7 +50,15 @@ main {
flex-direction: column;
align-items: center;
gap: 2rem;
gap: 1rem;
header {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
@media screen and (max-width: 512px) {
align-items: stretch;
@ -61,6 +69,17 @@ main {
// Components
//
.compound-input {
display: flex;
align-items: center;
gap: 0.25rem;
&.grow {
width: 100%;
}
}
.column {
display: flex;
flex-direction: column;
@ -85,6 +104,21 @@ main {
}
}
h1,
h2,
h3,
h4,
h5 {
a {
text-decoration: none;
color: inherit;
&:hover {
text-decoration: underline;
}
}
}
form {
@extend .panel;
@ -107,6 +141,14 @@ form {
font-weight: var(--ft-sans-wt-bold);
}
input {
justify-self: end;
}
& > :last-child {
margin-top: 1rem;
}
@media screen and (max-width: 512px) {
grid-template-columns: 1fr;
gap: 0.25rem;
@ -158,10 +200,21 @@ button,
&:hover {
background: var(--accent-350);
}
&.square {
padding: 0.5rem;
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
}
}
input[type='text'],
input[type='password'],
input[type='number'],
textarea {
border: none;
border-radius: 2px;
@ -187,11 +240,23 @@ hr {
height: 1px;
border: none;
background: var(--accent-500);
margin: 0;
padding: 0;
}
p {
margin: 0;
line-height: 1.75;
line-height: 1.5;
&.center {
text-align: center;
}
}
b,
strong {
font-weight: var(--ft-sans-wt-bold);
}
// Headings
@ -234,3 +299,11 @@ h1 {
h2 {
color: var(--accent-350);
}
//
// Utilities
//
.hidden {
display: none;
}

@ -1,7 +1,9 @@
import { defineConfig } from 'vite'
import { resolve } from 'path'
const entryPoints = ['index', 'login', 'user', 'game', 'crea-partita']
import preact from '@preact/preset-vite'
const entryPoints = ['index', 'login', 'register', 'user', 'game', 'crea-partita']
const redirect = redirectMap => ({
name: 'redirect',
@ -34,5 +36,5 @@ export default defineConfig({
'/api': 'http://127.0.0.1:4000',
},
},
plugins: [redirect(redirectMap)],
plugins: [preact(), redirect(redirectMap)],
})

@ -11,5 +11,6 @@ require (
github.com/valyala/fasthttp v1.37.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
)

@ -15,6 +15,8 @@ github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7Fw
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"fmt"
"log"
"time"
@ -17,6 +18,10 @@ func requestUser(c *fiber.Ctx) *User {
func RequireLoggedMiddleware(auth Auth) fiber.Handler {
return func(c *fiber.Ctx) error {
token := c.Cookies("sid")
if token == "" {
return fmt.Errorf(`request has no session token`)
}
username, err := auth.UserForSession(token)
if err != nil {
return err
@ -79,19 +84,34 @@ func mountApiRoutes(api fiber.Router) {
})
api.Post("/logout", func(c *fiber.Ctx) error {
panic("TODO: not implemented")
c.Cookie(&fiber.Cookie{
Name: "sid",
Value: "",
Path: "/",
Expires: time.Now(),
})
return c.SendString("ok")
})
api.Post("/register", func(c *fiber.Ctx) error {
var loginForm struct {
Username string `form:"username"`
Password string `form:"password"`
Password2 string `form:"password2"`
}
if err := c.BodyParser(&loginForm); err != nil {
return err
}
if err := validateUsername(loginForm.Username); err != nil {
return err
}
if err := validatePasswords(loginForm.Password, loginForm.Password2); err != nil {
return err
}
if err := auth.Register(loginForm.Username, loginForm.Password); err != nil {
return err
}

@ -3,6 +3,7 @@ package main
import (
"math/rand"
"time"
"unicode"
)
const alphabet = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
@ -20,3 +21,13 @@ func GenerateRandomString(n int) string {
return string(b)
}
func IsWhitespaceFree(s string) bool {
for _, r := range s {
if unicode.IsSpace(r) {
return false
}
}
return true
}

@ -0,0 +1,26 @@
package main
import "fmt"
func validateUsername(username string) error {
if !IsWhitespaceFree(username) {
return fmt.Errorf(`username cannot contain spaces`)
}
if len(username) < 4 {
return fmt.Errorf(`username too short`)
}
if len(username) > 1000 {
return fmt.Errorf(`username too long`)
}
return nil
}
func validatePasswords(password, password2 string) error {
if password != password2 {
return fmt.Errorf(`the two password fields don't match`)
}
if len(password) == 0 {
return fmt.Errorf(`password cannot be empty`)
}
return nil
}
Loading…
Cancel
Save