Skip to content

Commit

Permalink
Add incrementers in DAF form (#3285)
Browse files Browse the repository at this point in the history
* convert daf noncontext

* add error message

* reuse incrementer in  daf form

* add spacing
  • Loading branch information
ap-justin authored Sep 4, 2024
1 parent e0b5116 commit effe585
Show file tree
Hide file tree
Showing 14 changed files with 126 additions and 87 deletions.
6 changes: 3 additions & 3 deletions src/components/Signup/ConfirmForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { yupResolver } from "@hookform/resolvers/yup";
import { AuthError, confirmSignUp, resendSignUpCode } from "aws-amplify/auth";
import { Field, Form } from "components/form";
import { Field, RhfForm } from "components/form";
import { useErrorContext } from "contexts/ErrorContext";
import { useState } from "react";
import { type UseFormReturn, useForm } from "react-hook-form";
Expand All @@ -27,7 +27,7 @@ export default function ConfirmForm(props: Props) {
type FV = typeof methods extends UseFormReturn<infer U> ? U : never;

return (
<Form
<RhfForm
className={`${props.classes ?? ""} grid`}
disabled={isSubmitting || isRequestingNewCode}
methods={methods}
Expand Down Expand Up @@ -93,6 +93,6 @@ export default function ConfirmForm(props: Props) {
>
Resend code
</button>
</Form>
</RhfForm>
);
}
68 changes: 23 additions & 45 deletions src/components/donation/Steps/DonateMethods/Daf/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,62 @@
import { yupResolver } from "@hookform/resolvers/yup";
import CurrencySelector from "components/CurrencySelector";
import { Field, Form as FormContainer } from "components/form";
import { useController, useForm } from "react-hook-form";
import { schema, stringNumber } from "schemas/shape";
import type { DetailedCurrency } from "types/components";
import { NativeField as Field, Form as FormContainer } from "components/form";
import { useDonationState } from "../../Context";
import ContinueBtn from "../../common/ContinueBtn";
import Incrementers from "../../common/Incrementers";
import { ProgramSelector } from "../../common/ProgramSelector";
import { DEFAULT_PROGRAM } from "../../common/constants";
import { usdOption } from "../../common/constants";
import { nextFormState } from "../helpers";
import type { FormValues as FV, Props } from "./types";

/**
* Only USD donations are permissible for DAF donations.
* The minimum amount differs depending on which provider is selected.
*/
const USD_CURRENCY: DetailedCurrency = { code: "usd", rate: 1, min: 1 };
import type { Props } from "./types";
import { useRhf } from "./useRhf";

export default function Form(props: Props) {
const { setState } = useDonationState();

const initial: FV = {
amount: "",
currency: USD_CURRENCY,
program: DEFAULT_PROGRAM,
};

const methods = useForm<FV>({
defaultValues: props.details || initial,
resolver: yupResolver(
schema<FV>({
amount: stringNumber(
(s) => s.required("Please enter an amount"),
(n) => n.positive("Amount must be greater than 0")
),
})
),
});
const { handleSubmit, control } = methods;

const { field: program } = useController<FV, "program">({
control: control,
name: "program",
});
const rhf = useRhf(props);

return (
<FormContainer
methods={methods}
onSubmit={handleSubmit((fv) =>
disabled={rhf.isSubmitting}
onSubmit={rhf.handleSubmit((fv) =>
setState((prev) => nextFormState(prev, { ...fv, method: "daf" }))
)}
className="grid gap-4"
>
<CurrencySelector
currencies={[USD_CURRENCY]}
currencies={[usdOption]}
label="Currency"
// only one currency available, so can't change it
onChange={() => {}}
value={USD_CURRENCY}
value={usdOption}
classes={{
label: "font-semibold",
combobox: "field-container-donate",
container: "field-donate",
}}
required
/>
<Field<FV>
name="amount"
<Field
{...rhf.register("amount")}
label="Donation amount"
placeholder="Enter amount"
classes={{ label: "font-semibold", container: "field-donate mt-1" }}
required
tooltip="The minimum donation amount will depend on your DAF provider."
error={rhf.errors.amount?.message}
/>

<Incrementers
onIncrement={rhf.onIncrement}
code={usdOption.code}
rate={usdOption.rate}
increments={props.init.config?.increments}
classes="mb-4"
/>

{(props.init.recipient.progDonationsAllowed ?? true) && (
<ProgramSelector
endowId={props.init.recipient.id}
program={program.value}
onChange={program.onChange}
program={rhf.program.value}
onChange={rhf.program.onChange}
/>
)}

Expand Down
56 changes: 56 additions & 0 deletions src/components/donation/Steps/DonateMethods/Daf/useRhf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { yupResolver } from "@hookform/resolvers/yup";
import { useController, useForm } from "react-hook-form";
import { schema, stringNumber } from "schemas/shape";
import type { OnIncrement } from "../../common/Incrementers";
import { DEFAULT_PROGRAM, usdOption } from "../../common/constants";
import type { FormValues as FV, Props } from "./types";

export function useRhf(props: Props) {
const initial: FV = {
amount: "",
currency: usdOption,
program: DEFAULT_PROGRAM,
};

const {
register,
control,
handleSubmit,
getValues,
trigger,
setValue,
formState: { isSubmitting, errors },
} = useForm<FV>({
defaultValues: props.details || initial,
resolver: yupResolver(
schema<FV>({
amount: stringNumber(
(s) => s.required("Please enter an amount"),
(n) => n.positive("Amount must be greater than 0")
),
})
),
});

const { field: program } = useController<FV, "program">({
control: control,
name: "program",
});

const onIncrement: OnIncrement = (inc) => {
const amntNum = Number(getValues("amount"));
if (Number.isNaN(amntNum)) return trigger("amount", { shouldFocus: true });
setValue("amount", `${inc + amntNum}`);
};

return {
handleSubmit,
isSubmitting,
register,
errors,
//controllers
program,
//utils
onIncrement,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { userIsSignedIn } from "types/auth";
import type { Currency } from "types/components";
import { useDonationState } from "../../Context";
import ContinueBtn from "../../common/ContinueBtn";
import Incrementers from "../../common/Incrementers";
import { ProgramSelector } from "../../common/ProgramSelector";
import { USD_CODE } from "../../common/constants";
import { nextFormState } from "../helpers";
import Frequency from "./Frequency";
import Incrementers from "./Incrementers";
import type { FormProps, Props } from "./types";
import { useRhf } from "./useRhf";

Expand Down
2 changes: 0 additions & 2 deletions src/components/donation/Steps/DonateMethods/Stripe/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,3 @@ export type FormProps = Props & {
currencies: DetailedCurrency[];
defaultCurr?: DetailedCurrency;
};

export type OnIncrement = (increment: number) => void;
3 changes: 2 additions & 1 deletion src/components/donation/Steps/DonateMethods/Stripe/useRhf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { useController, useForm } from "react-hook-form";
import { schema, stringNumber } from "schemas/shape";
import { requiredString } from "schemas/string";
import type { Currency } from "types/components";
import type { OnIncrement } from "../../common/Incrementers";
import { DEFAULT_PROGRAM, usdOption } from "../../common/constants";
import type { FormValues as FV, FormProps, OnIncrement } from "./types";
import type { FormValues as FV, FormProps } from "./types";

export function useRhf(props: Omit<FormProps, "currencies">) {
const initial: FV = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { DONATION_INCREMENTS } from "constants/common";
import { humanize, roundDownToNum } from "helpers";
import type { OnIncrement } from "./types";

export type OnIncrement = (increment: number) => void;

interface Props {
rate: number;
code: string;
onIncrement: OnIncrement;
increments?: number[];
classes?: string;
}

export default function Incrementers({
increments = DONATION_INCREMENTS,
classes = "",
...props
}: Props) {
return (
<div className="flex justify-center flex-wrap gap-3">
<div className={`flex justify-center flex-wrap gap-3 ${classes}`}>
{increments
.toSorted((a, b) => a - b)
.map((inc) => (
Expand Down
39 changes: 22 additions & 17 deletions src/components/form/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import { type FormHTMLAttributes, forwardRef } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form";

type Props = FormHTMLAttributes<HTMLFormElement> & {
methods?: UseFormReturn<any, any, any>;
type El = HTMLFormElement;

interface IForm extends FormHTMLAttributes<El> {
disabled?: boolean;
};
export default forwardRef<HTMLFormElement, Props>(function Form(
{ methods, disabled, children, ...props },
ref
) {
const form = (
<form ref={ref} {...props}>
{disabled ? (
}

export const Form = forwardRef<El, IForm>(
({ disabled, children, ...props }, ref) => {
return (
<form ref={ref} {...props}>
<fieldset disabled={disabled} className="contents">
{children}
</fieldset>
) : (
children
)}
</form>
);
</form>
);
}
);

if (!methods) return form;
interface Props extends IForm {
methods: UseFormReturn<any, any, any>;
}

return <FormProvider {...methods}>{form}</FormProvider>;
export default forwardRef<El, Props>(({ methods, ...props }, ref) => {
return (
<FormProvider {...methods}>
<Form {...props} ref={ref} />
</FormProvider>
);
});
2 changes: 1 addition & 1 deletion src/components/form/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ export * from "./Input";
export * from "./PasswordInput";
export { CheckField, NativeCheckField } from "./CheckField";
export { Radio } from "./Radio";
export { default as Form } from "./Form";
export { default as RhfForm, Form } from "./Form";
export { dateToFormFormat } from "./helpers";
export { default as DateInput } from "./DateInput";
4 changes: 2 additions & 2 deletions src/pages/Admin/Charity/Media/VideoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
import Icon from "components/Icon";
import Modal from "components/Modal";
import Prompt from "components/Prompt";
import { Field, Form } from "components/form";
import { Field, RhfForm } from "components/form";
import { useErrorContext } from "contexts/ErrorContext";
import { useModalContext } from "contexts/ModalContext";
import { type UseFormReturn, useForm } from "react-hook-form";
Expand Down Expand Up @@ -63,7 +63,7 @@ export default function VideoEditor(props: Props) {
});
}
})}
as={Form}
as={RhfForm}
methods={methods}
className="fixed-center z-10 grid text-navy-d4 dark:text-white bg-white dark:bg-blue-d4 sm:w-full w-[90vw] sm:max-w-lg rounded overflow-hidden"
>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Admin/Charity/ProgramEditor/Milestone/Milestone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
import { DrawerIcon } from "components/Icon";
import ImgEditor from "components/ImgEditor/ImgEditor";
import { RichTextEditor } from "components/RichText";
import { Field, Form, Label, dateToFormFormat } from "components/form";
import { Field, Label, RhfForm, dateToFormFormat } from "components/form";
import { useForm } from "react-hook-form";
import type { Milestone as TMilestone } from "types/aws";
import { MAX_CHARS, MAX_SIZE_IN_BYTES, VALID_MIME_TYPES } from "../common";
Expand Down Expand Up @@ -53,7 +53,7 @@ export default function Milestone(props: Props) {
</div>

<DisclosurePanel
as={Form}
as={RhfForm}
className={({ open }) =>
`${
open ? "border-t border-gray-l4" : ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
import Group from "components/Group";
import ImgEditor from "components/ImgEditor";
import { RichTextEditor } from "components/RichText";
import { Field, Form, Label } from "components/form";
import { Field, Label, RhfForm } from "components/form";
import { useForm } from "react-hook-form";
import type { Program } from "types/aws";
import { MAX_CHARS, MAX_SIZE_IN_BYTES, VALID_MIME_TYPES } from "../common";
Expand Down Expand Up @@ -33,7 +33,7 @@ export default function ProgramInfo(props: Program) {

return (
<Group title="Program information">
<Form
<RhfForm
onSubmit={handleSubmit(submit)}
methods={methods}
disabled={isSubmitting}
Expand Down Expand Up @@ -80,7 +80,7 @@ export default function ProgramInfo(props: Program) {
>
Save changes
</button>
</Form>
</RhfForm>
</Group>
);
}
Loading

0 comments on commit effe585

Please sign in to comment.