diff --git a/app/routes/authenticated/_layout.tsx b/app/routes/authenticated/_layout.tsx
index 959568c..9cc5309 100644
--- a/app/routes/authenticated/_layout.tsx
+++ b/app/routes/authenticated/_layout.tsx
@@ -1,7 +1,7 @@
import { Dashboard, Logout, RateReview } from "@mui/icons-material";
import { Box } from "@mui/material";
import { styled } from "@mui/material/styles";
-import { Link, NavLink, Outlet } from "react-router";
+import { Form, NavLink, Outlet } from "react-router";
import phlasklogo from "~/assets/PHLASK_v2.svg";
import { ThemeToggle } from "~/components/ThemeToggle";
import { WaveDivider } from "~/components/WaveDivider";
@@ -168,30 +168,37 @@ export default function DashboardLayout() {
- ({
- display: "flex",
- alignItems: "center",
- gap: 1.25,
- borderRadius: 3,
- px: 2,
- py: 1.25,
- fontSize: "0.875rem",
- fontWeight: 500,
- textDecoration: "none",
- color: navy[600],
- transition: "background-color 0.2s ease",
- "&:hover": { bgcolor: brand[50] },
- ...theme.applyStyles("dark", {
- color: `${brand[100]}cc`,
- "&:hover": { bgcolor: navy[800] },
- }),
- })}
- >
-
- Logout
+
+ ({
+ display: "flex",
+ width: "100%",
+ alignItems: "center",
+ gap: 1.25,
+ borderRadius: 3,
+ border: "none",
+ bgcolor: "transparent",
+ px: 2,
+ py: 1.25,
+ fontSize: "0.875rem",
+ fontFamily: "inherit",
+ fontWeight: 500,
+ textAlign: "left",
+ cursor: "pointer",
+ color: navy[600],
+ transition: "background-color 0.2s ease",
+ "&:hover": { bgcolor: brand[50] },
+ ...theme.applyStyles("dark", {
+ color: `${brand[100]}cc`,
+ "&:hover": { bgcolor: navy[800] },
+ }),
+ })}
+ >
+
+ Logout
+
diff --git a/app/routes/authenticated/logout.tsx b/app/routes/authenticated/logout.tsx
index e2d000d..e0ea3db 100644
--- a/app/routes/authenticated/logout.tsx
+++ b/app/routes/authenticated/logout.tsx
@@ -1,10 +1,18 @@
-import { type LoaderFunction, redirect } from "react-router";
+import {
+ type ActionFunction,
+ type LoaderFunction,
+ redirect,
+} from "react-router";
import { getDatabaseClient } from "~/api/client.server";
-export const loader: LoaderFunction = async ({ request }) => {
- const { client } = getDatabaseClient(request);
+// Signing out is a mutation, so it belongs in the action (POST), not a GET
+// loader. A direct GET to this route just bounces back to the dashboard.
+export const loader: LoaderFunction = () => redirect("/");
+
+export const action: ActionFunction = async ({ request }) => {
+ const { client, headers } = getDatabaseClient(request);
await client.auth.signOut();
- return redirect("/auth");
+ return redirect("/auth", { headers });
};
diff --git a/app/routes/authenticated/reviews/detail.tsx b/app/routes/authenticated/reviews/detail.tsx
index 5669d93..65d6544 100644
--- a/app/routes/authenticated/reviews/detail.tsx
+++ b/app/routes/authenticated/reviews/detail.tsx
@@ -16,7 +16,7 @@ import {
TextField,
Typography,
} from "@mui/material";
-import { useEffect, useState } from "react";
+import { type SubmitEvent, useEffect, useState } from "react";
import {
type ActionFunction,
data,
@@ -282,7 +282,13 @@ const ReviewDetail = () => {
value: EditableValues["bathroom"][K],
) => setValues((v) => ({ ...v, bathroom: { ...v.bathroom, [key]: value } }));
- const handleSave = () => {
+ const handleSave = (event: SubmitEvent) => {
+ // The payload is a nested object (not flat form fields), so it's sent as
+ // JSON via the fetcher rather than a native form-encoded submission —
+ // still routed through a real below for the submit
+ // semantics (Enter-to-submit, pending state, progressive enhancement).
+ event.preventDefault();
+
const payload: Partial = {
name: values.name || null,
resource_type: values.resource_type,
@@ -776,14 +782,16 @@ const ReviewDetail = () => {
{isPending && (
-
+
+
+