Skip to content

feat: enhance RecipientDetailsForm with NGN account validation#479

Open
sundayonah wants to merge 4 commits into
mainfrom
fix/ngn-nuban-10-digit-input
Open

feat: enhance RecipientDetailsForm with NGN account validation#479
sundayonah wants to merge 4 commits into
mainfrom
fix/ngn-nuban-10-digit-input

Conversation

@sundayonah
Copy link
Copy Markdown
Collaborator

@sundayonah sundayonah commented Apr 29, 2026

  • Added logic to cap account number input at 6 or 10 digits based on the selected institution for NGN currency.
  • Implemented validation for account number length, providing user feedback for incorrect input.
  • Updated input type to text for better handling of numeric input and added maxLength attribute for NGN accounts.
  • Improved state management for account identifier registration and manual entry handling.

Description

Describe the purpose of this PR along with any background information and the impacts of the proposed change. For the benefit of the community, please do not assume prior context.

Provide details that support your chosen implementation, including: breaking changes, alternatives considered, changes to the API, contracts etc.

References

Include any links supporting this change such as a:

If there are no references, simply delete this section.

Testing

Describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. If this project has unit and/or integration testing, tests should be added for new functionality and existing tests should complete without errors.

Please include any manual steps for testing end-to-end or functionality not covered by unit/integration tests.

Also include details of the environment this PR was developed in (language/platform/browser version).

  • This change adds test coverage for new/changed/fixed functionality

Checklist

  • I have added documentation and tests for new/changed functionality in this PR
  • All active GitHub checks for tests, formatting, and security are passing
  • The correct base branch is being used, if not main

By submitting a PR, I agree to Paycrest's Contributor Code of Conduct and Contribution Guide.

Summary by CodeRabbit

  • Bug Fixes
    • Account input switched to text field with numeric input mode; autocomplete disabled to prevent unwanted suggestions.
    • Enforced NGN-specific digit rules (6 digits for certain banks, 10 for others) with client-side max length.
    • User input is sanitized/truncated as typed to maintain valid numeric length.
    • Recipient-name lookup now avoids spurious error clearing, reducing false error states.

- Added logic to cap account number input at 6 or 10 digits based on the selected institution for NGN currency.
- Implemented validation for account number length, providing user feedback for incorrect input.
- Updated input type to text for better handling of numeric input and added maxLength attribute for NGN accounts.
- Improved state management for account identifier registration and manual entry handling.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

Centralizes NGN account-digit rules and validation, switches account input from numeric to text with numeric inputMode and sanitization/truncation for NGN, adds NGN-specific maxLength, introduces an accountIdentifierRegister with required + exact-length NGN checks, and updates the recipient-name fetch effect to depend on currency and simplify error clearing.

Changes

Account Identifier Validation & Input Handling

Layer / File(s) Summary
Data derivation
app/components/recipient/RecipientDetailsForm.tsx
Adds ngnAccountMaxDigits memo to derive NGN max digits (6 for SAFAKEPC, otherwise 10) based on currency and selected institution code.
Validation registration
app/components/recipient/RecipientDetailsForm.tsx
Introduces accountIdentifierRegister for accountIdentifier with required validation and NGN-only exact-length checks (6 or 10 digits as derived).
Input wiring / UX
app/components/recipient/RecipientDetailsForm.tsx
Replaces type="number" with type="text", inputMode="numeric", autoComplete="off"; applies NGN-only maxLength; sanitizes input on change (strip non-digits, truncate to NGN max) and wires through the register.
Effect / Fetch logic
app/components/recipient/RecipientDetailsForm.tsx
Updates recipient-name fetching useEffect to include currency in deps, removes prior "length too short" error-clearing branch, and validates/sets NGN digit-length errors using sanitized digits.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • 5ran6
  • onahprosper

Poem

🐰 I tidy digits, hop and mend,
NGN rules sorted end to end,
From number box to text I bound,
I strip, truncate — neat and sound,
Hooray for clean accounts — thump-thump, friend!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR makes NGN account validation changes but is not aligned with linked issue #407, which requires recipient name editing, alerts, analytics tracking, and alert reset logic. Either link this PR to the correct issue addressing NGN account validation, or ensure this PR implements all objectives from issue #407 including RecipientAlert component, isRecipientNameEditable state, and analytics events.
Out of Scope Changes check ⚠️ Warning The PR focuses solely on NGN account validation (input type, maxLength, digit enforcement) which appears orthogonal to issue #407's requirements for recipient name editing and alerts. Clarify whether NGN account validation is a prerequisite or separate feature; if separate, link to correct issue or create new issue reference; if prerequisite, document the relationship in PR description.
Description check ❓ Inconclusive The description covers the key changes but lacks Testing and References sections required by the template, and omits details about alternatives, breaking changes, and development environment. Complete the Testing section with specific steps, environment details, and test coverage information; add References section if applicable or clarify why none exist.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: NGN account validation enhancements for the RecipientDetailsForm.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ngn-nuban-10-digit-input

