Ask for the name in a dialog instead of a field that is always there
Create in both overviews now opens a dialog, so the toolbar is a search box and one button. The specs and the capture script open it first. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7LwYgJfpkbLCTeiAf8U4A
This commit is contained in:
@@ -86,6 +86,7 @@ async function captureDashboards(page, dir) {
|
||||
await page.goto(`${APP_URL}/dashboards`, { waitUntil: "networkidle" })
|
||||
|
||||
if (!(await page.getByTestId("dashboard-card").count())) {
|
||||
await page.getByTestId("new-dashboard").click()
|
||||
await page.getByTestId("new-dashboard-name").fill("panel")
|
||||
await page.getByTestId("create-dashboard").click()
|
||||
await page.waitForURL(/\/dashboards\/.+/, { timeout: 15000 })
|
||||
@@ -113,6 +114,7 @@ async function captureFlows(page, dir) {
|
||||
if (await page.getByTestId("flow-card").count()) {
|
||||
await page.getByTestId("flow-card").first().click()
|
||||
} else {
|
||||
await page.getByTestId("new-flow").click()
|
||||
await page.getByTestId("new-flow-name").fill("first_flow")
|
||||
await page.getByTestId("create-flow").click()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,15 @@ import {
|
||||
dashboardsQueryOptions,
|
||||
} from "@/components/Dashboard/queries"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import useCustomToast from "@/hooks/useCustomToast"
|
||||
import { handleError } from "@/utils"
|
||||
@@ -24,12 +33,14 @@ function Dashboards() {
|
||||
const { showErrorToast } = useCustomToast()
|
||||
const [name, setName] = useState("")
|
||||
const [search, setSearch] = useState("")
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
|
||||
const create = useMutation({
|
||||
mutationFn: (dashboard: string) =>
|
||||
DashboardsService.createDashboard({ name: dashboard }),
|
||||
onSuccess: (created) => {
|
||||
queryClient.invalidateQueries({ queryKey: dashboardKeys.all })
|
||||
setDialogOpen(false)
|
||||
navigate({
|
||||
to: "/dashboards/$name",
|
||||
params: { name: created.name },
|
||||
@@ -67,37 +78,54 @@ function Dashboards() {
|
||||
data-testid="search-dashboards"
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
/>
|
||||
<form
|
||||
className="flex items-center gap-2"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
if (slug) create.mutate(slug)
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
value={name}
|
||||
placeholder="New dashboard"
|
||||
aria-label="New dashboard name"
|
||||
data-testid="new-dashboard-name"
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="secondary"
|
||||
disabled={!slug || create.isPending}
|
||||
data-testid="create-dashboard"
|
||||
>
|
||||
<Plus />
|
||||
Create
|
||||
</Button>
|
||||
</form>
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary" data-testid="new-dashboard">
|
||||
<Plus />
|
||||
Create
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<form
|
||||
className="grid gap-4"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
if (slug) create.mutate(slug)
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>New dashboard</DialogTitle>
|
||||
<DialogDescription>
|
||||
Name it after the panel it will hang on, or what it shows.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
value={name}
|
||||
placeholder="kitchen"
|
||||
aria-label="New dashboard name"
|
||||
autoComplete="off"
|
||||
data-testid="new-dashboard-name"
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={!slug || create.isPending}
|
||||
data-testid="create-dashboard"
|
||||
>
|
||||
Create dashboard
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
{dashboards.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{needle
|
||||
? "No dashboard matches that."
|
||||
: "No dashboards yet. Name one above to start."}
|
||||
: "No dashboards yet. Create one to start."}
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
|
||||
@@ -6,6 +6,15 @@ import { useState } from "react"
|
||||
import { FlowsService } from "@/client"
|
||||
import { flowKeys, flowsQueryOptions } from "@/components/Flow/queries"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import useCustomToast from "@/hooks/useCustomToast"
|
||||
import { handleError } from "@/utils"
|
||||
@@ -25,6 +34,7 @@ function Flows() {
|
||||
const { showErrorToast } = useCustomToast()
|
||||
const [name, setName] = useState("")
|
||||
const [search, setSearch] = useState("")
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
|
||||
const create = useMutation({
|
||||
mutationFn: (flow: string) =>
|
||||
@@ -34,6 +44,7 @@ function Flows() {
|
||||
}),
|
||||
onSuccess: (_saved, flow) => {
|
||||
queryClient.invalidateQueries({ queryKey: flowKeys.all })
|
||||
setDialogOpen(false)
|
||||
navigate({ to: "/flows/$flowName", params: { flowName: flow } })
|
||||
},
|
||||
onError: handleError.bind(showErrorToast),
|
||||
@@ -67,37 +78,54 @@ function Flows() {
|
||||
data-testid="search-flows"
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
/>
|
||||
<form
|
||||
className="flex items-center gap-2"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
if (NAME.test(slug)) create.mutate(slug)
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
value={name}
|
||||
placeholder="New flow"
|
||||
aria-label="New flow name"
|
||||
data-testid="new-flow-name"
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="secondary"
|
||||
disabled={!NAME.test(slug) || create.isPending}
|
||||
data-testid="create-flow"
|
||||
>
|
||||
<Plus />
|
||||
Create
|
||||
</Button>
|
||||
</form>
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary" data-testid="new-flow">
|
||||
<Plus />
|
||||
Create
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<form
|
||||
className="grid gap-4"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
if (NAME.test(slug)) create.mutate(slug)
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>New flow</DialogTitle>
|
||||
<DialogDescription>
|
||||
Flows are small on purpose. Name this one after what it does.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
value={name}
|
||||
placeholder="heating"
|
||||
aria-label="New flow name"
|
||||
autoComplete="off"
|
||||
data-testid="new-flow-name"
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={!NAME.test(slug) || create.isPending}
|
||||
data-testid="create-flow"
|
||||
>
|
||||
Create flow
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
{flows.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{needle
|
||||
? "No flow matches that."
|
||||
: "No flows yet. Name one above to start."}
|
||||
: "No flows yet. Create one to start."}
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
|
||||
@@ -19,6 +19,7 @@ test.afterAll(async ({ browser }) => {
|
||||
|
||||
test("a draft stays off the engine until it is published", async ({ page }) => {
|
||||
await page.goto("/flows")
|
||||
await page.getByTestId("new-flow").click()
|
||||
await page.getByTestId("new-flow-name").fill(flowName)
|
||||
await page.getByTestId("create-flow").click()
|
||||
await page.waitForURL(`/flows/${flowName}`)
|
||||
|
||||
@@ -73,6 +73,7 @@ test("a flow can be created, wired up, and comes back after a reload", async ({
|
||||
await page.goto("/flows")
|
||||
|
||||
// Create a flow of our own so the test does not lean on existing data.
|
||||
await page.getByTestId("new-flow").click()
|
||||
await page.getByTestId("new-flow-name").fill(flowName)
|
||||
await page.getByTestId("create-flow").click()
|
||||
await page.waitForURL(`/flows/${flowName}`)
|
||||
|
||||
Reference in New Issue
Block a user