> ## Documentation Index
> Fetch the complete documentation index at: https://docs.frankieone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# IDV with Review

> IDV flow extended with an OCR review screen so users can verify extracted document data before submission.

## Overview

This flow extends the basic IDV flow by adding an OCR review screen after document capture. After the camera captures and processes the document, the extracted data is shown in a review form for the user to confirm or correct before verification is submitted. This is useful when you want users to verify OCR accuracy.

**Modules used:** [`idv`](/docs/sdk-reference/idv-module), [`form`](/docs/sdk-reference/form-module) (WELCOME, CONSENT, REVIEW, LOADING, RESULT screens)

<Callout icon="star" color="#3DD892" iconType="regular">
  Instead of building from scratch, fork an existing flow from the [public sample codes](https://github.com/FrankieOne/frontend-onesdk-public-sample-codes/).
</Callout>

## Flow Diagram

<Steps>
  <Step title="Welcome Screen">
    Initial greeting and introduction to the verification process.
  </Step>

  <Step title="Consent Screen">
    Capture user consent for data processing.
  </Step>

  <Step title="IDV Capture">
    Camera capture UI for document scanning. Document is automatically detected and captured.
  </Step>

  <Step title="Loading / Extraction">
    SDK extracts data from the captured document. A loading screen is shown.
  </Step>

  <Step title="Review Screen">
    Extracted data is displayed in a review form for user confirmation.
  </Step>

  <Step title="Verification Submitted">
    After review confirmation, verification is submitted automatically.
  </Step>

  <Step title="Result">
    Verification outcome is shown.
  </Step>
</Steps>

<Callout icon="bell" color="#FFCA16" iconType="regular">
  This flow requires a **Google Places API key** (`googleApiKey`) in the form provider configuration. The key powers the address autocomplete field in the review form. Without it, the address search will not work. Get a key from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials) with the Places API enabled.
</Callout>

## Full Implementation

