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 && ( - + + +