Audit short-term: currency preview, date bounds, success destination, safe-area

- H3: Live currency preview below currency inputs shows the value formatted
  with thousands separators (en-US, USD) using Intl.NumberFormat. Skipped
  inside matrix cells to keep the Y1 monthly table compact.
- M1: Date inputs now apply min/max bounds. Default window is 1900-01-01 to
  2100-12-31; per-field override via FieldConfig.min/max as ISO strings.
- H6: On successful submit, replace the form with a SuccessDestination card
  (large checkmark, org name, "Submit another" + "safe to close" affordance).
  Prevents accidental duplicate submits from back-button / autofill replay.
- M6: Sticky submit bar respects iOS safe-area-inset-bottom.

FieldRenderer now takes a control prop so the currency preview can subscribe
to its single field via useWatch without re-rendering the whole form.
This commit is contained in:
Joel Brock
2026-05-11 09:48:03 -07:00
parent 0d84b9654b
commit a804650f65
4 changed files with 144 additions and 14 deletions

View File

@@ -2,7 +2,12 @@
import { useEffect, useId, useMemo, useState } from "react";
import type { StageSectionConfig, SelectOption } from "@/types/form";
import type { UseFormRegister, FieldValues, FieldErrors } from "react-hook-form";
import type {
UseFormRegister,
FieldValues,
FieldErrors,
Control,
} from "react-hook-form";
import { FieldRenderer } from "./fields/FieldRenderer";
import { MatrixGroup } from "./MatrixGroup";
import { StageIcon } from "./StageIcon";
@@ -11,6 +16,7 @@ import { evaluate } from "@/lib/conditional";
interface StageSectionProps {
section: StageSectionConfig;
register: UseFormRegister<FieldValues>;
control: Control<FieldValues>;
errors: FieldErrors;
/** Live form values, used to evaluate per-field visibility rules. */
formValues: Record<string, unknown>;
@@ -36,6 +42,7 @@ interface StageSectionProps {
export function StageSection({
section,
register,
control,
errors,
formValues,
isCurrent,
@@ -153,6 +160,7 @@ export function StageSection({
<FieldRenderer
field={f}
register={register}
control={control}
errors={errors}
readonlyValue={formValues[f.name]}
resolvedOptions={resolvedOptions}