Skip to content

Conversation

@franm91
Copy link
Member

@franm91 franm91 commented Jan 19, 2026


Open with Devin

Summary by CodeRabbit

  • New Features

    • New token-based KYC initiation and a user-facing KYC startup flow; changeset added for a patch release.
  • Bug Fixes

    • Improved KYC error messaging ("no KYC", "not started", "bad KYC") and added user-facing toasts.
    • Identity alerts, onboarding steps, and benefits visibility now follow unified KYC status flags.
  • Chores

    • Removed legacy credential/template-based KYC paths and consolidated flows under the new approach.

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link

changeset-bot bot commented Jan 19, 2026

🦋 Changeset detected

Latest commit: cc673ed

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Replaces template-based Persona inquiry flow with a token-based KYC flow: server APIs now return tokens (getKYCTokens/getKYCStatus), persona utilities expose startKYC, a new useBeginKYC hook initiates KYC, and UI components switch to status.code checks (e.g., "ok"/"legacy kyc") and call beginKYC/startKYC.

Changes

Cohort / File(s) Change Summary
Changeset
\.changeset/dark-bars-feel.md
Adds patch changeset for @exactly/mobile noting the new KYC flow.
Server & Tokens
src/utils/server.ts
Renamed/reworked APIs: getKYCLink(templateId, ...)getKYCTokens(scope = "basic", ...); getKYCStatus(templateId)getKYCStatus(scope = "basic"). Payloads and APIError extraction updated; responses now return JSON token/status objects.
Persona & KYC entrypoint
src/utils/persona.ts
Adds startKYC() using getKYCTokens() and one-time links; removes createInquiry, resumeInquiry, and KYC template constants; web/native handling simplified to token-driven flows.
Begin KYC hook
src/utils/useBeginKYC.ts
New hook useBeginKYC() that checks getKYCStatus(), suppresses specific APIError texts, calls startKYC() when needed, invalidates ["kyc","status"], and reports errors/toasts.
Query defaults & error mapping
src/utils/queryClient.ts
Adjusts suppressed error text for ["card","details"] query from "kyc not approved""bad kyc"; removes legacy KYC status default.
Card & Home components
src/components/card/Card.tsx, src/components/home/Home.tsx
Switched to getKYCStatus() (no templateId); derive needsMigration/isKYCApproved from status.code; updated UI conditionals, info alerts, and refetch logic.
Getting Started & Verify flows
src/components/getting-started/GettingStarted.tsx, src/components/home/GettingStarted.tsx, src/components/home/card-upgrade/VerifyIdentity.tsx
Removed credential/inquiry-driven branches; replace internal KYC initiation with useBeginKYC/beginKYC and call startKYC() where required; error suppression/handling updated.
KYC initiation UI changes
src/components/card/*, src/components/home/*
InfoAlert and section rendering now depend on isKYCApproved (status.code "ok" or "legacy kyc"); reveal/activation flows updated to call startKYC/beginKYC.
Minor / tooling
cspell.json, package.json, manifest_file
Adds ignore words "noopener" and "noreferrer"; small manifest/package adjustments.

Sequence Diagram(s)

sequenceDiagram
    participant UI as UI Component
    participant Server as KYC Server (getKYCStatus / getKYCTokens)
    participant Persona as Persona utils (startKYC)
    participant Platform as Platform (Web / Native)

    UI->>Server: getKYCStatus()
    activate Server
    Server-->>UI: { code: "ok" | "legacy kyc" | "not started" | ... }
    deactivate Server

    alt Not approved (e.g., "not started")
        UI->>Persona: startKYC()
        activate Persona
        Persona->>Server: getKYCTokens(scope, redirectURI?)
        activate Server
        Server-->>Persona: { otl, inquiryId, sessionToken }
        deactivate Server

        alt Web
            Persona->>Platform: open OTL URL (oneTimeLink)
            Platform-->>Persona: onComplete / onCancel
        else Native
            Persona->>Platform: init inquiry with inquiryId & sessionToken
            Platform-->>Persona: onComplete / onCancel
        end

        Persona-->>UI: onComplete / onCanceled / onError
        deactivate Persona
    else Approved
        UI-->>UI: render benefits/reveal UI using isKYCApproved
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • cruzdanilo
  • nfmelendez
  • dieguezguille
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the primary change across the pull request: refactoring the KYC (Know Your Customer) flow to use a new implementation, which is evident in all modified files.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch front-kyc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist
Copy link

Summary of Changes

Hello @franm91, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly overhauls the application's Know Your Customer (KYC) integration. The primary goal is to modernize and simplify how users interact with the KYC process, from initial verification to status checks. By introducing a new unified function for starting KYC and standardizing API responses, the changes aim to improve the robustness and maintainability of the identity verification system.

Highlights

  • Refactored KYC Flow: The application's Know Your Customer (KYC) process has been completely refactored to use a new, streamlined flow.
  • Consolidated KYC Initiation: The previous createInquiry and resumeInquiry functions have been replaced by a single startKYC utility, simplifying the initiation and resumption of KYC.
  • Standardized KYC Status Handling: The getKYCStatus function now returns a structured object with a code property, and error messages have been updated for consistency (e.g., "no kyc", "bad kyc").
  • Removed Legacy KYC Template IDs: Constants for KYC_TEMPLATE_ID and LEGACY_KYC_TEMPLATE_ID have been removed, indicating a shift away from template-specific KYC logic.
  • Simplified KYC API Integration: The getKYCLink function has been replaced by getKYCTokens, which is used to obtain necessary tokens for the KYC process.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the KYC flow to use a new, unified startKYC function, which simplifies the client-side logic across several components. The changes are largely consistent and improve maintainability by centralizing the KYC initiation process. I've identified a critical regression in the web platform logic that affects mini-app functionality, and a medium-severity issue concerning redundant error reporting in one component. Addressing these points will ensure the new flow is both robust and correct.

Comment on lines 31 to 38
} catch (error) {
if (!(error instanceof APIError)) {
reportError(error);
return;
throw error;
}
if (error.text === "kyc required" || error.text === "kyc not found" || error.text === "kyc not started") {
await createInquiry(credential);
return;
if (error.text !== "not started" && error.text !== "no kyc") {
reportError(error);
throw error;
}
reportError(error);
}

Choose a reason for hiding this comment

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

medium

The reportError calls within the catch block of the mutationFn are redundant. Since the errors are re-thrown, they will be handled by the onError callback of the useMutation hook, which also calls reportError. This results in double reporting of the same error.

To fix this, you should remove the reportError calls from within the mutationFn's catch block.

      } catch (error) {
        if (!(error instanceof APIError)) {
          throw error;
        }
        if (error.text !== "not started" && error.text !== "no kyc") {
          throw error;
        }
      }

Copy link
Member

Choose a reason for hiding this comment

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

@coderabbitai review this finding, especially considering the latest force-pushed code

Copy link

Choose a reason for hiding this comment

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

@cruzdanilo The finding from gemini-code-assist has been addressed in the latest force-pushed code.

Looking at the current implementation (lines 31-38), the catch block no longer contains any reportError calls:

} catch (error) {
  if (!(error instanceof APIError)) {
    throw error;
  }
  if (error.text !== "not started" && error.text !== "no kyc") {
    throw error;
  }
}

All errors are now properly thrown and handled exclusively by the onError callback (line 48), which calls reportError(error) once. This eliminates the double reporting issue that was flagged. ✅


🧠 Learnings used
Learnt from: franm91
Repo: exactly/exa PR: 653
File: src/components/home/card-upgrade/VerifyIdentity.tsx:27-29
Timestamp: 2026-01-22T19:01:50.250Z
Learning: In `src/components/home/card-upgrade/VerifyIdentity.tsx`, the KYC mutation logic intentionally differs from the `GettingStarted.tsx` variants. The differences include calling `queryClient.setQueryData(["card-upgrade"], 1)` on success status and showing a toast via `onError` callback. These are intentional flow-specific behaviors for the card upgrade context.

Learnt from: franm91
Repo: exactly/exa PR: 653
File: src/components/getting-started/GettingStarted.tsx:132-133
Timestamp: 2026-01-22T18:44:46.740Z
Learning: In the KYC flow refactor, the `{ inquiryId, sessionToken }` response from the KYC API is a legacy format that was only returned when a `templateId` parameter was provided. This legacy format is being deprecated. The new flow in `src/components/getting-started/GettingStarted.tsx` and related files calls `getKYCStatus()` without the `templateId` parameter, so the legacy response shape will never be returned, and the `"code" in status` type guard is sufficient.
<!--

Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/app.mdc:0-0
Timestamp: 2025-12-23T19:56:31.043Z
Learning: Applies to src/**/*.{ts,tsx} : Use TanStack Query mutations for any server-side effects (creating, updating, deleting data). Handle `onSuccess` and `onError` to invalidate relevant queries and provide user feedback.