Review rate limit: 4/5 reviews remaining, refill in 12 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/components/recipient/RecipientDetailsForm.tsx`:
- Around line 431-447: The validation message in accountIdentifierRegister uses
inconsistent capitalization ("account Number"); update the message in the
validate block of accountIdentifierRegister to use "account number" (lowercase
"number") and also make the matching change inside the related useEffect that
sets/uses the same error text so both places (the register validate logic and
the useEffect error handling) use the exact lowercase "account number" phrasing;
locate these by the symbols accountIdentifierRegister, register(...) and the
useEffect that references account number validation for selectedInstitution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 045ada1c-5324-40c8-b302-6031cfd85364

📥 Commits

Reviewing files that changed from the base of the PR and between 1d7a4c3 and c1b9dbb.

📒 Files selected for processing (1)
  • app/components/recipient/RecipientDetailsForm.tsx

Comment thread app/components/recipient/RecipientDetailsForm.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/components/recipient/RecipientDetailsForm.tsx (1)

302-333: ⚠️ Potential issue | 🟠 Major

Clear stale recipient names when the identifier becomes invalid or changes.

These early returns leave the previous recipientName in place, so the UI can keep showing a verified name for an empty/invalid account number. The same effect also accepts late fetchAccountName results from an older input and can write that stale name back after the user has already edited the field.

Suggested fix
   useEffect(() => {
     let timeoutId: NodeJS.Timeout;
+    let isStale = false;
+
     const getRecipientName = async () => {
       if (!isManualEntry) return;
 
       const isNGN = currency === "NGN";
       const digits = String(accountIdentifier ?? "").replace(/\D/g, "");
       const requiredLen = selectedInstitution?.code === "SAFAKEPC" ? 6 : 10;
 
       if (!institution || !accountIdentifier) {
+        setIsFetchingRecipientName(false);
+        setValue("recipientName", "");
         setRecipientNameError("");
         return;
       }
 
       if (isNGN && digits.length !== requiredLen) {
+        setIsFetchingRecipientName(false);
+        setValue("recipientName", "");
         if (digits.length > 0) {
           setRecipientNameError(
             requiredLen === 10
               ? "Please enter a valid 10-digit account number."
               : "Invalid account number. Please enter a 6-digit account number.",
@@
       try {
         const accountName = await fetchAccountName({
           institution: institution.toString(),
           accountIdentifier: accountIdentifier.toString(),
         });
+        if (isStale) return;
         setValue("recipientName", accountName);
         setIsFetchingRecipientName(false);
       } catch (error) {
+        if (isStale) return;
         setRecipientNameError("No recipient account found.");
         setIsFetchingRecipientName(false);
       }
     };
@@
     return () => {
+      isStale = true;
       clearTimeout(timeoutId);
     };
   }, [

Based on learnings, this component uses recipientName as the signal that verification has completed, so leaving an old value behind breaks that contract.

Also applies to: 344-346

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/recipient/RecipientDetailsForm.tsx` around lines 302 - 333,
Early returns leave a stale recipientName in the form and allow out-of-order
fetchAccountName results to overwrite current input; clear the recipient name on
any early return (call setValue("recipientName", "") where you currently return
when !institution || !accountIdentifier and when isNGN digits length is invalid)
and guard the async fetchAccountName result with a request token/sequence check
(generate a local requestId before calling fetchAccountName and only call
setValue("recipientName", accountName) if the requestId still matches) so late
responses from previous inputs cannot write stale names; keep existing
setRecipientNameError and setIsFetchingRecipientName handling around these
changes.
🧹 Nitpick comments (1)
app/components/recipient/RecipientDetailsForm.tsx (1)

91-95: Finish centralizing the NGN account rules into one helper.

The required length and invalid-length copy are still repeated across the memo, the verification effect, the field registration, and the inline maxLength prop. A small local helper like getNgnAccountRules(selectedInstitution?.code) would keep the 6/10-digit rule and message aligned in one place.

Also applies to: 431-447, 582-588

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/recipient/RecipientDetailsForm.tsx` around lines 91 - 95,
Centralize the NGN account validation by extracting a helper function (e.g.,
getNgnAccountRules(selectedInstitutionCode)) that returns the max digit count
and the associated messages/rules (required length and invalid-length copy),
then replace the inline logic and literals currently in ngnAccountMaxDigits
(useMemo), the account verification effect, the field registration rules, and
the inline maxLength prop with values from that helper; ensure all locations
reference getNgnAccountRules(selectedInstitution?.code) so the 6/10 digit rule
and messages stay consistent across the component.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@app/components/recipient/RecipientDetailsForm.tsx`:
- Around line 302-333: Early returns leave a stale recipientName in the form and
allow out-of-order fetchAccountName results to overwrite current input; clear
the recipient name on any early return (call setValue("recipientName", "") where
you currently return when !institution || !accountIdentifier and when isNGN
digits length is invalid) and guard the async fetchAccountName result with a
request token/sequence check (generate a local requestId before calling
fetchAccountName and only call setValue("recipientName", accountName) if the
requestId still matches) so late responses from previous inputs cannot write
stale names; keep existing setRecipientNameError and setIsFetchingRecipientName
handling around these changes.

