# Web SDK 2.0 Phone Module

This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation [here](https://developer.incode.com/docs/web-sdk-reference). Contact your Incode Representative for upgrade information and check if you are a candidate for this upgrade.

Full rollout to all clients still TBD.

The Phone module verifies a user's phone number via SMS OTP (One-Time Password).

## Tag  
`<incode-phone>` is a standard Web Component. Importing the UI subpath registers the custom element; importing the CSS applies the module's styles.

### Code Snippet  
```ts
import '@incodetech/web/phone';
import '@incodetech/web/phone/styles.css';
```

## Properties  
Set these as JavaScript properties on the element (not as HTML attributes):

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `config` | `PhoneConfig` | ✅ | Configuration options |
| `onFinish` | `() => void` | ❌ | Called when verification completes |
| `onError` | `(error: string) => void` | ❌ | Called when an error occurs |

## Usage  
### Vanilla HTML / TypeScript  
### Code Snippet  
```html
<incode-phone></incode-phone>

<script type="module">
  import { setup } from '@incodetech/core';
  import '@incodetech/web/phone';
  import '@incodetech/web/phone/styles.css';

await setup({
    apiURL: 'https://demo-api.incodesmile.com',
    token: 'your-session-token',
  });

const phone = document.querySelector('incode-phone');
  phone.config = {
    otpVerification: true,
    otpExpirationInMinutes: 5,
    prefill: false,
  };
  phone.onFinish = () => console.log('Phone verified!');
  phone.onError = (err) => console.error('Phone error:', err);
</script>
```

### React  
### Code Snippet  
```tsx
import { useEffect, useRef } from 'react';
import { setup } from '@incodetech/core';
import type { PhoneConfig } from '@incodetech/core/phone';
import '@incodetech/web/phone';
import '@incodetech/web/phone/styles.css';

type PhoneElement = HTMLElement & {
  config: PhoneConfig;
  onFinish: () => void;
  onError: (error: string) => void;
};

await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'your-session-token',
});

export function PhoneVerification() {
  const ref = useRef<PhoneElement>(null);

useEffect(() => {
    const el = ref.current;
    if (!el) return;
    el.config = {
      otpVerification: true,
      otpExpirationInMinutes: 5,
      prefill: false,
    };
    el.onFinish = () => console.log('Phone verified!');
    el.onError = (err) => console.error('Phone error:', err);
  }, []);

return <incode-phone ref={ref} />;
}
```

## Headless Mode  
For complete UI control, use the `createPhoneManager` from `@incodetech/core/phone`.

### Quick Start  
### Code Snippet  
```tsx
import { setup } from '@incodetech/core';
import { createPhoneManager } from '@incodetech/core/phone';

// Initialize SDK first
await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'your-session-token',
});

const manager = createPhoneManager({
  config: {
    otpVerification: true,
    otpExpirationInMinutes: 5,
    prefill: false,
  },
});

manager.subscribe((state) => {
  console.log('Status:', state.status);

if (state.status === 'finished') {
    console.log('Phone verified!');
    manager.stop();
  }
});

// Start the flow
manager.load();

// When state is 'inputting', set the phone number and submit
manager.setPhoneNumber('+14155551234', true);
manager.submit();

// When state is 'awaitingOtp', submit the OTP code
manager.submitOtp('ABC123');
```

### State Machine Flow  
```plaintext
load

submit

submitOtp

back

resendOtp

idle

inputting

awaitingOtp

finished
```

### States Reference  
| Status | Description | Key Properties |
| --- | --- | --- |
| `idle` | Initial state, waiting for `load()` | – |
| `loadingPrefill` | Fetching pre-filled phone from backend | – |
| `loadingStartInfo` | Fetching start info (`ipIsoCode`, `phonePrefix`) before the field is interactive | `prefilledPhone?`, `otpVerification?`, `optinEnabled?` |
| `inputting` | Ready for phone input | `countryCode`, `phonePrefix`, `prefilledPhone?`, `phoneError?`, `validationErrors?`, `isValid`, `otpVerification?`, `optinEnabled?` |
| `submitting` | Submitting phone number to backend | – |
| `sendingInitialOtp` | Sending OTP code via SMS for the first time | – |
| `resendingOtp` | Resending OTP code via SMS | `otpCode`, `otpLength` |
| `awaitingOtp` | Waiting for user to enter OTP | `resendTimer`, `canResend`, `attemptsRemaining`, `otpCode`, `otpLength`, `otpValidationErrors?`, `isOtpValid` |
| `verifyingOtp` | Verifying OTP code with backend | `resendTimer`, `canResend`, `otpCode`, `otpLength` |
| `otpError` | OTP verification failed | `otpError`, `attemptsRemaining`, `resendTimer`, `canResend`, `otpCode`, `otpLength`, `otpValidationErrors?`, `isOtpValid` |
| `finished` | Verification complete | – |
| `error` | Fatal error occurred | `error` |

### API Methods  
| Method | Description | When to Use |
| --- | --- | --- |
| `load()` | Initializes the flow | Always call first |
| `setPhoneNumber(phone, isValid)` | Sets phone number and validation state | When `inputting`, before `submit()` |
| `submit()` | Submits the phone number | After setting valid phone |
| `submitOtp(code)` | Sets and submits OTP code | When `awaitingOtp` or `otpError` |
| `resendOtp()` | Requests new OTP code | When `canResend` is `true` |
| `back()` | Returns to phone input | When `awaitingOtp` |
| `reset()` | Resets to initial state | After `finished` or `error` |
| `stop()` | Cleanup resources | When unmounting |
| `getState()` | Returns current state synchronously | Anytime |
| `subscribe(callback)` | Subscribe to state changes | Returns unsubscribe function |

### React Example  
### Code Snippet  
```tsx
import { useState, useEffect } from 'react';
import { createPhoneManager, type PhoneState } from '@incodetech/core/phone';

function CustomPhoneVerification() {
  const [manager] = useState(() =>
    createPhoneManager({
      config: {
        otpVerification: true,
        otpExpirationInMinutes: 5,
        prefill: false,
      },
    }),
  );
  const [state, setState] = useState<PhoneState>({ status: 'idle' });
  const [phone, setPhone] = useState('');
  const [otp, setOtp] = useState('');

useEffect(() => {
    const unsubscribe = manager.subscribe(setState);
    manager.load();
    return () => {
      unsubscribe();
      manager.stop();
    };
  }, [manager]);

const handleSubmitPhone = () => {
    const isValid = phone.length >= 10;
    manager.setPhoneNumber(phone, isValid);
    manager.submit();
  };

switch (state.status) {
    case 'inputting':
      return (
        <div>
          <p>
            Country: {state.countryCode} ({state.phonePrefix})
          </p>
          <input
            type="tel"
            value={phone}
            onChange={(e) => setPhone(e.target.value)}
            placeholder="Enter phone number"
          />
          {state.phoneError && <p className="error">{state.phoneError}</p>}
          <button onClick={handleSubmitPhone}>Send OTP</button>
        </div>
      );

case 'awaitingOtp':
      return (
        <div>
          <p>Enter the 6-character code sent to your phone</p>
          <input
            type="text"
            value={otp}
            onChange={(e) => setOtp(e.target.value.toUpperCase())}
            placeholder="ABC123"
            maxLength={6}
          />
          <button onClick={() => manager.submitOtp(otp)}>Verify</button>

{state.canResend ? (
            <button onClick={() => manager.resendOtp()}>Resend Code</button>
          ) : (
            <p>Resend available in {state.resendTimer}s</p>
          )}
        </div>
      );

case 'otpError':
      return (
        <div>
          <p className="error">{state.otpError}</p>
          <p>Attempts remaining: {state.attemptsRemaining}</p>
          <input
            type="text"
            value={otp}
            onChange={(e) => setOtp(e.target.value.toUpperCase())}
            maxLength={6}
          />
          <button onClick={() => manager.submitOtp(otp)}>Try Again</button>
        </div>
      );

case 'finished':
      return <div>Phone verified successfully!</div>;

case 'error':
      return <div className="error">Error: {state.error}</div>;

default:
      return <div>Loading...</div>;
  }
}
```

## Configuration Options  
`PhoneConfig` shape:

| Option | Type | Required | Description |
| --- | --- | --- | --- |
| `otpVerification` | `boolean` | ✅ | Require OTP (SMS code) verification. If `false`, the phone is verified immediately after submission. |
| `otpExpirationInMinutes` | `number` | ✅ | How long the OTP code remains valid. After expiration the user must request a new code. |
| `prefill` | `boolean` | ✅ | Pre-populate with the user's previously stored phone number. Useful for returning users. |
| `isInstantVerify` | `boolean` | ❌ | Use carrier-based instant verification instead of OTP. Not available in all regions. Default `false`. |
| `optinEnabled` | `boolean` | ❌ | Show a marketing opt-in checkbox. The user's preference is sent with the phone submission. Default `false`. |
| `maxOtpAttempts` | `number` | ❌ | Maximum OTP verification attempts before lockout. Default `3`. |

## Troubleshooting  
### OTP Not Received  
- Verify the phone number is correct (include country code)
- Check the phone can receive SMS
- Some VoIP numbers may not receive SMS
- Check with your carrier if SMS is blocked

### Invalid OTP Error  
- OTPs expire after the configured time (default 5 minutes)
- Each OTP can only be used once
- Request a new OTP if the current one expires
- Check `attemptsRemaining` before lockout

### Phone Number Rejected  
- Ensure the number includes country code
- Some numbers may be blocked
- Corporate or VoIP numbers may not be accepted
