import { useState } from "react"; import { Link, useLoaderData, useNavigate } from "react-router"; import { startAuthentication, startRegistration } from "@simplewebauthn/browser"; import type { Route } from "./+types/login"; import { ensureSetupToken, isLoggedIn } from "~/lib/auth.server"; export async function loader({ request }: Route.LoaderArgs) { ensureSetupToken(); const url = new URL(request.url); const next = url.searchParams.get("next"); return { loggedIn: isLoggedIn(request), token: url.searchParams.get("token"), // Only allow same-site relative redirects next: next && /^\/(?![/\\])/.test(next) ? next : "/favorites", }; } async function postJson(path: string, body: unknown) { const res = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const data = await res.json(); if (!res.ok || data.ok === false) throw new Error(data.error ?? `HTTP ${res.status}`); return data; } export default function Login() { const { loggedIn, token, next } = useLoaderData(); const navigate = useNavigate(); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [label, setLabel] = useState(""); const canRegister = loggedIn || !!token; async function run(fn: () => Promise) { setBusy(true); setError(null); try { await fn(); navigate(next); } catch (e) { const err = e as Error; setError(err.name === "NotAllowedError" ? "キャンセルされました" : err.message); } finally { setBusy(false); } } const login = () => run(async () => { const optionsJSON = await postJson("/api/auth/login-options", {}); const response = await startAuthentication({ optionsJSON }); await postJson("/api/auth/login-verify", { response }); }); const register = () => run(async () => { const optionsJSON = await postJson("/api/auth/register-options", { token }); const response = await startRegistration({ optionsJSON }); await postJson("/api/auth/register-verify", { token, response, label: label || null }); }); return (
🎸 ライブに行くしかない {!loggedIn && ( )} {loggedIn && !token && (

ログイン済みです。{" "} お気に入りへ

)} {canRegister && (

{loggedIn ? "このデバイスのパスキーを追加登録" : "パスキーを新規登録"}

setLabel(e.target.value)} placeholder="デバイス名(例: iPhone)" className="rounded-md bg-gray-800 border border-gray-700 px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500" />
)} {error &&

{error}

}
); }