@abstraxn/signer-react 2.1.5 → 2.2.0

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/src/WalletModal.css +0 -1
  3. package/dist/src/WalletModal.js +10 -4
  4. package/dist/src/WalletModal.js.map +1 -1
  5. package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js +557 -81
  6. package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js.map +1 -1
  7. package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.d.ts +2 -1
  8. package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.js +72 -8
  9. package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.js.map +1 -1
  10. package/dist/src/components/AbstraxnProvider/useWalletInitialization.js +16 -11
  11. package/dist/src/components/AbstraxnProvider/useWalletInitialization.js.map +1 -1
  12. package/dist/src/components/AbstraxnProvider/utils.d.ts +5 -0
  13. package/dist/src/components/AbstraxnProvider/utils.js +9 -0
  14. package/dist/src/components/AbstraxnProvider/utils.js.map +1 -1
  15. package/dist/src/components/OnboardingUI/OnboardingUI.css +186 -6
  16. package/dist/src/components/OnboardingUI/OnboardingUIReact.js +18 -4
  17. package/dist/src/components/OnboardingUI/OnboardingUIReact.js.map +1 -1
  18. package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +21 -0
  19. package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +528 -20
  20. package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -1
  21. package/dist/src/components/OnboardingUI/components/EmailForm.js +2 -1
  22. package/dist/src/components/OnboardingUI/components/EmailForm.js.map +1 -1
  23. package/dist/src/components/OnboardingUI/components/MfaForm.d.ts +17 -0
  24. package/dist/src/components/OnboardingUI/components/MfaForm.js +81 -0
  25. package/dist/src/components/OnboardingUI/components/MfaForm.js.map +1 -0
  26. package/dist/src/components/OnboardingUI/components/index.d.ts +2 -0
  27. package/dist/src/components/OnboardingUI/components/index.js +1 -0
  28. package/dist/src/components/OnboardingUI/components/index.js.map +1 -1
  29. package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js +3 -0
  30. package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js.map +1 -1
  31. package/dist/src/components/OnboardingUI/hooks/useOnboarding.d.ts +4 -1
  32. package/dist/src/components/OnboardingUI/hooks/useOnboarding.js +65 -2
  33. package/dist/src/components/OnboardingUI/hooks/useOnboarding.js.map +1 -1
  34. package/dist/src/components/WalletModal/components/ManageWalletModal.css +1443 -2
  35. package/dist/src/components/WalletModal/components/ManageWalletModal.js +737 -23
  36. package/dist/src/components/WalletModal/components/ManageWalletModal.js.map +1 -1
  37. package/dist/src/components/WalletModal/components/ReceiveModal.css +0 -1
  38. package/dist/src/components/WalletModal/hooks/useSendTransaction.js +67 -12
  39. package/dist/src/components/WalletModal/hooks/useSendTransaction.js.map +1 -1
  40. package/dist/src/types.d.ts +16 -0
  41. package/dist/src/wagmiConfig.js +1 -0
  42. package/dist/src/wagmiConfig.js.map +1 -1
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +3 -2
@@ -346,30 +346,62 @@ const ONBOARDING_UI_STYLES = `
346
346
  right: 8px;
347
347
  top: 50%;
348
348
  transform: translateY(-50%);
349
+ width: 32px;
350
+ height: 32px;
349
351
  background: none;
350
352
  border: none;
351
353
  cursor: pointer;
352
- padding: 4px;
354
+ padding: 0;
353
355
  display: flex;
354
356
  align-items: center;
355
357
  justify-content: center;
356
358
  z-index: 2;
357
- transition: all 0.2s ease;
358
- border-radius: 4px;
359
+ transition: background-color 0.2s ease, color 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
360
+ border-radius: 6px;
359
361
  }
360
362
 
361
363
  /* Light theme arrow button */
362
364
  .onboarding-theme-light .onboarding-input-arrow-button {
363
- color: #111827;
365
+ color: #6b7280;
364
366
  }
365
367
 
366
- .onboarding-theme-light .onboarding-input-arrow-button:hover {
367
- background-color: rgba(17, 24, 39, 0.1);
368
+ .onboarding-theme-light .onboarding-input-arrow-button:hover:not(:disabled) {
369
+ background-color: rgba(139, 152, 202, 0.35);
368
370
  color: #1f2937;
371
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
372
+ }
373
+
374
+ .onboarding-theme-light .onboarding-input-arrow-button:active:not(:disabled) {
375
+ transform: translateY(-50%) scale(0.94);
376
+ background-color: rgba(139, 152, 202, 0.5);
377
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
378
+ }
379
+
380
+ .onboarding-theme-light .onboarding-input-arrow-button:focus-visible {
381
+ outline: none;
382
+ box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px rgba(139, 152, 202, 0.5);
383
+ }
384
+
385
+ .onboarding-theme-light .onboarding-input-arrow-button-filled {
386
+ background-color: rgba(139, 152, 202, 0.45);
387
+ color: #1f2937;
388
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
389
+ }
390
+
391
+ .onboarding-theme-light .onboarding-input-arrow-button-filled:hover:not(:disabled) {
392
+ background-color: rgba(139, 152, 202, 0.6);
393
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
369
394
  }
370
395
 
371
- .onboarding-theme-light .onboarding-input-arrow-button:active {
372
- background-color: rgba(17, 24, 39, 0.15);
396
+ .onboarding-theme-light .onboarding-input-arrow-button-filled:active:not(:disabled) {
397
+ transform: translateY(-50%) scale(0.94);
398
+ background-color: rgba(139, 152, 202, 0.7);
399
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);
400
+ }
401
+
402
+ .onboarding-theme-light .onboarding-input-arrow-button-filled:focus-visible {
403
+ outline: none;
404
+ box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px rgba(139, 152, 202, 0.55);
373
405
  }
374
406
 
375
407
  /* Dark theme arrow button */
@@ -377,17 +409,37 @@ const ONBOARDING_UI_STYLES = `
377
409
  color: #9ca3af;
