central-auth-js-sdk 1.2.6 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -111,6 +111,9 @@ When **`autoResumeOAuth`** is `true` (default) and **`defaultContainer`** is set
111
111
  | `loginMethods` | object | — | Default login methods (see Login config) |
112
112
  | `frsAsSecondFactor` | boolean | `false` | Default: require face verification after primary auth |
113
113
  | `enableFaceRegistration` | boolean | `true` | Whether sign-up includes face registration step |
114
+ | `collectOrgDetails` | boolean \| object | `true` | Whether to show/collect organization fields (Team, Sub-team, Designation) during registration (`true` \| `false`, or `{ team, subTeam, designation }`). |
115
+ | `mobileVerification` | string | `'india_only'` | Mobile verification mode: `'india_only'` \| `'none'` \| `'all'`. Default `'india_only'` verifies +91 via OTP and auto-skips OTP for international numbers. |
116
+ | `skipMobileOtp` | boolean | `false` | Explicitly skip mobile OTP verification for all numbers when `true`. |
114
117
  | `onContinue` | function | — | Default callback when user clicks Continue on success (see [§ 9](#9-returning-to-the-parent-application)) |
115
118
  | `onBack` | function | — | Default callback when user clicks ← Back on the first screen of signup/login (see [§ 9](#9-returning-to-the-parent-application)) |
116
119
  | `onLoginComplete` | function | — | Default login-complete callback |
@@ -135,6 +138,8 @@ const auth = initializeAuth({
135
138
  appIdentifier: 'my-app',
136
139
  defaultContainer: '#auth-container',
137
140
  autoResumeOAuth: true,
141
+ mobileVerification: 'india_only', // default: verify +91 with OTP, skip for international
142
+ collectOrgDetails: true, // set false to hide Team, Sub-team, Designation
138
143
  loginMethods: {
139
144
  emailPassword: true,
140
145
  emailOtp: true,
@@ -162,6 +167,10 @@ Starts the **sign-up flow** (email + name → verify → mobile → verify → p
162
167
  | `env` | string | no | Environment: `'local'` \| `'dev'` \| `'staging'` \| `'prod'` (default from init or `'local'`) |
163
168
  | `appIdentifier` | string | no | Optional. When provided, sent with relevant API requests; omit to not send. |
164
169
  | `enableFaceRegistration` | boolean | no | Include face registration step (default from init or `true`) |
170
+ | `collectOrgDetails` | boolean \| object | no | Show/collect Team, Sub-team, Designation during registration (default: `true`, set `false` to hide all, or pass `{ team, subTeam, designation }` for granular control). See [§ 5.3](#53-organization-details-team-sub-team--designation). |
171
+ | `showOrgDetails` | boolean | no | Alias for `collectOrgDetails`. |
172
+ | `mobileVerification` | string | no | Mobile OTP verification policy: `'india_only'` (default) \| `'none'` \| `'all'`. See [§ 5.2](#52-mobile-verification--international-numbers-skip-otp). |
173
+ | `skipMobileOtp` | boolean | no | Skip mobile OTP verification directly (`true` / `false`). |
165
174
  | **`onSignupComplete(payload)`** | function | no | Called **once** when signup completes. `payload`: `email`, `mobile`, `userId` + any fields from final API (e.g. `access_token`, `refresh_token`). |
166
175
  | **`onContinue()`** | function | no | Called when user clicks **Continue to application** on the success screen. Use to unmount and redirect. |
167
176
  | **`onBack()`** | function | no | Called when user clicks **← Back** on the **first screen** of signup. Use to unmount the SDK and return to your app. See [§ 9](#9-returning-to-the-parent-application). |
@@ -279,6 +288,122 @@ auth.startLogin({ theme: { primaryColor: '#2563EB' } }); // overrides to blue f
279
288
 
280
289
  ---
281
290
 
291
+ ## 5.2. Mobile verification & international numbers (Skip OTP)
292
+
293
+ By default, the SDK uses **`mobileVerification: 'india_only'`**. This provides frictionless onboarding for international users while preserving carrier-grade SMS/WhatsApp OTP verification for domestic Indian numbers.
294
+
295
+ ### Verification modes
296
+
297
+ | Mode | Behavior | Button Label | WhatsApp OTP Option |
298
+ |------|----------|--------------|---------------------|
299
+ | `'india_only'` *(Default)* | **+91 (India):** Sends OTP via SMS or WhatsApp and requires 6-digit verification.<br>**International (+1, +44, +971, +65, etc.):** Collects phone number, skips OTP verification, and transitions directly to Password Setup. | "Send Code" (+91)<br>"Continue" (International) | Shown for +91, Hidden for International |
300
+ | `'none'` | Skips OTP for **all** phone numbers. Collects mobile number and transitions immediately to Password Setup. | "Continue" | Hidden |
301
+ | `'all'` | Enforces OTP verification for **all** phone numbers regardless of country code. | "Send Code" | Shown |
302
+
303
+ > **Shortcut:** You can also pass `skipMobileOtp: true` in `startSignup` or `initializeAuth` to bypass mobile OTP verification completely.
304
+
305
+ ### How it works under the hood
306
+
307
+ 1. **Dynamic UI:** When an international country code is selected (or when OTP is skipped), the SDK dynamically switches the CTA button from *"Send Code"* to *"Continue"*, hides the *"Send WhatsApp OTP instead"* button, and updates the prompt text to *"Enter your mobile number to continue"*.
308
+ 2. **Flexible length validation:** Indian numbers (`+91`) require $\ge 10$ digits; international numbers accept between 7 and 15 digits (E.164 standard) to support numbers across different countries (e.g. Singapore `+65` with 8 digits, UAE `+971` with 9 digits).
309
+ 3. **Backend integration:**
310
+ - **Standard flow (with OTP):** Calls `POST /signup/mobile/send_otp` $\rightarrow$ user enters OTP $\rightarrow$ calls `POST /signup/mobile/verify`.
311
+ - **Skip OTP flow:** Calls `POST /signup/mobile/save` with `{ email, mobile }` to persist the mobile number, advances user signup status to `MOBILE_VERIFIED`, and transitions directly to `STEPS.PASSWORD`.
312
+
313
+ ### Example configurations
314
+
315
+ #### 1. Default (India OTP only, international numbers skip OTP)
316
+
317
+ No extra configuration needed! This is the out-of-the-box default:
318
+
319
+ ```javascript
320
+ const instance = startSignup({
321
+ container: '#auth-container',
322
+ env: 'dev',
323
+ // mobileVerification: 'india_only' is active by default
324
+ onSignupComplete(payload) {
325
+ console.log('User signed up:', payload);
326
+ },
327
+ });
328
+ ```
329
+
330
+ #### 2. Skip mobile OTP for all countries
331
+
332
+ ```javascript
333
+ const instance = startSignup({
334
+ container: '#auth-container',
335
+ env: 'dev',
336
+ mobileVerification: 'none', // or pass: skipMobileOtp: true
337
+ onSignupComplete(payload) {
338
+ console.log('Signed up without mobile OTP:', payload);
339
+ },
340
+ });
341
+ ```
342
+
343
+ #### 3. Require OTP for all numbers (Global OTP verification)
344
+
345
+ ```javascript
346
+ const instance = startSignup({
347
+ container: '#auth-container',
348
+ env: 'dev',
349
+ mobileVerification: 'all',
350
+ });
351
+ ```
352
+
353
+ ---
354
+
355
+ ## 5.3. Organization details (Team, Sub-team & Designation)
356
+
357
+ During the email verification step, the SDK by default renders fields to collect **Team**, **Sub-team**, and **Designation** alongside First and Last Name.
358
+
359
+ If your application does not need to collect organization details, or if you only want to collect a subset of them, use **`collectOrgDetails`** (or its alias **`showOrgDetails`**).
360
+
361
+ ### Configuration options
362
+
363
+ | Value | Behavior |
364
+ |-------|----------|
365
+ | `true` *(Default)* | Shows all 3 fields (**Team**, **Sub-team**, **Designation**). |
366
+ | `false` | Completely hides **Team**, **Sub-team**, and **Designation** inputs from the registration screen. The user only enters OTP, First Name, and Last Name. |
367
+ | Object `{ team, subTeam, designation }` | Granular control to show only specific fields (e.g. `{ team: true, subTeam: false, designation: true }`). |
368
+
369
+ ### Examples
370
+
371
+ #### 1. Hide all organization fields during signup
372
+
373
+ ```javascript
374
+ const instance = startSignup({
375
+ container: '#auth-container',
376
+ env: 'dev',
377
+ collectOrgDetails: false, // or showOrgDetails: false
378
+ });
379
+ ```
380
+
381
+ #### 2. Granular control (e.g. collect Team and Designation, hide Sub-team)
382
+
383
+ ```javascript
384
+ const instance = startSignup({
385
+ container: '#auth-container',
386
+ env: 'dev',
387
+ collectOrgDetails: {
388
+ team: true,
389
+ subTeam: false,
390
+ designation: true,
391
+ },
392
+ });
393
+ ```
394
+
395
+ #### 3. Set globally via `initializeAuth`
396
+
397
+ ```javascript
398
+ const auth = initializeAuth({
399
+ env: 'dev',
400
+ defaultContainer: '#auth-container',
401
+ collectOrgDetails: false, // will apply to all startSignup calls by default
402
+ });
403
+ ```
404
+
405
+ ---
406
+
282
407
  ## 6. startLogin(config)
283
408
 
284
409
  Starts the **login flow**. If the parent enables **only one** login method, the SDK skips the method picker and goes straight to that flow.
package/auth.js CHANGED
@@ -122,6 +122,9 @@ export function initializeAuth(config = {}) {
122
122
  loginMethods: null,
123
123
  frsAsSecondFactor: false,
124
124
  enableFaceRegistration: true,
125
+ collectOrgDetails: true,
126
+ mobileVerification: 'india_only',
127
+ skipMobileOtp: false,
125
128
  theme: null,
126
129
  ...config,
127
130
  };
@@ -156,6 +159,10 @@ export function initializeAuth(config = {}) {
156
159
  * @param {string} [config.env] - Environment: 'local' | 'dev' | 'staging' | 'prod' (default from init or 'local')
157
160
  * @param {string} [config.appIdentifier] - Override app identifier
158
161
  * @param {boolean} [config.enableFaceRegistration] - Include face registration step (default from init or true)
162
+ * @param {boolean|Object} [config.collectOrgDetails=true] - Whether to show/collect Team, Sub-team, and Designation during registration (or pass object for granular control: { team, subTeam, designation })
163
+ * @param {boolean} [config.showOrgDetails] - Alias for collectOrgDetails
164
+ * @param {string} [config.mobileVerification] - Mobile verification mode: 'india_only' | 'none' | 'all' (default: 'india_only')
165
+ * @param {boolean} [config.skipMobileOtp] - Explicitly skip mobile OTP verification (default: false)
159
166
  * @param {Function} [config.onSignupComplete] - Called when signup completes with (payload). Payload: email, mobile, userId, plus any token/fields from the final API response (e.g. access_token, refresh_token).
160
167
  * @param {Function} [config.onContinue] - Called when user clicks Continue on the success screen (e.g. unmount and redirect).
161
168
  * @param {Function} [config.onSwitchToLogin] - Called when user clicks "Sign in" (e.g. unmount signup and start login, e.g. login + FRS).
@@ -176,6 +183,10 @@ export function startSignup(config = {}) {
176
183
  apiBaseUrl: resolveApiBaseUrl(merged),
177
184
  appIdentifier: merged.appIdentifier,
178
185
  enableFaceRegistration: merged.enableFaceRegistration,
186
+ collectOrgDetails: config.collectOrgDetails ?? config.showOrgDetails ?? merged.collectOrgDetails,
187
+ showOrgDetails: config.showOrgDetails ?? merged.showOrgDetails,
188
+ mobileVerification: config.mobileVerification ?? merged.mobileVerification,
189
+ skipMobileOtp: config.skipMobileOtp ?? merged.skipMobileOtp,
179
190
  theme: merged.theme || undefined,
180
191
  onSignupComplete: config.onSignupComplete ?? merged.onSignupComplete,
181
192
  onContinue: config.onContinue ?? merged.onContinue,
package/core/api.js CHANGED
@@ -77,6 +77,11 @@ export function createApi(baseUrl = DEFAULT_BASE, options = {}) {
77
77
  return request('POST', '/signup/mobile/verify', { email, mobile, otp });
78
78
  },
79
79
 
80
+ /** POST /signup/mobile/save — Save mobile without OTP verification (e.g. international numbers) */
81
+ async signupMobileSave(email, mobile) {
82
+ return request('POST', '/signup/mobile/save', { email, mobile });
83
+ },
84
+
80
85
  /** POST /signup/password/set — Password step */
81
86
  async signupPasswordSet(email, password) {
82
87
  return request('POST', '/signup/password/set', { email, password });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "central-auth-js-sdk",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "description": "Vanilla JS sign-up and login flow SDK — embeddable auth UI with configurable methods",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/signup.js CHANGED
@@ -70,10 +70,20 @@ export function mount(container, options = {}) {
70
70
  onContinue = null,
71
71
  onSwitchToLogin = null,
72
72
  onBack = null,
73
+ skipMobileOtp = false,
74
+ mobileVerification = 'india_only',
75
+ collectOrgDetails = true,
76
+ showOrgDetails,
73
77
  } = options;
74
78
 
75
79
  const flow = new FlowController();
76
80
  const state = new StateManager();
81
+ state.set('skipMobileOtp', skipMobileOtp);
82
+ state.set('mobileVerification', mobileVerification);
83
+ state.set('collectOrgDetails', collectOrgDetails);
84
+ if (showOrgDetails !== undefined) {
85
+ state.set('showOrgDetails', showOrgDetails);
86
+ }
77
87
  const api = createApi(apiBaseUrl);
78
88
 
79
89
  const layout = createLayout(el, () => {});
@@ -172,11 +182,24 @@ export function mount(container, options = {}) {
172
182
  }
173
183
  }
174
184
 
175
- async function handleMobileSubmit(mobile, channel = 'sms') {
185
+ async function handleMobileSubmit(mobile, channel = 'sms', opts = {}) {
176
186
  state.clearError();
177
187
  state.set('mobile', mobile);
178
188
  state.set('mobileChannel', channel);
179
189
  try {
190
+ const shouldSkipOtp = opts?.skipOtp ?? (
191
+ state.get('skipMobileOtp') === true ||
192
+ state.get('mobileVerification') === 'none' ||
193
+ (state.get('mobileVerification') !== 'all' && !mobile.startsWith('+91'))
194
+ );
195
+
196
+ if (shouldSkipOtp) {
197
+ // Skip OTP verification, save mobile directly and advance to password step
198
+ await api.signupMobileSave(state.get('email'), mobile);
199
+ flow.setStep(STEPS.PASSWORD);
200
+ return;
201
+ }
202
+
180
203
  let res;
181
204
  if (isSocial()) {
182
205
  const token = state.get('loginToken');
@@ -189,7 +212,7 @@ export function mount(container, options = {}) {
189
212
  if (res?.channel) state.set('mobileChannel', res.channel);
190
213
  flow.goNext();
191
214
  } catch (err) {
192
- const msg = err?.data?.error || err?.message || 'Failed to send code';
215
+ const msg = err?.data?.error || err?.message || 'Failed to submit mobile number';
193
216
  state.set('error', msg);
194
217
  showError(msg);
195
218
  }
@@ -7,10 +7,54 @@ const MAIL_CHECK = 'M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2
7
7
  const ARROW_LEFT = 'M19 12H5m7 7l-7-7 7-7';
8
8
  const ARROW_RIGHT = 'M5 12h14m-7-7l7 7-7 7';
9
9
 
10
- export function createEmailVerifyScreen(email, onNext, onBack, prefill = {}) {
10
+ export function createEmailVerifyScreen(email, onNext, onBack, prefill = {}, options = {}) {
11
11
  const wrap = document.createElement('div');
12
12
  wrap.className = 'wf-screen';
13
13
 
14
+ const orgOpt = options.collectOrgDetails !== undefined ? options.collectOrgDetails : options.showOrgDetails;
15
+ const showTeam = typeof orgOpt === 'object' && orgOpt !== null ? Boolean(orgOpt.team) : orgOpt !== false;
16
+ const showSubTeam = typeof orgOpt === 'object' && orgOpt !== null ? Boolean(orgOpt.subTeam) : orgOpt !== false;
17
+ const showDesignation = typeof orgOpt === 'object' && orgOpt !== null ? Boolean(orgOpt.designation) : orgOpt !== false;
18
+
19
+ let orgFieldsHtml = '';
20
+ if (showTeam && showSubTeam) {
21
+ orgFieldsHtml += `
22
+ <div class="wf-row-2">
23
+ <div class="wf-field">
24
+ <label class="wf-label" for="wf-team">Team</label>
25
+ <input type="text" id="wf-team" placeholder="Engineering" class="wf-input" autocomplete="organization" />
26
+ </div>
27
+ <div class="wf-field">
28
+ <label class="wf-label" for="wf-sub-team">Sub-team</label>
29
+ <input type="text" id="wf-sub-team" placeholder="Backend" class="wf-input" />
30
+ </div>
31
+ </div>
32
+ `;
33
+ } else if (showTeam) {
34
+ orgFieldsHtml += `
35
+ <div class="wf-field">
36
+ <label class="wf-label" for="wf-team">Team</label>
37
+ <input type="text" id="wf-team" placeholder="Engineering" class="wf-input" autocomplete="organization" />
38
+ </div>
39
+ `;
40
+ } else if (showSubTeam) {
41
+ orgFieldsHtml += `
42
+ <div class="wf-field">
43
+ <label class="wf-label" for="wf-sub-team">Sub-team</label>
44
+ <input type="text" id="wf-sub-team" placeholder="Backend" class="wf-input" />
45
+ </div>
46
+ `;
47
+ }
48
+
49
+ if (showDesignation) {
50
+ orgFieldsHtml += `
51
+ <div class="wf-field">
52
+ <label class="wf-label" for="wf-designation">Designation</label>
53
+ <input type="text" id="wf-designation" placeholder="Software Engineer" class="wf-input" autocomplete="organization-title" />
54
+ </div>
55
+ `;
56
+ }
57
+
14
58
  const otpInputs = Array(6).fill(0).map((_, i) => `
15
59
  <input type="text" inputmode="numeric" maxlength="1" class="wf-otp-input" id="wf-email-otp-${i}" data-idx="${i}" autocomplete="one-time-code" />
16
60
  `).join('');
@@ -38,20 +82,7 @@ export function createEmailVerifyScreen(email, onNext, onBack, prefill = {}) {
38
82
  <input type="text" id="wf-last-name" placeholder="Doe" class="wf-input" required autocomplete="family-name" />
39
83
  </div>
40
84
  </div>
41
- <div class="wf-row-2">
42
- <div class="wf-field">
43
- <label class="wf-label" for="wf-team">Team</label>
44
- <input type="text" id="wf-team" placeholder="Engineering" class="wf-input" autocomplete="organization" />
45
- </div>
46
- <div class="wf-field">
47
- <label class="wf-label" for="wf-sub-team">Sub-team</label>
48
- <input type="text" id="wf-sub-team" placeholder="Backend" class="wf-input" />
49
- </div>
50
- </div>
51
- <div class="wf-field">
52
- <label class="wf-label" for="wf-designation">Designation</label>
53
- <input type="text" id="wf-designation" placeholder="Software Engineer" class="wf-input" autocomplete="organization-title" />
54
- </div>
85
+ ${orgFieldsHtml}
55
86
  <div class="wf-btn-row">
56
87
  <button type="button" class="wf-btn wf-btn-outline wf-btn-lg" data-action="back" aria-label="Go back">
57
88
  <svg class="wf-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${ARROW_LEFT}"/></svg>
@@ -44,11 +44,18 @@ export function createScreen(step, state, flow, callbacks) {
44
44
  team: state.get('team') || '',
45
45
  subTeam: state.get('subTeam') || '',
46
46
  designation: state.get('designation') || '',
47
+ },
48
+ {
49
+ collectOrgDetails: state.get('collectOrgDetails'),
50
+ showOrgDetails: state.get('showOrgDetails'),
47
51
  }
48
52
  );
49
53
 
50
54
  case STEPS.MOBILE:
51
- return createMobileEntryScreen(onMobileSubmit, onBack);
55
+ return createMobileEntryScreen(onMobileSubmit, onBack, {
56
+ skipMobileOtp: state.get('skipMobileOtp'),
57
+ mobileVerification: state.get('mobileVerification'),
58
+ });
52
59
 
53
60
  case STEPS.MOBILE_VERIFY:
54
61
  return createMobileVerifyScreen(
@@ -23,7 +23,7 @@ const COUNTRY_CODES = [
23
23
  { code: "52", label: "+52" },
24
24
  ];
25
25
 
26
- export function createMobileEntryScreen(onNext, onBack) {
26
+ export function createMobileEntryScreen(onNext, onBack, options = {}) {
27
27
  const wrap = document.createElement("div");
28
28
  wrap.className = "wf-screen";
29
29
 
@@ -37,7 +37,7 @@ export function createMobileEntryScreen(onNext, onBack) {
37
37
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${PHONE_ICON}"/></svg>
38
38
  </div>
39
39
  <h2 class="wf-screen-title">Add your mobile number</h2>
40
- <p class="wf-screen-desc">We will send a verification code to confirm this number</p>
40
+ <p class="wf-screen-desc" id="wf-mobile-desc">We will send a verification code to confirm this number</p>
41
41
  </div>
42
42
  <form class="wf-form" id="wf-mobile-form">
43
43
  <div class="wf-field">
@@ -54,11 +54,11 @@ export function createMobileEntryScreen(onNext, onBack) {
54
54
  <svg class="wf-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${ARROW_LEFT}"/></svg>
55
55
  </button>
56
56
  <button type="submit" class="wf-btn wf-btn-primary wf-btn-lg wf-btn-flex" id="wf-btn-send-sms">
57
- Send Code
57
+ <span id="wf-mobile-btn-text">Send Code</span>
58
58
  <svg class="wf-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${ARROW_RIGHT}"/></svg>
59
59
  </button>
60
60
  </div>
61
- <div class="wf-channel-alt">
61
+ <div class="wf-channel-alt" id="wf-mobile-channel-alt">
62
62
  <div class="wf-divider-text"><span>or</span></div>
63
63
  <button type="button" class="wf-btn wf-btn-outline wf-btn-whatsapp-outline wf-btn-lg wf-btn-full" id="wf-btn-send-wa">
64
64
  <svg class="wf-icon-sm" viewBox="0 0 24 24" fill="currentColor"><path d="${WHATSAPP_ICON}"/></svg>
@@ -71,14 +71,55 @@ export function createMobileEntryScreen(onNext, onBack) {
71
71
  const form = wrap.querySelector("#wf-mobile-form");
72
72
  const countrySelect = wrap.querySelector("#wf-mobile-country");
73
73
  const input = wrap.querySelector("#wf-mobile");
74
+ const descEl = wrap.querySelector("#wf-mobile-desc");
75
+ const btnTextEl = wrap.querySelector("#wf-mobile-btn-text");
76
+ const channelAlt = wrap.querySelector("#wf-mobile-channel-alt");
74
77
  const waBtn = wrap.querySelector("#wf-btn-send-wa");
75
78
 
79
+ function isSkipOtp() {
80
+ if (options.skipMobileOtp === true || options.mobileVerification === "none") {
81
+ return true;
82
+ }
83
+ if (options.mobileVerification === "all") {
84
+ return false;
85
+ }
86
+ // Default: india_only -> skip OTP for non-+91 numbers
87
+ return countrySelect.value !== "91";
88
+ }
89
+
90
+ function updateUiState() {
91
+ const skip = isSkipOtp();
92
+ if (skip) {
93
+ if (descEl) descEl.textContent = "Enter your mobile number to continue";
94
+ if (btnTextEl) btnTextEl.textContent = "Continue";
95
+ if (channelAlt) channelAlt.style.display = "none";
96
+ } else {
97
+ if (descEl) descEl.textContent = "We will send a verification code to confirm this number";
98
+ if (btnTextEl) btnTextEl.textContent = "Send Code";
99
+ if (channelAlt) channelAlt.style.display = "";
100
+ }
101
+ }
102
+
103
+ countrySelect.addEventListener("change", updateUiState);
104
+ updateUiState();
105
+
106
+ input.addEventListener("input", () => {
107
+ input.setCustomValidity("");
108
+ });
109
+
76
110
  function submitMobile(channel = "sms") {
77
111
  const digits = input.value.trim().replace(/\D/g, "");
78
- if (digits.length >= 10) {
112
+ const minLen = countrySelect.value === "91" ? 10 : 7;
113
+ if (digits.length >= minLen && digits.length <= 15) {
79
114
  const full = "+" + countrySelect.value + digits;
80
- onNext(full, channel);
115
+ const skipOtp = isSkipOtp();
116
+ onNext(full, channel, { skipOtp });
81
117
  } else {
118
+ input.setCustomValidity(
119
+ digits.length < minLen
120
+ ? `Please enter a valid phone number (at least ${minLen} digits)`
121
+ : "Phone number cannot exceed 15 digits"
122
+ );
82
123
  input.focus();
83
124
  if (typeof input.reportValidity === "function") {
84
125
  input.reportValidity();