Files
games/features/common/components/shared.templ
Ryan Hamamura 06b3839c3a
All checks were successful
CI / Deploy / test (pull_request) Successful in 14s
CI / Deploy / lint (pull_request) Successful in 26s
CI / Deploy / deploy (pull_request) Has been skipped
refactor: patch connection indicator with timestamp
Server patches the ConnectionIndicator element with a timestamp on
each heartbeat. Client-side JS checks every second if the timestamp
is stale (>20s) and toggles red/green accordingly.

This properly detects connection loss since the indicator will turn
red if no patches are received.
2026-03-03 10:30:55 -10:00

117 lines
3.3 KiB
Plaintext

package components
import (
"fmt"
"github.com/starfederation/datastar-go/datastar"
)
templ BackToLobby() {
<a class="link text-sm opacity-70" href="/">&larr; Back</a>
}
templ StealthTitle(class string) {
<span class={ class }>
<span style="color:#4a2a3a">&#9679;</span>
<span style="color:#2a4545">&#9679;</span>
<span style="color:#4a2a3a">&#9679;</span>
<span style="color:#2a4545">&#9679;</span>
</span>
}
templ NicknamePrompt(returnPath string) {
<main class="max-w-sm mx-auto mt-8 text-center" data-signals="{nickname: ''}">
<h1 class="text-3xl font-bold">Join Game</h1>
<p class="mb-4">Enter your nickname to join the game.</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"
data-on:keydown={ "evt.key === 'Enter' && " + datastar.PostSSE("%s", returnPath) }
required
autofocus
/>
</fieldset>
<button
class="btn btn-primary w-full"
type="button"
data-on:click={ datastar.PostSSE("%s", returnPath) }
>
Join
</button>
</form>
</main>
}
func isStale(lastPing int64) bool {
return lastPing == 0
}
// ConnectionIndicator shows a small dot indicating SSE connection status.
// Server patches this with a timestamp; client JS detects staleness.
templ ConnectionIndicator(lastPing int64) {
<div
id="connection-indicator"
class="fixed top-2 right-2"
data-last-ping={ fmt.Sprintf("%d", lastPing) }
>
<div class="inline-grid *:[grid-area:1/1]">
<div
id="connection-ping"
class={
"status status-sm",
templ.KV("status-success animate-ping", !isStale(lastPing)),
templ.KV("status-error", isStale(lastPing)),
}
></div>
<div
id="connection-dot"
class={
"status status-sm",
templ.KV("status-success", !isStale(lastPing)),
templ.KV("status-error", isStale(lastPing)),
}
></div>
</div>
</div>
@connectionWatcher()
}
script connectionWatcher() {
setInterval(function() {
var el = document.getElementById('connection-indicator');
var dot = document.getElementById('connection-dot');
var ping = document.getElementById('connection-ping');
if (!el || !dot || !ping) return;
var lastPing = parseInt(el.dataset.lastPing, 10) || 0;
var stale = Date.now() - lastPing > 20000;
dot.classList.toggle('status-success', !stale);
dot.classList.toggle('status-error', stale);
ping.classList.toggle('status-success', !stale);
ping.classList.toggle('status-error', stale);
ping.classList.toggle('animate-ping', !stale);
}, 1000);
}
templ GameJoinPrompt(loginURL string, registerURL string, gamePath string) {
<main class="max-w-sm mx-auto mt-8 text-center">
<h1 class="text-3xl font-bold">Join Game</h1>
<p class="mb-4">Log in to track your game history, or continue as a guest.</p>
<div class="flex flex-col gap-2 my-4">
<a class="btn btn-primary w-full" href={ templ.SafeURL(loginURL) }>Login</a>
<a class="btn btn-secondary w-full" href={ templ.SafeURL(gamePath + "?guest=1") }>Continue as Guest</a>
</div>
<p class="text-sm opacity-60">
Don't have an account?
<a class="link" href={ templ.SafeURL(registerURL) }>Register</a>
</p>
</main>
}