On List, Record, Form, Stepped flow, Settings, Field result, Find, Help
Refusal
The remedy first shows the field it moves to.
Booking page
- Address
- oakmere
A Refusal inside a Dialog, its causes on the page behind it.
Upcoming bookings in Main Hall
Oakmere Yoga
Riverside Scouts
Remove Main Hall
A Refusal on its line alone, with no cause to go to.
Issue refund is refused until both transfers on this invoice have arrived.
A submit refused by two fields, each cause going to its field.
Source
module Refusal = KitReact.Roles.Refusal
@set external disclose: (Dom.element, bool) => unit = "open"
type toggleEvent
@send external listen: (Dom.element, string, toggleEvent => unit) => unit = "addEventListener"
@send external unlisten: (Dom.element, string, toggleEvent => unit) => unit = "removeEventListener"
let cause = (key, label): Refusal.cause => {
key,
href: "#" ++ key,
label,
props: {},
linkProps: {},
}
let rowProps = (id, subject, facts): KitReact.Roles.Row.props => {subjectElement: Span, props: {id: id}, subject, facts: [{key: "reason", content: facts, props: {}}]}
let row = (id, subject, facts) => React.jsxKeyed(KitReact.Roles.Row.make, rowProps(id, subject, facts), ~key=id, ())
let emailError = value => String.includes(value, "@") && String.includes(value, ".") ? None : Some("Enter a complete billing email, such as aisha@oakmereprimary.org.")
let phoneError = value => value->String.replaceRegExp(%re("/\D/g"), "")->String.length >= 10 ? None : Some("Enter the phone number with its area code, such as 01632 960 123.")
@react.component
let make = () => {
let (email, setEmail) = React.useState(() => "accounts@")
let (phone, setPhone) = React.useState(() => "960 123")
let fieldErrors = [("refusal-fields-email", emailError(email)), ("refusal-fields-phone", phoneError(phone))]->Array.filterMap(((key, error)) => error->Option.map(words => (key, words)))
let caseRef = React.useRef(Nullable.null)
let dialogRef = React.useRef(Nullable.null)
let (dialogOpen, setDialogOpen) = React.useState(() => false)
React.useEffect0(() => switch (Nullable.toOption(caseRef.current), Nullable.toOption(dialogRef.current)) {
| (Some(details), Some(dialog)) =>
let discloseCase = _ => details->disclose(true)
listen(dialog, "beforetoggle", discloseCase)
Some(() => unlisten(dialog, "beforetoggle", discloseCase))
| _ => None
})
<>
{ReactDOM.jsxs("form", KitReact.Dom.bind(~layout=#form, {id: "refusal-handle-form", children: React.array([
ReactDOM.jsx("div", KitReact.Dom.bind(~region=KitReact.Bindings.Layouts.Form.region(#notices), {ariaLive: #polite})),
ReactDOM.jsx("section", KitReact.Dom.bind(~region=KitReact.Bindings.Layouts.Form.region(#"task-context"), {children: <h2>{React.string("Choose an address")}</h2>})),
ReactDOM.jsxs("div", KitReact.Dom.bind(~region=KitReact.Bindings.Layouts.Form.region(#"form-groups"), {children: React.array([
ReactDOM.jsx("div", KitReact.Dom.bind(~region=KitReact.Bindings.Layouts.Form.region(#notices), {ariaLive: #polite})),
<KitReact.Roles.FormGroup id="refusal-handle-form-group" kind=Text(Medium)
label={React.string("Address")} labelProps={{htmlFor: "refusal-handle"}}
controlProps={{id: "refusal-handle", name: "handle"}} controlValue="oakmere" printedValue={React.string("oakmere")} />,
])})),
<KitReact.Roles.ActionGroup props={KitReact.Dom.bind(~region=KitReact.Bindings.Layouts.Form.region(#"submit-return"), {})}
refusal={{id: "refusal-handle-taken", line: React.string("That address is taken. Choose another."), causes: [cause("refusal-handle", React.string("Use oakmere-hall"))]}}
lead=PrimaryAction({children: React.string("Continue"), props: {type_: "button"}})
return={<KitReact.Roles.WayBack href="#refusal-booking-page">{React.string("Return to booking page")}</KitReact.Roles.WayBack>} statusLines=[{children: React.null}] />,
])}))}
<p className="specimen-case-line">{React.string("The remedy first shows the field it moves to.")}</p>
{ReactDOM.jsxs("form", KitReact.Dom.bind(~layout=#form, {id: "refusal-mounted-form", children: React.array([
ReactDOM.jsx("div", KitReact.Dom.bind(~region=KitReact.Bindings.Layouts.Form.region(#notices), {ariaLive: #polite})),
ReactDOM.jsx("section", KitReact.Dom.bind(~region=KitReact.Bindings.Layouts.Form.region(#"task-context"), {children: <h2>{React.string("Choose an address")}</h2>})),
ReactDOM.jsx("div", KitReact.Dom.bind(~region=KitReact.Bindings.Layouts.Form.region(#"form-groups"), {id: "refusal-mounted-fields", children: ReactDOM.jsx("div", KitReact.Dom.bind(~region=KitReact.Bindings.Layouts.Form.region(#notices), {ariaLive: #polite}))})),
<KitReact.Roles.ActionGroup props={KitReact.Dom.bind(~region=KitReact.Bindings.Layouts.Form.region(#"submit-return"), {})}
refusal={{id: "refusal-mounted-taken", line: React.string("That address is taken. Choose another."), causes: [cause("refusal-mounted-fields", React.string("Use oakmere-hall"))]}}
lead=PrimaryAction({children: React.string("Continue"), props: {type_: "button"}})
return={<KitReact.Roles.WayBack href="#refusal-booking-page">{React.string("Return to booking page")}</KitReact.Roles.WayBack>} statusLines=[{children: React.null}] />,
])}))}
<KitReact.Roles.HeadedRegion props={{id: "refusal-booking-page"}} heading=[KitReact.Words.Text("Booking page")]
content={<KitReact.Roles.FactGroup><KitReact.Roles.LabelledFact name={React.string("Address")} value=Words([KitReact.Words.Text("oakmere")]) /></KitReact.Roles.FactGroup>} />
<p className="specimen-case-line">{React.string("A Refusal inside a Dialog, its causes on the page behind it.")}</p>
<section ariaLabelledby="refusal-bookings-title">
<h3 id="refusal-bookings-title">{React.string("Upcoming bookings in Main Hall")}</h3>
{row("refusal-booking-yoga", React.string("Oakmere Yoga"), Words([KitReact.Words.Time(KitReact.Time.intervalWithDay(Specimen_Reader.reader, ~start=Date.fromString("2026-05-12T17:00:00Z"), ~end=Date.fromString("2026-05-12T18:30:00Z")))]))}
{row("refusal-booking-scouts", React.string("Riverside Scouts"), Words([KitReact.Words.Time(KitReact.Time.intervalWithDay(Specimen_Reader.reader, ~start=Date.fromString("2026-05-15T18:00:00Z"), ~end=Date.fromString("2026-05-15T20:00:00Z")))]))}
</section>
<details ref={ReactDOM.Ref.domRef(caseRef)}>
<summary>{React.string("Remove Main Hall")}</summary>
<KitReact.Roles.SecondaryAction props={{id: "refusal-remove-hall-opener", type_: "button", onClick: _ => setDialogOpen(_ => true)}}>{React.string("Remove Main Hall")}</KitReact.Roles.SecondaryAction>
<KitReact.Roles.Dialog
open_=dialogOpen
returnFocus="refusal-remove-hall-opener"
onOpen={() => setDialogOpen(_ => true)}
onClose={() => setDialogOpen(_ => false)}
id="refusal-remove-hall-dialog"
props={{ref: ReactDOM.Ref.domRef(dialogRef), ariaLabelledby: "refusal-remove-hall-title"}}
heading={React.string("Remove Main Hall")}
headingProps={{id: "refusal-remove-hall-title"}}
lead={PrimaryAction({children: React.string("Remove hall"), props: {type_: "button"}})}
secondaryAction={{children: React.string("Keep hall"), props: {type_: "button"}}}
refusal={{
id: "refusal-remove-hall",
line: React.string("Remove Main Hall is refused while it has upcoming bookings:"),
causes: [
cause("refusal-booking-yoga", <>{React.string("Oakmere Yoga, ")}<time dateTime="2026-05-12T18:00">{KitReact.TimeParts.elements(["12 May 2026", ", 18:00"])} </time></>),
cause("refusal-booking-scouts", <>{React.string("Riverside Scouts, ")}<time dateTime="2026-05-15T19:00">{KitReact.TimeParts.elements(["15 May 2026", ", 19:00"])} </time></>),
],
}}
>
<KitReact.Roles.InlineConsequence
region=KitReact.Roles.InlineConsequence.Consequence
props={{ariaLabel: "Remove Main Hall"}}
subject={React.string("Main Hall")}
consequence={React.string("Removing Main Hall takes it off your booking page and out of the diary.")}
effectiveTime={React.string("Takes effect as soon as you confirm.")}
correctionPath={React.string("You can restore Main Hall from Trash.")}
/>
</KitReact.Roles.Dialog>
</details>
<p className="specimen-case-line">{React.string("A Refusal on its line alone, with no cause to go to.")}</p>
<div>
<Refusal
id="refusal-refund-line"
line={React.string("Issue refund is refused until both transfers on this invoice have arrived.")}
/>
<KitReact.Roles.ActionGroup
lead=PrimaryAction({children: React.string("Issue refund"), props: {type_: "button"}})
/>
</div>
<p className="specimen-case-line">{React.string("A submit refused by two fields, each cause going to its field.")}</p>
<form id="refusal-fields-form">
<KitReact.Roles.FormGroup id="refusal-fields-email-form-group"
kind=Email
label={React.string("Billing email")}
labelProps={{htmlFor: "refusal-fields-email"}}
controlProps={{id: "refusal-fields-email", name: "email", onChange: event => {
let value = ReactEvent.Form.currentTarget(event)["value"]
setEmail(_ => value)
}}}
controlValue="accounts@"
printedValue={React.string(email)}
error=?{emailError(email)->Option.map(React.string)}
/>
<KitReact.Roles.FormGroup id="refusal-fields-phone-form-group"
kind=Telephone
label={React.string("Phone")}
labelProps={{htmlFor: "refusal-fields-phone"}}
controlProps={{id: "refusal-fields-phone", name: "phone", onChange: event => {
let value = ReactEvent.Form.currentTarget(event)["value"]
setPhone(_ => value)
}}}
controlValue="960 123"
printedValue={React.string(phone)}
error=?{phoneError(phone)->Option.map(React.string)}
/>
{switch fieldErrors {
| [] => React.null
| errors =>
<Refusal
id="refusal-fields"
line={React.string(switch errors->Array.length {
| 1 => "1 field needs a change."
| count => `${Int.toString(count)} fields need a change.`
})}
causes={errors->Array.map(((key, words)) => cause(key, React.string(words)))}
/>
}}
<KitReact.Roles.ActionGroup
lead=PrimaryAction({children: React.string("Save hirer"), props: {type_: "button"}})
/>
</form>
</>
}