@sentry
Copy link

sentry bot commented Jan 19, 2026

Codecov Report

❌ Patch coverage is 35.59322% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.58%. Comparing base (c6a1241) to head (cc673ed).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/utils/useBeginKYC.ts 25.00% 12 Missing ⚠️
...rc/components/home/card-upgrade/VerifyIdentity.tsx 0.00% 11 Missing ⚠️
src/utils/persona.ts 0.00% 6 Missing ⚠️
src/utils/server.ts 50.00% 6 Missing ⚠️
src/components/getting-started/GettingStarted.tsx 50.00% 1 Missing ⚠️
src/components/home/GettingStarted.tsx 50.00% 1 Missing ⚠️
src/utils/queryClient.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #653      +/-   ##
==========================================
+ Coverage   61.05%   61.58%   +0.52%     
==========================================
  Files         168      169       +1     
  Lines        5272     5248      -24     
  Branches     1491     1487       -4     
==========================================
+ Hits         3219     3232      +13     
+ Misses       1892     1854      -38     
- Partials      161      162       +1     
Flag Coverage Δ
e2e 61.22% <35.59%> (+1.35%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@franm91
Copy link
Member Author

franm91 commented Jan 21, 2026

@coderabbitai are the findings fixed?

@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Get PR details
echo "=== PR Details ==="
gh pr view 653 --json title,state,author,reviews,reviewDecision

echo -e "\n=== Review Comments from CodeRabbit ==="
# Get all review comments from coderabbitai
gh api repos/:owner/:repo/pulls/653/comments --paginate | jq '.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, path: .path, line: .line, body: .body, created_at: .created_at, in_reply_to_id: .in_reply_to_id}'

echo -e "\n=== Unresolved Conversation Threads ==="
# Check for unresolved threads
gh pr view 653 --json reviewThreads | jq '.reviewThreads[] | select(.isResolved == false) | {path: .path, line: .line, isResolved: .isResolved, comments: [.comments[] | {author: .author.login, body: .body}]}'

Length of output: 1753


🏁 Script executed:

#!/bin/bash

echo "=== All PR Reviews ==="
gh api repos/:owner/:repo/pulls/653/reviews --paginate | jq '.[] | {id: .id, user: .user.login, state: .state, body: .body, submitted_at: .submitted_at}'

echo -e "\n=== PR Issue Comments ==="
gh api repos/:owner/:repo/issues/653/comments --paginate | jq '.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, created_at: .created_at, body: .body[0:500]}'

echo -e "\n=== Latest Reviews Detail ==="
gh pr view 653 --json latestReviews | jq '.latestReviews[] | {author: .author.login, state: .state, body: .body[0:500]}'

echo -e "\n=== All Review Comments (line comments) ==="
gh api repos/:owner/:repo/pulls/653/comments --paginate | jq '.[] | {user: .user.login, path: .path, line: .line, body: .body[0:200], created_at: .created_at}'

Length of output: 2687


🏁 Script executed:

#!/bin/bash

echo "=== Full Comment Thread for persona.ts finding ==="
# Get the specific comment and any replies
gh api repos/:owner/:repo/pulls/653/comments --paginate | jq '.[] | select(.path == "src/utils/persona.ts" and .user.login == "gemini-code-assist[bot]") | {id: .id, body: .body, created_at: .created_at}'

echo -e "\n=== Replies to persona.ts comment ==="
COMMENT_ID=$(gh api repos/:owner/:repo/pulls/653/comments --paginate | jq -r '.[] | select(.path == "src/utils/persona.ts" and .user.login == "gemini-code-assist[bot]") | .id' | head -1)
if [ ! -z "$COMMENT_ID" ]; then
  gh api repos/:owner/:repo/pulls/653/comments --paginate | jq --arg cid "$COMMENT_ID" '.[] | select(.in_reply_to_id == ($cid | tonumber))'
fi

echo -e "\n=== Full Comment Thread for VerifyIdentity.tsx finding ==="
gh api repos/:owner/:repo/pulls/653/comments --paginate | jq '.[] | select(.path == "src/components/home/card-upgrade/VerifyIdentity.tsx" and .user.login == "gemini-code-assist[bot]") | {id: .id, body: .body, created_at: .created_at}'

echo -e "\n=== Replies to VerifyIdentity.tsx comment ==="
COMMENT_ID2=$(gh api repos/:owner/:repo/pulls/653/comments --paginate | jq -r '.[] | select(.path == "src/components/home/card-upgrade/VerifyIdentity.tsx" and .user.login == "gemini-code-assist[bot]") | .id' | head -1)
if [ ! -z "$COMMENT_ID2" ]; then
  gh api repos/:owner/:repo/pulls/653/comments --paginate | jq --arg cid "$COMMENT_ID2" '.[] | select(.in_reply_to_id == ($cid | tonumber))'
fi

echo -e "\n=== Latest commit on PR ==="
gh api repos/:owner/:repo/pulls/653 | jq '{head_sha: .head.sha, updated_at: .updated_at}'

Length of output: 2178


🏁 Script executed:

#!/bin/bash

echo "=== Checking src/utils/persona.ts ==="
cat src/utils/persona.ts

echo -e "\n\n=== Checking src/components/home/card-upgrade/VerifyIdentity.tsx ==="
cat src/components/home/card-upgrade/VerifyIdentity.tsx

Length of output: 5337


@franm91 Yes, both findings from gemini-code-assist appear to be fixed!

I checked the current code state and can confirm:

  1. ✅ Critical issue in src/utils/persona.ts - FIXED

    • The sdk.isInMiniApp() check now comes before the embeddingContext check (lines 19-26), which is the correct order to prevent regression in mini-app functionality
    • This matches the suggested fix exactly
  2. ✅ Medium issue in src/components/home/card-upgrade/VerifyIdentity.tsx - FIXED

    • The redundant reportError calls have been removed from the catch block (lines 31-38)
    • Error reporting now only happens in the onError callback (line 48), avoiding double reporting
    • This matches the suggested fix exactly

Both issues were addressed in commits after the findings were posted (PR updated on Jan 21, findings posted on Jan 19).

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional flags.

Open in Devin Review

Base automatically changed from kyc to main January 22, 2026 14:38
Copy link

@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: 0

Caution

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

⚠️ Outside diff range comments (3)
src/components/home/GettingStarted.tsx (1)

24-54: Add explicit onError and onSuccess handlers to the KYC mutation.

Per coding guidelines, mutations must use onSuccess and onError callbacks to handle cache invalidation and user feedback. Currently, error handling is buried in mutationFn and invalidation happens only in onSettled. Move error reporting to onError, use onSuccess for controlled invalidation, and remove the .catch(reportError) at the call site to prevent duplicate logging.

src/components/getting-started/GettingStarted.tsx (1)

128-157: Use onError callback for the KYC mutation to align with coding guidelines.

The current code embeds error handling in mutationFn with error reporting only at the call site via .catch(reportError). Per coding guidelines, KYC mutations should handle onSuccess and onError to provide user feedback and manage cache invalidation. Move error reporting from .catch() at the call site to a dedicated onError handler in the mutation definition, keep the cache invalidation in onSettled, and remove the .catch(reportError) wrapper at the call site to avoid duplicate handling.

src/utils/persona.ts (1)

12-47: Rename otl to a descriptive local identifier.

otl is terse; a clearer local name improves readability.

♻️ Proposed rename
-export async function startKYC() {
-  const { otl, inquiryId, sessionToken } = await getKYCTokens("basic", await getRedirectURI());
+export async function startKYC() {
+  const { otl: oneTimeLink, inquiryId, sessionToken } = await getKYCTokens("basic", await getRedirectURI());
 
   if (Platform.OS === "web") {
     if (await sdk.isInMiniApp()) {
-      await sdk.actions.openUrl(otl);
+      await sdk.actions.openUrl(oneTimeLink);
       return;
     }
     const embeddingContext = queryClient.getQueryData<EmbeddingContext>(["embedding-context"]);
     if (embeddingContext && !embeddingContext.endsWith("-web")) {
-      window.location.replace(otl);
+      window.location.replace(oneTimeLink);
       return;
     }
-    window.open(otl);
+    window.open(oneTimeLink);
     return;
   }
As per coding guidelines, prefer full-word identifiers over abbreviations.
♻️ Duplicate comments (1)
src/components/home/card-upgrade/VerifyIdentity.tsx (1)

70-74: Avoid double‑reporting errors from mutateAsync.

onError already calls reportError, and the .catch(reportError) in the press handler will log the same error twice. Call beginKYC() without the catch (or void beginKYC()) to prevent duplicate reporting.

🔧 Proposed fix
-            beginKYC().catch(reportError);
+            void beginKYC();
TanStack Query v5 mutateAsync promise rejection handling onError

Copy link

@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: 2

Caution

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

⚠️ Outside diff range comments (2)
src/components/getting-started/GettingStarted.tsx (1)

128-149: Add error handler to surface KYC verification failures to the user.

The mutation currently swallows errors by catching them in mutationFn and returning early, which prevents onError from firing and leaves users without feedback. Rethrow non-gate errors so they propagate to onError, where you can display a toast and report the error, consistent with the mutation guidance and patterns used in VerifyIdentity.tsx.

♻️ Suggested change
   const { mutateAsync: beginKYC } = useMutation({
     mutationKey: ["kyc"],
     mutationFn: async () => {
       try {
         const status = await getKYCStatus();
         if ("code" in status && (status.code === "ok" || status.code === "legacy kyc")) return;
       } catch (error) {
-        if (!(error instanceof APIError)) {
-          reportError(error);
-          return;
-        }
-        if (error.text !== "not started" && error.text !== "no kyc") {
-          reportError(error);
-          return;
-        }
+        if (!(error instanceof APIError)) throw error;
+        if (error.text !== "not started" && error.text !== "no kyc") throw error;
       }
       await startKYC();
     },
+    onError: (error) => {
+      toast.show(t("Error starting verification"), {
+        native: true,
+        duration: 1000,
+        burntOptions: { haptic: "error", preset: "error" },
+      });
+      reportError(error);
+    },
     onSettled: async () => {
       await queryClient.invalidateQueries({ queryKey: ["kyc", "status"] });
     },
   });
src/components/home/Home.tsx (1)

145-155: Update the hardcoded upgrade deadline.

The alert shows May 18, 2025, which is already in the past as of January 22, 2026. Please update the date or source it from a config/server value to avoid showing an expired deadline.

🤖 Fix all issues with AI agents
In `@src/utils/persona.ts`:
- Around line 16-30: The window.open call in startKYC should include noopener
and noreferrer to prevent reverse-tabnabbing; change the invocation of
window.open(otl) inside startKYC to open a new tab with target '_blank' and the
features string 'noopener,noreferrer' (e.g. window.open(otl, '_blank',
'noopener,noreferrer')), or alternatively null out window.opener on the returned
window; update the window.open usage in the startKYC function accordingly.

In `@src/utils/server.ts`:
- Around line 151-159: Add valibot schemas KycTokensSchema and KycStatusSchema
to `@exactly/common/validation` and use them to validate API responses in
getKYCTokens and getKYCStatus: after calling api.kyc.$post (in getKYCTokens) and
the corresponding api call in getKYCStatus, parse the response JSON and run it
through the appropriate schema (KycTokensSchema for getKYCTokens expecting {
inquiryId, otl, sessionToken, legacy } and KycStatusSchema for getKYCStatus
expecting { code: "ok" | "not started" | "legacy kyc" }), throwing a
validation/error if parsing fails, and return the validated/parsed value instead
of the raw response.json() result. Ensure you import the schemas from
`@exactly/common/validation` and use the valibot parsing function consistent with
project conventions.
♻️ Duplicate comments (1)
src/components/home/card-upgrade/VerifyIdentity.tsx (1)

71-74: Avoid double‑reporting mutation errors.

onError already calls reportError, so the catch(reportError) duplicates logging. Keep the catch to prevent unhandled rejections, but make it a no‑op.

♻️ Suggested change
-            beginKYC().catch(reportError);
+            beginKYC().catch(() => undefined);

Copy link

@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: 0

Caution

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

⚠️ Outside diff range comments (2)
src/components/home/Home.tsx (1)

145-156: Update the migration deadline copy (currently May 18, 2025).

The new condition can now show this alert, but the displayed deadline is May 18, 2025, which is in the past relative to January 22, 2026. Consider sourcing the deadline from config/server or updating the date to avoid misleading users.

src/components/getting-started/GettingStarted.tsx (1)

128-157: Prevent multiple KYC mutations with in-flight guard.

Line 156 can trigger repeated calls to beginKYC(), potentially starting multiple KYC sessions. Add isPending to the mutation destructuring and guard the handler to prevent execution while the mutation is in flight.

Suggested fix
-  const { mutateAsync: beginKYC } = useMutation({
+  const { mutateAsync: beginKYC, isPending: isBeginningKYC } = useMutation({
     mutationKey: ["kyc"],
     mutationFn: async () => {
       try {
         const status = await getKYCStatus();
         if ("code" in status && (status.code === "ok" || status.code === "legacy kyc")) return;
       } catch (error) {
         if (!(error instanceof APIError)) {
           reportError(error);
           return;
         }
         if (error.text !== "not started" && error.text !== "no kyc") {
           reportError(error);
           return;
         }
       }
       await startKYC();
     },
     onSettled: async () => {
       await queryClient.invalidateQueries({ queryKey: ["kyc", "status"] });
     },
   });
   function handleAction() {
+    if (currentStep?.id === "verify-identity" && isBeginningKYC) return;
     switch (currentStep?.id) {
       case "add-funds":
         router.push("/add-funds/add-crypto");
         break;
       case "verify-identity":
         beginKYC().catch(reportError);
         break;
♻️ Duplicate comments (3)
src/utils/server.ts (1)

151-169: Validate KYC responses with valibot before returning.

getKYCTokens() and getKYCStatus() return unvalidated JSON. Define and apply KycTokensSchema / KycStatusSchema and parse responses to avoid runtime shape drift. As per coding guidelines, runtime API responses must be validated with valibot.

#!/bin/bash
# Locate existing schemas/usages for KYC responses
rg -n "KycTokensSchema|KycStatusSchema" -t ts -t tsx
rg -n "getKYCTokens|getKYCStatus" src/utils/server.ts
src/components/home/card-upgrade/VerifyIdentity.tsx (1)

70-74: Avoid double-reporting errors from mutateAsync.

onError already calls reportError; the .catch(reportError) here will duplicate reporting. Prefer relying on onError or handling errors in one place.

♻️ Suggested change
-            beginKYC().catch(reportError);
+            void beginKYC();
TanStack Query useMutation mutateAsync onError promise rejection behavior
src/utils/persona.ts (1)

16-30: Add noopener,noreferrer to the window.open call.

Line 29 opens a new tab without these flags, enabling reverse‑tabnabbing. Use a safe target+features string (or null out window.opener).

🔒 Suggested fix
-    window.open(otl);
+    window.open(otl, "_blank", "noopener,noreferrer");

@franm91 franm91 force-pushed the front-kyc branch 2 times, most recently from 314c4f8 to 66fb2a3 Compare January 22, 2026 17:10
Copy link

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
src/components/getting-started/GettingStarted.tsx (1)

128-149: Consider extracting shared KYC mutation logic.

This mutation logic is nearly identical to src/components/home/GettingStarted.tsx (lines 24-45). Consider extracting a shared hook or utility to reduce duplication and ensure consistent behavior across components.

// e.g., in src/utils/useBeginKYC.ts
export function useBeginKYC() {
  return useMutation({
    mutationKey: ["kyc"],
    mutationFn: async () => {
      try {
        const status = await getKYCStatus();
        if ("code" in status && (status.code === "ok" || status.code === "legacy kyc")) return;
      } catch (error) {
        if (!(error instanceof APIError)) {
          reportError(error);
          return;
        }
        if (error.text !== "not started" && error.text !== "no kyc") {
          reportError(error);
          return;
        }
      }
      await startKYC();
    },
    onSettled: async () => {
      await queryClient.invalidateQueries({ queryKey: ["kyc", "status"] });
    },
  });
}
🤖 Fix all issues with AI agents
In `@src/components/getting-started/GettingStarted.tsx`:
- Around line 132-133: getKYCStatus currently returns response.json() without an
explicit return type, so callers using the runtime guard ("code" in status) miss
the branch where the API returns { inquiryId, sessionToken } (no code). Add a
discriminated union return type (e.g. KycOk | KycLegacy | KycPending |
KycExpired | KycError — matching all response shapes from the KYC API) to
getKYCStatus, update its signature to return Promise<YourKycUnion>, and adjust
the caller in GettingStarted (the code that awaits getKYCStatus and uses "code"
in status) to explicitly handle the inquiry/sessionToken branch (check for
inquiryId or sessionToken) and all code-bearing branches (check status.code ===
"ok" | "legacy kyc" | other error codes) so each union case is handled
deterministically.

In `@src/components/home/card-upgrade/VerifyIdentity.tsx`:
- Around line 27-29: The KYC mutation in VerifyIdentity.tsx diverges from the
GettingStarted.tsx variants by calling
queryClient.setQueryData(["card-upgrade"], 1) and showing a toast on success;
please either add a short comment in VerifyIdentity.tsx documenting this
intentional difference (referencing the mutation handler and the
queryClient.setQueryData(["card-upgrade"], 1) line and the toast call) or
refactor to consolidate the shared KYC success handling into a single helper
(e.g., a handleKycSuccess function used by both VerifyIdentity and
GettingStarted) so the mutation behavior is consistent and maintainable across
components.

In `@src/components/home/GettingStarted.tsx`:
- Line 53: The .catch(reportError) call on beginKYC() in GettingStarted.tsx is
redundant because the mutation's catch block already calls reportError and
returns; remove the trailing .catch(reportError) from the beginKYC() invocation
to avoid misleading double-handling, or alternatively change the mutation inside
beginKYC (the function referenced) to rethrow errors and add a unified onError
handler like in VerifyIdentity.tsx—either remove the .catch(reportError) or make
the mutation throw and wire an onError so error handling is consistent with
VerifyIdentity.tsx.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View issue and 14 additional flags in Devin Review.

Open in Devin Review

Copy link

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
src/components/getting-started/GettingStarted.tsx (1)

128-149: Consider adding an onError handler for consistency.

Unlike src/components/home/GettingStarted.tsx which has an onError callback showing a toast and reporting errors, this mutation silently fails if startKYC() throws. The .catch(reportError) at line 156 handles error reporting, but users won't see feedback.

♻️ Optional: Add onError for user feedback
     onSettled: async () => {
       await queryClient.invalidateQueries({ queryKey: ["kyc", "status"] });
     },
+    onError: (error) => {
+      reportError(error);
+    },
   });

Or add a toast similar to home/GettingStarted.tsx for consistent UX.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View issue and 14 additional flags in Devin Review.

Open in Devin Review

@franm91 franm91 force-pushed the front-kyc branch 2 times, most recently from 55e75bc to 7f87603 Compare January 22, 2026 20:01
Copy link

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
src/components/home/card-upgrade/VerifyIdentity.tsx (1)

22-52: Move KYC side‑effects out of src/components to keep this presentational.

This component now owns server‑side KYC mutation logic. Per project guidelines for src/components/**, this should be a dumb/presentational component that receives data and callbacks via props. Consider lifting the mutation to a screen/container (e.g., src/app/**) or a dedicated hook, and pass beginKYC/isPending as props. As per coding guidelines, keep src/components free of app state and side‑effects.

🤖 Fix all issues with AI agents
In `@src/components/getting-started/GettingStarted.tsx`:
- Around line 130-144: The KYC mutationFn in GettingStarted (and duplicated in
home/GettingStarted) should be extracted into a shared hook (e.g., useBeginKYC)
to remove duplication: create a hook that returns useMutation with the same
mutationKey ["kyc"], mutationFn logic (call getKYCStatus, handle APIError cases,
call startKYC), and the same onSettled/onError behavior (invalidate
["kyc","status"], toast + reportError), then replace local useMutation calls in
both components with const { mutate: beginKYC, isPending } = useBeginKYC(); so
callers import and use useBeginKYC instead of duplicating mutationFn logic.

In `@src/components/home/Home.tsx`:
- Line 144: The JSX condition combining isKYCFetched, needsMigration, bytecode,
installedPlugins, and isLatestPlugin is hard to read; extract two boolean
variables (e.g., showKycMigration = isKYCFetched && needsMigration and
showPluginOutdated = !!bytecode && !!installedPlugins && !isLatestPlugin) then
replace the inline expression with (showKycMigration || showPluginOutdated) in
the render to make the intent clearer and easier to maintain.
♻️ Duplicate comments (1)
src/utils/server.ts (1)

151-169: Validate KYC responses before returning JSON.

getKYCTokens and getKYCStatus return unvalidated JSON; please parse with valibot schemas before returning. This matches the runtime validation requirement for API responses. As per coding guidelines, use valibot for all runtime validation.

♻️ Proposed fix (within this file)
 export async function getKYCTokens(scope: "basic" = "basic", redirectURI?: string) {
   await auth();
   const response = await api.kyc.$post({ json: { scope, redirectURI } });
   if (!response.ok) {
     const { code } = await response.json();
     throw new APIError(response.status, code);
   }
-  return response.json();
+  const json = await response.json();
+  return parse(KycTokensSchema, json);
 }
 
 export async function getKYCStatus(scope: "basic" = "basic") {
   await auth();
   const response = await api.kyc.$get({ query: { scope } });
   queryClient.setQueryData(["user", "country"], response.headers.get("User-Country"));
   if (!response.ok) {
     const { code } = await response.json();
     throw new APIError(response.status, code);
   }
-  return response.json();
+  const json = await response.json();
+  return parse(KycStatusSchema, json);
 }

Please also add/extend KycTokensSchema and KycStatusSchema in @exactly/common/validation and import them here.

#!/bin/bash
# Verify whether KycTokensSchema/KycStatusSchema exist and are used for KYC responses.
# Expected: schema definitions and parse/safeParse usage for KYC response handling.

rg -n "KycTokensSchema|KycStatusSchema" -g '*.ts' -g '*.tsx'
rg -n "parse\(|safeParse\(" src/utils/server.ts

Copy link

@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: 2

🤖 Fix all issues with AI agents
In `@src/utils/persona.ts`:
- Around line 16-30: The variable name otl in startKYC is an unclear
abbreviation; rename it to a descriptive local name (e.g., kycUrl or
onboardingLink) everywhere it's referenced in the function (the destructuring
from getKYCTokens and subsequent uses in sdk.actions.openUrl(otl),
window.location.replace(otl), and window.open(otl,...)) to improve readability
while preserving behavior and return values; ensure you update all occurrences
within startKYC and keep inquiryId and sessionToken unchanged.

In `@src/utils/useBeginKYC.ts`:
- Around line 16-38: The mutation object in useBeginKYC currently uses full
function expressions for keys mutationFn, onSettled and onError; update them to
use object method shorthand (e.g. mutationFn: async () => { ... } -> async
mutationFn() { ... }, same for async onSettled() and onError(error)) while
preserving existing logic and async behavior and keeping references to
getKYCStatus, startKYC, queryClient.invalidateQueries, toast.show and
reportError intact.

@cruzdanilo cruzdanilo merged commit cc673ed into main Jan 22, 2026
12 of 14 checks passed
@cruzdanilo cruzdanilo deleted the front-kyc branch January 22, 2026 21:38
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