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:
parent
f63fb664dd
commit
6a663a5d82
@ -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),
|
||||
|
||||
@ -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">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user