Skip to content

SAA-645 Require note on savings fund transfer form#1772

Open
jeff-clay wants to merge 7 commits into
mainfrom
SAA-645-require-note-on-transfer-form
Open

SAA-645 Require note on savings fund transfer form#1772
jeff-clay wants to merge 7 commits into
mainfrom
SAA-645-require-note-on-transfer-form

Conversation

@jeff-clay
Copy link
Copy Markdown

@jeff-clay jeff-clay commented May 12, 2026

Description

  • Makes the Note field required on one-time fund transfers (previously labeled "Note (Optional)", which produced a confusing SAA 422 when left blank).
  • Defaults the Note to <lastName> Savings Fund Transfer in MPDX using the current user's lastName. Editable — clearing it triggers the required-field error.
  • Front-end enforcement only; recurring transfers are unaffected (their description is server-synthesized).

Related: SAA-645

Testing

  • Open HR Tools → Savings Fund Transfer → New Transfer, One Time schedule.
  • Confirm Note is pre-filled with <your-last-name> Savings Fund Transfer in MPDX.
  • Submit unchanged → succeeds.
  • Open another new transfer, clear Note (or enter only whitespace), submit → "Note is required" inline error, no submission.
  • Switch to Monthly schedule — no Note field shown, recurring submits normally.

Checklist:

  • I have given my PR a title with the format "MPDX-(JIRA#) (summary sentence max 80 chars)" — using SAA-645 since this work is tracked in the SAA project's Jira
  • I have applied the appropriate labels (Add the label "Preview" to automatically create a preview environment)
  • I have run the Claude Code /pr-review command locally and fixed any relevant suggestions
  • I have requested a review from another person on the project
  • I have tested my changes in preview or in staging
  • I have cleaned up my commit history

@jeff-clay jeff-clay added the Preview Environment Add this label to create an Amplify Preview label May 12, 2026
@github-actions
Copy link
Copy Markdown
Contributor

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

Bundle sizes [mpdx-react]

Compared against 1dea58b

No significant changes found

Copy link
Copy Markdown
Author

@jeff-clay jeff-clay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small, well-scoped form/validation change. All SAA-645 acceptance criteria met (required field with visual asterisk, whitespace-only rejected via .trim().min(1), mutation never fires when invalid). Default-note feature is a documented scope addition per product owner. Refactor to useGetUserQuery over parsing staffAccount.name is the right architectural call — Apollo's cache makes the duplicate hook call free. No must-fix issues; a few nice-to-have hardening suggestions and one optional whitespace test below.

const type = data.type || TransferTypeEnum.New;
const isNew = type === TransferTypeEnum.New;

const defaultNote = lastName
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice-to-have] Consider trimming lastName before interpolating, so an upstream value like ' Sleight ' doesn't produce a default with leading/trailing whitespace. const trimmed = lastName?.trim(); const defaultNote = trimmed ? t(...) : '';

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx Outdated
Comment thread src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.test.tsx Outdated
Comment thread src/components/HrTools/SavingsFundTransfer/TransfersPage/TransfersPage.tsx Outdated
@jeff-clay jeff-clay requested a review from dr-bizz May 12, 2026 17:29
Copy link
Copy Markdown
Contributor

@dr-bizz dr-bizz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good, but you don't need to add the min onto the yup logic as required does that already.

Comment thread src/components/HrTools/SavingsFundTransfer/TransferModal/TransferModal.tsx Outdated
Comment thread src/components/HrTools/SavingsFundTransfer/TransfersPage/TransfersPage.tsx Outdated
@jeff-clay
Copy link
Copy Markdown
Author

@dr-bizz Changes made. I know you approved already, but not sure if you want to do a review of changes. Not sure of protocol.

Comment on lines +77 to +97
const buildCacheWithUser = (lastName: string) => {
const cache = createCache();
cache.writeQuery<GetUserQuery>({
query: GetUserDocument,
data: {
user: {
__typename: 'User',
id: 'test-user-id',
firstName: '',
lastName,
avatar: '',
staffAccountId: null,
primaryDesignation: null,
userType: UserTypeEnum.UsStaff,
preferences: null,
},
},
});
return cache;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a different way to do this. Let me type it up

Copy link
Copy Markdown
Contributor

@dr-bizz dr-bizz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for fixing that issue. We have a few more issues that have arisen.

lastName?: string;
}