<Tabs>
  <Tab title="Vanilla HTML/JS">
    ```html theme={null}
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>OneSDK IDV with Review</title>
        <script src="https://assets.frankiefinancial.io/one-sdk/v1/oneSdk.umd.js"></script>
        <style>
          body { margin: 0; font-family: sans-serif; background: #fff; }
          #onesdk-wrapper { position: relative; width: 100%; height: 100vh; }
          #onesdk-container { width: 100%; height: 100%; }
          #loading-overlay {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            z-index: 10; background: #fff;
          }
        </style>
      </head>
      <body>
        <div id="onesdk-wrapper">
          <div id="onesdk-container"></div>
          <div id="loading-overlay"></div>
        </div>

        <script>
          async function startOneSDK() {
            try {
              // 1. Generate session token (move to your backend in production)
              const tokenResponse = await fetch(
                "https://backend.kycaml.uat.frankiefinancial.io/auth/v2/machine-session",
                {
                  method: "POST",
                  headers: {
                    authorization: "machine " + btoa("<CUSTOMER_ID>:<CUSTOMER_CHILD_ID>:<API_KEY>"),
                    "Content-Type": "application/json",
                  },
                  body: JSON.stringify({
                    permissions: {
                      preset: "one-sdk",
                      reference: "idv-review-" + Date.now(),
                    },
                  }),
                }
              );
              const session = await tokenResponse.json();

              // 2. Initialize OneSDK
              const oneSdk = await OneSDK({
                session: session,
                mode: "development",
                recipe: {
                  form: {
                    provider: {
                      name: "react",
                      googleApiKey: "<YOUR_GOOGLE_API_KEY>",
                    },
                  },
                },
              });

              const appContainer = "#onesdk-container";

              // 3. Create form and IDV components
              const welcome = oneSdk.component("form", {
                name: "WELCOME",
                type: "ocr",
              });

              const consent = oneSdk.component("form", { name: "CONSENT" });

              const idv = oneSdk.flow("idv");

              const review = oneSdk.component("form", {
                name: "REVIEW",
                type: "ocr",
              });

              const idvLoading = oneSdk.component("form", {
                name: "LOADING",
                title: { label: "Loading..." },
                descriptions: [{ label: "" }],
              });

              const extractLoading = oneSdk.component("form", {
                name: "LOADING",
                title: { label: "Extracting data..." },
                descriptions: [
                  {
                    label: "Hold tight, this can take up to 30 seconds. Please do not refresh this page or click the back button.",
                  },
                ],
              });

              const submitLoading = oneSdk.component("form", {
                name: "LOADING",
                title: { label: "Submitting verification..." },
                descriptions: [
                  {
                    label: "Please do not refresh this page or click the back button.",
                  },
                ],
              });

              const result = oneSdk.component("form", {
                name: "RESULT",
                type: "manual",
                state: "SUCCESS",
                title: { label: "Thanks, you're all done" },
                cta: null,
              });

              // 4. Wire up welcome → consent → IDV
              welcome.on("form:welcome:ready", () => {
                consent.mount(appContainer);
              });

              consent.on("form:consent:ready", () => {
                idvLoading.mount("#loading-overlay");
                idv.mount(appContainer);
              });

              // 5. Hide IDV loading once IDV component is ready
              let detectionStarted = false;

              idv.on("loading", (isLoading) => {
                if (!detectionStarted && !isLoading) {
                  idvLoading.unmount();
                }
              });

              // 6. After document capture, show extraction loading
              idv.on("detection_complete", () => {
                detectionStarted = true;
                extractLoading.mount(appContainer);
              });

              // 7. Handle IDV completion — show review screen
              idv.on("results", ({ checkStatus }) => {
                if (checkStatus) {
                  extractLoading.unmount();
                  review.mount(appContainer);
                }
              });

              // 8. Handle review completion — submit and show result
              review.on("form:review:ready", async () => {
                submitLoading.mount(appContainer);
                try {
                  await oneSdk.individual().submit({ verify: true });
                  result.mount(appContainer);
                } catch (err) {
                  console.error("Verification submission failed:", err);
                  document.getElementById("onesdk-container").innerHTML =
                    "<h2>Verification error</h2><p>Please try again.</p>";
                }
              });

              // 9. Handle errors
              idv.on("error", ({ message }) => {
                console.error("IDV error:", message);
                document.getElementById("onesdk-container").innerHTML =
                  "<h2>Verification error</h2><p>Please try again.</p>";
              });

              // 10. Mount welcome screen to start
              welcome.mount(appContainer);
            } catch (error) {
              console.error("OneSDK initialization failed:", error);
            }
          }

          startOneSDK();
        </script>
      </body>
    </html>
    ```
  </Tab>

  <Tab title="React">
    ```jsx theme={null}
    import { useEffect, useRef } from "react";
    import OneSdk from "@frankieone/one-sdk";

    export default function IDVReviewFlow({ session }) {
      const initRef = useRef(false);

      useEffect(() => {
        if (!session || initRef.current) return;
        initRef.current = true;

        (async () => {
          try {
            const oneSdk = await OneSdk({
              session,
              mode: "development",
              recipe: {
                form: {
                  provider: {
                    name: "react",
                    googleApiKey: import.meta.env.VITE_GOOGLE_PLACES_API_KEY,
                  },
                },
              },
            });

            const appContainer = "#onesdk-container";

            const welcome = oneSdk.component("form", { name: "WELCOME", type: "ocr" });
            const consent = oneSdk.component("form", { name: "CONSENT" });
            const idv = oneSdk.flow("idv");

            const review = oneSdk.component("form", {
              name: "REVIEW",
              type: "ocr",
            });

            const idvLoading = oneSdk.component("form", {
              name: "LOADING",
              title: { label: "Loading..." },
              descriptions: [{ label: "" }],
            });

            const extractLoading = oneSdk.component("form", {
              name: "LOADING",
              title: { label: "Extracting data..." },
              descriptions: [
                {
                  label: "Hold tight, this can take up to 30 seconds. Please do not refresh this page or click the back button.",
                },
              ],
            });

            const submitLoading = oneSdk.component("form", {
              name: "LOADING",
              title: { label: "Submitting verification..." },
              descriptions: [
                {
                  label: "Please do not refresh this page or click the back button.",
                },
              ],
            });

            const result = oneSdk.component("form", {
              name: "RESULT",
              type: "manual",
              state: "SUCCESS",
              title: { label: "Thanks, you're all done" },
              cta: null,
            });

            welcome.on("form:welcome:ready", () => consent.mount(appContainer));
            consent.on("form:consent:ready", () => {
              idvLoading.mount("#loading-overlay");
              idv.mount(appContainer);
            });

            let detectionStarted = false;

            idv.on("loading", (isLoading) => {
              if (!detectionStarted && !isLoading) {
                idvLoading.unmount();
              }
            });

            idv.on("detection_complete", () => {
              detectionStarted = true;
              extractLoading.mount(appContainer);
            });

            idv.on("results", ({ checkStatus }) => {
              if (checkStatus) {
                extractLoading.unmount();
                review.mount(appContainer);
              }
            });

            review.on("form:review:ready", async () => {
              submitLoading.mount(appContainer);
              try {
                await oneSdk.individual().submit({ verify: true });
                result.mount(appContainer);
              } catch (err) {
                console.error("Verification submission failed:", err);
              }
            });

            idv.on("error", ({ message }) => {
              console.error("IDV error:", message);
            });

            welcome.mount(appContainer);
          } catch (error) {
            console.error("OneSDK initialization failed:", error);
          }
        })();
      }, [session]);

      return (
        <div id="onesdk-wrapper" style={{ position: "relative", width: "100%", height: "100vh" }}>
          <div id="onesdk-container" style={{ width: "100%", height: "100%" }} />
          <div
            id="loading-overlay"
            style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", zIndex: 10, background: "#fff" }}
          />
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

## Step-by-Step Breakdown

### 1. Initialize the SDK

Pass the session object to [`OneSDK()`](/docs/sdk-reference/sdk-initialization). Include the `recipe.form.provider` configuration with a `googleApiKey` since this flow uses [form components](/docs/sdk-reference/form-module) for the WELCOME, CONSENT, REVIEW, LOADING, and RESULT screens. The Google Places API key is required for the address autocomplete field in the review form.

### 2. Configure Components

Create WELCOME and CONSENT [form components](/docs/sdk-reference/form-module) to collect user consent before starting capture. Create the [IDV flow](/docs/sdk-reference/idv-module) and form components for the REVIEW, LOADING, and RESULT screens.

### 3. Wire Up Events

Listen for [events](/docs/sdk-reference/events):

* `form:welcome:ready` on welcome — user completed the welcome screen, mount consent
* `form:consent:ready` on consent — user gave consent, mount the IDV flow
* `loading` on IDV — show/hide loading screen while IDV component initializes (before capture)
* `detection_complete` on IDV — document capture finished, show extraction loading screen
* `results` on IDV — extraction complete, mount the review screen
* `form:review:ready` on review — user confirmed data, submit verification

### 4. Handle Results

After the review form emits `form:review:ready`, mount a loading screen, then call [`sdk.individual().submit({ verify: true })`](/docs/sdk-reference/individual-module) and mount the result screen. The loading screen prevents a blank screen during the async submission. See [Error Scenarios](/docs/sdk-reference/error-scenarios) for details.

## Loading Screens

Three loading screens cover the async gaps in this flow:

<Callout icon="bell" color="#FFCA16" iconType="regular">
  **Unmount clears the container.** Calling `.unmount()` on a component removes **all content** from the element it was mounted to — not just the component itself. If you mount a LOADING screen and the IDV flow to the same container, unmounting the loading screen will also destroy the IDV content. Always mount loading screens to a **separate element** when they need to overlay another component.
</Callout>

**IDV loading** — shown while the IDV component initializes. The loading screen is mounted to a separate overlay div (`#loading-overlay`) that sits on top of the main container, so `unmount()` only clears the overlay without affecting the IDV content underneath:

