ソースを参照

quick patch to fix qr

simo 2 日 前
コミット
5e78155fb6
2 ファイル変更305 行追加194 行削除
  1. 3 2
      src/pages/api/qr/[id].ts
  2. 302 192
      src/pages/play.astro

+ 3 - 2
src/pages/api/qr/[id].ts

@@ -5,8 +5,9 @@ export const GET: APIRoute = async ({ params, request }) => {
   const id = params.id;
   if (!id) return new Response("Missing id", { status: 400 });
 
-  const origin = new URL(request.url).origin;
-  const url = `${origin}/game/${id}`;
+  // const origin = new URL(request.url).origin;
+  // Temp QR code fix
+  const url = `https://klask.simo.ng/game/${id}`;
 
   const svg = await QRCode.toString(url, {
     type: "svg",

+ 302 - 192
src/pages/play.astro

@@ -8,214 +8,324 @@ await init();
 
 const currentUserName = getUserFromRequest(Astro.request);
 if (!currentUserName) {
-  return Astro.redirect("/login?redirect=/play");
+    return Astro.redirect("/login?redirect=/play");
 }
 
 const currentUser = await getUser(currentUserName);
 ---
 
-<Layout title="Play" currentUser={currentUserName} currentRating={currentUser?.rating ?? null}>
-  <Nav currentUser={currentUserName} currentRating={currentUser?.rating ?? null} activePage="play" />
-
-  <main class="container play-page">
-    <h1 class="page-title" style="margin: 32px 0 24px">Start a Game</h1>
-
-    <!-- Mode selector -->
-    <div class="mode-tabs">
-      <button class="mode-tab active" id="tab-inperson" data-mode="inperson">
-        <span class="mode-icon">🏓</span>
-        <span class="mode-label">In Person</span>
-        <span class="mode-desc">Track score for a physical game</span>
-      </button>
-      <button class="mode-tab" id="tab-virtual" data-mode="virtual">
-        <span class="mode-icon">🌐</span>
-        <span class="mode-label">Virtual</span>
-        <span class="mode-desc">Play online with PeerJS</span>
-      </button>
-    </div>
-
-    <div class="play-grid">
-      <!-- Create Game -->
-      <div class="card">
-        <h2 class="card-title">Create Game</h2>
-        <p class="card-desc" id="create-desc">Generate a link and share it with your opponent.</p>
-
-        <button id="create-btn" class="btn" style="width:100%; padding:12px; margin-top:8px">
-          Create Game Room
-        </button>
-
-        <div id="created-result" style="display:none; margin-top:24px">
-          <div class="created-link-box">
-            <p class="section-label">Game Link</p>
-            <div class="link-row">
-              <input type="text" id="game-link" readonly />
-              <button id="copy-btn" class="btn btn-secondary" style="flex-shrink:0; width:auto; padding:10px 14px">Copy</button>
-            </div>
-          </div>
+<Layout
+    title="Play"
+    currentUser={currentUserName}
+    currentRating={currentUser?.rating ?? null}
+>
+    <Nav
+        currentUser={currentUserName}
+        currentRating={currentUser?.rating ?? null}
+        activePage="play"
+    />
+
+    <main class="container play-page">
+        <h1 class="page-title" style="margin: 32px 0 24px">Start a Game</h1>
+
+        <!-- Mode selector -->
+        <div class="mode-tabs">
+            <button
+                class="mode-tab active"
+                id="tab-inperson"
+                data-mode="inperson"
+            >
+                <span class="mode-icon">🏓</span>
+                <span class="mode-label">In Person</span>
+                <span class="mode-desc">Track score for a physical game</span>
+            </button>
+            <button class="mode-tab" id="tab-virtual" data-mode="virtual">
+                <span class="mode-icon">🌐</span>
+                <span class="mode-label">Virtual</span>
+                <span class="mode-desc">Play online with PeerJS</span>
+            </button>
+        </div>
 
-          <div class="qr-section">
-            <p class="section-label">QR Code</p>
-            <div id="qr-container" class="qr-box">
-              <img id="qr-img" src="" alt="QR code" />
+        <div class="play-grid">
+            <!-- Create Game -->
+            <div class="card">
+                <h2 class="card-title">Create Game</h2>
+                <p class="card-desc" id="create-desc">
+                    Generate a link and share it with your opponent.
+                </p>
+
+                <button
+                    id="create-btn"
+                    class="btn"
+                    style="width:100%; padding:12px; margin-top:8px"
+                >
+                    Create Game Room
+                </button>
+
+                <div id="created-result" style="display:none; margin-top:24px">
+                    <div class="created-link-box">
+                        <p class="section-label">Game Link</p>
+                        <div class="link-row">
+                            <input type="text" id="game-link" readonly />
+                            <button
+                                id="copy-btn"
+                                class="btn btn-secondary"
+                                style="flex-shrink:0; width:auto; padding:10px 14px"
+                                >Copy</button
+                            >
+                        </div>
+                    </div>
+
+                    <div class="qr-section">
+                        <p class="section-label">QR Code</p>
+                        <div id="qr-container" class="qr-box">
+                            <img id="qr-img" src="" alt="QR code" />
+                        </div>
+                    </div>
+
+                    <a
+                        id="open-game-btn"
+                        href="#"
+                        class="btn"
+                        style="width:100%; padding:12px; display:block; text-align:center; margin-top:16px"
+                    >
+                        Open Game Room →
+                    </a>
+                </div>
             </div>
-          </div>
 
-          <a id="open-game-btn" href="#" class="btn" style="width:100%; padding:12px; display:block; text-align:center; margin-top:16px">
-            Open Game Room →
-          </a>
+            <!-- Join Game -->
+            <div class="card">
+                <h2 class="card-title">Join Game</h2>
+                <p class="card-desc">
+                    Have a game ID or link? Enter it below to join.
+                </p>
+
+                <form id="join-form" style="margin-top:8px">
+                    <div class="form-group">
+                        <label for="join-input">Game ID or Link</label>
+                        <input
+                            type="text"
+                            id="join-input"
+                            placeholder="e.g. abc123 or full URL"
+                            autocomplete="off"
+                        />
+                    </div>
+                    <div id="join-error" class="error-msg" style="display:none">
+                    </div>
+                    <button
+                        type="submit"
+                        class="btn"
+                        style="width:100%; padding:12px"
+                    >
+                        Join Game →
+                    </button>
+                </form>
+            </div>
         </div>
-      </div>
-
-      <!-- Join Game -->
-      <div class="card">
-        <h2 class="card-title">Join Game</h2>
-        <p class="card-desc">Have a game ID or link? Enter it below to join.</p>
-
-        <form id="join-form" style="margin-top:8px">
-          <div class="form-group">
-            <label for="join-input">Game ID or Link</label>
-            <input type="text" id="join-input" placeholder="e.g. abc123 or full URL" autocomplete="off" />
-          </div>
-          <div id="join-error" class="error-msg" style="display:none"></div>
-          <button type="submit" class="btn" style="width:100%; padding:12px">
-            Join Game →
-          </button>
-        </form>
-      </div>
-    </div>
-  </main>
+    </main>
 </Layout>
 
 <script>
-  let selectedMode = "inperson";
-
-  const tabs = document.querySelectorAll(".mode-tab");
-  tabs.forEach(tab => {
-    tab.addEventListener("click", () => {
-      tabs.forEach(t => t.classList.remove("active"));
-      tab.classList.add("active");
-      selectedMode = (tab as HTMLElement).dataset.mode!;
-      // Hide any previous result when switching modes
-      const result = document.getElementById("created-result") as HTMLElement;
-      result.style.display = "none";
+    let selectedMode = "inperson";
+
+    const tabs = document.querySelectorAll(".mode-tab");
+    tabs.forEach((tab) => {
+        tab.addEventListener("click", () => {
+            tabs.forEach((t) => t.classList.remove("active"));
+            tab.classList.add("active");
+            selectedMode = (tab as HTMLElement).dataset.mode!;
+            // Hide any previous result when switching modes
+            const result = document.getElementById(
+                "created-result",
+            ) as HTMLElement;
+            result.style.display = "none";
+        });
     });
-  });
-
-  const createBtn = document.getElementById("create-btn") as HTMLButtonElement;
-  const createdResult = document.getElementById("created-result") as HTMLElement;
-  const gameLink = document.getElementById("game-link") as HTMLInputElement;
-  const copyBtn = document.getElementById("copy-btn") as HTMLButtonElement;
-  const qrImg = document.getElementById("qr-img") as HTMLImageElement;
-  const openGameBtn = document.getElementById("open-game-btn") as HTMLAnchorElement;
-
-  createBtn.addEventListener("click", async () => {
-    createBtn.disabled = true;
-    createBtn.textContent = "Creating...";
-
-    const res = await fetch("/api/game", {
-      method: "POST",
-      headers: { "Content-Type": "application/json" },
-      body: JSON.stringify({ mode: selectedMode }),
+
+    const createBtn = document.getElementById(
+        "create-btn",
+    ) as HTMLButtonElement;
+    const createdResult = document.getElementById(
+        "created-result",
+    ) as HTMLElement;
+    const gameLink = document.getElementById("game-link") as HTMLInputElement;
+    const copyBtn = document.getElementById("copy-btn") as HTMLButtonElement;
+    const qrImg = document.getElementById("qr-img") as HTMLImageElement;
+    const openGameBtn = document.getElementById(
+        "open-game-btn",
+    ) as HTMLAnchorElement;
+
+    createBtn.addEventListener("click", async () => {
+        createBtn.disabled = true;
+        createBtn.textContent = "Creating...";
+
+        const res = await fetch("/api/game", {
+            method: "POST",
+            headers: { "Content-Type": "application/json" },
+            body: JSON.stringify({ mode: selectedMode }),
+        });
+
+        if (!res.ok) {
+            createBtn.disabled = false;
+            createBtn.textContent = "Create Game Room";
+            alert("Failed to create game. Are you signed in?");
+            return;
+        }
+
+        const data = await res.json();
+        const url = `${location.origin}/game/${data.id}`;
+
+        gameLink.value = url;
+        openGameBtn.href = `/game/${data.id}`;
+        qrImg.src = `/api/qr/${data.id}`;
+        createdResult.style.display = "block";
+        createBtn.textContent = "Create Another";
+        createBtn.disabled = false;
     });
 
-    if (!res.ok) {
-      createBtn.disabled = false;
-      createBtn.textContent = "Create Game Room";
-      alert("Failed to create game. Are you signed in?");
-      return;
-    }
-
-    const data = await res.json();
-    const url = `${location.origin}/game/${data.id}`;
-
-    gameLink.value = url;
-    openGameBtn.href = `/game/${data.id}`;
-    qrImg.src = `/api/qr/${data.id}`;
-    createdResult.style.display = "block";
-    createBtn.textContent = "Create Another";
-    createBtn.disabled = false;
-  });
-
-  copyBtn.addEventListener("click", () => {
-    navigator.clipboard.writeText(gameLink.value);
-    copyBtn.textContent = "Copied!";
-    setTimeout(() => { copyBtn.textContent = "Copy"; }, 2000);
-  });
-
-  const joinForm = document.getElementById("join-form") as HTMLFormElement;
-  const joinInput = document.getElementById("join-input") as HTMLInputElement;
-  const joinError = document.getElementById("join-error") as HTMLElement;
-
-  joinForm.addEventListener("submit", (e) => {
-    e.preventDefault();
-    joinError.style.display = "none";
-    let val = joinInput.value.trim();
-    if (!val) return;
-    let id = val;
-    try {
-      const url = new URL(val);
-      const parts = url.pathname.split("/").filter(Boolean);
-      if (parts.length >= 2 && parts[0] === "game") id = parts[1];
-    } catch {}
-    if (!id) { joinError.textContent = "Invalid game ID"; joinError.style.display = "block"; return; }
-    location.href = `/game/${id}`;
-  });
+    copyBtn.addEventListener("click", () => {
+        navigator.clipboard.writeText(gameLink.value);
+        copyBtn.textContent = "Copied!";
+        setTimeout(() => {
+            copyBtn.textContent = "Copy";
+        }, 2000);
+    });
+
+    const joinForm = document.getElementById("join-form") as HTMLFormElement;
+    const joinInput = document.getElementById("join-input") as HTMLInputElement;
+    const joinError = document.getElementById("join-error") as HTMLElement;
+
+    joinForm.addEventListener("submit", (e) => {
+        e.preventDefault();
+        joinError.style.display = "none";
+        let val = joinInput.value.trim();
+        if (!val) return;
+        let id = val;
+        try {
+            const url = new URL(val);
+            const parts = url.pathname.split("/").filter(Boolean);
+            if (parts.length >= 2 && parts[0] === "game") id = parts[1];
+        } catch {}
+        if (!id) {
+            joinError.textContent = "Invalid game ID";
+            joinError.style.display = "block";
+            return;
+        }
+        location.href = `/game/${id}`;
+    });
 </script>
 
 <style>
-  .play-page { padding-bottom: 48px; }
-
-  .mode-tabs {
-    display: flex;
-    gap: 12px;
-    margin-bottom: 24px;
-  }
-
-  .mode-tab {
-    flex: 1;
-    background: var(--card);
-    border: 2px solid var(--border);
-    border-radius: 4px;
-    padding: 16px;
-    cursor: pointer;
-    text-align: left;
-    display: flex;
-    flex-direction: column;
-    gap: 4px;
-    transition: border-color 0.15s, background 0.15s;
-    font-family: 'DM Mono', monospace;
-  }
-
-  .mode-tab:hover { border-color: var(--border-light); }
-
-  .mode-tab.active {
-    border-color: var(--green);
-    background: rgba(129, 182, 76, 0.06);
-  }
-
-  .mode-icon { font-size: 20px; }
-
-  .mode-label {
-    font-family: 'Bebas Neue', sans-serif;
-    font-size: 1.3rem;
-    letter-spacing: 0.05em;
-    color: var(--text);
-  }
-
-  .mode-desc {
-    font-size: 11px;
-    color: var(--text-muted);
-  }
-
-  .play-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
-  .card-title { font-family: 'Bebas Neue', sans-serif; font-size: 1.5rem; letter-spacing: 0.05em; margin-bottom: 8px; }
-  .card-desc { color: var(--text-muted); font-size: 13px; margin-bottom: 0; line-height: 1.6; }
-  .created-link-box { margin-bottom: 20px; }
-  .link-row { display: flex; gap: 8px; }
-  .link-row input { flex: 1; font-size: 12px; }
-  .qr-section { margin-bottom: 8px; }
-  .qr-box { background: white; border-radius: 4px; display: inline-block; padding: 12px; }
-  .qr-box img { display: block; width: 160px; height: 160px; }
-  .error-msg { color: var(--red); font-size: 12px; margin-bottom: 12px; padding: 8px 12px; background: rgba(224,110,110,0.1); border-radius: 3px; border: 1px solid rgba(224,110,110,0.2); }
-  @media (max-width: 640px) { .play-grid { grid-template-columns: 1fr; } .mode-tabs { flex-direction: column; } }
+    .play-page {
+        padding-bottom: 48px;
+    }
+
+    .mode-tabs {
+        display: flex;
+        gap: 12px;
+        margin-bottom: 24px;
+    }
+
+    .mode-tab {
+        flex: 1;
+        background: var(--card);
+        border: 2px solid var(--border);
+        border-radius: 4px;
+        padding: 16px;
+        cursor: pointer;
+        text-align: left;
+        display: flex;
+        flex-direction: column;
+        gap: 4px;
+        transition:
+            border-color 0.15s,
+            background 0.15s;
+        font-family: "DM Mono", monospace;
+    }
+
+    .mode-tab:hover {
+        border-color: var(--border-light);
+    }
+
+    .mode-tab.active {
+        border-color: var(--green);
+        background: rgba(129, 182, 76, 0.06);
+    }
+
+    .mode-icon {
+        font-size: 20px;
+    }
+
+    .mode-label {
+        font-family: "Bebas Neue", sans-serif;
+        font-size: 1.3rem;
+        letter-spacing: 0.05em;
+        color: var(--text);
+    }
+
+    .mode-desc {
+        font-size: 11px;
+        color: var(--text-muted);
+    }
+
+    .play-grid {
+        display: grid;
+        grid-template-columns: 1fr 1fr;
+        gap: 24px;
+    }
+    .card-title {
+        font-family: "Bebas Neue", sans-serif;
+        font-size: 1.5rem;
+        letter-spacing: 0.05em;
+        margin-bottom: 8px;
+    }
+    .card-desc {
+        color: var(--text-muted);
+        font-size: 13px;
+        margin-bottom: 0;
+        line-height: 1.6;
+    }
+    .created-link-box {
+        margin-bottom: 20px;
+    }
+    .link-row {
+        display: flex;
+        gap: 8px;
+    }
+    .link-row input {
+        flex: 1;
+        font-size: 12px;
+    }
+    .qr-section {
+        margin-bottom: 8px;
+    }
+    .qr-box {
+        background: white;
+        border-radius: 4px;
+        display: inline-block;
+        padding: 12px;
+    }
+    .qr-box img {
+        display: block;
+        width: 160px;
+        height: 160px;
+    }
+    .error-msg {
+        color: var(--red);
+        font-size: 12px;
+        margin-bottom: 12px;
+        padding: 8px 12px;
+        background: rgba(224, 110, 110, 0.1);
+        border-radius: 3px;
+        border: 1px solid rgba(224, 110, 110, 0.2);
+    }
+    @media (max-width: 640px) {
+        .play-grid {
+            grid-template-columns: 1fr;
+        }
+        .mode-tabs {
+            flex-direction: column;
+        }
+    }
 </style>