378
410
  }
379
411
 
380
- .onboarding-theme-dark .onboarding-input-arrow-button:hover {
381
- background-color: rgba(156, 163, 175, 0.2);
382
- color: #d1d5db;
412
+ .onboarding-theme-dark .onboarding-input-arrow-button:hover:not(:disabled) {
413
+ color: #ffffff;
414
+ background-color: rgba(255, 255, 255, 0.12);
415
+ }
416
+
417
+ .onboarding-theme-dark .onboarding-input-arrow-button:active:not(:disabled) {
418
+ transform: translateY(-50%) scale(0.94);
419
+ background-color: rgba(255, 255, 255, 0.18);
420
+ }
421
+
422
+ .onboarding-theme-dark .onboarding-input-arrow-button:focus-visible {
423
+ outline: none;
424
+ box-shadow: 0 0 0 2px #1f2937, 0 0 0 4px rgba(156, 163, 175, 0.4);
425
+ }
426
+
427
+ .onboarding-theme-dark .onboarding-input-arrow-button-filled {
428
+ color: #ffffff;
429
+ background-color: rgba(255, 255, 255, 0.12);
430
+ }
431
+
432
+ .onboarding-theme-dark .onboarding-input-arrow-button-filled:hover:not(:disabled) {
433
+ background-color: rgba(255, 255, 255, 0.18);
383
434
  }
384
435
 
385
- .onboarding-theme-dark .onboarding-input-arrow-button:active {
386
- background-color: rgba(156, 163, 175, 0.25);
436
+ .onboarding-theme-dark .onboarding-input-arrow-button-filled:active:not(:disabled) {
437
+ transform: translateY(-50%) scale(0.94);
438
+ background-color: rgba(255, 255, 255, 0.22);
387
439
  }
388
440
 
389
441
  .onboarding-input-arrow-button:disabled {
390
- opacity: 0.5;
442
+ opacity: 0.35;
391
443
  cursor: not-allowed;
392
444
  }
393
445
 
@@ -823,6 +875,12 @@ const ONBOARDING_UI_STYLES = `
823
875
  justify-content: center;
824
876
  margin-bottom: 24px;
825
877
  width: 100%;
878
+ padding: 0 10px;
879
+ box-sizing: border-box;
880
+ }
881
+
882
+ .onboarding-otp-inputs-single {
883
+ padding: 0;
826
884
  }
827
885
 
828
886
  .onboarding-otp-input {
@@ -869,6 +927,54 @@ const ONBOARDING_UI_STYLES = `
869
927
  color: #ffffff;
870
928
  }
871
929
 
930
+ .onboarding-mfa-backup-field {
931
+ width: 100%;
932
+ max-width: 320px;
933
+ height: 52px;
934
+ border: 1.5px solid #e5e7eb;
935
+ border-radius: 8px;
936
+ background-color: inherit;
937
+ color: inherit;
938
+ text-align: center;
939
+ font-size: 18px;
940
+ font-weight: 600;
941
+ letter-spacing: 0.16em;
942
+ padding: 0 14px;
943
+ box-sizing: border-box;
944
+ text-transform: uppercase;
945
+ transition: all 0.15s ease;
946
+ }
947
+
948
+ .onboarding-theme-light .onboarding-mfa-backup-field {
949
+ background-color: #ffffff;
950
+ border-color: #e5e7eb;
951
+ color: #000000;
952
+ }
953
+
954
+ .onboarding-theme-light .onboarding-mfa-backup-field:focus {
955
+ outline: none;
956
+ border-color: #111827;
957
+ box-shadow: 0 0 0 3px rgba(17, 24, 39, 0.1);
958
+ }
959
+
960
+ .onboarding-theme-dark .onboarding-mfa-backup-field {
961
+ border-color: #4b5563;
962
+ background-color: #374151;
963
+ color: #ffffff;
964
+ }
965
+
966
+ .onboarding-theme-dark .onboarding-mfa-backup-field:focus {
967
+ outline: none;
968
+ border-color: #9ca3af;
969
+ box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
970
+ background-color: #4b5563;
971
+ }
972
+
973
+ .onboarding-mfa-backup-field:disabled {
974
+ opacity: 0.6;
975
+ cursor: not-allowed;
976
+ }
977
+
872
978
  .onboarding-otp-resend {
873
979
  text-align: center;
874
980
  margin-top: 20px;
@@ -1005,6 +1111,11 @@ export class OnboardingUIWeb {
1005
1111
  divider = null;
1006
1112
  footer = null;
1007
1113
  verifyButton = null;
1114
+ mfaVerificationScreen = null;
1115
+ mfaCodeInputs = [];
1116
+ mfaVerifyButton = null;
1117
+ mfaCode = "";
1118
+ mfaUseBackupCode = false;
1008
1119
  email = "";
1009
1120
  otp = "";
1010
1121
  otpSent = false;
@@ -1092,13 +1203,15 @@ export class OnboardingUIWeb {
1092
1203
  detectedProvider === "twitter" ||
1093
1204
  detectedProvider === "x");
1094
1205
  const hasOAuthParams = isOAuthCallback || (success === "true" && !!accessToken);
1095
- // If success=true and it's an OAuth callback (Google, Discord, Twitter), show loading modal with header and footer
1206
+ // If success=true and it's an OAuth callback (Google, Discord, Twitter), show loading modal unless MFA is required
1096
1207
  if (success === "true" && hasOAuthParams) {
1097
- // Show loading modal immediately - it creates its own overlay
1098
- // Use requestAnimationFrame + setTimeout to ensure DOM is ready
1208
+ const mfaRequired = params.get("mfaRequired") === "true";
1209
+ if (mfaRequired) {
1210
+ // Don't show "Verifying login..." - provider will show MFA screen instead; avoid it appearing after MFA modal
1211
+ return;
1212
+ }
1099
1213
  requestAnimationFrame(() => {
1100
1214
  setTimeout(() => {
1101
- // Directly show loading modal - it will create its own overlay with header and footer
1102
1215
  this.showLoadingModal();
1103
1216
  }, 100);
1104
1217
  });
@@ -2439,7 +2552,11 @@ export class OnboardingUIWeb {
2439
2552
  if (this.config.onEmailOtpVerify) {
2440
2553
  const result = await this.config.onEmailOtpVerify(this.email, this.otp);
2441
2554
  if (result.success) {
2442
- this.config.onLoginSuccess?.({ token: result.token });
2555
+ if (result.mfaRequired === true) {
2556
+ this.showMfaVerificationScreen();
2557
+ return;
2558
+ }
2559
+ this.config.onLoginSuccess?.({ token: result.token, user: result.user });
2443
2560
  }
2444
2561
  else {
2445
2562
  throw new Error("Invalid OTP");
@@ -2976,6 +3093,10 @@ export class OnboardingUIWeb {
2976
3093
  if (this.config.onEmailOtpVerify) {
2977
3094
  const result = await this.config.onEmailOtpVerify(this.email, this.otp);
2978
3095
  if (result.success) {
3096
+ if (result.mfaRequired === true) {
3097
+ this.showMfaVerificationScreen();
3098
+ return;
3099
+ }
2979
3100
  this.config.onLoginSuccess?.(result);
2980
3101
  }
2981
3102
  else {
@@ -3044,6 +3165,377 @@ export class OnboardingUIWeb {
3044
3165
  this.setLoading(false);
3045
3166
  }
3046
3167
  }
3168
+ /**
3169
+ * Show MFA verification screen for OAuth callback when mfaRequired is true.
3170
+ * Public method: hides loading modal, then shows 6-digit MFA form with Cancel (no Back to OTP).
3171
+ */
3172
+ showMfaVerificationScreenForOAuth() {
3173
+ this.hideLoadingModal();
3174
+ this.showMfaVerificationScreen({ fromOAuth: true });
3175
+ }
3176
+ /**
3177
+ * Show MFA verification screen (auth code / TOTP) after email OTP when mfaRequired is true
3178
+ * @param options.fromOAuth - when true, show Cancel instead of Back and do not assume OTP screen exists
3179
+ */
3180
+ showMfaVerificationScreen(options) {
3181
+ const fromOAuth = options?.fromOAuth === true;
3182
+ if (!this.rootElement)
3183
+ return;
3184
+ const card = this.rootElement.querySelector(".onboarding-card");
3185
+ if (!card)
3186
+ return;
3187
+ if (!this.config.onMfaVerify) {
3188
+ this.setError("MFA verification is required but not configured");
3189
+ return;
3190
+ }
3191
+ // Remove existing MFA screen if any
3192
+ if (this.mfaVerificationScreen) {
3193
+ this.mfaVerificationScreen.remove();
3194
+ this.mfaVerificationScreen = null;
3195
+ this.mfaVerifyButton = null;
3196
+ this.mfaCodeInputs = [];
3197
+ }
3198
+ card.classList.add("onboarding-mfa-active");
3199
+ // Hide external wallet CTA ("Continue with wallet") and its divider on all MFA screens
3200
+ if (this.externalWalletContainer) {
3201
+ this.externalWalletContainer.style.display = "none";
3202
+ }
3203
+ if (this.externalWalletDivider) {
3204
+ this.externalWalletDivider.style.display = "none";
3205
+ }
3206
+ // Hide "Powered by Abstraxn" footer on all MFA screens
3207
+ if (this.footer) {
3208
+ this.footer.style.display = "none";
3209
+ }
3210
+ if (fromOAuth) {
3211
+ // Hide email form and social sections so only MFA is visible (OAuth flow)
3212
+ if (this.emailForm)
3213
+ this.emailForm.style.display = "none";
3214
+ const header = this.rootElement.querySelector(".onboarding-header");
3215
+ if (header)
3216
+ header.style.display = "none";
3217
+ if (this.divider)
3218
+ this.divider.style.display = "none";
3219
+ if (this.googleButton)
3220
+ this.googleButton.style.display = "none";
3221
+ const twitterBtn = this.rootElement.querySelector(".onboarding-button-twitter");
3222
+ if (twitterBtn)
3223
+ twitterBtn.style.display = "none";
3224
+ const discordBtn = this.rootElement.querySelector(".onboarding-button-discord");
3225
+ if (discordBtn)
3226
+ discordBtn.style.display = "none";
3227
+ const passkeyBtn = this.rootElement.querySelector(".onboarding-button-passkey");
3228
+ if (passkeyBtn)
3229
+ passkeyBtn.style.display = "none";
3230
+ const passkeyLink = this.rootElement.querySelector(".onboarding-passkey-signup-link");
3231
+ if (passkeyLink)
3232
+ passkeyLink.style.display = "none";
3233
+ const socialGrid = this.rootElement.querySelector(".onboarding-social-grid");
3234
+ if (socialGrid)
3235
+ socialGrid.style.display = "none";
3236
+ }
3237
+ else {
3238
+ // Remove OTP screen from DOM so only MFA screen is visible (separate step)
3239
+ if (this.otpVerificationScreen && this.otpVerificationScreen.parentNode === card) {
3240
+ card.removeChild(this.otpVerificationScreen);
3241
+ }
3242
+ }
3243
+ this.mfaVerificationScreen = this.createElement("div", {
3244
+ className: "onboarding-mfa-verification onboarding-otp-verification",
3245
+ });
3246
+ if (fromOAuth) {
3247
+ const cancelButton = this.createElement("button", {
3248
+ type: "button",
3249
+ className: "onboarding-otp-back-button",
3250
+ "aria-label": "Cancel",
3251
+ });
3252
+ cancelButton.innerHTML = `
3253
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3254
+ <path d="M19 12H5M12 19l-7-7 7-7" />
3255
+ </svg>
3256
+ `;
3257
+ cancelButton.addEventListener("click", () => {
3258
+ if (this.loading)
3259
+ return;
3260
+ if (this.mfaUseBackupCode) {
3261
+ this.mfaUseBackupCode = false;
3262
+ this.mfaCode = "";
3263
+ this.showMfaVerificationScreen(options);
3264
+ return;
3265
+ }
3266
+ this.close();
3267
+ });
3268
+ this.mfaVerificationScreen.appendChild(cancelButton);
3269
+ }
3270
+ else {
3271
+ const backButton = this.createElement("button", {
3272
+ type: "button",
3273
+ className: "onboarding-otp-back-button",
3274
+ "aria-label": "Go back to OTP",
3275
+ });
3276
+ backButton.innerHTML = `
3277
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3278
+ <path d="M19 12H5M12 19l-7-7 7-7" />
3279
+ </svg>
3280
+ `;
3281
+ backButton.addEventListener("click", () => {
3282
+ if (this.loading)
3283
+ return;
3284
+ if (this.mfaUseBackupCode) {
3285
+ this.mfaUseBackupCode = false;
3286
+ this.mfaCode = "";
3287
+ this.showMfaVerificationScreen(options);
3288
+ return;
3289
+ }
3290
+ if (!this.loading && this.mfaVerificationScreen && this.otpVerificationScreen && card) {
3291
+ this.mfaVerificationScreen.remove();
3292
+ this.mfaVerificationScreen = null;
3293
+ this.mfaVerifyButton = null;
3294
+ this.mfaCodeInputs = [];
3295
+ this.mfaCode = "";
3296
+ this.mfaUseBackupCode = false;
3297
+ card.appendChild(this.otpVerificationScreen);
3298
+ card.classList.remove("onboarding-mfa-active");
3299
+ // Restore external wallet CTA and footer when returning to OTP screen
3300
+ if (this.externalWalletContainer) {
3301
+ this.externalWalletContainer.style.display = this.externalWalletsEnabled ? "" : "none";
3302
+ }
3303
+ if (this.externalWalletDivider) {
3304
+ this.externalWalletDivider.style.display = this.externalWalletsEnabled ? "" : "none";
3305
+ }
3306
+ if (this.footer && this.config.showFooter) {
3307
+ this.footer.style.display = "";
3308
+ }
3309
+ }
3310
+ });
3311
+ this.mfaVerificationScreen.appendChild(backButton);
3312
+ }
3313
+ const iconContainer = this.createElement("div", {
3314
+ className: "onboarding-otp-icon-container",
3315
+ });
3316
+ const iconInner = this.createElement("div", { className: "onboarding-otp-icon-inner" });
3317
+ const lockIcon = this.createElement("span", {
3318
+ innerHTML: `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`,
3319
+ });
3320
+ iconInner.appendChild(lockIcon);
3321
+ iconContainer.appendChild(iconInner);
3322
+ this.mfaVerificationScreen.appendChild(iconContainer);
3323
+ const title = this.createElement("h1", {
3324
+ className: "onboarding-otp-title",
3325
+ textContent: this.mfaUseBackupCode
3326
+ ? "Enter backup code"
3327
+ : this.config.labels?.mfaVerifyTitle ?? "Enter authentication code",
3328
+ });
3329
+ this.mfaVerificationScreen.appendChild(title);
3330
+ const instruction = this.createElement("p", {
3331
+ className: "onboarding-otp-instruction",
3332
+ textContent: this.mfaUseBackupCode
3333
+ ? "Enter the 8-character backup code to continue"
3334
+ : this.config.labels?.mfaVerifyInstruction ?? "Enter the 6-digit code from your authenticator app",
3335
+ });
3336
+ this.mfaVerificationScreen.appendChild(instruction);
3337
+ const inputsContainer = this.createElement("div", {
3338
+ className: `onboarding-otp-inputs-container${this.mfaUseBackupCode ? " onboarding-otp-inputs-single" : ""}`,
3339
+ });
3340
+ const mfaCodeLength = this.mfaUseBackupCode ? 8 : 6;
3341
+ this.mfaCodeInputs = [];
3342
+ if (this.mfaUseBackupCode) {
3343
+ const input = this.createElement("input", {
3344
+ type: "text",
3345
+ className: "onboarding-mfa-backup-field",
3346
+ maxLength: 8,
3347
+ inputMode: "text",
3348
+ pattern: "[A-Za-z0-9]*",
3349
+ // placeholder: "Enter backup code",
3350
+ });
3351
+ input.addEventListener("input", (e) => {
3352
+ const target = e.target;
3353
+ target.value = target.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 8);
3354
+ this.updateMfaCodeValue();
3355
+ });
3356
+ input.addEventListener("paste", (e) => {
3357
+ e.preventDefault();
3358
+ const pasted = (e.clipboardData?.getData("text") || "")
3359
+ .toUpperCase()
3360
+ .replace(/[^A-Z0-9]/g, "")
3361
+ .slice(0, 8);
3362
+ input.value = pasted;
3363
+ this.updateMfaCodeValue();
3364
+ });
3365
+ this.mfaCodeInputs.push(input);
3366
+ inputsContainer.appendChild(input);
3367
+ }
3368
+ else {
3369
+ for (let i = 0; i < mfaCodeLength; i++) {
3370
+ const input = this.createElement("input", {
3371
+ type: "text",
3372
+ className: "onboarding-otp-input",
3373
+ maxLength: 1,
3374
+ inputMode: "numeric",
3375
+ pattern: "[0-9]*",
3376
+ });
3377
+ input.addEventListener("input", (e) => this.handleMfaInput(e, i));
3378
+ input.addEventListener("keydown", (e) => this.handleMfaKeydown(e, i));
3379
+ input.addEventListener("paste", (e) => this.handleMfaPaste(e));
3380
+ this.mfaCodeInputs.push(input);
3381
+ inputsContainer.appendChild(input);
3382
+ }
3383
+ }
3384
+ this.mfaVerificationScreen.appendChild(inputsContainer);
3385
+ if (!this.mfaUseBackupCode) {
3386
+ const backupLinkText = this.createElement("p", {
3387
+ className: "onboarding-mfa-trouble-text",
3388
+ });
3389
+ const backupLinkButton = this.createElement("button", {
3390
+ type: "button",
3391
+ className: "onboarding-mfa-backup-link",
3392
+ textContent: "Use a backup code",
3393
+ });
3394
+ backupLinkButton.addEventListener("click", () => {
3395
+ if (this.loading)
3396
+ return;
3397
+ this.mfaUseBackupCode = true;
3398
+ this.mfaCode = "";
3399
+ this.showMfaVerificationScreen(options);
3400
+ });
3401
+ backupLinkText.append("Having trouble? ", backupLinkButton);
3402
+ this.mfaVerificationScreen.appendChild(backupLinkText);
3403
+ }
3404
+ const mfaErrorElement = this.createElement("div", {
3405
+ className: "onboarding-error",
3406
+ style: "display: none;",
3407
+ });
3408
+ this.mfaVerificationScreen.appendChild(mfaErrorElement);
3409
+ this.mfaVerificationScreen.errorElement = mfaErrorElement;
3410
+ this.mfaVerifyButton = this.createElement("button", {
3411
+ type: "button",
3412
+ className: "onboarding-button onboarding-button-primary",
3413
+ textContent: "Verify",
3414
+ });
3415
+ this.mfaVerifyButton.disabled = true;
3416
+ this.mfaVerifyButton.addEventListener("click", () => this.handleMfaVerify());
3417
+ this.mfaVerificationScreen.appendChild(this.mfaVerifyButton);
3418
+ card.appendChild(this.mfaVerificationScreen);
3419
+ this.setError(null);
3420
+ if (this.mfaCodeInputs[0]) {
3421
+ this.mfaCodeInputs[0].focus();
3422
+ }
3423
+ }
3424
+ updateMfaCodeValue() {
3425
+ this.mfaCode = this.mfaCodeInputs.map((inp) => inp.value).join("");
3426
+ const mfaCodeLength = this.mfaUseBackupCode ? 8 : 6;
3427
+ if (this.mfaVerifyButton) {
3428
+ this.mfaVerifyButton.disabled = this.loading || this.mfaCode.length !== mfaCodeLength;
3429
+ }
3430
+ }
3431
+ handleMfaInput(e, i) {
3432
+ const input = e.target;
3433
+ if (this.mfaUseBackupCode) {
3434
+ input.value = input.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 1);
3435
+ }
3436
+ else {
3437
+ input.value = input.value.replace(/\D/g, "").slice(0, 1);
3438
+ }
3439
+ this.updateMfaCodeValue();
3440
+ const mfaCodeLength = this.mfaUseBackupCode ? 8 : 6;
3441
+ // Auto-focus next input when user types a character
3442
+ if (this.mfaCodeInputs[i]?.value && i < mfaCodeLength - 1 && this.mfaCodeInputs[i + 1]) {
3443
+ this.mfaCodeInputs[i + 1].focus();
3444
+ }
3445
+ // Auto-verify only for 6-digit authenticator flow.
3446
+ if (!this.mfaUseBackupCode && this.mfaCode.length === 6 && !this.loading) {
3447
+ this.handleMfaVerify();
3448
+ }
3449
+ }
3450
+ handleMfaKeydown(e, i) {
3451
+ if (e.key === "Backspace" && !this.mfaCodeInputs[i].value && i > 0 && this.mfaCodeInputs[i - 1]) {
3452
+ this.mfaCodeInputs[i - 1].focus();
3453
+ }
3454
+ this.updateMfaCodeValue();
3455
+ }
3456
+ handleMfaPaste(e) {
3457
+ e.preventDefault();
3458
+ const mfaCodeLength = this.mfaUseBackupCode ? 8 : 6;
3459
+ const pasted = this.mfaUseBackupCode
3460
+ ? (e.clipboardData?.getData("text") || "").toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 8)
3461
+ : (e.clipboardData?.getData("text") || "").replace(/\D/g, "").slice(0, 6);
3462
+ for (let i = 0; i < mfaCodeLength; i++) {
3463
+ if (this.mfaCodeInputs[i]) {
3464
+ this.mfaCodeInputs[i].value = pasted[i] ?? "";
3465
+ }
3466
+ }
3467
+ this.updateMfaCodeValue();
3468
+ const focusIndex = Math.min(Math.max(pasted.length - 1, 0), mfaCodeLength - 1);
3469
+ if (pasted.length > 0 && this.mfaCodeInputs[focusIndex]) {
3470
+ this.mfaCodeInputs[focusIndex].focus();
3471
+ }
3472
+ // Auto-verify only for 6-digit authenticator flow.
3473
+ if (!this.mfaUseBackupCode && this.mfaCode.length === 6 && !this.loading) {
3474
+ this.handleMfaVerify();
3475
+ }
3476
+ }
3477
+ async handleMfaVerify() {
3478
+ this.updateMfaCodeValue();
3479
+ const normalizedCode = this.mfaCode.trim().toUpperCase();
3480
+ const isTotpCode = /^\d{6}$/.test(normalizedCode);
3481
+ const isBackupCode = /^[A-Z0-9]{8}$/.test(normalizedCode);
3482
+ if (!isTotpCode && !isBackupCode) {
3483
+ this.setMfaError("Please enter a valid 6-digit or 8-character backup code");
3484
+ return;
3485
+ }
3486
+ const onMfaVerify = this.config.onMfaVerify;
3487
+ if (!onMfaVerify) {
3488
+ this.setMfaError("MFA verification is not configured");
3489
+ return;
3490
+ }
3491
+ this.mfaCodeInputs.forEach((input) => { input.disabled = true; });
3492
+ this.loading = true;
3493
+ if (this.mfaVerifyButton) {
3494
+ this.mfaVerifyButton.disabled = true;
3495
+ this.mfaVerifyButton.textContent = "Verifying...";
3496
+ }
3497
+ this.setMfaError(null);
3498
+ try {
3499
+ const result = await onMfaVerify(normalizedCode);
3500
+ if (result.success) {
3501
+ this.config.onLoginSuccess?.({});
3502
+ }
3503
+ else {
3504
+ this.setMfaError(result.error ?? "Invalid code");
3505
+ this.mfaCodeInputs.forEach((input) => { input.value = ""; input.disabled = false; });
3506
+ this.mfaCode = "";
3507
+ this.updateMfaCodeValue();
3508
+ if (this.mfaCodeInputs[0])
3509
+ this.mfaCodeInputs[0].focus();
3510
+ }
3511
+ }
3512
+ catch (err) {
3513
+ const msg = err instanceof Error ? err.message : "Verification failed";
3514
+ this.setMfaError(msg);
3515
+ this.mfaCodeInputs.forEach((input) => { input.value = ""; input.disabled = false; });
3516
+ this.mfaCode = "";
3517
+ this.updateMfaCodeValue();
3518
+ if (this.mfaCodeInputs[0])
3519
+ this.mfaCodeInputs[0].focus();
3520
+ }
3521
+ finally {
3522
+ this.loading = false;
3523
+ if (this.mfaVerifyButton) {
3524
+ this.mfaVerifyButton.textContent = "Verify";
3525
+ this.updateMfaCodeValue();
3526
+ }
3527
+ }
3528
+ }
3529
+ setMfaError(message) {
3530
+ const el = this.mfaVerificationScreen ? this.mfaVerificationScreen.errorElement : null;
3531
+ if (el) {
3532
+ el.textContent = message ?? "";
3533
+ el.style.display = message ? "block" : "none";
3534
+ }
3535
+ if (message) {
3536
+ this.config.onLoginError?.(new Error(message));
3537
+ }
3538
+ }
3047
3539
  /**
3048
3540
  * Handle resend OTP
3049
3541
  */
@@ -3323,12 +3815,14 @@ export class OnboardingUIWeb {
3323
3815
  */
3324
3816
  updateButtonState() {
3325
3817
  const isValid = isValidEmail(this.email.trim());
3818
+ const hasEmailValue = this.email.trim().length > 0;
3326
3819
  if (this.continueButton) {
3327
3820
  this.continueButton.disabled = this.loading || !isValid;
3328
3821
  }
3329
3822
  // Update arrow button state (when all auth methods are enabled)
3330
3823
  if (this.emailArrowButton) {
3331
3824
  this.emailArrowButton.disabled = this.loading || !isValid;
3825
+ this.emailArrowButton.classList.toggle("onboarding-input-arrow-button-filled", hasEmailValue);
3332
3826
  }
3333
3827
  }
3334
3828
  /**
@@ -3383,12 +3877,22 @@ export class OnboardingUIWeb {
3383
3877
  this.otpVerificationScreen.remove();
3384
3878
  this.otpVerificationScreen = null;
3385
3879
  }
3386
- // Remove loading/error mode classes and OTP active class
3880
+ // Clear MFA verification screen
3881
+ if (this.mfaVerificationScreen) {
3882
+ this.mfaVerificationScreen.remove();
3883
+ this.mfaVerificationScreen = null;
3884
+ this.mfaVerifyButton = null;
3885
+ this.mfaCodeInputs = [];
3886
+ this.mfaCode = "";
3887
+ this.mfaUseBackupCode = false;
3888
+ }
3889
+ // Remove loading/error mode classes and OTP/MFA active class
3387
3890
  const card = this.rootElement?.querySelector(".onboarding-card");
3388
3891
  if (card) {
3389
3892
  card.classList.remove("onboarding-mode-loading");
3390
3893
  card.classList.remove("onboarding-mode-error");
3391
3894
  card.classList.remove("onboarding-otp-active");
3895
+ card.classList.remove("onboarding-mfa-active");
3392
3896
  }
3393
3897
  // Hide loading modal if exists (for inline components)
3394
3898
  this.hideLoadingModal();
@@ -3575,6 +4079,10 @@ export class OnboardingUIWeb {
3575
4079
  this.errorElement = null;
3576
4080
  this.otpGroup = null;
3577
4081
  this.otpVerificationScreen = null; // Clear OTP screen reference
4082
+ this.mfaVerificationScreen = null;
4083
+ this.mfaVerifyButton = null;
4084
+ this.mfaCodeInputs = [];
4085
+ this.mfaUseBackupCode = false;
3578
4086
  this.externalWalletContainer = null; // Clear external wallet container reference
3579
4087
  this.externalWalletDivider = null; // Clear external wallet divider reference
3580
4088
  }