aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-03-10 03:01:44 +0900
committernsfisis <nsfisis@gmail.com>2025-03-10 03:33:22 +0900
commita3a2bc9dc1c339e26cf93e3b510f280acaab5027 (patch)
tree91537e7a9bf9d3edf462e278b0415f17b39192d7 /frontend/app
parentc44b3383a7e55553cc95bba7bd5574f71c2e3406 (diff)
downloadphperkaigi-2025-albatross-a3a2bc9dc1c339e26cf93e3b510f280acaab5027.tar.gz
phperkaigi-2025-albatross-a3a2bc9dc1c339e26cf93e3b510f280acaab5027.tar.zst
phperkaigi-2025-albatross-a3a2bc9dc1c339e26cf93e3b510f280acaab5027.zip
feat(fontend): migrate from Remix to React Router
Diffstat (limited to 'frontend/app')
-rw-r--r--frontend/app/.server/auth.ts59
-rw-r--r--frontend/app/.server/cookie.ts2
-rw-r--r--frontend/app/.server/session.ts2
-rw-r--r--frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx2
-rw-r--r--frontend/app/components/NavigateLink.tsx2
-rw-r--r--frontend/app/entry.client.tsx12
-rw-r--r--frontend/app/entry.server.tsx116
-rw-r--r--frontend/app/root.tsx10
-rw-r--r--frontend/app/routes.ts4
-rw-r--r--frontend/app/routes/_index.tsx2
-rw-r--r--frontend/app/routes/dashboard.tsx4
-rw-r--r--frontend/app/routes/golf.$gameId.play.tsx4
-rw-r--r--frontend/app/routes/golf.$gameId.watch.tsx4
-rw-r--r--frontend/app/routes/login.tsx8
-rw-r--r--frontend/app/routes/logout.tsx2
15 files changed, 54 insertions, 179 deletions
diff --git a/frontend/app/.server/auth.ts b/frontend/app/.server/auth.ts
index 386eb70..cbeb141 100644
--- a/frontend/app/.server/auth.ts
+++ b/frontend/app/.server/auth.ts
@@ -1,6 +1,5 @@
-import { redirect } from "@remix-run/node";
-import type { Session } from "@remix-run/server-runtime";
import { jwtDecode } from "jwt-decode";
+import { redirect } from "react-router";
import { Authenticator } from "remix-auth";
import { FormStrategy } from "remix-auth-form";
import { apiPostLogin } from "../api/client";
@@ -8,7 +7,7 @@ import { components } from "../api/schema";
import { createUnstructuredCookie } from "./cookie";
import { cookieOptions, sessionStorage } from "./session";
-const authenticator = new Authenticator<string>(sessionStorage);
+const authenticator = new Authenticator<string>();
authenticator.use(
new FormStrategy(async ({ form }) => {
@@ -29,14 +28,12 @@ const tokenCookie = createUnstructuredCookie("albatross_token", cookieOptions);
* @throws Error on failure
*/
export async function login(request: Request): Promise<never> {
- const jwt = await authenticator.authenticate("default", request, {
- throwOnError: true,
- });
+ const jwt = await authenticator.authenticate("default", request);
const session = await sessionStorage.getSession(
request.headers.get("cookie"),
);
- session.set(authenticator.sessionKey, jwt);
+ session.set("user", jwt);
throw redirect("/dashboard", {
headers: [
@@ -46,34 +43,42 @@ export async function login(request: Request): Promise<never> {
});
}
-export async function logout(request: Request | Session): Promise<never> {
- try {
- return await authenticator.logout(request, { redirectTo: "/" });
- } catch (response) {
- if (response instanceof Response) {
- response.headers.append(
+export async function logout(request: Request): Promise<never> {
+ const session = await sessionStorage.getSession(
+ request.headers.get("cookie"),
+ );
+ throw redirect("/", {
+ headers: [
+ ["Set-Cookie", await sessionStorage.destroySession(session)],
+ [
"Set-Cookie",
await tokenCookie.serialize("", { maxAge: 0, expires: new Date(0) }),
- );
- }
- throw response;
- }
+ ],
+ ],
+ });
}
export async function ensureUserLoggedIn(
- request: Request | Session,
+ request: Request,
): Promise<{ user: User; token: string }> {
- const token = await authenticator.isAuthenticated(request, {
- failureRedirect: "/login",
- });
+ const session = await sessionStorage.getSession(
+ request.headers.get("cookie"),
+ );
+ const token = session.get("user");
+ if (!token) {
+ throw redirect("/login");
+ }
const user = jwtDecode<User>(token);
return { user, token };
}
-export async function ensureUserNotLoggedIn(
- request: Request | Session,
-): Promise<null> {
- return await authenticator.isAuthenticated(request, {
- successRedirect: "/dashboard",
- });
+export async function ensureUserNotLoggedIn(request: Request): Promise<null> {
+ const session = await sessionStorage.getSession(
+ request.headers.get("cookie"),
+ );
+ const token = session.get("user");
+ if (token) {
+ throw redirect("/dashboard");
+ }
+ return null;
}
diff --git a/frontend/app/.server/cookie.ts b/frontend/app/.server/cookie.ts
index 4552081..c8365eb 100644
--- a/frontend/app/.server/cookie.ts
+++ b/frontend/app/.server/cookie.ts
@@ -1,5 +1,5 @@
-import { Cookie, CookieOptions } from "@remix-run/server-runtime";
import { parse as parseCookie, serialize as serializeCookie } from "cookie";
+import { Cookie, CookieOptions } from "react-router";
// Remix's createCookie() returns "structured" cookies, which are cookies that hold a JSON-encoded object.
// This is not suitable for interoperation with other systems that expect a simple string value.
diff --git a/frontend/app/.server/session.ts b/frontend/app/.server/session.ts
index 4730305..2d9a652 100644
--- a/frontend/app/.server/session.ts
+++ b/frontend/app/.server/session.ts
@@ -1,4 +1,4 @@
-import { createCookieSessionStorage } from "@remix-run/node";
+import { createCookieSessionStorage } from "react-router";
export const cookieOptions = {
sameSite: "lax" as const,
diff --git a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
index 76ffcb8..ec92556 100644
--- a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
+++ b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
@@ -1,6 +1,6 @@
-import { Link } from "@remix-run/react";
import { useAtomValue } from "jotai";
import React, { useRef } from "react";
+import { Link } from "react-router";
import SubmitButton from "../../components/SubmitButton";
import {
gamingLeftTimeSecondsAtom,
diff --git a/frontend/app/components/NavigateLink.tsx b/frontend/app/components/NavigateLink.tsx
index 95c3bcf..c4ee7aa 100644
--- a/frontend/app/components/NavigateLink.tsx
+++ b/frontend/app/components/NavigateLink.tsx
@@ -1,4 +1,4 @@
-import { Link, LinkProps } from "@remix-run/react";
+import { Link, LinkProps } from "react-router";
export default function NavigateLink(props: LinkProps) {
return (
diff --git a/frontend/app/entry.client.tsx b/frontend/app/entry.client.tsx
deleted file mode 100644
index 92d1585..0000000
--- a/frontend/app/entry.client.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { RemixBrowser } from "@remix-run/react";
-import { StrictMode, startTransition } from "react";
-import { hydrateRoot } from "react-dom/client";
-
-startTransition(() => {
- hydrateRoot(
- document,
- <StrictMode>
- <RemixBrowser />
- </StrictMode>,
- );
-});
diff --git a/frontend/app/entry.server.tsx b/frontend/app/entry.server.tsx
deleted file mode 100644
index 6234421..0000000
--- a/frontend/app/entry.server.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { PassThrough } from "node:stream";
-
-import type { EntryContext } from "@remix-run/node";
-import { createReadableStreamFromReadable } from "@remix-run/node";
-import { RemixServer } from "@remix-run/react";
-import { isbot } from "isbot";
-import { renderToPipeableStream } from "react-dom/server";
-
-export const streamTimeout = 5000;
-
-export default function handleRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
-) {
- return isbot(request.headers.get("user-agent") || "")
- ? handleBotRequest(
- request,
- responseStatusCode,
- responseHeaders,
- remixContext,
- )
- : handleBrowserRequest(
- request,
- responseStatusCode,
- responseHeaders,
- remixContext,
- );
-}
-
-function handleBotRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
-) {
- return new Promise((resolve, reject) => {
- let shellRendered = false;
- const { pipe, abort } = renderToPipeableStream(
- <RemixServer context={remixContext} url={request.url} />,
- {
- onAllReady() {
- shellRendered = true;
- const body = new PassThrough();
- const stream = createReadableStreamFromReadable(body);
-
- responseHeaders.set("Content-Type", "text/html");
-
- resolve(
- new Response(stream, {
- headers: responseHeaders,
- status: responseStatusCode,
- }),
- );
-
- pipe(body);
- },
- onShellError(error: unknown) {
- reject(error);
- },
- onError(error: unknown) {
- responseStatusCode = 500;
- if (shellRendered) {
- console.error(error);
- }
- },
- },
- );
-
- +setTimeout(abort, streamTimeout + 1000);
- });
-}
-
-function handleBrowserRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
-) {
- return new Promise((resolve, reject) => {
- let shellRendered = false;
- const { pipe, abort } = renderToPipeableStream(
- <RemixServer context={remixContext} url={request.url} />,
- {
- onShellReady() {
- shellRendered = true;
- const body = new PassThrough();
- const stream = createReadableStreamFromReadable(body);
-
- responseHeaders.set("Content-Type", "text/html");
-
- resolve(
- new Response(stream, {
- headers: responseHeaders,
- status: responseStatusCode,
- }),
- );
-
- pipe(body);
- },
- onShellError(error: unknown) {
- reject(error);
- },
- onError(error: unknown) {
- responseStatusCode = 500;
- if (shellRendered) {
- console.error(error);
- }
- },
- },
- );
-
- setTimeout(abort, streamTimeout + 1000);
- });
-}
diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx
index 20aa910..5721a2b 100644
--- a/frontend/app/root.tsx
+++ b/frontend/app/root.tsx
@@ -1,13 +1,7 @@
import { config } from "@fortawesome/fontawesome-svg-core";
import "@fortawesome/fontawesome-svg-core/styles.css";
-import type { LinksFunction } from "@remix-run/node";
-import {
- Links,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
-} from "@remix-run/react";
+import type { LinksFunction } from "react-router";
+import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
import "./tailwind.css";
import "./shiki.css";
diff --git a/frontend/app/routes.ts b/frontend/app/routes.ts
new file mode 100644
index 0000000..4c05936
--- /dev/null
+++ b/frontend/app/routes.ts
@@ -0,0 +1,4 @@
+import { type RouteConfig } from "@react-router/dev/routes";
+import { flatRoutes } from "@react-router/fs-routes";
+
+export default flatRoutes() satisfies RouteConfig;
diff --git a/frontend/app/routes/_index.tsx b/frontend/app/routes/_index.tsx
index 06cca78..651a61c 100644
--- a/frontend/app/routes/_index.tsx
+++ b/frontend/app/routes/_index.tsx
@@ -1,4 +1,4 @@
-import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
+import type { LoaderFunctionArgs, MetaFunction } from "react-router";
import { ensureUserNotLoggedIn } from "../.server/auth";
import BorderedContainer from "../components/BorderedContainer";
import NavigateLink from "../components/NavigateLink";
diff --git a/frontend/app/routes/dashboard.tsx b/frontend/app/routes/dashboard.tsx
index ab170b5..78ed531 100644
--- a/frontend/app/routes/dashboard.tsx
+++ b/frontend/app/routes/dashboard.tsx
@@ -1,5 +1,5 @@
-import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
-import { Form, useLoaderData } from "@remix-run/react";
+import type { LoaderFunctionArgs, MetaFunction } from "react-router";
+import { Form, useLoaderData } from "react-router";
import { ensureUserLoggedIn } from "../.server/auth";
import { apiGetGames } from "../api/client";
import BorderedContainer from "../components/BorderedContainer";
diff --git a/frontend/app/routes/golf.$gameId.play.tsx b/frontend/app/routes/golf.$gameId.play.tsx
index e523187..dc8eb38 100644
--- a/frontend/app/routes/golf.$gameId.play.tsx
+++ b/frontend/app/routes/golf.$gameId.play.tsx
@@ -1,6 +1,6 @@
-import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
-import { useLoaderData } from "@remix-run/react";
import { useHydrateAtoms } from "jotai/utils";
+import type { LoaderFunctionArgs, MetaFunction } from "react-router";
+import { useLoaderData } from "react-router";
import { ensureUserLoggedIn } from "../.server/auth";
import {
ApiAuthTokenContext,
diff --git a/frontend/app/routes/golf.$gameId.watch.tsx b/frontend/app/routes/golf.$gameId.watch.tsx
index fed06aa..674abc4 100644
--- a/frontend/app/routes/golf.$gameId.watch.tsx
+++ b/frontend/app/routes/golf.$gameId.watch.tsx
@@ -1,6 +1,6 @@
-import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
-import { useLoaderData } from "@remix-run/react";
import { useHydrateAtoms } from "jotai/utils";
+import type { LoaderFunctionArgs, MetaFunction } from "react-router";
+import { useLoaderData } from "react-router";
import { ensureUserLoggedIn } from "../.server/auth";
import {
ApiAuthTokenContext,
diff --git a/frontend/app/routes/login.tsx b/frontend/app/routes/login.tsx
index 5ca6217..dd5b9e7 100644
--- a/frontend/app/routes/login.tsx
+++ b/frontend/app/routes/login.tsx
@@ -2,8 +2,8 @@ import type {
ActionFunctionArgs,
LoaderFunctionArgs,
MetaFunction,
-} from "@remix-run/node";
-import { Form, json, useActionData } from "@remix-run/react";
+} from "react-router";
+import { Form, data, useActionData } from "react-router";
import { ensureUserNotLoggedIn, login } from "../.server/auth";
import BorderedContainer from "../components/BorderedContainer";
import InputText from "../components/InputText";
@@ -22,7 +22,7 @@ export async function action({ request }: ActionFunctionArgs) {
const username = String(formData.get("username"));
const password = String(formData.get("password"));
if (username === "" || password === "") {
- return json(
+ return data(
{
message: "ユーザー名またはパスワードが誤っています",
errors: {
@@ -40,7 +40,7 @@ export async function action({ request }: ActionFunctionArgs) {
await login(request);
} catch (error) {
if (error instanceof Error) {
- return json(
+ return data(
{
message: error.message,
errors: {
diff --git a/frontend/app/routes/logout.tsx b/frontend/app/routes/logout.tsx
index d697be2..9616b4d 100644
--- a/frontend/app/routes/logout.tsx
+++ b/frontend/app/routes/logout.tsx
@@ -1,4 +1,4 @@
-import type { ActionFunctionArgs } from "@remix-run/node";
+import type { ActionFunctionArgs } from "react-router";
import { logout } from "../.server/auth";
export async function action({ request }: ActionFunctionArgs) {