App
Source
/**
* @typedef DateEntryProps
* @property {string} label
* @property {string} date
* @property {(newDate: string) => void} setDate
* @property {string | null} errorMsg
* @property {boolean} [disabled]
*/
/**
* @param {DateEntryProps} props
*/
export function DateEntry(props) {
const inputId = `${props.label}-date`;
return (
<div class={"form-group" + (props.errorMsg ? " has-error" : "")}>
<label class="form-label" for={inputId}>
{props.label}
</label>
<input
id={inputId}
class="form-input"
type="text"
value={props.date}
onInput={e => props.setDate(e.currentTarget.value)}
disabled={props.disabled}
/>
{props.errorMsg && <p class="form-input-hint">{props.errorMsg}</p>}
</div>
);
}
export const ONE_WAY_FLIGHT = "one-way";
export const RETURN_FLIGHT = "return";
/**
* @param {{ tripType: string; setTripType(value: string): void; }} props
*/
export function TripType(props) {
return (
<div class="form-group">
<label class="form-label" for="trip-type">
Trip type
</label>
<select
id="trip-type"
class="form-select"
value={props.tripType}
onInput={e => props.setTripType(e.currentTarget.value)}
>
<option value={ONE_WAY_FLIGHT}>one-way flight</option>
<option value={RETURN_FLIGHT}>return flight</option>
</select>
</div>
);
}
import { createMemo, createSignal } from "solid-js";
import { render } from "solid-js/web";
import { today, validateDate } from "../../../../lib/date";
import { TripType, RETURN_FLIGHT, ONE_WAY_FLIGHT } from "./TripType";
import { DateEntry } from "./DateEntry";
const initialDate = today();
/**
* @typedef {import('solid-js').Accessor<T>} Accessor
* @template T
*/
/**
* @param {string} initialDate
* @returns {[Accessor<string>, Accessor<string | null>, (string) => void]}
*/
function useDate(initialDate) {
const [date, setDate] = createSignal(initialDate);
const [error, setError] = createSignal(null);
/** @type {(newDate: string) => void} */
function updateDate(newDate) {
setDate(newDate);
try {
validateDate(newDate);
setError(null);
} catch (error) {
setError(error.message);
}
}
return [date, error, updateDate];
}
function App() {
const [tripType, setTripType] = createSignal(ONE_WAY_FLIGHT);
const [departing, departingError, setDeparting] = useDate(initialDate);
const [returning, returningError, setReturning] = useDate(initialDate);
const finalReturningError = createMemo(() => {
if (
departingError() == null &&
returningError() == null &&
tripType() == RETURN_FLIGHT &&
returning() < departing()
) {
return "Returning date must be on or after departing date.";
} else {
return returningError();
}
});
function bookFlight() {
const type = tripType() === RETURN_FLIGHT ? "return" : "one-way";
let message = `You have booked a ${type} flight, departing ${departing()}`;
if (tripType() == RETURN_FLIGHT) {
message += ` and returning ${returning()}`;
}
alert(message);
}
return (
<>
<TripType
tripType={tripType()}
setTripType={value => setTripType(value)}
/>
<DateEntry
label="Departing"
date={departing()}
setDate={newDate => setDeparting(newDate)}
errorMsg={departingError()}
/>
<DateEntry
label="Returning"
date={returning()}
setDate={newDate => setReturning(newDate)}
errorMsg={finalReturningError()}
disabled={tripType() !== RETURN_FLIGHT}
/>
<div class="form-group">
<button
disabled={Boolean(departingError() || finalReturningError())}
onClick={bookFlight}
class="btn btn-primary"
>
book
</button>
</div>
</>
);
}
render(App, document.getElementById("app"));