Put the camera login where the picture would be

A camera that can't log in has no picture to show, so the form stands in
for the snapshot instead of appearing under it, with the status badge
above it so the card still says what is wrong. Saving brings the picture
back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2026-09-20 07:27:00 -05:00
parent f63fb664dd
commit 6a663a5d82
2 changed files with 41 additions and 18 deletions

View File

@ -258,6 +258,20 @@ describe("CameraCard", () => {
expect(JSON.parse(String(init?.body))).toEqual({ username: "saved-user", password: "pw" });
});
it("stands in for the picture, rather than appearing below it", async () => {
stubFetch({
[`GET ${base}/info`]: () => json({ error: "Wrong login", code: "auth" }, 401),
[`GET ${base}/credentials`]: () => json(noLogin),
});
const { container } = renderWithQuery(<CameraCard cam={cam} intervalMs={1000} />);
expect(await screen.findByText("Camera login")).toBeTruthy();
expect(screen.queryByRole("img", { name: "Porch" })).toBeNull();
expect(screen.queryByRole("link", { name: "Pop out Porch" })).toBeNull();
// The status still says what is wrong, where the picture would have been.
expect(container.querySelector("[data-status='auth']")).toBeTruthy();
});
it("keeps a username the user typed over the saved one", async () => {
stubFetch({
[`GET ${base}/info`]: () => json({ error: "Wrong login", code: "auth" }, 401),

View File

@ -169,17 +169,35 @@ export default function CameraCard({
const showSetup = problem?.kind === "inactive";
const showLogin = !showSetup && (problem?.kind === "auth" || editingLogin);
const status = cameraStatus({
problem: problem?.kind ?? null,
hasFrame: snapshot.data !== undefined,
});
return (
<li className="flex flex-col gap-3 rounded-lg border border-zinc-200 p-4 dark:border-zinc-800">
<Snapshot
cam={cam}
frame={snapshot.data}
paused={problem !== null || !info.isSuccess}
poppedOut={poppedOut}
intervalMs={intervalMs}
aspect={aspect}
status={cameraStatus({ problem: problem?.kind ?? null, hasFrame: snapshot.data !== undefined })}
/>
{/* A camera that can't log in has no picture to show, so the form takes its place. */}
{showLogin ? (
<div className="flex flex-col gap-2 rounded bg-zinc-100 p-3 dark:bg-zinc-900">
<StatusBadge status={status} className="self-start" />
<CredentialsForm
cam={cam}
reason={problem?.kind === "auth" ? problem.message : undefined}
onSaved={() => setEditingLogin(false)}
onCancel={problem?.kind === "auth" ? undefined : () => setEditingLogin(false)}
/>
</div>
) : (
<Snapshot
cam={cam}
frame={snapshot.data}
paused={problem !== null || !info.isSuccess}
poppedOut={poppedOut}
intervalMs={intervalMs}
aspect={aspect}
status={status}
/>
)}
<div className="flex items-center justify-between gap-2">
<Link href={`/cameras/${cam.id}`} className="font-medium hover:underline">
@ -189,15 +207,6 @@ export default function CameraCard({
{showSetup && <SetupPanel cam={cam} onCheckAgain={retry} />}
{showLogin && (
<CredentialsForm
cam={cam}
reason={problem?.kind === "auth" ? problem.message : undefined}
onSaved={() => setEditingLogin(false)}
onCancel={problem?.kind === "auth" ? undefined : () => setEditingLogin(false)}
/>
)}
{problem?.kind === "signed-out" && (
<div className="flex items-center justify-between gap-2 text-sm text-red-600">
<span className="inline-flex items-center gap-1.5">