```html theme={null}
<div id="onesdk-wrapper" style="position: relative;">
  <div id="onesdk-container"></div>
  <div id="loading-overlay"></div>
</div>
```

```javascript theme={null}
const idvLoading = oneSdk.component("form", {
  name: "LOADING",
  title: { label: "Loading..." },
  descriptions: [{ label: "" }],
});

// Mount loading to the overlay, IDV to the main container
idvLoading.mount("#loading-overlay");
idv.mount("#onesdk-container");

let detectionStarted = false;

idv.on("loading", (isLoading) => {
  if (!detectionStarted && !isLoading) {
    idvLoading.unmount(); // Safe — only clears #loading-overlay
  }
});
```

**Extraction loading** — shown after document capture while the SDK extracts data. Mount it on `detection_complete` and unmount when `results` fires:

```javascript theme={null}
const extractLoading = oneSdk.component("form", {
  name: "LOADING",
  title: { label: "Extracting data..." },
  descriptions: [
    {
      label: "Hold tight, this can take up to 30 seconds. Please do not refresh this page or click the back button.",
    },
  ],
});

idv.on("detection_complete", () => {
  detectionStarted = true;
  extractLoading.mount(appContainer);
});
```

**Submission loading** — shown after the user confirms the review while `submit()` completes. Mount it immediately in the `form:review:ready` handler before calling `submit()`:

```javascript theme={null}
const submitLoading = oneSdk.component("form", {
  name: "LOADING",
  title: { label: "Submitting verification..." },
  descriptions: [
    {
      label: "Please do not refresh this page or click the back button.",
    },
  ],
});
```

## Review Screen Configuration

The review screen displays extracted OCR data for user confirmation:

```javascript theme={null}
const review = oneSdk.component("form", {
  name: "REVIEW",
  type: "ocr",
});
```

Set `type: "ocr"` so the review form knows to display OCR-extracted fields. See [Form Screens](/docs/sdk-reference/form-module/screens) for customization options.

## Submitting After Review

When the user confirms the review, the `form:review:ready` event fires. Mount a loading screen first so the user is not left with a blank screen during submission:

```javascript theme={null}
review.on("form:review:ready", async () => {
  submitLoading.mount(appContainer);
  try {
    await oneSdk.individual().submit({ verify: true });
    resultScreen.mount(appContainer);
  } catch (error) {
    console.error("Verification submission failed:", error);
  }
});
```

## Customization Reference

| Aspect                     | Reference                                                    |
| -------------------------- | ------------------------------------------------------------ |
| SDK initialization options | [SDK Initialization](/docs/sdk-reference/sdk-initialization) |
| IDV flow configuration     | [IDV Module](/docs/sdk-reference/idv-module)                 |
| Form screen configuration  | [Form Module](/docs/sdk-reference/form-module)               |
| Screen names and types     | [Form Screens](/docs/sdk-reference/form-module/screens)      |
| Individual submission      | [Individual Module](/docs/sdk-reference/individual-module)   |
| Event names and payloads   | [Events](/docs/sdk-reference/events)                         |
| Error handling patterns    | [Error Scenarios](/docs/sdk-reference/error-scenarios)       |
