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" })
|
await page.goto(`${APP_URL}/dashboards`, { waitUntil: "networkidle" })
|
||||||
|
|
||||||
if (!(await page.getByTestId("dashboard-card").count())) {
|
if (!(await page.getByTestId("dashboard-card").count())) {
|
||||||
|
await page.getByTestId("new-dashboard").click()
|
||||||
await page.getByTestId("new-dashboard-name").fill("panel")
|
await page.getByTestId("new-dashboard-name").fill("panel")
|
||||||
await page.getByTestId("create-dashboard").click()
|
await page.getByTestId("create-dashboard").click()
|
||||||
await page.waitForURL(/\/dashboards\/.+/, { timeout: 15000 })
|
await page.waitForURL(/\/dashboards\/.+/, { timeout: 15000 })
|
||||||
@@ -113,6 +114,7 @@ async function captureFlows(page, dir) {
|
|||||||
if (await page.getByTestId("flow-card").count()) {
|
if (await page.getByTestId("flow-card").count()) {
|
||||||
await page.getByTestId("flow-card").first().click()
|
await page.getByTestId("flow-card").first().click()
|
||||||
} else {
|
} else {
|
||||||
|
await page.getByTestId("new-flow").click()
|
||||||
await page.getByTestId("new-flow-name").fill("first_flow")
|
await page.getByTestId("new-flow-name").fill("first_flow")
|
||||||
await page.getByTestId("create-flow").click()
|
await page.getByTestId("create-flow").click()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ import {
|
|||||||
dashboardsQueryOptions,
|
dashboardsQueryOptions,
|
||||||
} from "@/components/Dashboard/queries"
|
} from "@/components/Dashboard/queries"
|
||||||
import { Button } from "@/components/ui/button"
|
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 { Input } from "@/components/ui/input"
|
||||||
import useCustomToast from "@/hooks/useCustomToast"
|
import useCustomToast from "@/hooks/useCustomToast"
|
||||||
import { handleError } from "@/utils"
|
import { handleError } from "@/utils"
|
||||||
@@ -24,12 +33,14 @@ function Dashboards() {
|
|||||||
const { showErrorToast } = useCustomToast()
|
const { showErrorToast } = useCustomToast()
|
||||||
const [name, setName] = useState("")
|
const [name, setName] = useState("")
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
|
const [dialogOpen, setDialogOpen] = useState(false)
|
||||||
|
|
||||||
const create = useMutation({
|
const create = useMutation({
|
||||||
mutationFn: (dashboard: string) =>
|
mutationFn: (dashboard: string) =>
|
||||||
DashboardsService.createDashboard({ name: dashboard }),
|
DashboardsService.createDashboard({ name: dashboard }),
|
||||||
onSuccess: (created) => {
|
onSuccess: (created) => {
|
||||||
queryClient.invalidateQueries({ queryKey: dashboardKeys.all })
|
queryClient.invalidateQueries({ queryKey: dashboardKeys.all })
|
||||||
|
setDialogOpen(false)
|
||||||
navigate({
|
navigate({
|
||||||
to: "/dashboards/$name",
|
to: "/dashboards/$name",
|
||||||
params: { name: created.name },
|
params: { name: created.name },
|
||||||
@@ -67,37 +78,54 @@ function Dashboards() {
|
|||||||
data-testid="search-dashboards"
|
data-testid="search-dashboards"
|
||||||
onChange={(event) => setSearch(event.target.value)}
|
onChange={(event) => setSearch(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="secondary" data-testid="new-dashboard">
|
||||||
|
<Plus />
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
<form
|
<form
|
||||||
className="flex items-center gap-2"
|
className="grid gap-4"
|
||||||
onSubmit={(event) => {
|
onSubmit={(event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (slug) create.mutate(slug)
|
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
|
<Input
|
||||||
value={name}
|
value={name}
|
||||||
placeholder="New dashboard"
|
placeholder="kitchen"
|
||||||
aria-label="New dashboard name"
|
aria-label="New dashboard name"
|
||||||
|
autoComplete="off"
|
||||||
data-testid="new-dashboard-name"
|
data-testid="new-dashboard-name"
|
||||||
onChange={(event) => setName(event.target.value)}
|
onChange={(event) => setName(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="secondary"
|
|
||||||
disabled={!slug || create.isPending}
|
disabled={!slug || create.isPending}
|
||||||
data-testid="create-dashboard"
|
data-testid="create-dashboard"
|
||||||
>
|
>
|
||||||
<Plus />
|
Create dashboard
|
||||||
Create
|
|
||||||
</Button>
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
</form>
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{dashboards.length === 0 ? (
|
{dashboards.length === 0 ? (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{needle
|
{needle
|
||||||
? "No dashboard matches that."
|
? "No dashboard matches that."
|
||||||
: "No dashboards yet. Name one above to start."}
|
: "No dashboards yet. Create one to start."}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
<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 { FlowsService } from "@/client"
|
||||||
import { flowKeys, flowsQueryOptions } from "@/components/Flow/queries"
|
import { flowKeys, flowsQueryOptions } from "@/components/Flow/queries"
|
||||||
import { Button } from "@/components/ui/button"
|
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 { Input } from "@/components/ui/input"
|
||||||
import useCustomToast from "@/hooks/useCustomToast"
|
import useCustomToast from "@/hooks/useCustomToast"
|
||||||
import { handleError } from "@/utils"
|
import { handleError } from "@/utils"
|
||||||
@@ -25,6 +34,7 @@ function Flows() {
|
|||||||
const { showErrorToast } = useCustomToast()
|
const { showErrorToast } = useCustomToast()
|
||||||
const [name, setName] = useState("")
|
const [name, setName] = useState("")
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
|
const [dialogOpen, setDialogOpen] = useState(false)
|
||||||
|
|
||||||
const create = useMutation({
|
const create = useMutation({
|
||||||
mutationFn: (flow: string) =>
|
mutationFn: (flow: string) =>
|
||||||
@@ -34,6 +44,7 @@ function Flows() {
|
|||||||
}),
|
}),
|
||||||
onSuccess: (_saved, flow) => {
|
onSuccess: (_saved, flow) => {
|
||||||
queryClient.invalidateQueries({ queryKey: flowKeys.all })
|
queryClient.invalidateQueries({ queryKey: flowKeys.all })
|
||||||
|
setDialogOpen(false)
|
||||||
navigate({ to: "/flows/$flowName", params: { flowName: flow } })
|
navigate({ to: "/flows/$flowName", params: { flowName: flow } })
|
||||||
},
|
},
|
||||||
onError: handleError.bind(showErrorToast),
|
onError: handleError.bind(showErrorToast),
|
||||||
@@ -67,37 +78,54 @@ function Flows() {
|
|||||||
data-testid="search-flows"
|
data-testid="search-flows"
|
||||||
onChange={(event) => setSearch(event.target.value)}
|
onChange={(event) => setSearch(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="secondary" data-testid="new-flow">
|
||||||
|
<Plus />
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
<form
|
<form
|
||||||
className="flex items-center gap-2"
|
className="grid gap-4"
|
||||||
onSubmit={(event) => {
|
onSubmit={(event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (NAME.test(slug)) create.mutate(slug)
|
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
|
<Input
|
||||||
value={name}
|
value={name}
|
||||||
placeholder="New flow"
|
placeholder="heating"
|
||||||
aria-label="New flow name"
|
aria-label="New flow name"
|
||||||
|
autoComplete="off"
|
||||||
data-testid="new-flow-name"
|
data-testid="new-flow-name"
|
||||||
onChange={(event) => setName(event.target.value)}
|
onChange={(event) => setName(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="secondary"
|
|
||||||
disabled={!NAME.test(slug) || create.isPending}
|
disabled={!NAME.test(slug) || create.isPending}
|
||||||
data-testid="create-flow"
|
data-testid="create-flow"
|
||||||
>
|
>
|
||||||
<Plus />
|
Create flow
|
||||||
Create
|
|
||||||
</Button>
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
</form>
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{flows.length === 0 ? (
|
{flows.length === 0 ? (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{needle
|
{needle
|
||||||
? "No flow matches that."
|
? "No flow matches that."
|
||||||
: "No flows yet. Name one above to start."}
|
: "No flows yet. Create one to start."}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
<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 }) => {
|
test("a draft stays off the engine until it is published", async ({ page }) => {
|
||||||
await page.goto("/flows")
|
await page.goto("/flows")
|
||||||
|
await page.getByTestId("new-flow").click()
|
||||||
await page.getByTestId("new-flow-name").fill(flowName)
|
await page.getByTestId("new-flow-name").fill(flowName)
|
||||||
await page.getByTestId("create-flow").click()
|
await page.getByTestId("create-flow").click()
|
||||||
await page.waitForURL(`/flows/${flowName}`)
|
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")
|
await page.goto("/flows")
|
||||||
|
|
||||||
// Create a flow of our own so the test does not lean on existing data.
|
// 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("new-flow-name").fill(flowName)
|
||||||
await page.getByTestId("create-flow").click()
|
await page.getByTestId("create-flow").click()
|
||||||
await page.waitForURL(`/flows/${flowName}`)
|
await page.waitForURL(`/flows/${flowName}`)
|
||||||
|
|||||||
Reference in New Issue
Block a user