const buildCacheWithUser = (lastName: string) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to build the cache. We can do the same as we've done here: src/components/DonationTable/DonationTable.test.tsx

We create the mocks on the GqlMockedProvider, which will mock the GQL queries

.min(0.01, i18n.t('Amount must be at least $0.01')),
note: yup.string().nullable(),
note: yup.string().when('schedule', {
is: ScheduleEnum.OneTime,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we only adding the required note if it is a one time payment?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, a few reasons:

  • The note field currently only renders for one time transfers.
  • The recurring GraphQL mutation doesn't accept a description
  • SAA's recurring transfer synthesizes its own description server-side from the fund/account name.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createTransfer: CreateTransferMutation;
updateRecurringTransfer: UpdateRecurringTransferMutation;
}>
cache={buildCacheWithUser(lastName)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can mock the response of the GQL query here so we don't need to store it in the cache. (I don't know if this will work out the box, as I haven't tested it, you might need to edit the GetUser)

We only need to enter the values we want to mock as we are randomly generating everything else.

Suggested change
cache={buildCacheWithUser(lastName)}
mocks={{
GetUser {
user {
id
firstName
lastName
}
}
}}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

@jeff-clay jeff-clay May 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried your mocks suggestion. It works for the mocks setup, but with useGetUserQuery inside the modal the data resolves async, and Formik captures initialValues at mount, so the default note ends up empty in that brief window.

Added a render-guard (if (userLoading) return null) as the smallest fix. In production it never fires (cache is warmed at app boot by UserPreferenceProvider). In tests it lets the mock resolve before assertions run, and let me drop the cache builder entirely.

If you'd prefer a different shape (e.g. compute the default via useEffect + setFieldValue, or not handle the loading case in the modal at all) happy to change. Just let me know.

@jeff-clay jeff-clay requested a review from dr-bizz May 15, 2026 15:02
Copy link
Copy Markdown
Contributor

@dr-bizz dr-bizz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for making those edits. MPDX React is a large app, which means we need to keep it consistent throughout.

I noticed some more issues and have added my suggestions for you to implement.

The loading from GraphQL gets triggered every time it loads, both during the initial load and subsequent sequential loads.

I believe we can resolve this issue with a useMemo, which updates the default value when userData changes.

Comment on lines +179 to +181
if (userLoading) {
return null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to do this, as it can refetch data, and then we would no longer render the component.

See the next comment

Comment on lines +177 to 191
const { data: userData, loading: userLoading } = useGetUserQuery();

if (userLoading) {
return null;
}

const trimmedLastName = userData?.user?.lastName?.trim();
const defaultNote = trimmedLastName
? t('{{lastName}} Savings Fund Transfer in MPDX', {
lastName: trimmedLastName,
})
: '';

const title =
type === TransferTypeEnum.New
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use useMemo to reinitialise the variable defaultNote upon userData changing.

Suggested change
const { data: userData, loading: userLoading } = useGetUserQuery();
if (userLoading) {
return null;
}
const trimmedLastName = userData?.user?.lastName?.trim();
const defaultNote = trimmedLastName
? t('{{lastName}} Savings Fund Transfer in MPDX', {
lastName: trimmedLastName,
})
: '';
const title =
type === TransferTypeEnum.New
const { data: userData } = useGetUserQuery();
const defaultNote = useMemo(() => {
const trimmedLastName = userData?.user?.lastName?.trim();
return trimmedLastName
? t('{{lastName}} Savings Fund Transfer in MPDX', {
lastName: trimmedLastName,
})
: '';
}, [userData, t]);
const title =
type === TransferTypeEnum.New

Comment on lines +111 to +115
const renderModal = async (props: ComponentsProps = {}) => {
const result = render(<Components {...props} />);
await result.findByText(/Fund Transfer/);
return result;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With now we're using useMemo, I'm unsure if you need this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Preview Environment Add this label to create an Amplify Preview

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants