Learning cohort editor
This form combines a loaded draft with session formats, price bands, media, and four enrollment offers. A fake title service returns suggestions for the current title.
Try the complete flow
Edit the title and choose a remote suggestion. Reorder session formats or
create overlapping price bands. Enter Reserved cohort to see save-conflict
feedback without losing the draft.
What this form demonstrates
useWatchfor a query key and a live preview.form.api.setValuefor an application-owned suggestion control.- Two generated arrays with cross-row schema validation.
- Conditional offer fields that preserve hidden values.
- Three sequential mutations from transformed submit output.
Copy the source
"use client"
import {
,
,
,
,
} from "@tanstack/react-query"
import {
,
type ,
type ,
,
type ,
} from "form-please"
import { } from "form-please/default-slots"
import { } from "form-please/native-controls"
import { } from "react"
import { } from "zod"
import { } from "./query-to-resource"
const =
.({
: .({
: .().(5, "Use a specific cohort title"),
: .(["writing", "data", "craft", "leadership"]),
: .(["foundation", "intermediate", "advanced"]),
: .().().(1).(52),
}),
:
.(
.({
: .(["seminar", "studio", "clinic", "critique"]),
: .().().(2).(120),
: .().().(1).(20),
}),
)
.(1, "Add at least one session format"),
:
.(
.({
: .().().(1),
: .().().(1),
: .().(0),
}),
)
.(1, "Add at least one price band"),
: .({
:
.<File | undefined>(
() =>
=== ||
(typeof !== "undefined" && instanceof ),
"Choose an image file",
)
.(),
: .(
.({
: .().("Enter a valid resource URL"),
: .().(2, "Add a caption"),
}),
),
}),
: .({
: .({
: .(),
: .().(1).(80).(),
: .().(),
}),
: .({
: .(),
: .().().(2).(),
: .().(1).(80).(),
}),
: .({
: .(),
: .().().(1).(),
}),
: .({
: .(),
: .().(1).(80).(),
}),
}),
})
.((, ) => {
for (const [, ] of ..()) {
if (. < .) {
.({
: "custom",
: ["priceBands", , "maximumSeats"],
: "Maximum seats must be at least the minimum",
})
}
}
const = [....].(
(, ) => . - .,
)
for (let = 1; < .; += 1) {
const = [ - 1]
const = []
if (
!== &&
!== &&
. <= .
) {
.({
: "custom",
: ["priceBands"],
: "Price bands cannot overlap",
})
break
}
}
const : readonly [
boolean,
unknown,
string,
(string | number)[],
][] = [
[
...,
...,
"Set the early-bird reduction",
["offers", "earlyBird", "percent"],
],
[
...,
...,
"Set the early-bird deadline",
["offers", "earlyBird", "deadline"],
],
[
...,
...,
"Set the team threshold",
["offers", "team", "minimumSeats"],
],
[
...,
...,
"Set the team reduction",
["offers", "team", "percent"],
],
[
...,
...,
"Set reserved scholarship seats",
["offers", "scholarship", "reservedSeats"],
],
[
...,
...,
"Set the alumni reduction",
["offers", "alumni", "percent"],
],
]
for (const [, , , ] of ) {
if ( && === ) {
.({ : "custom", , })
}
}
})
.(() => ({
...,
: ..(
(, ) => + .,
0,
),
}))
type = <typeof >
type = <typeof >
const = {
: {
: "Field Notes for Better Decisions",
: "leadership",
: "intermediate",
: 8,
},
: [
{ : "seminar", : 18, : 2 },
{ : "clinic", : 6, : 2 },
],
: [
{ : 1, : 9, : 480 },
{ : 10, : 24, : 420 },
],
: {
: ,
: [
{
: "https://example.test/resources/field-notes",
: "Sample workbook",
},
],
},
: {
: { : true, : 12, : "2027-02-15" },
: { : true, : 6, : 10 },
: { : true, : 3 },
: { : false, : },
},
} satisfies
const = ({
: (),
: (),
})
function ({
,
,
}: {
readonly : string
readonly : (: string) => void
}) {
const = ({
: ["cohort-title-suggestions", ],
: () =>
(
[
`${}: Practice Lab`,
`${}: Guided Studio`,
`${}: Working Sessions`,
],
300,
),
: .(). >= 5,
: 20_000,
})
const = ()
return (
<
="form-please-complex__embedded"
="Title suggestions"
>
<>Remote title suggestions</>
< ="form-please-complex__choice-list">
{(, {
: ({ }) => {
let = "Generating suggestions…"
if ( === "idle") {
= "Enter at least five characters."
}
return <>{}</>
},
: ({ , }) => (
<>
{. === "pending" && (
<>Refreshing suggestions…</>
)}
{. === "paused" && (
<>Refresh paused; showing saved suggestions.</>
)}
{. === "error" && (
<>Refresh failed; showing saved suggestions.</>
)}
{.(() => (
<
={}
={() => ()}
="button"
>
{}
</>
))}
</>
),
: () => <>Could not load title suggestions.</>,
})}
</>
</>
)
}
const = .(, {
: [
{
: "section",
: "identity",
: "Learning cohort",
: 2,
: [
{
: "field",
: "identity.title",
: "text",
: "Cohort title",
: "full",
},
{
: "field",
: "identity.discipline",
: "select",
: "Discipline",
: [
{ : "writing", : "Writing" },
{ : "data", : "Data practice" },
{ : "craft", : "Material craft" },
{ : "leadership", : "Leadership" },
],
},
{
: "field",
: "identity.level",
: "select",
: "Level",
: [
{ : "foundation", : "Foundation" },
{ : "intermediate", : "Intermediate" },
{ : "advanced", : "Advanced" },
],
},
{
: "field",
: "identity.durationWeeks",
: "number",
: "Duration in weeks",
: { : 1, : 52, : 1 },
},
],
},
{
: "array",
: "sessionFormats",
: "Session formats",
: "Model different seating and mentor configurations.",
: { : "seminar", : 8, : 1 },
: [
{
: "field",
: "format",
: "select",
: "Format",
: [
{ : "seminar", : "Seminar" },
{ : "studio", : "Studio" },
{ : "clinic", : "Clinic" },
{ : "critique", : "Critique" },
],
},
{
: "field",
: "cohortSize",
: "number",
: "Learners",
: { : 2, : 120, : 1 },
},
{
: "field",
: "mentorCount",
: "number",
: "Mentors",
: { : 1, : 20, : 1 },
},
],
},
{
: "array",
: "priceBands",
: "Per-capacity pricing",
: { : 1, : 1, : 0 },
: [
{
: "field",
: "minimumSeats",
: "number",
: "Minimum seats",
: { : 1, : 1 },
},
{
: "field",
: "maximumSeats",
: "number",
: "Maximum seats",
: { : 1, : 1 },
},
{
: "field",
: "pricePerLearner",
: "number",
: "Price per learner",
: { : 0, : 10 },
},
],
},
{
: "section",
: "media",
: "Media",
: [
{
: "field",
: "media.cover",
: "file",
: "Cover image",
: { : "image/*" },
},
{
: "array",
: "media.resources",
: "Resource gallery",
: { : "", : "" },
: [
{
: "field",
: "url",
: "text",
: "Resource URL",
},
{
: "field",
: "caption",
: "text",
: "Caption",
},
],
},
],
},
...(),
],
})
function () {
return [
{
: "section",
: "offer-early",
: "Early-bird offer",
: 2,
: [
{
: "field",
: "offers.earlyBird.enabled",
: "checkbox",
: "Enabled",
},
{
: "field",
: "offers.earlyBird.percent",
: "number",
: "Reduction percent",
: () => ...,
: { : 1, : 80, : 1 },
},
{
: "field",
: "offers.earlyBird.deadline",
: "date",
: "Deadline",
: () => ...,
},
],
},
{
: "section",
: "offer-team",
: "Team offer",
: 2,
: [
{
: "field",
: "offers.team.enabled",
: "checkbox",
: "Enabled",
},
{
: "field",
: "offers.team.minimumSeats",
: "number",
: "Minimum seats",
: () => ...,
: { : 2, : 1 },
},
{
: "field",
: "offers.team.percent",
: "number",
: "Reduction percent",
: () => ...,
: { : 1, : 80, : 1 },
},
],
},
{
: "section",
: "offer-scholarship",
: "Scholarship offer",
: 2,
: [
{
: "field",
: "offers.scholarship.enabled",
: "checkbox",
: "Enabled",
},
{
: "field",
: "offers.scholarship.reservedSeats",
: "number",
: "Reserved seats",
: () => ...,
: { : 1, : 1 },
},
],
},
{
: "section",
: "offer-alumni",
: "Returning learner offer",
: 2,
: [
{
: "field",
: "offers.alumni.enabled",
: "checkbox",
: "Enabled",
},
{
: "field",
: "offers.alumni.percent",
: "number",
: "Reduction percent",
: () => ...,
: { : 1, : 80, : 1 },
},
],
},
] satisfies readonly <, typeof .>[]
}
export function () {
const [] = (
() => new ({ : { : { : false } } }),
)
return (
< ={}>
< />
</>
)
}
function () {
const = ({
: ["learning-cohort-draft", "cohort-41"],
: () => (, 410),
})
const = ({
: async (: ) => {
await (, 380)
if (...() === "reserved cohort")
throw new ()
return { : . + 100 }
},
})
const = ({
: (: ) =>
({ : ... }, 290),
})
const = ({
: (: ) =>
(
{
: .(.).(() => .)
.,
},
340,
),
})
const [, ] = ("Draft loaded from the fake API.")
const = .(, {
: ,
async ({ }) {
try {
const = await .()
const = await .()
const = await .()
(
`Revision ${.} saved with ${.} resource(s) and ${.} active offer(s).`,
)
} catch () {
if ( instanceof ) {
(
"That title is already reserved. Choose a suggestion or edit it.",
)
return
}
("The cohort could not be saved. Your draft is intact.")
}
},
})
if (.)
return (
< ="form-please-complex">Loading cohort draft…</>
)
if (.)
return (
< ="form-please-complex">
Could not load the cohort draft.
</>
)
let =
if (. || . || .) {
= "Synchronizing three resources…"
}
const = ..()
const = ..(
(, ) => + .,
0,
)
return (
<
="Learning cohort editor example"
="form-please-complex"
>
< ="form-please-complex__kicker">Async cohort editor</>
< ="form-please-complex__summary">
Remote title suggestions, movable configuration rows, band validation,
media, four offer subforms, and conflict recovery remain typed end to
end.
</>
<. ="form-please-complex__form" ={}>
<
={..}
={() =>
..("identity.title", )
}
/>
< ="form-please-complex__actions">
<. ="form-please-complex__primary">
Save cohort
</.>
< ="polite">{}</>
</>
</.>
<
="Cohort preview"
="form-please-complex__preview"
>
<>{..}</>
<>
{..} formats · {} aggregate seats
</>
</>
</>
)
}
class extends {}
function <>(: , : number): <> {
return new (() =>
.(() => (), ),
)
}Compare the other workflows in Examples.