< Summary

Information
Class: locations.rooms.new.tsx
Assembly: app.routes
File(s): /home/runner/work/ClutterStock/ClutterStock/frontend/app/routes/locations.rooms.new.tsx
Tag: 58_25416222083
Line coverage
0%
Covered lines: 0
Uncovered lines: 28
Coverable lines: 28
Total lines: 85
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 22
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/ClutterStock/ClutterStock/frontend/app/routes/locations.rooms.new.tsx

#LineLine coverage
 1import { redirect } from "react-router";
 2import type { Route } from "./+types/locations.rooms.new";
 3import { getLocation, createRoom } from "~/api/client";
 4import { Breadcrumb } from "~/components/breadcrumb";
 5import { RoomForm } from "~/components/rooms";
 6import { tryApi } from "~/lib/action-helpers.server";
 7import { useToastFromActionData } from "~/lib/toasts";
 8import { pushFlash } from "~/lib/toasts.server";
 9
 010export async function loader({ params, request }: Route.LoaderArgs) {
 011  const locationId = Number(params.id);
 012  if (Number.isNaN(locationId)) throw new Response("Not found", { status: 404 });
 013  const location = await getLocation(locationId, request);
 014  if (!location) throw new Response("Not found", { status: 404 });
 015  return { location };
 16}
 17
 018export async function action({ request, params }: Route.ActionArgs) {
 019  const locationId = Number(params.id);
 020  if (Number.isNaN(locationId)) throw new Response("Not found", { status: 404 });
 021  const formData = await request.formData();
 022  const name = formData.get("name");
 023  const description = formData.get("description");
 024  if (typeof name !== "string" || !name.trim()) {
 025    return { ok: false as const, error: "Name is required" };
 26  }
 027  const result = await tryApi(() =>
 28    createRoom(
 29      {
 30        locationId,
 31        name: name.trim(),
 32        description:
 33          typeof description === "string" && description.trim()
 34            ? description.trim()
 35            : undefined,
 36      },
 37      request,
 38    ),
 39  );
 040  if (!result.ok) return result;
 041  await pushFlash(request, {
 42    kind: "success",
 43    message: `Room "${result.data.name}" created`,
 44  });
 045  return redirect(`/locations/${locationId}/rooms`);
 46}
 47
 048export function meta({ loaderData }: Route.MetaArgs) {
 049  const name = loaderData?.location?.name ?? "Location";
 050  return [{ title: `Add room · ${name} | ClutterStock` }];
 51}
 52
 053export default function LocationsRoomsNew({
 54  loaderData,
 55  actionData,
 56}: Route.ComponentProps) {
 057  useToastFromActionData(actionData);
 058  const location = loaderData.location;
 059  const locationId = location.id!;
 60  const error =
 061    actionData && "error" in actionData ? actionData.error : undefined;
 62  const fieldErrors =
 063    actionData && "fieldErrors" in actionData ? actionData.fieldErrors : undefined;
 64
 065  return (
 66    <>
 67      <Breadcrumb
 68        crumbs={[
 69          { label: "Locations", to: "/locations" },
 70          { label: location.name ?? "Location", to: `/locations/${locationId}/edit` },
 71          { label: "Rooms", to: `/locations/${locationId}/rooms` },
 72          { label: "Add room" },
 73        ]}
 74      />
 75      <RoomForm
 76        title="Add room"
 77        submitLabel="Create"
 78        error={error}
 79        fieldErrors={fieldErrors}
 80        locationId={locationId}
 81        cancelTo={`/locations/${locationId}/rooms`}
 82      />
 83    </>
 84  );
 85}