Datastar's NewSSE() flushes HTTP headers before SCS's session middleware can attach the Set-Cookie header, so the session cookie never reaches the browser after login/register/logout. Convert login, register, and logout to standard HTML forms with HTTP redirects, which lets SCS write cookies normally. Also fix return_url capture on the login page (was never being stored in the session). Add handler tests covering login, register, and logout flows.
170 lines
4.8 KiB
Plaintext
170 lines
4.8 KiB
Plaintext
package pages
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/ryanhamamura/games/features/common/components"
|
|
"github.com/ryanhamamura/games/features/common/layouts"
|
|
lobbycomponents "github.com/ryanhamamura/games/features/lobby/components"
|
|
"github.com/ryanhamamura/games/snake"
|
|
"github.com/starfederation/datastar-go/datastar"
|
|
)
|
|
|
|
templ LobbyPage(data LobbyData) {
|
|
@layouts.Base("Game Lobby") {
|
|
<main
|
|
class="max-w-md mx-auto mt-8 text-center"
|
|
data-signals="{activeTab: 'connect4', nickname: '', selectedSpeed: 1}"
|
|
>
|
|
// Auth header
|
|
if data.IsLoggedIn {
|
|
<div class="flex justify-center items-center gap-4 mb-4 p-2 bg-base-200 rounded-lg">
|
|
<span>Logged in as <strong>{ data.Username }</strong></span>
|
|
<form method="POST" action="/logout" class="inline">
|
|
<button type="submit" class="btn btn-ghost btn-sm">
|
|
Logout
|
|
</button>
|
|
</form>
|
|
</div>
|
|
} else {
|
|
<div class="alert text-sm mb-4">
|
|
Playing as guest.
|
|
<a class="link" href="/login">Login</a>
|
|
or
|
|
<a class="link" href="/register">Register</a>
|
|
to save your games.
|
|
</div>
|
|
}
|
|
// Title
|
|
<h1 class="text-3xl font-bold mb-4">
|
|
@components.StealthTitle("")
|
|
</h1>
|
|
// Tab buttons
|
|
<div class="tabs tabs-box mb-6 justify-center">
|
|
<button
|
|
class="tab"
|
|
type="button"
|
|
data-class="{'tab-active': $activeTab==='connect4'}"
|
|
data-on:click="$activeTab='connect4'"
|
|
>
|
|
@components.StealthTitle("")
|
|
</button>
|
|
<button
|
|
class="tab"
|
|
type="button"
|
|
data-class="{'tab-active': $activeTab==='snake'}"
|
|
data-on:click="$activeTab='snake'"
|
|
>
|
|
~~~~
|
|
</button>
|
|
</div>
|
|
// Connect4 tab
|
|
<div data-show="$activeTab==='connect4'">
|
|
<p class="mb-4">Start a new session</p>
|
|
<form>
|
|
<fieldset class="fieldset">
|
|
<label class="label" for="nickname">Your Nickname</label>
|
|
<input
|
|
class="input input-bordered w-full"
|
|
id="nickname"
|
|
type="text"
|
|
placeholder="Enter your nickname"
|
|
data-bind="nickname"
|
|
required
|
|
data-on:keydown={ "evt.key === 'Enter' && " + datastar.PostSSE("/games") }
|
|
/>
|
|
</fieldset>
|
|
<button
|
|
class="btn btn-primary w-full"
|
|
type="button"
|
|
data-on:click={ datastar.PostSSE("/games") }
|
|
>
|
|
Create Game
|
|
</button>
|
|
</form>
|
|
@lobbycomponents.GameList(data.UserGames)
|
|
</div>
|
|
// Snake tab
|
|
<div data-show="$activeTab==='snake'">
|
|
// Nickname
|
|
<div class="mb-4">
|
|
<fieldset class="fieldset">
|
|
<label class="label" for="snake-nickname">Your Nickname</label>
|
|
<input
|
|
class="input input-bordered w-full"
|
|
id="snake-nickname"
|
|
type="text"
|
|
placeholder="Enter your nickname"
|
|
data-bind="nickname"
|
|
required
|
|
/>
|
|
</fieldset>
|
|
</div>
|
|
// Speed selector
|
|
<div class="mb-4">
|
|
<label class="label">Speed</label>
|
|
<div class="btn-group">
|
|
for i, preset := range snake.SpeedPresets {
|
|
<button
|
|
class="btn btn-sm"
|
|
type="button"
|
|
data-class={ fmt.Sprintf("{'btn-active': $selectedSpeed===%d}", i) }
|
|
data-on:click={ fmt.Sprintf("$selectedSpeed=%d", i) }
|
|
>
|
|
{ preset.Name }
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
// Solo play
|
|
<div class="mb-6">
|
|
<h3 class="text-lg font-bold mb-2">Play Solo</h3>
|
|
<div class="flex gap-2 justify-center flex-wrap">
|
|
for i, preset := range snake.GridPresets {
|
|
<button
|
|
class="btn btn-secondary"
|
|
type="button"
|
|
data-on:click={ datastar.PostSSE("/snake?mode=solo&preset=%d", i) }
|
|
>
|
|
{ fmt.Sprintf("%s (%d\u00d7%d)", preset.Name, preset.Width, preset.Height) }
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
// Multiplayer
|
|
<div class="mb-6">
|
|
<h3 class="text-lg font-bold mb-2">Create Multiplayer Game</h3>
|
|
<div class="flex gap-2 justify-center flex-wrap">
|
|
for i, preset := range snake.GridPresets {
|
|
<button
|
|
class="btn btn-primary"
|
|
type="button"
|
|
data-on:click={ datastar.PostSSE("/snake?mode=multi&preset=%d", i) }
|
|
>
|
|
{ fmt.Sprintf("%s (%d\u00d7%d)", preset.Name, preset.Width, preset.Height) }
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
// Active snake games
|
|
if len(data.ActiveSnakeGames) > 0 {
|
|
<div class="mt-6">
|
|
<h3 class="text-lg font-bold mb-2 text-center">Join a Game</h3>
|
|
<div class="flex flex-col gap-2">
|
|
for _, g := range data.ActiveSnakeGames {
|
|
<a
|
|
href={ templ.SafeURL("/snake/" + g.ID) }
|
|
class="flex justify-between items-center p-3 bg-base-200 rounded-lg hover:bg-base-300 no-underline text-base-content"
|
|
>
|
|
<span>{ fmt.Sprintf("%d\u00d7%d \u2014 %d/8 players", g.Width, g.Height, g.PlayerCount) }</span>
|
|
<span class="text-sm opacity-60">{ g.StatusLabel }</span>
|
|
</a>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</main>
|
|
}
|
|
}
|