---

Nitpick comments:
In `@app/components/recipient/RecipientDetailsForm.tsx`:
- Around line 91-95: Centralize the NGN account validation by extracting a
helper function (e.g., getNgnAccountRules(selectedInstitutionCode)) that returns
the max digit count and the associated messages/rules (required length and
invalid-length copy), then replace the inline logic and literals currently in
ngnAccountMaxDigits (useMemo), the account verification effect, the field
registration rules, and the inline maxLength prop with values from that helper;
ensure all locations reference getNgnAccountRules(selectedInstitution?.code) so
the 6/10 digit rule and messages stay consistent across the component.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 57c44daa-daf4-4fee-8c75-052c25ce1e67

📥 Commits

Reviewing files that changed from the base of the PR and between c1b9dbb and 05c1deb.

📒 Files selected for processing (1)
  • app/components/recipient/RecipientDetailsForm.tsx

validate: (value) => {
if (currency !== "NGN") return true;
const digits = String(value ?? "").replace(/\D/g, "");
const requiredLen = selectedInstitution?.code === "SAFAKEPC" ? 6 : 10;
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.

why validating SAFAKEPC when currency is NGN?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

not treating SAFAKEPC as a currency. currency === "NGN" gates NUBAN-style digit rules. selectedInstitution?.code === "SAFAKEPC" is the existing institution-level exception in this app

Comment on lines -296 to -304
if (
!institution ||
!accountIdentifier ||
accountIdentifier.toString().length <
(selectedInstitution?.code === "SAFAKEPC" ? 6 : 10)
) {
if (!institution || !accountIdentifier) {
setRecipientNameError("");
}
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.

why is the minimum length for SAFAKEPC removed here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It wasn’t removed for NGN—it was moved out of the old combined guard that also ran for non-NGN.

Comment on lines +92 to +94
const ngnAccountMaxDigits = useMemo(() => {
if (currency !== "NGN") return null;
return selectedInstitution?.code === "SAFAKEPC" ? 6 : 10;
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.

why check for SAFAKEPC when non-NGN is already returning null?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

return null only means “no digit cap when currency isn’t NGN.”

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/components/recipient/RecipientDetailsForm.tsx (1)

584-590: ⚡ Quick win

Use ngnAccountMaxDigits for maxLength to avoid duplication.

The maxLength attribute recalculates the same logic that ngnAccountMaxDigits already computes. This violates DRY and could lead to divergence if the rules change.

♻️ Proposed fix
                  placeholder="Account number"
-                  maxLength={
-                    currency === "NGN"
-                      ? selectedInstitution?.code === "SAFAKEPC"
-                        ? 6
-                        : 10
-                      : undefined
-                  }
+                  maxLength={ngnAccountMaxDigits ?? undefined}
                   {...accountIdentifierRegister}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/recipient/RecipientDetailsForm.tsx` around lines 584 - 590,
Replace the inline ternary expression used for the input's maxLength with the
existing ngnAccountMaxDigits variable to avoid duplicating the NGN logic; locate
the maxLength prop in RecipientDetailsForm (the JSX where currency === "NGN" ?
... is used) and set maxLength={currency === "NGN" ? ngnAccountMaxDigits :
undefined} (or simply maxLength={currency === "NGN" && ngnAccountMaxDigits}
depending on surrounding code) so the component reuses the computed
ngnAccountMaxDigits value instead of recalculating the same logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/components/recipient/RecipientDetailsForm.tsx`:
- Around line 584-590: Replace the inline ternary expression used for the
input's maxLength with the existing ngnAccountMaxDigits variable to avoid
duplicating the NGN logic; locate the maxLength prop in RecipientDetailsForm
(the JSX where currency === "NGN" ? ... is used) and set maxLength={currency ===
"NGN" ? ngnAccountMaxDigits : undefined} (or simply maxLength={currency ===
"NGN" && ngnAccountMaxDigits} depending on surrounding code) so the component
reuses the computed ngnAccountMaxDigits value instead of recalculating the same
logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 530a55e7-c536-410c-bb27-fadc97c351eb

📥 Commits

Reviewing files that changed from the base of the PR and between 05c1deb and e46339e.

📒 Files selected for processing (1)
  • app/components/recipient/RecipientDetailsForm.tsx

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants