@openfort/react 0.2.5 → 0.2.6

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.
package/build/index.es.js CHANGED
@@ -15,7 +15,7 @@ import { safe, injected, coinbaseWallet, walletConnect } from '@wagmi/connectors
15
15
  import { useTransition } from 'react-transition-state';
16
16
  import ResizeObserver from 'resize-observer-polyfill';
17
17
  import { createPortal } from 'react-dom';
18
- import { numberToHex, formatUnits, parseUnits, isAddress, parseAbi, encodeFunctionData, parseSignature } from 'viem';
18
+ import { custom, createWalletClient, numberToHex, formatUnits, parseUnits, isAddress, parseAbi, encodeFunctionData, parseSignature } from 'viem';
19
19
  import { erc7811Actions, erc7715Actions } from 'viem/experimental';
20
20
  import calculateEntropy from 'fast-password-entropy';
21
21
  import QRCodeUtil from 'qrcode';
@@ -5643,7 +5643,7 @@ const FitText = ({ children, maxFontSize = 100, minFontSize = 70, justifyContent
5643
5643
  };
5644
5644
  FitText.displayName = 'FitText';
5645
5645
 
5646
- const OPENFORT_VERSION = '0.2.5';
5646
+ const OPENFORT_VERSION = '0.2.6';
5647
5647
 
5648
5648
  const Portal = (props) => {
5649
5649
  props = {
@@ -6492,8 +6492,6 @@ onInfo, }) => {
6492
6492
  return locales.downloadAppScreen_heading;
6493
6493
  case routes.ONBOARDING:
6494
6494
  return locales.onboardingScreen_heading;
6495
- case routes.CONNECTED:
6496
- return locales.profileScreen_heading;
6497
6495
  case routes.SWITCHNETWORKS:
6498
6496
  return locales.switchNetworkScreen_heading;
6499
6497
  default:
@@ -6550,7 +6548,8 @@ onInfo, }) => {
6550
6548
  duration: mobile ? 0 : 0.17,
6551
6549
  delay: mobile ? 0.01 : 0,
6552
6550
  }, children: jsx(FitText, { children: getHeading() }) }, `${route}-${'signedIn'}`) }) }), jsx(InnerContainer$1, { children: Object.keys(pages).map((key) => {
6553
- const page = pages[key];
6551
+ var _a, _b;
6552
+ const page = (_b = (_a = context.uiConfig.customPageComponents) === null || _a === void 0 ? void 0 : _a[key]) !== null && _b !== void 0 ? _b : pages[key];
6554
6553
  return (
6555
6554
  // OLD_TODO: We may need to use the follow check avoid unnecessary computations, but this causes a bug where the content flashes
6556
6555
  // (key === pageId || key === prevPage) && (
@@ -7096,7 +7095,7 @@ function useSignOut(hookOptions = {}) {
7096
7095
  };
7097
7096
  }
7098
7097
 
7099
- const PageContent = ({ children, width, onBack = 'back', logoutOnBack }) => {
7098
+ const PageContent = ({ children, width, onBack = 'back', logoutOnBack, header }) => {
7100
7099
  const { setOnBack, setRoute, setPreviousRoute, setRouteHistory } = useOpenfort();
7101
7100
  const { signOut } = useSignOut();
7102
7101
  useEffect(() => {
@@ -7139,7 +7138,7 @@ const PageContent = ({ children, width, onBack = 'back', logoutOnBack }) => {
7139
7138
  else
7140
7139
  setOnBack(null);
7141
7140
  }, [!!onBack, !!logoutOnBack]);
7142
- return jsx(PageContentStyle, { style: { width }, children: children });
7141
+ return (jsxs(PageContentStyle, { style: { width }, children: [header && jsx(ModalHeading, { children: header }), children] }));
7143
7142
  };
7144
7143
 
7145
7144
  const pulseAnim = { scale: [0.9, 1.25, 1.6], opacity: [0, 0.11, 0] };
@@ -7589,11 +7588,130 @@ const About = () => {
7589
7588
  } }, s.key))) }) }), jsx(Button, { href: ctaUrl, arrow: true, children: locales.aboutScreen_ctaText })] }));
7590
7589
  };
7591
7590
 
7591
+ /**
7592
+ * Hook for accessing current user information and authentication state
7593
+ *
7594
+ * This hook provides access to the current authenticated user's information and
7595
+ * authentication status. It also offers methods to manage access tokens and validate
7596
+ * user sessions. The hook automatically updates when authentication state changes.
7597
+ *
7598
+ * @returns Current user state and authentication utilities
7599
+ *
7600
+ * @example
7601
+ * ```tsx
7602
+ * const userHook = useUser();
7603
+ *
7604
+ * // Check if user is authenticated
7605
+ * if (userHook.isAuthenticated && userHook.user) {
7606
+ * console.log('User is authenticated:', userHook.user);
7607
+ * console.log('User ID:', userHook.user.id);
7608
+ * console.log('User email:', userHook.user.email);
7609
+ * console.log('Linked accounts:', userHook.linkedAccounts);
7610
+ * } else {
7611
+ * console.log('User is not authenticated');
7612
+ * }
7613
+ *
7614
+ * // Get fresh access token
7615
+ * const getToken = async () => {
7616
+ * try {
7617
+ * const token = await userHook.getAccessToken();
7618
+ * console.log('Access token:', token);
7619
+ * } catch (error) {
7620
+ * console.error('Failed to get access token:', error);
7621
+ * }
7622
+ * };
7623
+ *
7624
+ * // Validate and refresh token if needed
7625
+ * const refreshToken = async () => {
7626
+ * try {
7627
+ * await userHook.validateAndRefreshToken();
7628
+ * console.log('Token validated and refreshed');
7629
+ * } catch (error) {
7630
+ * console.error('Token validation failed:', error);
7631
+ * }
7632
+ * };
7633
+ * ```
7634
+ */
7635
+ function useUser() {
7636
+ const { user, client, embeddedState, linkedAccounts } = useOpenfortCore();
7637
+ const getAccessTokenAndUpdate = useCallback(async () => {
7638
+ try {
7639
+ await client.validateAndRefreshToken();
7640
+ const token = await client.getAccessToken();
7641
+ return token;
7642
+ }
7643
+ catch (error) {
7644
+ handleOAuthConfigError(error);
7645
+ throw error;
7646
+ }
7647
+ }, [client]);
7648
+ const validateAndRefresh = useCallback(async () => {
7649
+ try {
7650
+ await client.validateAndRefreshToken();
7651
+ }
7652
+ catch (error) {
7653
+ handleOAuthConfigError(error);
7654
+ throw error;
7655
+ }
7656
+ }, [client]);
7657
+ return {
7658
+ user,
7659
+ linkedAccounts,
7660
+ isAuthenticated: embeddedState !== EmbeddedState.NONE && embeddedState !== EmbeddedState.UNAUTHENTICATED,
7661
+ getAccessToken: getAccessTokenAndUpdate,
7662
+ validateAndRefreshToken: validateAndRefresh,
7663
+ };
7664
+ }
7665
+
7592
7666
  const useWalletAssets = ({ assets: hookCustomAssets, staleTime = 30000 } = {}) => {
7593
7667
  const chainId = useChainId();
7594
7668
  const { data: walletClient } = useWalletClient();
7595
- const { walletConfig } = useOpenfort();
7669
+ const { walletConfig, publishableKey, overrides, thirdPartyAuth } = useOpenfort();
7596
7670
  const { address } = useAccount();
7671
+ const { getAccessToken } = useUser();
7672
+ const buildHeaders = useCallback(async () => {
7673
+ if (thirdPartyAuth) {
7674
+ const accessToken = await thirdPartyAuth.getAccessToken();
7675
+ if (!accessToken) {
7676
+ throw new OpenfortError('Failed to get access token from third party auth provider', OpenfortReactErrorType.AUTHENTICATION_ERROR);
7677
+ }
7678
+ const headers = {
7679
+ 'Content-Type': 'application/json',
7680
+ 'x-auth-provider': thirdPartyAuth.provider,
7681
+ 'x-player-token': accessToken,
7682
+ 'x-token-type': 'idToken',
7683
+ Authorization: `Bearer ${publishableKey}`,
7684
+ };
7685
+ return headers;
7686
+ }
7687
+ const headers = {
7688
+ 'Content-Type': 'application/json',
7689
+ 'x-project-key': publishableKey,
7690
+ Authorization: `Bearer ${await getAccessToken()}`,
7691
+ };
7692
+ return headers;
7693
+ }, [publishableKey, getAccessToken, thirdPartyAuth]);
7694
+ const customTransport = useMemo(() => () => {
7695
+ return custom({
7696
+ async request({ method, params }) {
7697
+ const res = await fetch(`${(overrides === null || overrides === void 0 ? void 0 : overrides.backendUrl) || 'https://api.openfort.io'}/rpc`, {
7698
+ method: 'POST',
7699
+ headers: await buildHeaders(),
7700
+ body: JSON.stringify({
7701
+ method,
7702
+ params: params[0],
7703
+ id: 1,
7704
+ jsonrpc: '2.0',
7705
+ }),
7706
+ });
7707
+ const data = await res.json();
7708
+ if (data.error) {
7709
+ throw new Error(data.error.message);
7710
+ }
7711
+ return data.result;
7712
+ },
7713
+ });
7714
+ }, [publishableKey, getAccessToken]);
7597
7715
  const customAssetsToFetch = useMemo(() => {
7598
7716
  const assetsFromConfig = (walletConfig === null || walletConfig === void 0 ? void 0 : walletConfig.assets) ? walletConfig.assets[chainId] || [] : [];
7599
7717
  const assetsFromHook = hookCustomAssets ? hookCustomAssets[chainId] || [] : [];
@@ -7608,7 +7726,12 @@ const useWalletAssets = ({ assets: hookCustomAssets, staleTime = 30000 } = {}) =
7608
7726
  error: new Error('Wallet client not initialized'),
7609
7727
  });
7610
7728
  }
7611
- const extendedClient = walletClient.extend(erc7811Actions());
7729
+ const customClient = createWalletClient({
7730
+ account: walletClient.account,
7731
+ chain: walletClient.chain,
7732
+ transport: customTransport(),
7733
+ });
7734
+ const extendedClient = customClient.extend(erc7811Actions());
7612
7735
  // Fetch default assets
7613
7736
  const defaultAssetsPromise = extendedClient.getAssets({
7614
7737
  chainIds: [chainId],
@@ -10343,7 +10466,8 @@ const Connected = () => {
10343
10466
  const separator = ['web95', 'rounded', 'minimal'].includes((_c = (_b = themeContext.theme) !== null && _b !== void 0 ? _b : context.uiConfig.theme) !== null && _c !== void 0 ? _c : '')
10344
10467
  ? '....'
10345
10468
  : undefined;
10346
- return (jsxs(PageContent, { onBack: null, children: [jsx(ModalContent, { style: { paddingBottom: 6, gap: 6 }, children: address ? (jsxs(Fragment, { children: [jsx(AvatarContainer, { children: jsxs(AvatarInner, { children: [jsx(ChainSelectorContainer, { children: jsx(ChainSelector, {}) }), jsx(Avatar, { address: address })] }) }), jsx(ModalH1, { children: jsx(CopyText, { value: address, children: ensName !== null && ensName !== void 0 ? ensName : truncateEthAddress(address, separator) }) }), (context === null || context === void 0 ? void 0 : context.uiConfig.hideBalance) ? null : (jsxs(ModalBody, { children: [jsxs(BalanceContainer, { children: [!!assets && !isLoading && (jsx(TextLinkButton, { type: "button", onClick: () => {
10469
+ const locales = useLocales();
10470
+ return (jsxs(PageContent, { onBack: null, header: locales.profileScreen_heading, children: [jsx(ModalContent, { style: { paddingBottom: 6, gap: 6 }, children: address ? (jsxs(Fragment, { children: [jsx(AvatarContainer, { children: jsxs(AvatarInner, { children: [jsx(ChainSelectorContainer, { children: jsx(ChainSelector, {}) }), jsx(Avatar, { address: address })] }) }), jsx(ModalH1, { children: jsx(CopyText, { value: address, children: ensName !== null && ensName !== void 0 ? ensName : truncateEthAddress(address, separator) }) }), (context === null || context === void 0 ? void 0 : context.uiConfig.hideBalance) ? null : (jsxs(ModalBody, { children: [jsxs(BalanceContainer, { children: [!!assets && !isLoading && (jsx(TextLinkButton, { type: "button", onClick: () => {
10347
10471
  const firstBalanceAsset = assets === null || assets === void 0 ? void 0 : assets.find((a) => a.balance && a.balance > BigInt(0));
10348
10472
  if (!firstBalanceAsset) {
10349
10473
  setRoute(routes.NO_ASSETS_AVAILABLE);
@@ -11272,81 +11396,6 @@ const Connectors = ({ logoutOnBack }) => {
11272
11396
  return (jsx(PageContent, { logoutOnBack: logoutOnBack, width: 312, children: isMobile ? jsx(ConnectWithMobile$1, {}) : jsx(ConnectorList, {}) }));
11273
11397
  };
11274
11398
 
11275
- /**
11276
- * Hook for accessing current user information and authentication state
11277
- *
11278
- * This hook provides access to the current authenticated user's information and
11279
- * authentication status. It also offers methods to manage access tokens and validate
11280
- * user sessions. The hook automatically updates when authentication state changes.
11281
- *
11282
- * @returns Current user state and authentication utilities
11283
- *
11284
- * @example
11285
- * ```tsx
11286
- * const userHook = useUser();
11287
- *
11288
- * // Check if user is authenticated
11289
- * if (userHook.isAuthenticated && userHook.user) {
11290
- * console.log('User is authenticated:', userHook.user);
11291
- * console.log('User ID:', userHook.user.id);
11292
- * console.log('User email:', userHook.user.email);
11293
- * console.log('Linked accounts:', userHook.linkedAccounts);
11294
- * } else {
11295
- * console.log('User is not authenticated');
11296
- * }
11297
- *
11298
- * // Get fresh access token
11299
- * const getToken = async () => {
11300
- * try {
11301
- * const token = await userHook.getAccessToken();
11302
- * console.log('Access token:', token);
11303
- * } catch (error) {
11304
- * console.error('Failed to get access token:', error);
11305
- * }
11306
- * };
11307
- *
11308
- * // Validate and refresh token if needed
11309
- * const refreshToken = async () => {
11310
- * try {
11311
- * await userHook.validateAndRefreshToken();
11312
- * console.log('Token validated and refreshed');
11313
- * } catch (error) {
11314
- * console.error('Token validation failed:', error);
11315
- * }
11316
- * };
11317
- * ```
11318
- */
11319
- function useUser() {
11320
- const { user, client, embeddedState, linkedAccounts } = useOpenfortCore();
11321
- const getAccessTokenAndUpdate = useCallback(async () => {
11322
- try {
11323
- await client.validateAndRefreshToken();
11324
- const token = await client.getAccessToken();
11325
- return token;
11326
- }
11327
- catch (error) {
11328
- handleOAuthConfigError(error);
11329
- throw error;
11330
- }
11331
- }, [client]);
11332
- const validateAndRefresh = useCallback(async () => {
11333
- try {
11334
- await client.validateAndRefreshToken();
11335
- }
11336
- catch (error) {
11337
- handleOAuthConfigError(error);
11338
- throw error;
11339
- }
11340
- }, [client]);
11341
- return {
11342
- user,
11343
- linkedAccounts,
11344
- isAuthenticated: embeddedState !== EmbeddedState.NONE && embeddedState !== EmbeddedState.UNAUTHENTICATED,
11345
- getAccessToken: getAccessTokenAndUpdate,
11346
- validateAndRefreshToken: validateAndRefresh,
11347
- };
11348
- }
11349
-
11350
11399
  // get accounts from the same address and chain type
11351
11400
  const getSimpleAccounts = ({ address, embeddedAccounts, }) => {
11352
11401
  return embeddedAccounts
@@ -11436,7 +11485,7 @@ const mapWalletStatus = (status) => {
11436
11485
  */
11437
11486
  function useWallets(hookOptions = {}) {
11438
11487
  const { client, embeddedAccounts, isLoadingAccounts: isLoadingWallets, updateEmbeddedAccounts } = useOpenfortCore();
11439
- const { linkedAccounts } = useUser();
11488
+ const { linkedAccounts, user } = useUser();
11440
11489
  const { walletConfig, setOpen, setRoute, setConnector, uiConfig } = useOpenfort();
11441
11490
  const { connector, isConnected, address } = useAccount();
11442
11491
  const chainId = useChainId();
@@ -11477,24 +11526,88 @@ function useWallets(hookOptions = {}) {
11477
11526
  },
11478
11527
  });
11479
11528
  const openfortConnector = useMemo(() => { var _a; return (_a = availableWallets.find((c) => c.connector.id === embeddedWalletId)) === null || _a === void 0 ? void 0 : _a.connector; }, [availableWallets]);
11480
- const getEncryptionSession = useCallback(async () => {
11481
- if (!walletConfig || !walletConfig.createEncryptedSessionEndpoint) {
11482
- throw new Error('No createEncryptedSessionEndpoint set in walletConfig');
11529
+ const getEncryptionSession = useCallback(async ({ accessToken, userId, otpCode }) => {
11530
+ if (!walletConfig) {
11531
+ throw new Error('No walletConfig found');
11532
+ }
11533
+ if (walletConfig.getEncryptionSession) {
11534
+ return await walletConfig.getEncryptionSession({ userId, otpCode: otpCode, accessToken });
11535
+ }
11536
+ if (!walletConfig.createEncryptedSessionEndpoint) {
11537
+ throw new Error('No requestWalletRecoverOTPEndpoint set in walletConfig');
11483
11538
  }
11484
11539
  const resp = await fetch(walletConfig.createEncryptedSessionEndpoint, {
11485
11540
  method: 'POST',
11486
11541
  headers: {
11487
11542
  'Content-Type': 'application/json',
11488
11543
  },
11544
+ body: JSON.stringify({ user_id: userId, otp_code: otpCode }),
11489
11545
  });
11546
+ const respJSON = await resp.json();
11490
11547
  if (!resp.ok) {
11548
+ if (respJSON.error === 'OTP_REQUIRED')
11549
+ throw new Error('OTP_REQUIRED');
11491
11550
  throw new Error('Failed to create encryption session');
11492
11551
  }
11493
- const respJSON = await resp.json();
11494
11552
  return respJSON.session;
11495
11553
  }, [walletConfig]);
11554
+ const isWalletRecoveryOTPEnabled = useMemo(() => {
11555
+ return !!walletConfig && (!!walletConfig.requestWalletRecoverOTP || !!walletConfig.requestWalletRecoverOTPEndpoint);
11556
+ }, [walletConfig]);
11557
+ const requestWalletRecoverOTP = useCallback(async () => {
11558
+ try {
11559
+ logger.log('Requesting wallet recover OTP for user', { userId: user === null || user === void 0 ? void 0 : user.id });
11560
+ if (!walletConfig) {
11561
+ throw new Error('No walletConfig found');
11562
+ }
11563
+ const accessToken = await client.getAccessToken();
11564
+ if (!accessToken) {
11565
+ throw new OpenfortError('Openfort access token not found', OpenfortReactErrorType.AUTHENTICATION_ERROR);
11566
+ }
11567
+ if (!(user === null || user === void 0 ? void 0 : user.id)) {
11568
+ throw new OpenfortError('User not found', OpenfortReactErrorType.AUTHENTICATION_ERROR);
11569
+ }
11570
+ const userId = user.id;
11571
+ const email = user.email;
11572
+ const phone = user.email ? undefined : user.phoneNumber;
11573
+ if (!email && !phone) {
11574
+ throw new OpenfortError('No email or phone number found for user', OpenfortReactErrorType.AUTHENTICATION_ERROR);
11575
+ }
11576
+ logger.log('Requesting wallet recover OTP for user', { userId, email, phone });
11577
+ if (walletConfig.requestWalletRecoverOTP) {
11578
+ await walletConfig.requestWalletRecoverOTP({ userId, accessToken, email, phone });
11579
+ return { sentTo: email ? 'email' : 'phone', email, phone };
11580
+ }
11581
+ if (!walletConfig.requestWalletRecoverOTPEndpoint) {
11582
+ throw new Error('No requestWalletRecoverOTPEndpoint set in walletConfig');
11583
+ }
11584
+ const resp = await fetch(walletConfig.requestWalletRecoverOTPEndpoint, {
11585
+ method: 'POST',
11586
+ headers: {
11587
+ 'Content-Type': 'application/json',
11588
+ },
11589
+ body: JSON.stringify({ user_id: userId, email, phone }),
11590
+ });
11591
+ if (!resp.ok) {
11592
+ throw new Error('Failed to request wallet recover OTP');
11593
+ }
11594
+ return { sentTo: email ? 'email' : 'phone', email, phone };
11595
+ }
11596
+ catch (err) {
11597
+ logger.log('Error requesting wallet recover OTP:', err);
11598
+ const error = new OpenfortError('Failed to request wallet recover OTP', OpenfortReactErrorType.WALLET_ERROR);
11599
+ return onError({
11600
+ error,
11601
+ hookOptions,
11602
+ });
11603
+ }
11604
+ }, [walletConfig]);
11496
11605
  const parseWalletRecovery = useMemo(() => async function parseWalletRecovery(recovery, embeddedAccounts, walletAddress) {
11497
11606
  var _a, _b;
11607
+ const user = await client.user.get();
11608
+ if (!(user === null || user === void 0 ? void 0 : user.id)) {
11609
+ throw new OpenfortError('User not found', OpenfortReactErrorType.AUTHENTICATION_ERROR);
11610
+ }
11498
11611
  switch (recovery === null || recovery === void 0 ? void 0 : recovery.recoveryMethod) {
11499
11612
  case undefined:
11500
11613
  case RecoveryMethod.AUTOMATIC: {
@@ -11504,9 +11617,11 @@ function useWallets(hookOptions = {}) {
11504
11617
  }
11505
11618
  return {
11506
11619
  recoveryMethod: RecoveryMethod.AUTOMATIC,
11507
- encryptionSession: (walletConfig === null || walletConfig === void 0 ? void 0 : walletConfig.getEncryptionSession)
11508
- ? await walletConfig.getEncryptionSession(accessToken)
11509
- : await getEncryptionSession(),
11620
+ encryptionSession: await getEncryptionSession({
11621
+ accessToken,
11622
+ otpCode: recovery === null || recovery === void 0 ? void 0 : recovery.otpCode,
11623
+ userId: user.id,
11624
+ }),
11510
11625
  };
11511
11626
  }
11512
11627
  case RecoveryMethod.PASSWORD:
@@ -11544,7 +11659,7 @@ function useWallets(hookOptions = {}) {
11544
11659
  default:
11545
11660
  throw new OpenfortError('Invalid recovery method', OpenfortReactErrorType.VALIDATION_ERROR);
11546
11661
  }
11547
- }, [walletConfig, getEncryptionSession]);
11662
+ }, [walletConfig, getEncryptionSession, user === null || user === void 0 ? void 0 : user.id]);
11548
11663
  const userLinkedWalletConnectors = useMemo(() => {
11549
11664
  const userWallets = linkedAccounts
11550
11665
  ? linkedAccounts
@@ -11604,7 +11719,7 @@ function useWallets(hookOptions = {}) {
11604
11719
  connect({ connector: connectToConnector.connector });
11605
11720
  }, [connectToConnector]);
11606
11721
  const setActiveWallet = useCallback(async (options) => {
11607
- var _a, _b, _c, _d, _e, _f;
11722
+ var _a, _b, _c, _d, _e, _f, _g, _h;
11608
11723
  const optionsObject = typeof options === 'string' ? { walletId: options } : options;
11609
11724
  const { showUI } = optionsObject;
11610
11725
  let connector = null;
@@ -11715,6 +11830,7 @@ function useWallets(hookOptions = {}) {
11715
11830
  const recovery = {
11716
11831
  recoveryMethod: (_c = accountToRecover.recoveryMethod) !== null && _c !== void 0 ? _c : RecoveryMethod.AUTOMATIC,
11717
11832
  password: (_d = optionsObject.recovery) === null || _d === void 0 ? void 0 : _d.password,
11833
+ otpCode: (_e = optionsObject.recovery) === null || _e === void 0 ? void 0 : _e.otpCode,
11718
11834
  };
11719
11835
  const recoveryParams = await parseWalletRecovery(recovery, embeddedAccounts, walletAddress);
11720
11836
  embeddedAccount = await client.embeddedWallet.recover({
@@ -11741,8 +11857,9 @@ function useWallets(hookOptions = {}) {
11741
11857
  }
11742
11858
  logger.log('Found embedded wallet to recover (without walletAddress)', accountToRecover);
11743
11859
  const recovery = {
11744
- recoveryMethod: (_e = accountToRecover.recoveryMethod) !== null && _e !== void 0 ? _e : RecoveryMethod.AUTOMATIC,
11745
- password: (_f = optionsObject.recovery) === null || _f === void 0 ? void 0 : _f.password,
11860
+ recoveryMethod: (_f = accountToRecover.recoveryMethod) !== null && _f !== void 0 ? _f : RecoveryMethod.AUTOMATIC,
11861
+ password: (_g = optionsObject.recovery) === null || _g === void 0 ? void 0 : _g.password,
11862
+ otpCode: (_h = optionsObject.recovery) === null || _h === void 0 ? void 0 : _h.otpCode,
11746
11863
  };
11747
11864
  const recoveryParams = await parseWalletRecovery(recovery, embeddedAccounts, walletAddress);
11748
11865
  embeddedAccount = await client.embeddedWallet.recover({
@@ -11793,15 +11910,29 @@ function useWallets(hookOptions = {}) {
11793
11910
  }
11794
11911
  }
11795
11912
  logger.log('Error handling recovery with Openfort:', error, err);
11913
+ let isOTPRequired = false;
11914
+ if (error.message === 'OTP_REQUIRED') {
11915
+ if (!isWalletRecoveryOTPEnabled) {
11916
+ error = new OpenfortError('OTP code is required to recover the wallet.\nPlease set requestWalletRecoverOTP or requestWalletRecoverOTPEndpoint in OpenfortProvider.', OpenfortReactErrorType.WALLET_ERROR);
11917
+ }
11918
+ else {
11919
+ error = new OpenfortError('OTP code is required to recover the wallet.', OpenfortReactErrorType.WALLET_ERROR);
11920
+ }
11921
+ isOTPRequired = true;
11922
+ }
11796
11923
  setStatus({
11797
11924
  status: 'error',
11798
11925
  error,
11799
11926
  });
11800
- return onError({
11927
+ onError({
11801
11928
  error,
11802
11929
  options: optionsObject,
11803
11930
  hookOptions,
11804
11931
  });
11932
+ return {
11933
+ error,
11934
+ isOTPRequired,
11935
+ };
11805
11936
  }
11806
11937
  finally {
11807
11938
  if (hasToSwitchChain) {
@@ -11878,18 +12009,29 @@ function useWallets(hookOptions = {}) {
11878
12009
  }
11879
12010
  catch (e) {
11880
12011
  const errorObj = e instanceof Error ? e : new Error('Failed to create wallet');
11881
- const error = e instanceof OpenfortError
12012
+ let error = e instanceof OpenfortError
11882
12013
  ? e
11883
12014
  : new OpenfortError('Failed to create wallet', OpenfortReactErrorType.WALLET_ERROR, { error: errorObj });
12015
+ let isOTPRequired = false;
12016
+ if (error.message === 'OTP_REQUIRED') {
12017
+ if (!isWalletRecoveryOTPEnabled) {
12018
+ error = new OpenfortError('OTP code is required to recover the wallet.\nPlease set requestWalletRecoverOTP or requestWalletRecoverOTPEndpoint in OpenfortProvider.', OpenfortReactErrorType.WALLET_ERROR);
12019
+ }
12020
+ else {
12021
+ error = new OpenfortError('OTP code is required to recover the wallet.', OpenfortReactErrorType.WALLET_ERROR);
12022
+ }
12023
+ isOTPRequired = true;
12024
+ }
11884
12025
  setStatus({
11885
12026
  status: 'error',
11886
12027
  error,
11887
12028
  });
11888
- return onError({
12029
+ onError({
11889
12030
  error,
11890
12031
  hookOptions,
11891
12032
  options,
11892
12033
  });
12034
+ return { error, isOTPRequired };
11893
12035
  }
11894
12036
  }, [client, uiConfig, chainId]);
11895
12037
  const setRecovery = useCallback(async (params) => {
@@ -11938,6 +12080,8 @@ function useWallets(hookOptions = {}) {
11938
12080
  reset: () => setStatus({ status: 'idle' }),
11939
12081
  createWallet,
11940
12082
  setActiveWallet,
12083
+ requestWalletRecoverOTP,
12084
+ isWalletRecoveryOTPEnabled,
11941
12085
  ...mapWalletStatus(status),
11942
12086
  exportPrivateKey: () => client.embeddedWallet.exportPrivateKey(),
11943
12087
  };
@@ -12192,1381 +12336,1462 @@ const Input = ({ ...props }) => {
12192
12336
  return (jsx(InputContainer, { children: jsx(Input$1, { ...props }) }));
12193
12337
  };
12194
12338
 
12195
- const TickListContainer = styled.ul `
12196
- display: flex;
12197
- flex-direction: column;
12198
- gap: 8px;
12199
- padding-top: 8px;
12200
- padding-bottom: 8px;
12201
- `;
12202
- const TickItem = styled.li `
12203
- display: flex;
12204
- align-items: center;
12205
- text-align: left;
12206
- gap: 8px;
12207
- font-size: 16px;
12208
- line-height: 24px;
12339
+ const caretBlink = keyframes `
12340
+ 0%, 70%, 100% { opacity: 1; }
12341
+ 20%, 50% { opacity: 0; }
12209
12342
  `;
12210
- const TickIconWrapper = styled.span `
12343
+ const OtpContainer = styled.div `
12211
12344
  display: flex;
12212
12345
  align-items: center;
12213
12346
  justify-content: center;
12214
- width: 16px;
12215
- height: 16px;
12216
- flex-shrink: 0;
12217
- `;
12347
+ scale: ${({ scale }) => scale || '1'};
12218
12348
 
12219
- const TickList = ({ items }) => {
12220
- return (jsx(TickListContainer, { children: items.map((item) => (jsxs(TickItem, { children: [jsx(TickIconWrapper, { children: jsx(TickIcon, {}) }), jsx("span", { children: item })] }, item))) }));
12221
- };
12222
- TickList.displayName = 'TickList';
12223
-
12224
- var wave = (jsxs("svg", { "aria-hidden": "true", width: "298", height: "188", viewBox: "0 0 298 188", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsx("path", { d: "M1 55.2757L21.6438 46.0285C55.5896 30.8228 94.4104 30.8228 128.356 46.0286L169.644 64.5229C203.59 79.7287 242.41 79.7286 276.356 64.5229L297 55.2757M1 44.2118L21.6438 34.9646C55.5896 19.7589 94.4104 19.7589 128.356 34.9646L169.644 53.459C203.59 68.6647 242.41 68.6647 276.356 53.459L297 44.2118M1 33.1477L21.6438 23.9005C55.5896 8.69479 94.4104 8.69479 128.356 23.9005L169.644 42.3949C203.59 57.6006 242.41 57.6006 276.356 42.3949L297 33.1477M1 22.1477L21.6438 12.9005C55.5896 -2.30521 94.4104 -2.30521 128.356 12.9005L169.644 31.3949C203.59 46.6006 242.41 46.6006 276.356 31.3949L297 22.1477M1 66.3398L21.6438 57.0926C55.5896 41.8869 94.4104 41.8869 128.356 57.0926L169.644 75.587C203.59 90.7927 242.41 90.7927 276.356 75.587L297 66.3398M1 77.404L21.6438 68.1568C55.5896 52.9511 94.4104 52.9511 128.356 68.1569L169.644 86.6512C203.59 101.857 242.41 101.857 276.356 86.6512L297 77.404M1 88.4681L21.6438 79.2209C55.5896 64.0152 94.4104 64.0152 128.356 79.2209L169.644 97.7153C203.59 112.921 242.41 112.921 276.356 97.7153L297 88.4681M1 121.66L21.6438 112.413C55.5896 97.2075 94.4104 97.2075 128.356 112.413L169.644 130.908C203.59 146.113 242.41 146.113 276.356 130.908L297 121.66M1 110.596L21.6438 101.349C55.5896 86.1433 94.4104 86.1433 128.356 101.349L169.644 119.843C203.59 135.049 242.41 135.049 276.356 119.843L297 110.596M1 99.5321L21.6438 90.2849C55.5896 75.0792 94.4104 75.0792 128.356 90.2849L169.644 108.779C203.59 123.985 242.41 123.985 276.356 108.779L297 99.5321M1 132.724L21.6438 123.477C55.5896 108.271 94.4104 108.271 128.356 123.477L169.644 141.971C203.59 157.177 242.41 157.177 276.356 141.971L297 132.724M1 143.788L21.6438 134.541C55.5896 119.336 94.4104 119.336 128.356 134.541L169.644 153.036C203.59 168.241 242.41 168.241 276.356 153.036L297 143.788M1 154.853L21.6438 145.605C55.5896 130.4 94.4104 130.4 128.356 145.605L169.644 164.1C203.59 179.305 242.41 179.305 276.356 164.1L297 154.853M1 165.853L21.6438 156.605C55.5896 141.4 94.4104 141.4 128.356 156.605L169.644 175.1C203.59 190.305 242.41 190.305 276.356 175.1L297 165.853", stroke: "url(#paint0_linear_1094_2077)", strokeOpacity: "0.9", strokeLinecap: "round", strokeLinejoin: "round" }), jsx("defs", { children: jsxs("linearGradient", { id: "paint0_linear_1094_2077", x1: "1", y1: "112.587", x2: "297.034", y2: "79.6111", gradientUnits: "userSpaceOnUse", children: [jsx("stop", { stopColor: "var(--ck-graphic-wave-stop-01)" }), jsx("stop", { stopColor: "var(--ck-graphic-wave-stop-02)", offset: "0.239583" }), jsx("stop", { stopColor: "var(--ck-graphic-wave-stop-03)", offset: "0.515625" }), jsx("stop", { stopColor: "var(--ck-graphic-wave-stop-04)", offset: "0.739583" }), jsx("stop", { stopColor: "var(--ck-graphic-wave-stop-05)", offset: "1" })] }) })] }));
12225
-
12226
- const Graphic = styled(motion.div) `
12227
- position: relative;
12228
- margin-top: ${({ $marginTop }) => $marginTop !== null && $marginTop !== void 0 ? $marginTop : '16px'};
12229
- margin-bottom: ${({ $marginBottom }) => $marginBottom !== null && $marginBottom !== void 0 ? $marginBottom : '20px'};
12230
- margin-left: auto;
12231
- margin-right: auto;
12232
- height: ${({ $height }) => $height !== null && $height !== void 0 ? $height : '190px'};
12233
- max-width: 295px;
12234
- pointer-events: none;
12235
- user-select: none;
12236
- @media only screen and (max-width: ${defaultTheme.mobileWidth}px) {
12237
- height: 200px;
12238
- max-width: 100%;
12239
- margin-bottom: 32px;
12240
- }
12349
+ --border: ${({ showBorder }) => (showBorder ? 'var(--ck-body-color-muted)' : 'transparent')};
12241
12350
  `;
12242
- const LogoGroup = styled(motion.div) `
12243
- position: absolute;
12244
- inset: 0;
12245
- z-index: 2;
12246
- `;
12247
- const graphicIn = keyframes `
12248
- 0%{
12249
- opacity:0;
12250
- transform:scale(0.9);
12251
- }
12252
- 100%{
12253
- opacity:1;
12254
- transform:none;
12255
- }
12256
- `;
12257
- const GraphicBackground = styled(motion.div) `
12258
- z-index: 1;
12259
- position: absolute;
12260
- inset: 0;
12261
- top: -2px;
12262
- overflow: hidden;
12263
- &:before {
12264
- content: '';
12265
- position: absolute;
12266
- inset: 0;
12267
- background: var(--ck-body-background);
12268
- background: radial-gradient(
12269
- closest-side,
12270
- var(--ck-body-background-transparent, transparent) 18.75%,
12271
- var(--ck-body-background) 100%
12272
- );
12273
- background-size: 100%;
12274
- }
12275
- svg {
12276
- display: block;
12277
- width: 100%;
12278
- height: auto;
12279
- }
12280
- animation: ${graphicIn} 1000ms 100ms ease both;
12281
- @media only screen and (max-width: ${defaultTheme.mobileWidth}px) {
12282
- animation: none;
12351
+ const pulse = keyframes `
12352
+ 0% {
12353
+ opacity: 100%;
12283
12354
  }
12284
- `;
12285
- const logoIn = keyframes `
12286
- 0%{
12287
- opacity:0;
12288
- transform:scale(0) translateY(40%);
12355
+ 50% {
12356
+ opacity: 40%;
12289
12357
  }
12290
- 100%{
12291
- opacity:1;
12292
- transform:none;
12358
+ 100% {
12359
+ opacity: 100%;
12293
12360
  }
12294
12361
  `;
12295
- const LogoPosition = styled(motion.div) `
12296
- position: absolute;
12297
- inset: 0;
12298
- animation: cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite both;
12299
- animation-delay: inherit;
12362
+ const dist = 2;
12363
+ const shakeKeyframes = keyframes `
12364
+ 0%{ transform:none; }
12365
+ 25%{ transform:translateX(${dist}px); }
12366
+ 50%{ transform:translateX(-${dist}px); }
12367
+ 75%{ transform:translateX(${dist}px); }
12368
+ 100%{ transform:none; }
12300
12369
  `;
12301
- const LogoInner = styled(motion.div) `
12302
- position: absolute;
12370
+ const keyframeSuccess = keyframes `
12371
+ 0% { transform: scale(1); }
12372
+ 50% { transform: scale(1.1); }
12373
+ 100% { transform: scale(1); }
12303
12374
  `;
12304
- const LogoGraphic$1 = styled(motion.div) `
12305
- position: relative;
12306
- overflow: hidden;
12307
- height: 58px;
12308
- width: 58px;
12309
- border-radius: 13.84px;
12310
- box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 20px 0 rgba(0, 0, 0, 0.03);
12375
+ const OTPGroup = styled.div `
12376
+ display: flex;
12311
12377
 
12312
- svg {
12313
- display: block;
12314
- width: 100%;
12315
- height: 100%;
12316
- }
12317
- `;
12318
- const float = keyframes `
12319
- 0%,100%{ transform:none; }
12320
- 50%{ transform: translateY(-10%) }
12321
- `;
12322
- const FloatWrapper = styled(motion.div) `
12323
- position: relative;
12324
- animation: cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite both;
12325
- animation-name: ${float};
12326
- animation-duration: 3600ms;
12327
- `;
12328
- const rotate = keyframes `
12329
- 0%,100%{ transform:rotate(-3deg); }
12330
- 50%{ transform:rotate(3deg); }
12378
+ outline-width: 3px;
12379
+ outline-style: solid;
12380
+ border-radius: 0.375rem;
12381
+ transition: outline-color 0.3s, border-radius .5s;
12382
+
12383
+ outline-color: ${({ isError, isSuccess }) => {
12384
+ if (isError)
12385
+ return 'var(--ck-body-color-danger)';
12386
+ if (isSuccess)
12387
+ return 'var(--ck-body-color-valid)';
12388
+ return 'transparent';
12389
+ }};
12390
+
12391
+ ${({ isLoading }) => isLoading &&
12392
+ css `
12393
+ animation: ${pulse} 1s ease-in-out infinite;
12394
+ `}
12395
+
12396
+ ${({ isError }) => isError &&
12397
+ css `
12398
+ animation: ${shakeKeyframes} 220ms ease-out both;
12399
+ `}
12400
+
12401
+ ${({ isSuccess }) => isSuccess &&
12402
+ css `
12403
+ border-radius: 3rem;
12404
+ min-width: 3.5rem;
12405
+ ${OTPSlotWrapper} {
12406
+ width: 0;
12407
+ border: 0;
12408
+ transition: width .5s, border .5s;
12409
+ }
12410
+ animation: ${keyframeSuccess} 220ms ease-out both;
12411
+ animation-delay: 250ms;
12412
+ `}
12331
12413
  `;
12332
- const RotateWrapper = styled(motion.div) `
12414
+ const OTPSlotWrapper = styled.div `
12333
12415
  position: relative;
12334
- animation: cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite both;
12335
- animation-name: ${rotate};
12336
- animation-duration: 3200ms;
12337
- `;
12338
- const Logo$1 = styled(motion.div) `
12339
- position: absolute;
12340
- inset: 0;
12416
+ width: 2.5rem;
12417
+ height: 3.5rem;
12418
+ font-size: 2rem;
12341
12419
 
12342
- animation: ${logoIn} 750ms cubic-bezier(0.19, 1, 0.22, 1) both;
12343
- &:nth-child(1){ z-index:2; animation-delay:0ms; }
12344
- &:nth-child(2){ z-index:1; animation-delay:60ms; }
12345
- &:nth-child(3){ z-index:1; animation-delay:30ms; }
12346
- &:nth-child(4){ z-index:1; animation-delay:90ms; }
12347
- &:nth-child(5){ z-index:1; animation-delay:120ms;}
12420
+ display: flex;
12421
+ align-items: center;
12422
+ justify-content: center;
12348
12423
 
12349
- &:nth-child(1){ ${RotateWrapper}{ animation-delay:0ms; } }
12350
- &:nth-child(2){ ${RotateWrapper}{ animation-delay:-600ms; } }
12351
- &:nth-child(3){ ${RotateWrapper}{ animation-delay:-1200ms; } }
12352
- &:nth-child(4){ ${RotateWrapper}{ animation-delay:-1800ms; } }
12353
- &:nth-child(5){ ${RotateWrapper}{ animation-delay:-2400ms; } }
12424
+ transition: all 0.3s;
12354
12425
 
12355
- &:nth-child(1){ ${FloatWrapper}{ animation-delay:-200ms; } }
12356
- &:nth-child(2){ ${FloatWrapper}{ animation-delay:-600ms; } }
12357
- &:nth-child(3){ ${FloatWrapper}{ animation-delay:-800ms; } }
12358
- &:nth-child(4){ ${FloatWrapper}{ animation-delay:-300ms; } }
12359
- &:nth-child(5){ ${FloatWrapper}{ animation-delay:-3200ms; } }
12426
+ border-top: 1px solid var(--border);
12427
+ border-bottom: 1px solid var(--border);
12428
+ border-right: 0.5px solid var(--border);
12429
+ border-left: 0.5px solid var(--border);
12360
12430
 
12361
- @media only screen and (max-width: ${defaultTheme.mobileWidth}px) {
12362
- animation: none !important;
12363
- ${RotateWrapper},${FloatWrapper} {
12364
- animation: none !important;
12365
- }
12431
+ &:first-child {
12432
+ border-left: 1px solid var(--border);
12433
+ border-radius: 0.375rem 0 0 0.375rem;
12366
12434
  }
12367
12435
 
12368
- ${LogoInner} {
12369
- transform: translate(-50%, -50%);
12436
+ &:last-child {
12437
+ border-radius: 0 0.375rem 0.375rem 0;
12370
12438
  }
12371
12439
 
12372
- &:nth-child(1) ${LogoPosition} {
12373
- transform: translate(50%, 50%);
12374
- ${LogoGraphic$1} {
12375
- border-radius: 17.2px;
12376
- width: 72px;
12377
- height: 72px;
12378
- }
12379
- }
12380
- &:nth-child(2) ${LogoPosition} {
12381
- transform: translate(21%, 21.5%);
12382
- }
12383
- &:nth-child(3) ${LogoPosition} {
12384
- transform: translate(78%, 14%);
12385
- }
12386
- &:nth-child(4) ${LogoPosition} {
12387
- transform: translate(22.5%, 76%);
12388
- }
12389
- &:nth-child(5) ${LogoPosition} {
12390
- transform: translate(76%, 80%);
12391
- }
12440
+ outline: ${({ isActive }) => (isActive ? '2px solid var(--ck-connectbutton-color)' : '0')};
12441
+ z-index: ${({ isActive }) => (isActive ? 1 : 0)};
12442
+ outline-offset: 0;
12443
+
12444
+ cursor: text;
12445
+ color: var(--ck-body-color);
12446
+ `;
12447
+ const OTPNumberValue = styled.div `
12448
+ opacity: ${({ $hide }) => ($hide ? 0 : 1)};
12449
+ transition: opacity 0.3s;
12450
+ `;
12451
+ const OTPHiddenInput = styled.input `
12452
+ position: absolute;
12453
+ inset: 0;
12454
+ opacity: 0;
12455
+ cursor: text;
12456
+ caret-color: transparent; /* Hide native caret */
12392
12457
  `;
12458
+ const FakeCaretWrapper = styled.div `
12459
+ position: absolute;
12460
+ inset: 0;
12461
+ pointer-events: none;
12393
12462
 
12394
- const LogoGraphic = ({ size = '100%', logo }) => {
12395
- return (jsx(Logo$1, { children: jsx(LogoPosition, { children: jsx(LogoInner, { children: jsx(FloatWrapper, { children: jsx(RotateWrapper, { children: jsx(LogoGraphic$1, { style: { transform: `scale(${size})` }, children: logo }) }) }) }) }) }));
12396
- };
12397
- const FloatingGraphic = ({ height = '130px', marginBottom, marginTop, logoCenter, logoTopRight, logoTopLeft, logoBottomRight, logoBottomLeft, }) => {
12398
- return (jsxs(Graphic, { "$height": height, "$marginBottom": marginBottom, "$marginTop": marginTop, children: [jsxs(LogoGroup, { children: [jsx(LogoGraphic, { ...logoCenter }), logoTopLeft ? jsx(LogoGraphic, { ...logoTopLeft }) : jsx("div", {}), logoTopRight ? jsx(LogoGraphic, { ...logoTopRight }) : jsx("div", {}), logoBottomLeft ? jsx(LogoGraphic, { ...logoBottomLeft }) : jsx("div", {}), logoBottomRight ? jsx(LogoGraphic, { ...logoBottomRight }) : jsx("div", {})] }), jsx(GraphicBackground, { children: wave })] }));
12399
- };
12463
+ display: flex;
12464
+ align-items: center;
12465
+ justify-content: center;
12400
12466
 
12401
- /**
12402
- * Password utility helpers for strength calculation and validation.
12403
- *
12404
- * Provides functions for password strength calculation, passphrase generation, and password validation.
12405
- */
12406
- // ============================================================================
12407
- // Constants and Regular Expressions
12408
- // ============================================================================
12409
- /**
12410
- * Regular expression to match lowercase letters.
12411
- */
12412
- const LOWERCASE_REGEX = /[a-z]/;
12413
- /**
12414
- * Regular expression to match uppercase letters.
12415
- */
12416
- const UPPERCASE_REGEX = /[A-Z]/;
12417
- /**
12418
- * Regular expression to match digits.
12419
- */
12420
- const DIGIT_REGEX = /[0-9]/;
12421
- /**
12422
- * Special characters allowed in passwords.
12423
- */
12424
- const SPECIAL_CHARACTERS = '!@#$%^&()\\-*+.';
12425
- /**
12426
- * Escape regex metacharacters for safe use inside a character class.
12427
- */
12428
- function escapeForCharClass(str) {
12429
- return str.replace(/[-\\^]/g, '\\$&');
12430
- // escapes -, \, and ^ (the only risky chars in [])
12431
- }
12432
- /**
12433
- * Regular expression to match special characters.
12434
- */
12435
- const SPECIAL_CHARACTER_REGEX = new RegExp(`[${escapeForCharClass(SPECIAL_CHARACTERS)}]`);
12436
- /**
12437
- * Maximum entropy score for normalization.
12438
- */
12439
- const MAX_ENTROPY_SCORE = 95;
12440
- /**
12441
- * Minimum password length for security.
12442
- */
12443
- const MIN_PASSWORD_LENGTH = 8;
12444
- /**
12445
- * Weight for diversity score in overall strength calculation.
12446
- */
12447
- const DIVERSITY_WEIGHT = 0.3;
12448
- const ENTROPY_WEIGHT = 0.7;
12449
- const MEDIUM_SCORE_THRESHOLD = 0.5;
12450
- const STRONG_SCORE_THRESHOLD = 0.75;
12451
- const VERY_STRONG_SCORE_THRESHOLD = 0.9;
12452
- /**
12453
- * Converts a numeric password strength score to a human-readable label.
12454
- *
12455
- * @param score - The strength score (0-1).
12456
- * @returns The corresponding strength label.
12457
- *
12458
- * @example
12459
- * ```ts
12460
- * const label = getPasswordStrengthLabel(0.8);
12461
- * // label === 'Strong'
12462
- * ```
12463
- */
12464
- function getPasswordStrengthLabel(score) {
12465
- if (score > VERY_STRONG_SCORE_THRESHOLD) {
12466
- return 'Very Strong';
12467
- }
12468
- else if (score > STRONG_SCORE_THRESHOLD) {
12469
- return 'Strong';
12470
- }
12471
- else if (score > MEDIUM_SCORE_THRESHOLD) {
12472
- return 'Medium';
12473
- }
12474
- else {
12475
- return 'Weak';
12476
- }
12477
- }
12478
- /**
12479
- * Calculates the diversity score of a password based on character types used.
12480
- *
12481
- * Considers lowercase, uppercase, digits, and special characters.
12482
- *
12483
- * @param password - The password to analyse.
12484
- * @returns A score between 0 and 1 representing character diversity.
12485
- *
12486
- * @example
12487
- * ```ts
12488
- * const diversity = calculatePasswordDiversityScore('Password123!');
12489
- * ```
12490
- */
12491
- function calculatePasswordDiversityScore(password) {
12492
- // Passwords shorter than minimum length get a score of 0
12493
- if (password.length < MIN_PASSWORD_LENGTH) {
12494
- return 0;
12495
- }
12496
- let characterTypesUsed = 0;
12497
- if (LOWERCASE_REGEX.test(password)) {
12498
- characterTypesUsed += 1;
12499
- }
12500
- if (UPPERCASE_REGEX.test(password)) {
12501
- characterTypesUsed += 1;
12502
- }
12503
- if (DIGIT_REGEX.test(password)) {
12504
- characterTypesUsed += 1;
12505
- }
12506
- if (SPECIAL_CHARACTER_REGEX.test(password)) {
12507
- characterTypesUsed += 1;
12508
- }
12509
- return Math.max(0, Math.min(1, characterTypesUsed / 4));
12467
+ animation: ${caretBlink} 1.2s ease-out infinite;
12468
+ `;
12469
+ const CaretBar = styled.div `
12470
+ width: 1px;
12471
+ height: 2rem;
12472
+ background: var(--ck-body-color);
12473
+ `;
12474
+ const keyframeWrapper = keyframes `
12475
+ 0% { transform: scale(0); }
12476
+ 100% { transform: scale(1); }
12477
+ `;
12478
+ const SuccessTickWrapper = styled.div `
12479
+ position: absolute;
12480
+ inset: 5px;
12481
+ display: flex;
12482
+ animation: ${keyframeWrapper} 200ms ease-out both;
12483
+ animation-delay: 200ms;
12484
+ color: var(--ck-body-color-valid);
12485
+ `;
12486
+
12487
+ function FakeCaret() {
12488
+ return (jsx(FakeCaretWrapper, { children: jsx(CaretBar, {}) }));
12510
12489
  }
12511
- /**
12512
- * Calculates the overall password strength combining diversity and entropy.
12513
- *
12514
- * @param password - The password to analyse.
12515
- * @returns A strength score between 0 and 1.
12516
- *
12517
- * @example
12518
- * ```ts
12519
- * const strength = getPasswordStrength('Password123!');
12520
- * ```
12521
- */
12522
- function getPasswordStrength(password = '') {
12523
- const diversityScore = calculatePasswordDiversityScore(password);
12524
- const entropyScore = calculateEntropy(password) / MAX_ENTROPY_SCORE;
12525
- return Math.min(diversityScore * DIVERSITY_WEIGHT + entropyScore * ENTROPY_WEIGHT, 1);
12490
+ function OtpInputStandalone({ length = 6, onChange, onComplete, isLoading, isError, isSuccess, scale, }) {
12491
+ const [values, setValues] = useState(Array(length).fill(''));
12492
+ const [activeIndex, setActiveIndex] = useState(0);
12493
+ const canEdit = !isLoading && !isError && !isSuccess;
12494
+ const inputsRef = useRef([]);
12495
+ const handleInput = (index, char) => {
12496
+ var _a;
12497
+ if (!char.match(/^[0-9]$/))
12498
+ return;
12499
+ if (!canEdit)
12500
+ return;
12501
+ const newValues = [...values];
12502
+ newValues[index] = char;
12503
+ setValues(newValues);
12504
+ onChange === null || onChange === void 0 ? void 0 : onChange(newValues.join(''));
12505
+ // Move cursor to next box
12506
+ if (index < length - 1) {
12507
+ setActiveIndex(index + 1);
12508
+ (_a = inputsRef.current[index + 1]) === null || _a === void 0 ? void 0 : _a.focus();
12509
+ }
12510
+ };
12511
+ useEffect(() => {
12512
+ if (values.every((v) => v !== '')) {
12513
+ onComplete === null || onComplete === void 0 ? void 0 : onComplete(values.join(''));
12514
+ }
12515
+ }, [values]);
12516
+ const handleBackspace = (index) => {
12517
+ var _a;
12518
+ const newValues = [...values];
12519
+ if (newValues[index] === '') {
12520
+ if (index > 0) {
12521
+ // Move back
12522
+ setActiveIndex(index - 1);
12523
+ (_a = inputsRef.current[index - 1]) === null || _a === void 0 ? void 0 : _a.focus();
12524
+ }
12525
+ newValues[index - 1] = '';
12526
+ }
12527
+ else {
12528
+ newValues[index] = '';
12529
+ }
12530
+ setValues(newValues);
12531
+ onChange === null || onChange === void 0 ? void 0 : onChange(newValues.join(''));
12532
+ };
12533
+ const handlePaste = (e) => {
12534
+ var _a;
12535
+ const pasted = e.clipboardData.getData('text').replace(/\D/g, '');
12536
+ if (!pasted)
12537
+ return;
12538
+ const arr = pasted.substring(0, length).split('');
12539
+ const newValues = [...values];
12540
+ arr.forEach((char, i) => {
12541
+ newValues[i] = char;
12542
+ });
12543
+ setValues(newValues);
12544
+ onChange === null || onChange === void 0 ? void 0 : onChange(newValues.join(''));
12545
+ const finalIndex = Math.min(arr.length - 1, length - 1);
12546
+ setActiveIndex(finalIndex);
12547
+ (_a = inputsRef.current[finalIndex]) === null || _a === void 0 ? void 0 : _a.focus();
12548
+ };
12549
+ const handleFocus = (i) => {
12550
+ var _a, _b;
12551
+ if (activeIndex !== -1) {
12552
+ setActiveIndex(i);
12553
+ return;
12554
+ }
12555
+ const firstEmptyIndex = values.indexOf('');
12556
+ if (firstEmptyIndex !== -1) {
12557
+ setActiveIndex(firstEmptyIndex);
12558
+ (_a = inputsRef.current[firstEmptyIndex]) === null || _a === void 0 ? void 0 : _a.focus();
12559
+ }
12560
+ else {
12561
+ setActiveIndex(length - 1);
12562
+ (_b = inputsRef.current[length - 1]) === null || _b === void 0 ? void 0 : _b.focus();
12563
+ }
12564
+ };
12565
+ useEffect(() => {
12566
+ var _a;
12567
+ if (!isError) {
12568
+ setValues(Array(length).fill(''));
12569
+ setActiveIndex(0);
12570
+ (_a = inputsRef.current[0]) === null || _a === void 0 ? void 0 : _a.focus();
12571
+ }
12572
+ }, [isError]);
12573
+ return (jsx(OtpContainer, { showBorder: !isSuccess, scale: scale, children: jsxs(OTPGroup, { isError: isError, isSuccess: isSuccess, isLoading: isLoading, children: [values.slice(0, length).map((value, idx) => {
12574
+ const index = idx;
12575
+ return (jsxs(OTPSlotWrapper, { isActive: canEdit && activeIndex === index, children: [jsx(OTPHiddenInput, { ref: (el) => {
12576
+ inputsRef.current[index] = el;
12577
+ }, value: "", inputMode: "numeric", onBlur: () => setActiveIndex(-1), autoComplete: "one-time-code", autoFocus: index === 0, onFocus: () => handleFocus(index), onPaste: handlePaste, onKeyDown: (e) => {
12578
+ if (!canEdit)
12579
+ return;
12580
+ if (e.key === 'Backspace') {
12581
+ e.preventDefault();
12582
+ handleBackspace(index);
12583
+ }
12584
+ }, onChange: (e) => handleInput(index, e.target.value) }), value && jsx(OTPNumberValue, { "$hide": isSuccess, children: value }), !value && activeIndex === index && jsx(FakeCaret, {})] }, index));
12585
+ }), isSuccess && (jsx(SuccessTickWrapper, { children: jsx(TickIcon, { width: '100%', height: '100%' }) }))] }) }));
12526
12586
  }
12527
12587
 
12528
- const Container$1 = styled.div `
12588
+ const TickListContainer = styled.ul `
12529
12589
  display: flex;
12530
12590
  flex-direction: column;
12531
- gap: 0.5rem;
12532
- margin-top: 0.5rem;
12533
- margin-bottom: 0.5rem;
12534
- text-align: left;
12535
- `;
12536
- const BarWrapper = styled.div `
12537
- width: 100%;
12538
- height: 4px;
12539
- background: var(--ck-secondary-button-background);
12540
- border-radius: 4px;
12541
- overflow: hidden;
12591
+ gap: 8px;
12592
+ padding-top: 8px;
12593
+ padding-bottom: 8px;
12542
12594
  `;
12543
- const Progress = styled(motion.div) `
12544
- height: 100%;
12545
- background: ${({ color }) => color};
12546
- border-radius: 4px;
12595
+ const TickItem = styled.li `
12596
+ display: flex;
12597
+ align-items: center;
12598
+ text-align: left;
12599
+ gap: 8px;
12600
+ font-size: 16px;
12601
+ line-height: 24px;
12547
12602
  `;
12548
- const Label$1 = styled.div `
12549
- font-size: 0.875rem;
12550
- font-weight: 500;
12551
- color: var(--ck-body-color-muted);
12603
+ const TickIconWrapper = styled.span `
12604
+ display: flex;
12605
+ align-items: center;
12606
+ justify-content: center;
12607
+ width: 16px;
12608
+ height: 16px;
12609
+ flex-shrink: 0;
12552
12610
  `;
12553
- const LabelColor = styled.span `
12554
- color: ${({ color }) => color};
12555
- `;
12556
- const PasswordStrengthIndicator = ({ password, showPasswordIsTooWeakError, }) => {
12557
- const passwordStrength = getPasswordStrength(password); // should return a number between 0 and 1
12558
- const label = getPasswordStrengthLabel(passwordStrength);
12559
- const color = useMemo(() => {
12560
- switch (label) {
12561
- case 'Weak':
12562
- return '#ef4444'; // red-500
12563
- case 'Medium':
12564
- return '#f59e0b'; // amber-500
12565
- case 'Strong':
12566
- return '#10b981'; // emerald-500
12567
- case 'Very Strong':
12568
- return '#3b82f6'; // blue-500
12569
- default:
12570
- return '#d1d5db'; // gray-300
12571
- }
12572
- }, [label]);
12573
- return (jsxs(Container$1, { children: [jsx(BarWrapper, { children: jsx(Progress, { color: color, initial: { width: 0 }, animate: { width: `${passwordStrength * 100}%` }, transition: { ease: 'easeOut', duration: 0.5 } }) }), jsxs("div", { style: { position: 'relative' }, children: [jsx(motion.div, { initial: { opacity: 1 }, animate: {
12574
- opacity: showPasswordIsTooWeakError ? 0 : 1,
12575
- y: showPasswordIsTooWeakError ? 5 : 0,
12576
- }, transition: { duration: 0.3 }, children: jsxs(Label$1, { children: ["Password strength: ", jsx(LabelColor, { color: color, children: label })] }) }), jsx(motion.div, { initial: { opacity: 0 }, animate: {
12577
- opacity: showPasswordIsTooWeakError ? 1 : 0,
12578
- y: showPasswordIsTooWeakError ? 0 : -5,
12579
- }, transition: { duration: 0.3 }, style: { color: '#ef4444', fontSize: '0.875rem', fontWeight: 500, position: 'absolute', top: '0' }, children: "Password is too weak" })] })] }));
12580
- };
12581
-
12582
- const ProviderInputInner = styled.div `
12583
- // Styles from react-international-phone (imported here to avoid importing the whole CSS file for nextjs compatibility)
12584
- .react-international-phone-country-selector{position:relative}.react-international-phone-country-selector-button{display:flex;height:var(--react-international-phone-height, 36px);box-sizing:border-box;align-items:center;justify-content:center;padding:0;border:1px solid var(--react-international-phone-country-selector-border-color, var(--react-international-phone-border-color, gainsboro));margin:0;appearance:button;-webkit-appearance:button;background-color:var(--react-international-phone-country-selector-background-color, var(--react-international-phone-background-color, white));cursor:pointer;text-transform:none;user-select:none}.react-international-phone-country-selector-button:hover{background-color:var(--react-international-phone-country-selector-background-color-hover, whitesmoke)}.react-international-phone-country-selector-button--hide-dropdown{cursor:auto}.react-international-phone-country-selector-button--hide-dropdown:hover{background-color:transparent}.react-international-phone-country-selector-button__button-content{display:flex;align-items:center;justify-content:center}.react-international-phone-country-selector-button__flag-emoji{margin:0 4px}.react-international-phone-country-selector-button__flag-emoji--disabled{opacity:.75}.react-international-phone-country-selector-button__dropdown-arrow{border-top:var(--react-international-phone-country-selector-arrow-size, 4px) solid var(--react-international-phone-country-selector-arrow-color, #777);border-right:var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent;border-left:var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent;margin-right:4px;transition:all .1s ease-out}.react-international-phone-country-selector-button__dropdown-arrow--active{transform:rotateX(180deg)}.react-international-phone-country-selector-button__dropdown-arrow--disabled{border-top-color:var(--react-international-phone-disabled-country-selector-arrow-color, #999)}.react-international-phone-country-selector-button--disabled,.react-international-phone-country-selector-button--disabled:hover{background-color:var(--react-international-phone-disabled-country-selector-background-color, var(--react-international-phone-disabled-background-color, whitesmoke))}.react-international-phone-country-selector-button--disabled{cursor:auto}.react-international-phone-flag-emoji{width:var(--react-international-phone-flag-width, 24px);height:var(--react-international-phone-flag-height, 24px);box-sizing:border-box}.react-international-phone-country-selector-dropdown{position:absolute;z-index:1;top:var(--react-international-phone-dropdown-top, 44px);left:var(--react-international-phone-dropdown-left, 0);display:flex;width:300px;max-height:200px;flex-direction:column;padding:4px 0;margin:0;background-color:var(--react-international-phone-dropdown-item-background-color, var(--react-international-phone-background-color, white));box-shadow:var(--react-international-phone-dropdown-shadow, 2px 2px 16px rgba(0, 0, 0, .25));color:var(--react-international-phone-dropdown-item-text-color, var(--react-international-phone-text-color, #222));list-style:none;overflow-y:scroll}.react-international-phone-country-selector-dropdown__preferred-list-divider{height:1px;border:none;margin:var(--react-international-phone-dropdown-preferred-list-divider-margin, 0);background:var(--react-international-phone-dropdown-preferred-list-divider-color, var(--react-international-phone-border-color, gainsboro))}.react-international-phone-country-selector-dropdown__list-item{display:flex;min-height:var(--react-international-phone-dropdown-item-height, 28px);box-sizing:border-box;align-items:center;padding:2px 8px}.react-international-phone-country-selector-dropdown__list-item-flag-emoji{margin-right:8px}.react-international-phone-country-selector-dropdown__list-item-country-name{overflow:hidden;margin-right:8px;font-size:var(--react-international-phone-dropdown-item-font-size, 14px);text-overflow:ellipsis;white-space:nowrap}.react-international-phone-country-selector-dropdown__list-item-dial-code{color:var(--react-international-phone-dropdown-item-dial-code-color, gray);font-size:var(--react-international-phone-dropdown-item-font-size, 14px)}.react-international-phone-country-selector-dropdown__list-item:hover{background-color:var(--react-international-phone-selected-dropdown-item-background-color, var(--react-international-phone-selected-dropdown-item-background-color, whitesmoke));cursor:pointer}.react-international-phone-country-selector-dropdown__list-item--selected,.react-international-phone-country-selector-dropdown__list-item--focused{background-color:var(--react-international-phone-selected-dropdown-item-background-color, whitesmoke);color:var(--react-international-phone-selected-dropdown-item-text-color, var(--react-international-phone-text-color, #222))}.react-international-phone-country-selector-dropdown__list-item--selected .react-international-phone-country-selector-dropdown__list-item-dial-code,.react-international-phone-country-selector-dropdown__list-item--focused .react-international-phone-country-selector-dropdown__list-item-dial-code{color:var(--react-international-phone-selected-dropdown-item-dial-code-color, var(--react-international-phone-dropdown-item-dial-code-color, gray))}.react-international-phone-country-selector-dropdown__list-item--focused{background-color:var(--react-international-phone-selected-dropdown-item-background-color, var(--react-international-phone-selected-dropdown-item-background-color, whitesmoke))}.react-international-phone-dial-code-preview{display:flex;align-items:center;justify-content:center;padding:0 8px;border:1px solid var(--react-international-phone-dial-code-preview-border-color, var(--react-international-phone-border-color, gainsboro));margin-right:-1px;background-color:var(--react-international-phone-dial-code-preview-background-color, var(--react-international-phone-background-color, white));color:var(--react-international-phone-dial-code-preview-text-color, var(--react-international-phone-text-color, #222));font-size:var(--react-international-phone-dial-code-preview-font-size, var(--react-international-phone-font-size, 13px))}.react-international-phone-dial-code-preview--disabled{background-color:var(--react-international-phone-dial-code-preview-disabled-background-color, var(--react-international-phone-disabled-background-color, whitesmoke));color:var(--react-international-phone-dial-code-preview-disabled-text-color, var(--react-international-phone-disabled-text-color, #666))}.react-international-phone-input-container{display:flex}.react-international-phone-input-container .react-international-phone-country-selector-button{border-radius:var(--react-international-phone-border-radius, 4px);margin-right:-1px;border-bottom-right-radius:0;border-top-right-radius:0}.react-international-phone-input-container .react-international-phone-input{overflow:visible;height:var(--react-international-phone-height, 36px);box-sizing:border-box;padding:0 8px;border:1px solid var(--react-international-phone-border-color, gainsboro);border-radius:var(--react-international-phone-border-radius, 4px);margin:0;background-color:var(--react-international-phone-background-color, white);border-bottom-left-radius:0;border-top-left-radius:0;color:var(--react-international-phone-text-color, #222);font-family:inherit;font-size:var(--react-international-phone-font-size, 13px)}.react-international-phone-input-container .react-international-phone-input:focus{outline:none}.react-international-phone-input-container .react-international-phone-input--disabled{background-color:var(--react-international-phone-disabled-background-color, whitesmoke);color:var(--react-international-phone-disabled-text-color, #666)}
12585
12611
 
12586
- border-radius: var(--ck-secondary-button-border-radius);
12587
- font-size: 1rem;
12588
- color: var(--ck-body-color);
12589
- transition: all 0.2s;
12590
- width: 100%;
12591
- height: 100%;
12592
- display: flex;
12593
- align-items: center;
12594
- justify-content: center;
12595
- margin-top: 12px;
12596
-
12597
- box-shadow: var(--ck-secondary-button-box-shadow);
12598
- background: var(--ck-secondary-button-background);
12599
-
12600
- padding-right: 20px;
12601
-
12602
- &:focus-within {
12603
- box-shadow: var(--ck-secondary-button-hover-box-shadow);
12604
- }
12605
-
12606
- input {
12607
- padding-left: 20px;
12608
- padding-right: 10px;
12609
-
12610
- border: none !important;
12611
- outline: none !important;
12612
- background: transparent !important;
12613
- box-shadow: none !important;
12612
+ const TickList = ({ items }) => {
12613
+ return (jsx(TickListContainer, { children: items.map((item) => (jsxs(TickItem, { children: [jsx(TickIconWrapper, { children: jsx(TickIcon, {}) }), jsx("span", { children: item })] }, item))) }));
12614
+ };
12615
+ TickList.displayName = 'TickList';
12614
12616
 
12615
- ::placeholder {
12616
- color: var(--ck-body-color-muted);
12617
- }
12618
-
12619
- width: 100%;
12620
- height: 100%;
12621
- }
12617
+ var wave = (jsxs("svg", { "aria-hidden": "true", width: "298", height: "188", viewBox: "0 0 298 188", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsx("path", { d: "M1 55.2757L21.6438 46.0285C55.5896 30.8228 94.4104 30.8228 128.356 46.0286L169.644 64.5229C203.59 79.7287 242.41 79.7286 276.356 64.5229L297 55.2757M1 44.2118L21.6438 34.9646C55.5896 19.7589 94.4104 19.7589 128.356 34.9646L169.644 53.459C203.59 68.6647 242.41 68.6647 276.356 53.459L297 44.2118M1 33.1477L21.6438 23.9005C55.5896 8.69479 94.4104 8.69479 128.356 23.9005L169.644 42.3949C203.59 57.6006 242.41 57.6006 276.356 42.3949L297 33.1477M1 22.1477L21.6438 12.9005C55.5896 -2.30521 94.4104 -2.30521 128.356 12.9005L169.644 31.3949C203.59 46.6006 242.41 46.6006 276.356 31.3949L297 22.1477M1 66.3398L21.6438 57.0926C55.5896 41.8869 94.4104 41.8869 128.356 57.0926L169.644 75.587C203.59 90.7927 242.41 90.7927 276.356 75.587L297 66.3398M1 77.404L21.6438 68.1568C55.5896 52.9511 94.4104 52.9511 128.356 68.1569L169.644 86.6512C203.59 101.857 242.41 101.857 276.356 86.6512L297 77.404M1 88.4681L21.6438 79.2209C55.5896 64.0152 94.4104 64.0152 128.356 79.2209L169.644 97.7153C203.59 112.921 242.41 112.921 276.356 97.7153L297 88.4681M1 121.66L21.6438 112.413C55.5896 97.2075 94.4104 97.2075 128.356 112.413L169.644 130.908C203.59 146.113 242.41 146.113 276.356 130.908L297 121.66M1 110.596L21.6438 101.349C55.5896 86.1433 94.4104 86.1433 128.356 101.349L169.644 119.843C203.59 135.049 242.41 135.049 276.356 119.843L297 110.596M1 99.5321L21.6438 90.2849C55.5896 75.0792 94.4104 75.0792 128.356 90.2849L169.644 108.779C203.59 123.985 242.41 123.985 276.356 108.779L297 99.5321M1 132.724L21.6438 123.477C55.5896 108.271 94.4104 108.271 128.356 123.477L169.644 141.971C203.59 157.177 242.41 157.177 276.356 141.971L297 132.724M1 143.788L21.6438 134.541C55.5896 119.336 94.4104 119.336 128.356 134.541L169.644 153.036C203.59 168.241 242.41 168.241 276.356 153.036L297 143.788M1 154.853L21.6438 145.605C55.5896 130.4 94.4104 130.4 128.356 145.605L169.644 164.1C203.59 179.305 242.41 179.305 276.356 164.1L297 154.853M1 165.853L21.6438 156.605C55.5896 141.4 94.4104 141.4 128.356 156.605L169.644 175.1C203.59 190.305 242.41 190.305 276.356 175.1L297 165.853", stroke: "url(#paint0_linear_1094_2077)", strokeOpacity: "0.9", strokeLinecap: "round", strokeLinejoin: "round" }), jsx("defs", { children: jsxs("linearGradient", { id: "paint0_linear_1094_2077", x1: "1", y1: "112.587", x2: "297.034", y2: "79.6111", gradientUnits: "userSpaceOnUse", children: [jsx("stop", { stopColor: "var(--ck-graphic-wave-stop-01)" }), jsx("stop", { stopColor: "var(--ck-graphic-wave-stop-02)", offset: "0.239583" }), jsx("stop", { stopColor: "var(--ck-graphic-wave-stop-03)", offset: "0.515625" }), jsx("stop", { stopColor: "var(--ck-graphic-wave-stop-04)", offset: "0.739583" }), jsx("stop", { stopColor: "var(--ck-graphic-wave-stop-05)", offset: "1" })] }) })] }));
12622
12618
 
12623
- .react-international-phone-country-selector-button {
12624
- padding-left: 20px;
12625
- padding-right: 10px;
12626
- --react-international-phone-border-color: none;
12627
- --react-international-phone-border-radius: var(--ck-secondary-button-border-radius);
12628
- --react-international-phone-background-color: var(--ck-secondary-button-background);
12629
- --react-international-phone-text-color: var(--ck-body-color);
12630
- border-radius: var(--ck-secondary-button-border-radius) 0px 0px var(--ck-secondary-button-border-radius);
12631
- transition: all .2s ease-out;
12619
+ const Graphic = styled(motion.div) `
12620
+ position: relative;
12621
+ margin-top: ${({ $marginTop }) => $marginTop !== null && $marginTop !== void 0 ? $marginTop : '16px'};
12622
+ margin-bottom: ${({ $marginBottom }) => $marginBottom !== null && $marginBottom !== void 0 ? $marginBottom : '20px'};
12623
+ margin-left: auto;
12624
+ margin-right: auto;
12625
+ height: ${({ $height }) => $height !== null && $height !== void 0 ? $height : '190px'};
12626
+ max-width: 295px;
12627
+ pointer-events: none;
12628
+ user-select: none;
12629
+ @media only screen and (max-width: ${defaultTheme.mobileWidth}px) {
12630
+ height: 200px;
12631
+ max-width: 100%;
12632
+ margin-bottom: 32px;
12632
12633
  }
12633
-
12634
- .react-international-phone-country-selector-dropdown {
12635
- box-shadow: var(--ck-secondary-button-hover-box-shadow);
12634
+ `;
12635
+ const LogoGroup = styled(motion.div) `
12636
+ position: absolute;
12637
+ inset: 0;
12638
+ z-index: 2;
12639
+ `;
12640
+ const graphicIn = keyframes `
12641
+ 0%{
12642
+ opacity:0;
12643
+ transform:scale(0.9);
12636
12644
  }
12637
-
12638
- .react-international-phone-country-selector-button__dropdown-arrow {
12639
- border-top: var(--react-international-phone-country-selector-arrow-size, 4px) solid var(--react-international-phone-country-selector-arrow-color, #777) !important;
12640
- border-right: var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent !important;
12641
- border-left: var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent !important;
12642
- margin-right: 4px;
12643
- transition: all .1s ease-out;
12645
+ 100%{
12646
+ opacity:1;
12647
+ transform:none;
12644
12648
  }
12645
12649
  `;
12646
- const EmailInnerButton = styled(motion.button) `
12647
- color: var(--ck-body-action-color);
12648
- transition: background-color 200ms ease, transform 100ms ease, color 200ms ease, transition 200ms ease, opacity 200ms ease;
12649
- border-radius: 16px;
12650
-
12650
+ const GraphicBackground = styled(motion.div) `
12651
+ z-index: 1;
12652
+ position: absolute;
12653
+ inset: 0;
12654
+ top: -2px;
12655
+ overflow: hidden;
12656
+ &:before {
12657
+ content: '';
12658
+ position: absolute;
12659
+ inset: 0;
12660
+ background: var(--ck-body-background);
12661
+ background: radial-gradient(
12662
+ closest-side,
12663
+ var(--ck-body-background-transparent, transparent) 18.75%,
12664
+ var(--ck-body-background) 100%
12665
+ );
12666
+ background-size: 100%;
12667
+ }
12651
12668
  svg {
12652
12669
  display: block;
12653
- position: relative;
12654
- padding: 4px;
12670
+ width: 100%;
12671
+ height: auto;
12655
12672
  }
12656
-
12657
- &:enabled {
12658
- cursor: pointer;
12659
- &:hover {
12660
- background: var(--ck-body-background-secondary);
12661
- color: var(--ck-body-action-hover-color);
12662
- }
12663
- &:active {
12664
- transform: scale(0.9);
12665
- }
12673
+ animation: ${graphicIn} 1000ms 100ms ease both;
12674
+ @media only screen and (max-width: ${defaultTheme.mobileWidth}px) {
12675
+ animation: none;
12666
12676
  }
12667
12677
  `;
12668
- const ProvidersButton = styled.div `
12669
- ${ButtonContainer} {
12670
- height: 64px;
12671
- font-size: 17px;
12672
- font-weight: var(--ck-primary-button-font-weight, 500);
12673
- line-height: 20px;
12674
- }
12675
-
12676
- ${ProviderInputInner} {
12677
- height: 64px;
12678
- }
12679
-
12680
- &:first-of-type {
12681
- ${ButtonContainer}, ${ProviderInputInner} {
12682
- margin-top: 0;
12683
- }
12684
- }
12685
-
12686
- ${ButtonContainerInner} {
12687
- padding: 0 20px;
12688
- justify-content: space-between;
12678
+ const logoIn = keyframes `
12679
+ 0%{
12680
+ opacity:0;
12681
+ transform:scale(0) translateY(40%);
12689
12682
  }
12690
-
12691
- ${InnerContainer} {
12692
- justify-content: space-between;
12693
- width: 100%;
12694
- max-width: 100%;
12683
+ 100%{
12684
+ opacity:1;
12685
+ transform:none;
12695
12686
  }
12696
12687
  `;
12697
- const ProviderLabel = styled.span `
12698
- display: flex;
12699
- align-items: center;
12700
- gap: 9px;
12701
- width: 100%;
12702
- overflow: hidden;
12703
- white-space: nowrap;
12704
- text-overflow: ellipsis;
12705
- padding: 2px 0;
12688
+ const LogoPosition = styled(motion.div) `
12689
+ position: absolute;
12690
+ inset: 0;
12691
+ animation: cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite both;
12692
+ animation-delay: inherit;
12706
12693
  `;
12707
- const ProviderIcon$1 = styled.div `
12708
- width: 32px;
12709
- height: 32px;
12710
- flex-shrink: 0;
12711
- svg,
12712
- img {
12694
+ const LogoInner = styled(motion.div) `
12695
+ position: absolute;
12696
+ `;
12697
+ const LogoGraphic$1 = styled(motion.div) `
12698
+ position: relative;
12699
+ overflow: hidden;
12700
+ height: 58px;
12701
+ width: 58px;
12702
+ border-radius: 13.84px;
12703
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 20px 0 rgba(0, 0, 0, 0.03);
12704
+
12705
+ svg {
12713
12706
  display: block;
12714
- position: relative;
12715
- pointer-events: none;
12716
- overflow: hidden;
12717
12707
  width: 100%;
12718
12708
  height: 100%;
12719
12709
  }
12720
-
12721
- &[data-shape='squircle'] {
12722
- border-radius: 22.5%;
12723
- }
12724
- &[data-shape='circle'] {
12725
- border-radius: 100%;
12726
- }
12727
- &[data-shape='square'] {
12728
- border-radius: 0;
12729
- }
12730
12710
  `;
12731
-
12732
- const OtherMethodButton = styled.button `
12733
- width: 100%;
12734
- color: var(--ck-body-color-muted);
12735
- background: none;
12736
- font-size: 14px;
12737
- margin-top: 10px;
12738
- transition: color 0.2s;
12739
-
12740
- &:hover {
12741
- color: var(--ck-body-color);
12742
- }
12711
+ const float = keyframes `
12712
+ 0%,100%{ transform:none; }
12713
+ 50%{ transform: translateY(-10%) }
12743
12714
  `;
12744
-
12745
- const OtherMethod = ({ currentMethod, onChangeMethod, }) => {
12746
- const { uiConfig } = useOpenfort();
12747
- const otherMethods = useMemo(() => {
12748
- const allowedMethods = uiConfig.walletRecovery.allowedMethods;
12749
- const otherMethods = allowedMethods.filter((method) => method !== currentMethod);
12750
- return otherMethods;
12751
- }, [uiConfig, currentMethod]);
12752
- if (otherMethods.length === 0)
12753
- return null;
12754
- if (otherMethods.length === 1) {
12755
- const method = otherMethods[0];
12756
- let text;
12757
- switch (method) {
12758
- case RecoveryMethod.PASSWORD:
12759
- text = 'Use password recovery instead';
12760
- break;
12761
- case RecoveryMethod.AUTOMATIC:
12762
- text = 'Skip for now';
12763
- break;
12764
- case RecoveryMethod.PASSKEY:
12765
- text = 'Use passkey recovery instead';
12766
- break;
12767
- default:
12768
- text = method;
12769
- }
12770
- return (jsx(OtherMethodButton, { onClick: () => {
12771
- onChangeMethod(method);
12772
- }, children: text }));
12715
+ const FloatWrapper = styled(motion.div) `
12716
+ position: relative;
12717
+ animation: cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite both;
12718
+ animation-name: ${float};
12719
+ animation-duration: 3600ms;
12720
+ `;
12721
+ const rotate = keyframes `
12722
+ 0%,100%{ transform:rotate(-3deg); }
12723
+ 50%{ transform:rotate(3deg); }
12724
+ `;
12725
+ const RotateWrapper = styled(motion.div) `
12726
+ position: relative;
12727
+ animation: cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite both;
12728
+ animation-name: ${rotate};
12729
+ animation-duration: 3200ms;
12730
+ `;
12731
+ const Logo$1 = styled(motion.div) `
12732
+ position: absolute;
12733
+ inset: 0;
12734
+
12735
+ animation: ${logoIn} 750ms cubic-bezier(0.19, 1, 0.22, 1) both;
12736
+ &:nth-child(1){ z-index:2; animation-delay:0ms; }
12737
+ &:nth-child(2){ z-index:1; animation-delay:60ms; }
12738
+ &:nth-child(3){ z-index:1; animation-delay:30ms; }
12739
+ &:nth-child(4){ z-index:1; animation-delay:90ms; }
12740
+ &:nth-child(5){ z-index:1; animation-delay:120ms;}
12741
+
12742
+ &:nth-child(1){ ${RotateWrapper}{ animation-delay:0ms; } }
12743
+ &:nth-child(2){ ${RotateWrapper}{ animation-delay:-600ms; } }
12744
+ &:nth-child(3){ ${RotateWrapper}{ animation-delay:-1200ms; } }
12745
+ &:nth-child(4){ ${RotateWrapper}{ animation-delay:-1800ms; } }
12746
+ &:nth-child(5){ ${RotateWrapper}{ animation-delay:-2400ms; } }
12747
+
12748
+ &:nth-child(1){ ${FloatWrapper}{ animation-delay:-200ms; } }
12749
+ &:nth-child(2){ ${FloatWrapper}{ animation-delay:-600ms; } }
12750
+ &:nth-child(3){ ${FloatWrapper}{ animation-delay:-800ms; } }
12751
+ &:nth-child(4){ ${FloatWrapper}{ animation-delay:-300ms; } }
12752
+ &:nth-child(5){ ${FloatWrapper}{ animation-delay:-3200ms; } }
12753
+
12754
+ @media only screen and (max-width: ${defaultTheme.mobileWidth}px) {
12755
+ animation: none !important;
12756
+ ${RotateWrapper},${FloatWrapper} {
12757
+ animation: none !important;
12773
12758
  }
12774
- return jsx(OtherMethodButton, { onClick: () => onChangeMethod('other'), children: "Choose another recovery method" });
12775
- };
12776
- const CreateWalletAutomaticRecovery = ({ onBack, logoutOnBack, }) => {
12777
- const { embeddedState } = useOpenfortCore();
12778
- const { createWallet } = useWallets();
12779
- const [shouldCreateWallet, setShouldCreateWallet] = useState(false);
12780
- useEffect(() => {
12781
- // To ensure the wallet is created only once
12782
- if (shouldCreateWallet) {
12783
- (async () => {
12784
- logger.log('Creating wallet Automatic recover');
12785
- const response = await createWallet();
12786
- if (response.error) {
12787
- logger.log('Error creating wallet', response.error);
12788
- }
12789
- })();
12790
- }
12791
- }, [shouldCreateWallet]);
12792
- useEffect(() => {
12793
- if (embeddedState === EmbeddedState.EMBEDDED_SIGNER_NOT_CONFIGURED) {
12794
- setShouldCreateWallet(true);
12795
- }
12796
- }, [embeddedState]);
12797
- return (jsx(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: jsx(Loader, { header: "Creating wallet..." }) }));
12798
- };
12799
- const CreateWalletPasskeyRecovery = ({ onChangeMethod, onBack, logoutOnBack, }) => {
12800
- const { triggerResize } = useOpenfort();
12801
- const { createWallet, error: recoveryError } = useWallets();
12802
- const [shouldCreateWallet, setShouldCreateWallet] = useState(false);
12803
- const { embeddedState } = useOpenfortCore();
12804
- useEffect(() => {
12805
- // To ensure the wallet is created only once
12806
- if (shouldCreateWallet) {
12807
- (async () => {
12808
- logger.log('Creating wallet passkey recovery');
12809
- const response = await createWallet({
12810
- recovery: {
12811
- recoveryMethod: RecoveryMethod.PASSKEY,
12812
- },
12813
- });
12814
- if (response.error) {
12815
- logger.log('Error creating wallet', response.error);
12816
- setShouldCreateWallet(false);
12817
- }
12818
- })();
12819
- }
12820
- }, [shouldCreateWallet]);
12821
- useEffect(() => {
12822
- if (embeddedState === EmbeddedState.EMBEDDED_SIGNER_NOT_CONFIGURED) {
12823
- setShouldCreateWallet(true);
12824
- }
12825
- }, [embeddedState]);
12826
- useEffect(() => {
12827
- if (recoveryError)
12828
- triggerResize();
12829
- }, [recoveryError]);
12830
- return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(Loader, { icon: jsx(FingerPrintIcon, {}), isError: !!recoveryError, header: recoveryError ? 'Invalid passkey.' : 'Creating wallet with passkey...', description: recoveryError ? 'There was an error creating your passkey. Please try again.' : undefined, onRetry: () => setShouldCreateWallet(true) }), jsx(OtherMethod, { currentMethod: RecoveryMethod.PASSKEY, onChangeMethod: onChangeMethod })] }));
12831
- };
12832
- const CreateWalletPasswordRecovery = ({ onChangeMethod, onBack, logoutOnBack, }) => {
12833
- const [recoveryPhrase, setRecoveryPhrase] = useState('');
12834
- const [recoveryError, setRecoveryError] = useState(false);
12835
- const { triggerResize } = useOpenfort();
12836
- const [showPasswordIsTooWeakError, setShowPasswordIsTooWeakError] = useState(false);
12837
- const [loading, setLoading] = useState(false);
12838
- const { createWallet } = useWallets();
12839
- const handleSubmit = async () => {
12840
- if (getPasswordStrength(recoveryPhrase) < MEDIUM_SCORE_THRESHOLD) {
12841
- setShowPasswordIsTooWeakError(true);
12842
- return;
12843
- }
12844
- setLoading(true);
12845
- const { error } = await createWallet({
12846
- recovery: {
12847
- recoveryMethod: RecoveryMethod.PASSWORD,
12848
- password: recoveryPhrase,
12849
- },
12850
- });
12851
- setLoading(false);
12852
- if (error) {
12853
- setRecoveryError(error.message || 'There was an error recovering your account');
12854
- }
12855
- else {
12856
- logger.log('Recovery success');
12857
- }
12858
- };
12859
- useEffect(() => {
12860
- if (recoveryError)
12861
- triggerResize();
12862
- }, [recoveryError]);
12863
- return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(FloatingGraphic, { height: "80px", logoCenter: {
12864
- logo: jsx(KeyIcon, {}),
12865
- size: '1.2',
12866
- }, logoTopLeft: {
12867
- logo: jsx(ShieldIcon, {}),
12868
- size: '0.75',
12869
- }, logoBottomRight: {
12870
- logo: jsx(LockIcon, {}),
12871
- size: '0.5',
12872
- } }), jsx(ModalHeading, { children: "Secure your wallet" }), jsxs(ModalBody, { style: { textAlign: 'center' }, children: [jsx(FitText, { children: "Set a password for your wallet." }), jsxs("form", { onSubmit: (e) => {
12873
- e.preventDefault();
12874
- handleSubmit();
12875
- }, children: [jsx(Input, { value: recoveryPhrase, onChange: (e) => {
12876
- if (showPasswordIsTooWeakError)
12877
- setShowPasswordIsTooWeakError(false);
12878
- setRecoveryPhrase(e.target.value);
12879
- }, type: "password", placeholder: "Enter your password", autoComplete: "off" }), jsx(PasswordStrengthIndicator, { password: recoveryPhrase, showPasswordIsTooWeakError: showPasswordIsTooWeakError }), jsx(TickList, { items: ['You will use this password to access your wallet', "Make sure it's strong and memorable"] }), recoveryError && (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, children: jsx(ModalBody, { style: { height: 24, marginTop: 12 }, "$error": true, children: jsx(FitText, { children: recoveryError }) }) }, recoveryError)), jsx(Button, { onClick: handleSubmit, waiting: loading, disabled: loading, children: "Create wallet" })] }), jsx(OtherMethod, { currentMethod: RecoveryMethod.PASSWORD, onChangeMethod: onChangeMethod })] })] }));
12880
- };
12881
- const ChooseRecoveryMethod = ({ onChangeMethod, onBack, logoutOnBack, }) => {
12882
- return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(ModalHeading, { children: "Choose a recovery method" }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.PASSKEY), children: [jsx(ProviderLabel, { children: "Passkey" }), jsx(ProviderIcon$1, { children: jsx(FingerPrintIcon, {}) })] }) }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.PASSWORD), children: [jsx(ProviderLabel, { children: "Password" }), jsx(ProviderIcon$1, { children: jsx(KeyIcon, {}) })] }) }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.AUTOMATIC), children: [jsx(ProviderLabel, { children: "Automatic" }), jsx(ProviderIcon$1, { children: jsx(LockIcon, {}) })] }) })] }));
12883
- };
12884
- const CreateEmbeddedWallet = ({ onBack, logoutOnBack }) => {
12885
- const { uiConfig, triggerResize } = useOpenfort();
12886
- const [userSelectedMethod, setUserSelectedMethod] = useState(null);
12887
- useEffect(() => {
12888
- triggerResize();
12889
- }, [userSelectedMethod]);
12890
- const method = userSelectedMethod !== null && userSelectedMethod !== void 0 ? userSelectedMethod : uiConfig.walletRecovery.defaultMethod;
12891
- switch (method) {
12892
- case RecoveryMethod.PASSWORD:
12893
- return (jsx(CreateWalletPasswordRecovery, { onChangeMethod: setUserSelectedMethod, onBack: onBack, logoutOnBack: logoutOnBack }));
12894
- case RecoveryMethod.AUTOMATIC:
12895
- return jsx(CreateWalletAutomaticRecovery, { onBack: onBack, logoutOnBack: logoutOnBack });
12896
- case RecoveryMethod.PASSKEY:
12897
- return (jsx(CreateWalletPasskeyRecovery, { onChangeMethod: setUserSelectedMethod, onBack: onBack, logoutOnBack: logoutOnBack }));
12898
- case 'other':
12899
- return (jsx(ChooseRecoveryMethod, { onChangeMethod: setUserSelectedMethod, onBack: () => {
12900
- setUserSelectedMethod(null);
12901
- }, logoutOnBack: logoutOnBack }));
12902
- default:
12903
- logger.error(`Unsupported recovery method: ${userSelectedMethod}${uiConfig.walletRecovery.defaultMethod}`);
12904
- return null;
12759
+ }
12760
+
12761
+ ${LogoInner} {
12762
+ transform: translate(-50%, -50%);
12763
+ }
12764
+
12765
+ &:nth-child(1) ${LogoPosition} {
12766
+ transform: translate(50%, 50%);
12767
+ ${LogoGraphic$1} {
12768
+ border-radius: 17.2px;
12769
+ width: 72px;
12770
+ height: 72px;
12905
12771
  }
12772
+ }
12773
+ &:nth-child(2) ${LogoPosition} {
12774
+ transform: translate(21%, 21.5%);
12775
+ }
12776
+ &:nth-child(3) ${LogoPosition} {
12777
+ transform: translate(78%, 14%);
12778
+ }
12779
+ &:nth-child(4) ${LogoPosition} {
12780
+ transform: translate(22.5%, 76%);
12781
+ }
12782
+ &:nth-child(5) ${LogoPosition} {
12783
+ transform: translate(76%, 80%);
12784
+ }
12785
+ `;
12786
+
12787
+ const LogoGraphic = ({ size = '100%', logo }) => {
12788
+ return (jsx(Logo$1, { children: jsx(LogoPosition, { children: jsx(LogoInner, { children: jsx(FloatWrapper, { children: jsx(RotateWrapper, { children: jsx(LogoGraphic$1, { style: { transform: `scale(${size})` }, children: logo }) }) }) }) }) }));
12906
12789
  };
12907
- const CreateOrConnectWallet = () => {
12908
- const [showCreateEmbeddedWallet, setShowCreateEmbeddedWallet] = useState(false);
12909
- const { setRoute } = useOpenfort();
12910
- if (showCreateEmbeddedWallet)
12911
- return jsx(CreateEmbeddedWallet, { onBack: () => setShowCreateEmbeddedWallet(false), logoutOnBack: false });
12912
- return (jsxs(PageContent, { onBack: routes.PROVIDERS, logoutOnBack: true, children: [jsx(ModalHeading, { children: "Choose an option" }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => setShowCreateEmbeddedWallet(true), children: [jsx(ProviderLabel, { children: "Create Wallet" }), jsx(ProviderIcon$1, { children: jsx(PlusIcon, {}) })] }) }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => {
12913
- setRoute({ route: routes.CONNECTORS, connectType: 'link' });
12914
- }, children: [jsx(ProviderLabel, { children: "Connect Wallet" }), jsx(ProviderIcon$1, { children: jsx(Logos.OtherWallets, {}) })] }) })] }));
12790
+ const FloatingGraphic = ({ height = '130px', marginBottom, marginTop, logoCenter, logoTopRight, logoTopLeft, logoBottomRight, logoBottomLeft, }) => {
12791
+ return (jsxs(Graphic, { "$height": height, "$marginBottom": marginBottom, "$marginTop": marginTop, children: [jsxs(LogoGroup, { children: [jsx(LogoGraphic, { ...logoCenter }), logoTopLeft ? jsx(LogoGraphic, { ...logoTopLeft }) : jsx("div", {}), logoTopRight ? jsx(LogoGraphic, { ...logoTopRight }) : jsx("div", {}), logoBottomLeft ? jsx(LogoGraphic, { ...logoBottomLeft }) : jsx("div", {}), logoBottomRight ? jsx(LogoGraphic, { ...logoBottomRight }) : jsx("div", {})] }), jsx(GraphicBackground, { children: wave })] }));
12915
12792
  };
12916
- const CreateWallet = () => {
12917
- const { uiConfig, walletConfig, setRoute } = useOpenfort();
12918
- const { user } = useOpenfortCore();
12919
- const { isConnected } = useAccount();
12920
- useEffect(() => {
12921
- if (isConnected && user)
12922
- setRoute(routes.CONNECTED_SUCCESS);
12923
- }, [isConnected, user]);
12924
- if (uiConfig.linkWalletOnSignUp === LinkWalletOnSignUpOption.OPTIONAL) {
12925
- return jsx(CreateOrConnectWallet, {});
12793
+
12794
+ /**
12795
+ * Password utility helpers for strength calculation and validation.
12796
+ *
12797
+ * Provides functions for password strength calculation, passphrase generation, and password validation.
12798
+ */
12799
+ // ============================================================================
12800
+ // Constants and Regular Expressions
12801
+ // ============================================================================
12802
+ /**
12803
+ * Regular expression to match lowercase letters.
12804
+ */
12805
+ const LOWERCASE_REGEX = /[a-z]/;
12806
+ /**
12807
+ * Regular expression to match uppercase letters.
12808
+ */
12809
+ const UPPERCASE_REGEX = /[A-Z]/;
12810
+ /**
12811
+ * Regular expression to match digits.
12812
+ */
12813
+ const DIGIT_REGEX = /[0-9]/;
12814
+ /**
12815
+ * Special characters allowed in passwords.
12816
+ */
12817
+ const SPECIAL_CHARACTERS = '!@#$%^&()\\-*+.';
12818
+ /**
12819
+ * Escape regex metacharacters for safe use inside a character class.
12820
+ */
12821
+ function escapeForCharClass(str) {
12822
+ return str.replace(/[-\\^]/g, '\\$&');
12823
+ // escapes -, \, and ^ (the only risky chars in [])
12824
+ }
12825
+ /**
12826
+ * Regular expression to match special characters.
12827
+ */
12828
+ const SPECIAL_CHARACTER_REGEX = new RegExp(`[${escapeForCharClass(SPECIAL_CHARACTERS)}]`);
12829
+ /**
12830
+ * Maximum entropy score for normalization.
12831
+ */
12832
+ const MAX_ENTROPY_SCORE = 95;
12833
+ /**
12834
+ * Minimum password length for security.
12835
+ */
12836
+ const MIN_PASSWORD_LENGTH = 8;
12837
+ /**
12838
+ * Weight for diversity score in overall strength calculation.
12839
+ */
12840
+ const DIVERSITY_WEIGHT = 0.3;
12841
+ const ENTROPY_WEIGHT = 0.7;
12842
+ const MEDIUM_SCORE_THRESHOLD = 0.5;
12843
+ const STRONG_SCORE_THRESHOLD = 0.75;
12844
+ const VERY_STRONG_SCORE_THRESHOLD = 0.9;
12845
+ /**
12846
+ * Converts a numeric password strength score to a human-readable label.
12847
+ *
12848
+ * @param score - The strength score (0-1).
12849
+ * @returns The corresponding strength label.
12850
+ *
12851
+ * @example
12852
+ * ```ts
12853
+ * const label = getPasswordStrengthLabel(0.8);
12854
+ * // label === 'Strong'
12855
+ * ```
12856
+ */
12857
+ function getPasswordStrengthLabel(score) {
12858
+ if (score > VERY_STRONG_SCORE_THRESHOLD) {
12859
+ return 'Very Strong';
12926
12860
  }
12927
- if (uiConfig.linkWalletOnSignUp === LinkWalletOnSignUpOption.REQUIRED ||
12928
- (!walletConfig && uiConfig.linkWalletOnSignUp !== LinkWalletOnSignUpOption.DISABLED)) {
12929
- return jsx(Connectors, { logoutOnBack: true });
12861
+ else if (score > STRONG_SCORE_THRESHOLD) {
12862
+ return 'Strong';
12863
+ }
12864
+ else if (score > MEDIUM_SCORE_THRESHOLD) {
12865
+ return 'Medium';
12866
+ }
12867
+ else {
12868
+ return 'Weak';
12930
12869
  }
12931
- return jsx(CreateEmbeddedWallet, { onBack: routes.PROVIDERS, logoutOnBack: true });
12932
- };
12933
-
12934
- function useWindowSize() {
12935
- const [windowSize, setWindowSize] = useState({
12936
- width: 0,
12937
- height: 0,
12938
- });
12939
- useEffect(() => {
12940
- function handleResize() {
12941
- setWindowSize({
12942
- width: window.innerWidth,
12943
- height: window.innerHeight,
12944
- });
12945
- }
12946
- window.addEventListener('resize', handleResize);
12947
- handleResize();
12948
- return () => window.removeEventListener('resize', handleResize);
12949
- }, []);
12950
- return windowSize;
12951
12870
  }
12952
-
12953
- const generateMatrix = (value, errorCorrectionLevel) => {
12954
- const arr = Array.prototype.slice.call(QRCodeUtil.create(value, { errorCorrectionLevel }).modules.data, 0);
12955
- const sqrt = Math.sqrt(arr.length);
12956
- return arr.reduce((rows, key, index) => (index % sqrt === 0 ? rows.push([key]) : rows[rows.length - 1].push(key)) && rows, []);
12957
- };
12958
- function QRCode({ ecl = 'M', size: sizeProp = 200, uri, clearArea = false, image, imageBackground = 'transparent', }) {
12959
- const logoSize = clearArea ? 76 : 0;
12960
- const size = sizeProp - 10 * 2;
12961
- const dots = useMemo(() => {
12962
- const dots = [];
12963
- const matrix = generateMatrix(uri, ecl);
12964
- const cellSize = size / matrix.length;
12965
- const qrList = [
12966
- { x: 0, y: 0 },
12967
- { x: 1, y: 0 },
12968
- { x: 0, y: 1 },
12969
- ];
12970
- qrList.forEach(({ x, y }) => {
12971
- const x1 = (matrix.length - 7) * cellSize * x;
12972
- const y1 = (matrix.length - 7) * cellSize * y;
12973
- for (let i = 0; i < 3; i++) {
12974
- dots.push(jsx("rect", { fill: i % 2 !== 0 ? 'var(--ck-qr-background, var(--ck-body-background))' : 'var(--ck-qr-dot-color)', rx: (i - 2) * -5 + (i === 0 ? 2 : 3), ry: (i - 2) * -5 + (i === 0 ? 2 : 3), width: cellSize * (7 - i * 2), height: cellSize * (7 - i * 2), x: x1 + cellSize * i, y: y1 + cellSize * i }, `${i}-${x}-${y}`));
12975
- }
12976
- });
12977
- if (image) {
12978
- const x1 = (matrix.length - 7) * cellSize * 1;
12979
- const y1 = (matrix.length - 7) * cellSize * 1;
12980
- dots.push(jsxs(Fragment, { children: [jsx("rect", { fill: imageBackground, rx: (0 - 2) * -5 + 2, ry: (0 - 2) * -5 + 2, width: cellSize * (7 - 0 * 2), height: cellSize * (7 - 0 * 2), x: x1 + cellSize * 0, y: y1 + cellSize * 0 }), jsx("foreignObject", { width: cellSize * (7 - 0 * 2), height: cellSize * (7 - 0 * 2), x: x1 + cellSize * 0, y: y1 + cellSize * 0, children: jsx("div", { style: { borderRadius: (0 - 2) * -5 + 2, overflow: 'hidden' }, children: image }) })] }));
12981
- }
12982
- const clearArenaSize = Math.floor((logoSize + 25) / cellSize);
12983
- const matrixMiddleStart = matrix.length / 2 - clearArenaSize / 2;
12984
- const matrixMiddleEnd = matrix.length / 2 + clearArenaSize / 2 - 1;
12985
- matrix.forEach((row, i) => {
12986
- row.forEach((_, j) => {
12987
- if (matrix[i][j]) {
12988
- // Do not render dots under position squares
12989
- if (!((i < 7 && j < 7) || (i > matrix.length - 8 && j < 7) || (i < 7 && j > matrix.length - 8))) {
12990
- //if (image && i > matrix.length - 9 && j > matrix.length - 9) return;
12991
- if (image ||
12992
- !(i > matrixMiddleStart && i < matrixMiddleEnd && j > matrixMiddleStart && j < matrixMiddleEnd)) {
12993
- dots.push(jsx("circle", { cx: i * cellSize + cellSize / 2, cy: j * cellSize + cellSize / 2, fill: "var(--ck-qr-dot-color)", r: cellSize / 3 }, `qr-dot-${i}-${j}`));
12994
- }
12995
- }
12996
- }
12997
- });
12998
- });
12999
- return dots;
13000
- }, [ecl, size, uri]);
13001
- return (jsxs("svg", { height: size, width: size, viewBox: `0 0 ${size} ${size}`, style: {
13002
- width: size,
13003
- height: size,
13004
- }, children: [jsx("title", { children: "QR Code" }), jsx("rect", { fill: "transparent", height: size, width: size }), dots] }));
12871
+ /**
12872
+ * Calculates the diversity score of a password based on character types used.
12873
+ *
12874
+ * Considers lowercase, uppercase, digits, and special characters.
12875
+ *
12876
+ * @param password - The password to analyse.
12877
+ * @returns A score between 0 and 1 representing character diversity.
12878
+ *
12879
+ * @example
12880
+ * ```ts
12881
+ * const diversity = calculatePasswordDiversityScore('Password123!');
12882
+ * ```
12883
+ */
12884
+ function calculatePasswordDiversityScore(password) {
12885
+ // Passwords shorter than minimum length get a score of 0
12886
+ if (password.length < MIN_PASSWORD_LENGTH) {
12887
+ return 0;
12888
+ }
12889
+ let characterTypesUsed = 0;
12890
+ if (LOWERCASE_REGEX.test(password)) {
12891
+ characterTypesUsed += 1;
12892
+ }
12893
+ if (UPPERCASE_REGEX.test(password)) {
12894
+ characterTypesUsed += 1;
12895
+ }
12896
+ if (DIGIT_REGEX.test(password)) {
12897
+ characterTypesUsed += 1;
12898
+ }
12899
+ if (SPECIAL_CHARACTER_REGEX.test(password)) {
12900
+ characterTypesUsed += 1;
12901
+ }
12902
+ return Math.max(0, Math.min(1, characterTypesUsed / 4));
12903
+ }
12904
+ /**
12905
+ * Calculates the overall password strength combining diversity and entropy.
12906
+ *
12907
+ * @param password - The password to analyse.
12908
+ * @returns A strength score between 0 and 1.
12909
+ *
12910
+ * @example
12911
+ * ```ts
12912
+ * const strength = getPasswordStrength('Password123!');
12913
+ * ```
12914
+ */
12915
+ function getPasswordStrength(password = '') {
12916
+ const diversityScore = calculatePasswordDiversityScore(password);
12917
+ const entropyScore = calculateEntropy(password) / MAX_ENTROPY_SCORE;
12918
+ return Math.min(diversityScore * DIVERSITY_WEIGHT + entropyScore * ENTROPY_WEIGHT, 1);
13005
12919
  }
13006
12920
 
13007
- const QRCodeContainer = styled(motion.div) `
13008
- z-index: 3;
13009
- position: relative;
13010
- overflow: hidden;
13011
- height: 0;
13012
- padding-bottom: 100% !important;
13013
- display: flex;
13014
- align-items: center;
13015
- justify-content: center;
13016
- margin: 1px 0 2px;
13017
- border-radius: var(--ck-qr-border-radius, 24px);
13018
- background: var(--ck-qr-background, transparent);
13019
- box-shadow: 0 0 0 1px var(--ck-qr-border-color);
13020
- backface-visibility: hidden;
13021
- svg {
13022
- display: block;
13023
- max-width: 100%;
13024
- width: 100%;
13025
- height: auto;
13026
- }
12921
+ const Container$1 = styled.div `
12922
+ display: flex;
12923
+ flex-direction: column;
12924
+ gap: 0.5rem;
12925
+ margin-top: 0.5rem;
12926
+ margin-bottom: 0.5rem;
12927
+ text-align: left;
12928
+ `;
12929
+ const BarWrapper = styled.div `
12930
+ width: 100%;
12931
+ height: 4px;
12932
+ background: var(--ck-secondary-button-background);
12933
+ border-radius: 4px;
12934
+ overflow: hidden;
12935
+ `;
12936
+ const Progress = styled(motion.div) `
12937
+ height: 100%;
12938
+ background: ${({ color }) => color};
12939
+ border-radius: 4px;
12940
+ `;
12941
+ const Label$1 = styled.div `
12942
+ font-size: 0.875rem;
12943
+ font-weight: 500;
12944
+ color: var(--ck-body-color-muted);
12945
+ `;
12946
+ const LabelColor = styled.span `
12947
+ color: ${({ color }) => color};
12948
+ `;
12949
+ const PasswordStrengthIndicator = ({ password, showPasswordIsTooWeakError, }) => {
12950
+ const passwordStrength = getPasswordStrength(password); // should return a number between 0 and 1
12951
+ const label = getPasswordStrengthLabel(passwordStrength);
12952
+ const color = useMemo(() => {
12953
+ switch (label) {
12954
+ case 'Weak':
12955
+ return '#ef4444'; // red-500
12956
+ case 'Medium':
12957
+ return '#f59e0b'; // amber-500
12958
+ case 'Strong':
12959
+ return '#10b981'; // emerald-500
12960
+ case 'Very Strong':
12961
+ return '#3b82f6'; // blue-500
12962
+ default:
12963
+ return '#d1d5db'; // gray-300
12964
+ }
12965
+ }, [label]);
12966
+ return (jsxs(Container$1, { children: [jsx(BarWrapper, { children: jsx(Progress, { color: color, initial: { width: 0 }, animate: { width: `${passwordStrength * 100}%` }, transition: { ease: 'easeOut', duration: 0.5 } }) }), jsxs("div", { style: { position: 'relative' }, children: [jsx(motion.div, { initial: { opacity: 1 }, animate: {
12967
+ opacity: showPasswordIsTooWeakError ? 0 : 1,
12968
+ y: showPasswordIsTooWeakError ? 5 : 0,
12969
+ }, transition: { duration: 0.3 }, children: jsxs(Label$1, { children: ["Password strength: ", jsx(LabelColor, { color: color, children: label })] }) }), jsx(motion.div, { initial: { opacity: 0 }, animate: {
12970
+ opacity: showPasswordIsTooWeakError ? 1 : 0,
12971
+ y: showPasswordIsTooWeakError ? 0 : -5,
12972
+ }, transition: { duration: 0.3 }, style: { color: '#ef4444', fontSize: '0.875rem', fontWeight: 500, position: 'absolute', top: '0' }, children: "Password is too weak" })] })] }));
12973
+ };
12974
+
12975
+ const Body$1 = styled.p `
12976
+ color: var(--ck-body-color);
12977
+ text-align: center;
12978
+ margin-bottom: 16px;
13027
12979
  `;
13028
- const QRCodeContent = styled(motion.div) `
13029
- position: absolute;
13030
- inset: 13px;
13031
- svg {
13032
- width: 100% !important;
13033
- height: auto !important;
12980
+ const ResultContainer$1 = styled.div `
12981
+ margin-top: 16px;
12982
+ height: 24px;
12983
+ text-align: center;
12984
+ `;
12985
+ const FooterButtonText$1 = styled.button `
12986
+ background: none;
12987
+ border: none;
12988
+ cursor: pointer;
12989
+ padding: 0;
12990
+ color: var(--ck-body-color-muted);
12991
+ transition: color 0.3s;
12992
+
12993
+ &:disabled {
12994
+ color: var(--ck-body-color-muted) !important;
12995
+ cursor: not-allowed;
13034
12996
  }
13035
12997
  `;
13036
- const PlaceholderKeyframes = keyframes `
13037
- 0%{ background-position: 100% 0; }
13038
- 100%{ background-position: -100% 0; }
12998
+ const FooterTextButton$1 = styled.p `
12999
+ color: var(--ck-body-color-muted);
13000
+ text-align: center;
13001
+ margin-top: 16px;
13002
+ &:hover {
13003
+ ${FooterButtonText$1} {
13004
+ color: var(--ck-body-color);
13005
+ }
13006
+ }
13039
13007
  `;
13040
- const QRPlaceholder = styled(motion.div) `
13041
- --color: var(--ck-qr-dot-color);
13042
- --bg: var(--ck-qr-background, var(--ck-body-background));
13043
13008
 
13044
- position: absolute;
13045
- inset: 0;
13009
+ const ProviderInputInner = styled.div `
13010
+ // Styles from react-international-phone (imported here to avoid importing the whole CSS file for nextjs compatibility)
13011
+ .react-international-phone-country-selector{position:relative}.react-international-phone-country-selector-button{display:flex;height:var(--react-international-phone-height, 36px);box-sizing:border-box;align-items:center;justify-content:center;padding:0;border:1px solid var(--react-international-phone-country-selector-border-color, var(--react-international-phone-border-color, gainsboro));margin:0;appearance:button;-webkit-appearance:button;background-color:var(--react-international-phone-country-selector-background-color, var(--react-international-phone-background-color, white));cursor:pointer;text-transform:none;user-select:none}.react-international-phone-country-selector-button:hover{background-color:var(--react-international-phone-country-selector-background-color-hover, whitesmoke)}.react-international-phone-country-selector-button--hide-dropdown{cursor:auto}.react-international-phone-country-selector-button--hide-dropdown:hover{background-color:transparent}.react-international-phone-country-selector-button__button-content{display:flex;align-items:center;justify-content:center}.react-international-phone-country-selector-button__flag-emoji{margin:0 4px}.react-international-phone-country-selector-button__flag-emoji--disabled{opacity:.75}.react-international-phone-country-selector-button__dropdown-arrow{border-top:var(--react-international-phone-country-selector-arrow-size, 4px) solid var(--react-international-phone-country-selector-arrow-color, #777);border-right:var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent;border-left:var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent;margin-right:4px;transition:all .1s ease-out}.react-international-phone-country-selector-button__dropdown-arrow--active{transform:rotateX(180deg)}.react-international-phone-country-selector-button__dropdown-arrow--disabled{border-top-color:var(--react-international-phone-disabled-country-selector-arrow-color, #999)}.react-international-phone-country-selector-button--disabled,.react-international-phone-country-selector-button--disabled:hover{background-color:var(--react-international-phone-disabled-country-selector-background-color, var(--react-international-phone-disabled-background-color, whitesmoke))}.react-international-phone-country-selector-button--disabled{cursor:auto}.react-international-phone-flag-emoji{width:var(--react-international-phone-flag-width, 24px);height:var(--react-international-phone-flag-height, 24px);box-sizing:border-box}.react-international-phone-country-selector-dropdown{position:absolute;z-index:1;top:var(--react-international-phone-dropdown-top, 44px);left:var(--react-international-phone-dropdown-left, 0);display:flex;width:300px;max-height:200px;flex-direction:column;padding:4px 0;margin:0;background-color:var(--react-international-phone-dropdown-item-background-color, var(--react-international-phone-background-color, white));box-shadow:var(--react-international-phone-dropdown-shadow, 2px 2px 16px rgba(0, 0, 0, .25));color:var(--react-international-phone-dropdown-item-text-color, var(--react-international-phone-text-color, #222));list-style:none;overflow-y:scroll}.react-international-phone-country-selector-dropdown__preferred-list-divider{height:1px;border:none;margin:var(--react-international-phone-dropdown-preferred-list-divider-margin, 0);background:var(--react-international-phone-dropdown-preferred-list-divider-color, var(--react-international-phone-border-color, gainsboro))}.react-international-phone-country-selector-dropdown__list-item{display:flex;min-height:var(--react-international-phone-dropdown-item-height, 28px);box-sizing:border-box;align-items:center;padding:2px 8px}.react-international-phone-country-selector-dropdown__list-item-flag-emoji{margin-right:8px}.react-international-phone-country-selector-dropdown__list-item-country-name{overflow:hidden;margin-right:8px;font-size:var(--react-international-phone-dropdown-item-font-size, 14px);text-overflow:ellipsis;white-space:nowrap}.react-international-phone-country-selector-dropdown__list-item-dial-code{color:var(--react-international-phone-dropdown-item-dial-code-color, gray);font-size:var(--react-international-phone-dropdown-item-font-size, 14px)}.react-international-phone-country-selector-dropdown__list-item:hover{background-color:var(--react-international-phone-selected-dropdown-item-background-color, var(--react-international-phone-selected-dropdown-item-background-color, whitesmoke));cursor:pointer}.react-international-phone-country-selector-dropdown__list-item--selected,.react-international-phone-country-selector-dropdown__list-item--focused{background-color:var(--react-international-phone-selected-dropdown-item-background-color, whitesmoke);color:var(--react-international-phone-selected-dropdown-item-text-color, var(--react-international-phone-text-color, #222))}.react-international-phone-country-selector-dropdown__list-item--selected .react-international-phone-country-selector-dropdown__list-item-dial-code,.react-international-phone-country-selector-dropdown__list-item--focused .react-international-phone-country-selector-dropdown__list-item-dial-code{color:var(--react-international-phone-selected-dropdown-item-dial-code-color, var(--react-international-phone-dropdown-item-dial-code-color, gray))}.react-international-phone-country-selector-dropdown__list-item--focused{background-color:var(--react-international-phone-selected-dropdown-item-background-color, var(--react-international-phone-selected-dropdown-item-background-color, whitesmoke))}.react-international-phone-dial-code-preview{display:flex;align-items:center;justify-content:center;padding:0 8px;border:1px solid var(--react-international-phone-dial-code-preview-border-color, var(--react-international-phone-border-color, gainsboro));margin-right:-1px;background-color:var(--react-international-phone-dial-code-preview-background-color, var(--react-international-phone-background-color, white));color:var(--react-international-phone-dial-code-preview-text-color, var(--react-international-phone-text-color, #222));font-size:var(--react-international-phone-dial-code-preview-font-size, var(--react-international-phone-font-size, 13px))}.react-international-phone-dial-code-preview--disabled{background-color:var(--react-international-phone-dial-code-preview-disabled-background-color, var(--react-international-phone-disabled-background-color, whitesmoke));color:var(--react-international-phone-dial-code-preview-disabled-text-color, var(--react-international-phone-disabled-text-color, #666))}.react-international-phone-input-container{display:flex}.react-international-phone-input-container .react-international-phone-country-selector-button{border-radius:var(--react-international-phone-border-radius, 4px);margin-right:-1px;border-bottom-right-radius:0;border-top-right-radius:0}.react-international-phone-input-container .react-international-phone-input{overflow:visible;height:var(--react-international-phone-height, 36px);box-sizing:border-box;padding:0 8px;border:1px solid var(--react-international-phone-border-color, gainsboro);border-radius:var(--react-international-phone-border-radius, 4px);margin:0;background-color:var(--react-international-phone-background-color, white);border-bottom-left-radius:0;border-top-left-radius:0;color:var(--react-international-phone-text-color, #222);font-family:inherit;font-size:var(--react-international-phone-font-size, 13px)}.react-international-phone-input-container .react-international-phone-input:focus{outline:none}.react-international-phone-input-container .react-international-phone-input--disabled{background-color:var(--react-international-phone-disabled-background-color, whitesmoke);color:var(--react-international-phone-disabled-text-color, #666)}
13012
+
13013
+ border-radius: var(--ck-secondary-button-border-radius);
13014
+ font-size: 1rem;
13015
+ color: var(--ck-body-color);
13016
+ transition: all 0.2s;
13017
+ width: 100%;
13018
+ height: 100%;
13046
13019
  display: flex;
13047
13020
  align-items: center;
13048
13021
  justify-content: center;
13049
- > div {
13050
- z-index: 4;
13051
- position: relative;
13052
- width: 28%;
13053
- height: 28%;
13054
- border-radius: 20px;
13055
- background: var(--bg);
13056
- box-shadow: 0 0 0 7px var(--bg);
13022
+ margin-top: 12px;
13023
+
13024
+ box-shadow: var(--ck-secondary-button-box-shadow);
13025
+ background: var(--ck-secondary-button-background);
13026
+
13027
+ padding-right: 20px;
13028
+
13029
+ &:focus-within {
13030
+ box-shadow: var(--ck-secondary-button-hover-box-shadow);
13057
13031
  }
13058
- > span {
13059
- z-index: 4;
13060
- position: absolute;
13061
- background: var(--color);
13062
- border-radius: 12px;
13063
- width: 13.25%;
13064
- height: 13.25%;
13065
- box-shadow: 0 0 0 4px var(--bg);
13066
- &:before {
13067
- content: '';
13068
- position: absolute;
13069
- inset: 9px;
13070
- border-radius: 3px;
13071
- box-shadow: 0 0 0 4px var(--bg);
13072
- }
13073
- &:nth-child(1) {
13074
- top: 0;
13075
- left: 0;
13076
- }
13077
- &:nth-child(2) {
13078
- top: 0;
13079
- right: 0;
13080
- }
13081
- &:nth-child(3) {
13082
- bottom: 0;
13083
- left: 0;
13032
+
13033
+ input {
13034
+ padding-left: 20px;
13035
+ padding-right: 10px;
13036
+
13037
+ border: none !important;
13038
+ outline: none !important;
13039
+ background: transparent !important;
13040
+ box-shadow: none !important;
13041
+
13042
+ ::placeholder {
13043
+ color: var(--ck-body-color-muted);
13084
13044
  }
13045
+
13046
+ width: 100%;
13047
+ height: 100%;
13085
13048
  }
13086
- &:before {
13087
- z-index: 3;
13088
- content: '';
13089
- position: absolute;
13090
- inset: 0;
13091
- background: repeat;
13092
- background-size: 1.888% 1.888%;
13093
- background-image: radial-gradient(var(--color) 41%, transparent 41%);
13049
+
13050
+ .react-international-phone-country-selector-button {
13051
+ padding-left: 20px;
13052
+ padding-right: 10px;
13053
+ --react-international-phone-border-color: none;
13054
+ --react-international-phone-border-radius: var(--ck-secondary-button-border-radius);
13055
+ --react-international-phone-background-color: var(--ck-secondary-button-background);
13056
+ --react-international-phone-text-color: var(--ck-body-color);
13057
+ border-radius: var(--ck-secondary-button-border-radius) 0px 0px var(--ck-secondary-button-border-radius);
13058
+ transition: all .2s ease-out;
13094
13059
  }
13095
- &:after {
13096
- z-index: 5;
13097
- content: '';
13098
- position: absolute;
13099
- inset: 0;
13100
- transform: scale(1.5) rotate(45deg);
13101
- background-image: linear-gradient(
13102
- 90deg,
13103
- rgba(255, 255, 255, 0) 50%,
13104
- rgba(255, 255, 255, 1),
13105
- rgba(255, 255, 255, 0)
13106
- );
13107
- background-size: 200% 100%;
13108
- animation: ${PlaceholderKeyframes} 1000ms linear infinite both;
13060
+
13061
+ .react-international-phone-country-selector-dropdown {
13062
+ box-shadow: var(--ck-secondary-button-hover-box-shadow);
13063
+ }
13064
+
13065
+ .react-international-phone-country-selector-button__dropdown-arrow {
13066
+ border-top: var(--react-international-phone-country-selector-arrow-size, 4px) solid var(--react-international-phone-country-selector-arrow-color, #777) !important;
13067
+ border-right: var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent !important;
13068
+ border-left: var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent !important;
13069
+ margin-right: 4px;
13070
+ transition: all .1s ease-out;
13109
13071
  }
13110
13072
  `;
13111
- const LogoContainer$1 = styled(motion.div) `
13112
- z-index: 6;
13113
- position: absolute;
13114
- top: 0;
13115
- left: 0;
13116
- width: 100%;
13117
- height: 100%;
13118
- transform: translateY(50%) scale(0.9999); // Shifting fix
13073
+ const EmailInnerButton = styled(motion.button) `
13074
+ color: var(--ck-body-action-color);
13075
+ transition: background-color 200ms ease, transform 100ms ease, color 200ms ease, transition 200ms ease, opacity 200ms ease;
13076
+ border-radius: 16px;
13077
+
13078
+ svg {
13079
+ display: block;
13080
+ position: relative;
13081
+ padding: 4px;
13082
+ }
13083
+
13084
+ &:enabled {
13085
+ cursor: pointer;
13086
+ &:hover {
13087
+ background: var(--ck-body-background-secondary);
13088
+ color: var(--ck-body-action-hover-color);
13089
+ }
13090
+ &:active {
13091
+ transform: scale(0.9);
13092
+ }
13093
+ }
13119
13094
  `;
13120
- const LogoIcon = styled(motion.div) `
13121
- z-index: 6;
13122
- position: absolute;
13123
- left: 50%;
13124
- overflow: hidden;
13095
+ const ProvidersButton = styled.div `
13096
+ ${ButtonContainer} {
13097
+ height: 64px;
13098
+ font-size: 17px;
13099
+ font-weight: var(--ck-primary-button-font-weight, 500);
13100
+ line-height: 20px;
13101
+ }
13102
+
13103
+ ${ProviderInputInner} {
13104
+ height: 64px;
13105
+ }
13106
+
13107
+ &:first-of-type {
13108
+ ${ButtonContainer}, ${ProviderInputInner} {
13109
+ margin-top: 0;
13110
+ }
13111
+ }
13125
13112
 
13126
- transform: translate(-50%, -50%) scale(0.9999); // Shifting fix
13113
+ ${ButtonContainerInner} {
13114
+ padding: 0 20px;
13115
+ justify-content: space-between;
13116
+ }
13127
13117
 
13128
- svg {
13118
+ ${InnerContainer} {
13119
+ justify-content: space-between;
13120
+ width: 100%;
13121
+ max-width: 100%;
13122
+ }
13123
+ `;
13124
+ const ProviderLabel = styled.span `
13125
+ display: flex;
13126
+ align-items: center;
13127
+ gap: 9px;
13128
+ width: 100%;
13129
+ overflow: hidden;
13130
+ white-space: nowrap;
13131
+ text-overflow: ellipsis;
13132
+ padding: 2px 0;
13133
+ `;
13134
+ const ProviderIcon$1 = styled.div `
13135
+ width: 32px;
13136
+ height: 32px;
13137
+ flex-shrink: 0;
13138
+ svg,
13139
+ img {
13129
13140
  display: block;
13130
13141
  position: relative;
13142
+ pointer-events: none;
13143
+ overflow: hidden;
13131
13144
  width: 100%;
13132
13145
  height: 100%;
13133
13146
  }
13134
13147
 
13135
- ${(props) => props.$wcLogo
13136
- ? css `
13137
- width: 29%;
13138
- height: 20.5%;
13139
- `
13140
- : css `
13141
- width: 28%;
13142
- height: 28%;
13143
- border-radius: 17px;
13144
- &:before {
13145
- pointer-events: none;
13146
- z-index: 2;
13147
- content: '';
13148
- position: absolute;
13149
- inset: 0;
13150
- border-radius: inherit;
13151
- box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.02);
13152
- }
13153
- `}
13148
+ &[data-shape='squircle'] {
13149
+ border-radius: 22.5%;
13150
+ }
13151
+ &[data-shape='circle'] {
13152
+ border-radius: 100%;
13153
+ }
13154
+ &[data-shape='square'] {
13155
+ border-radius: 0;
13156
+ }
13154
13157
  `;
13155
13158
 
13156
- function CustomQRCode({ value, image, imageBackground, imagePosition = 'center', tooltipMessage }) {
13157
- const windowSize = useWindowSize();
13158
- const Logo = windowSize.width > 920 && tooltipMessage ? (jsx(Tooltip, { xOffset: 139, yOffset: 5, delay: 0.1, message: tooltipMessage, children: image })) : (image);
13159
- return (jsx(QRCodeContainer, { children: jsxs(QRCodeContent, { children: [image && (jsx(LogoContainer$1, { children: jsx(LogoIcon, { "$wcLogo": imagePosition !== 'center', style: {
13160
- background: imagePosition === 'center' ? imageBackground : undefined,
13161
- }, children: Logo }) })), jsx(AnimatePresence, { initial: false, children: value ? (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, position: 'absolute', inset: [0, 0] }, transition: {
13162
- duration: 0.2,
13163
- }, children: jsx(QRCode, { uri: value, size: 288, ecl: "M", clearArea: !!(imagePosition === 'center' && image) }) }, value)) : (jsxs(QRPlaceholder, { initial: { opacity: 0.1 }, animate: { opacity: 0.1 }, exit: { opacity: 0, position: 'absolute', inset: [0, 0] }, transition: {
13164
- duration: 0.2,
13165
- }, children: [jsx("span", {}), jsx("span", {}), jsx("span", {}), jsx("div", {})] })) })] }) }));
13166
- }
13167
- CustomQRCode.displayName = 'CustomQRCode';
13168
-
13169
- const DownloadApp = () => {
13170
- var _a, _b, _c;
13171
- const context = useOpenfort();
13172
- const wallet = useWallet(context.connector.id);
13173
- const locales = useLocales({
13174
- CONNECTORNAME: wallet === null || wallet === void 0 ? void 0 : wallet.name,
13175
- });
13176
- if (!wallet)
13177
- return jsx(Fragment, { children: "Wallet not found" });
13178
- const downloads = {
13179
- ios: (_a = wallet.downloadUrls) === null || _a === void 0 ? void 0 : _a.ios,
13180
- android: (_b = wallet.downloadUrls) === null || _b === void 0 ? void 0 : _b.android,
13181
- redirect: (_c = wallet.downloadUrls) === null || _c === void 0 ? void 0 : _c.download,
13182
- };
13183
- const bodycopy = downloads.ios && downloads.android
13184
- ? locales.downloadAppScreen_iosAndroid
13185
- : downloads.ios
13186
- ? locales.downloadAppScreen_ios
13187
- : locales.downloadAppScreen_android;
13188
- return (jsx(PageContent, { children: jsxs(ModalContent, { style: { paddingBottom: 4, gap: 14 }, children: [downloads.redirect && jsx(CustomQRCode, { value: downloads.redirect }), !downloads.redirect && jsx(Fragment, { children: "No download link available" }), jsx(ModalBody, { style: { fontSize: 15, lineHeight: '20px', padding: '0 12px' }, children: bodycopy })] }) }));
13189
- };
13159
+ const OtherMethodButton = styled.button `
13160
+ width: 100%;
13161
+ color: var(--ck-body-color-muted);
13162
+ background: none;
13163
+ font-size: 14px;
13164
+ margin-top: 10px;
13165
+ transition: color 0.2s;
13190
13166
 
13191
- const isValidEmail = (email) => {
13192
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
13193
- return emailRegex.test(email);
13194
- };
13167
+ &:hover {
13168
+ color: var(--ck-body-color);
13169
+ }
13170
+ `;
13195
13171
 
13196
- const buildCallbackUrl = ({ email, callbackUrl, provider, isOpen, }) => {
13197
- if (callbackUrl && !callbackUrl.startsWith('http')) {
13198
- callbackUrl = `${window.location.origin}${callbackUrl.startsWith('/') ? '' : '/'}${callbackUrl}`;
13199
- }
13200
- const redirectUrl = new URL(callbackUrl || window.location.origin);
13201
- redirectUrl.searchParams.append('openfortAuthProvider', provider);
13202
- if (email) {
13203
- redirectUrl.searchParams.append('email', email);
13204
- }
13205
- if (isOpen) {
13206
- redirectUrl.searchParams.append('openfortEmailVerificationUI', 'true');
13172
+ const OtherMethod = ({ currentMethod, onChangeMethod, }) => {
13173
+ const { uiConfig } = useOpenfort();
13174
+ const otherMethods = useMemo(() => {
13175
+ const allowedMethods = uiConfig.walletRecovery.allowedMethods;
13176
+ const otherMethods = allowedMethods.filter((method) => method !== currentMethod);
13177
+ return otherMethods;
13178
+ }, [uiConfig, currentMethod]);
13179
+ if (otherMethods.length === 0)
13180
+ return null;
13181
+ if (otherMethods.length === 1) {
13182
+ const method = otherMethods[0];
13183
+ let text;
13184
+ switch (method) {
13185
+ case RecoveryMethod.PASSWORD:
13186
+ text = 'Use password recovery instead';
13187
+ break;
13188
+ case RecoveryMethod.AUTOMATIC:
13189
+ text = 'Skip for now';
13190
+ break;
13191
+ case RecoveryMethod.PASSKEY:
13192
+ text = 'Use passkey recovery instead';
13193
+ break;
13194
+ default:
13195
+ text = method;
13196
+ }
13197
+ return (jsx(OtherMethodButton, { onClick: () => {
13198
+ onChangeMethod(method);
13199
+ }, children: text }));
13207
13200
  }
13208
- return redirectUrl.toString();
13201
+ return jsx(OtherMethodButton, { onClick: () => onChangeMethod('other'), children: "Choose another recovery method" });
13209
13202
  };
13210
-
13211
- /**
13212
- * Hook for email-based authentication operations
13213
- *
13214
- * This hook manages email authentication flows including sign-in, sign-up, password reset,
13215
- * email verification, and email linking. It handles both password and passwordless authentication
13216
- * and automatically manages wallet connection after successful authentication.
13217
- *
13218
- * @param hookOptions - Optional configuration with callback functions and authentication options
13219
- * @returns Current authentication state with email auth actions
13220
- *
13221
- * @example
13222
- * ```tsx
13223
- * const emailAuth = useEmailAuth({
13224
- * onSignInEmailSuccess: (result) => console.log('Signed in:', result.user),
13225
- * onSignInEmailError: (error) => console.error('Sign-in failed:', error),
13226
- * emailVerificationRedirectTo: 'https://yourapp.com/verify',
13227
- * recoverWalletAutomatically: true,
13228
- * });
13229
- *
13230
- * // Sign up with email and password
13231
- * await emailAuth.signUpEmail({
13232
- * email: 'user@example.com',
13233
- * password: 'securePassword123',
13234
- * name: 'John Doe',
13235
- * });
13236
- *
13237
- * // Sign in with email and password
13238
- * await emailAuth.signInEmail({
13239
- * email: 'user@example.com',
13240
- * password: 'securePassword123',
13241
- * });
13242
- *
13243
- * // Request password reset
13244
- * await emailAuth.requestResetPassword({
13245
- * email: 'user@example.com',
13246
- * });
13247
- *
13248
- * // Reset password with state token
13249
- * await emailAuth.resetPassword({
13250
- * email: 'user@example.com',
13251
- * password: 'newPassword123',
13252
- * state: 'reset-token-from-email',
13253
- * });
13254
- *
13255
- * // Verify email with state token
13256
- * await emailAuth.verifyEmail({
13257
- * email: 'user@example.com',
13258
- * state: 'verification-token-from-email',
13259
- * });
13260
- *
13261
- * // Link email to existing authenticated account
13262
- * await emailAuth.linkEmail({
13263
- * email: 'secondary@example.com',
13264
- * password: 'password123',
13265
- * });
13266
- *
13267
- * // Check authentication state
13268
- * if (emailAuth.isLoading) {
13269
- * console.log('Processing authentication...');
13270
- * } else if (emailAuth.isError) {
13271
- * console.error('Authentication error:', emailAuth.error);
13272
- * } else if (emailAuth.isSuccess) {
13273
- * console.log('Authentication successful');
13274
- * }
13275
- *
13276
- * // Handle email verification requirement
13277
- * if (emailAuth.requiresEmailVerification) {
13278
- * console.log('Please check your email to verify your account');
13279
- * }
13280
- * ```
13281
- */
13282
- const useEmailAuth = (hookOptions = {}) => {
13283
- const { client, updateUser } = useOpenfortCore();
13284
- const { isOpen } = useUI();
13285
- const [requiresEmailVerification, setRequiresEmailVerification] = useState(false);
13286
- const [status, setStatus] = useState({
13287
- status: 'idle',
13288
- });
13289
- const reset = useCallback(() => {
13290
- setStatus({
13291
- status: 'idle',
13203
+ const CreateWalletAutomaticRecovery = ({ onBack, logoutOnBack, }) => {
13204
+ var _a;
13205
+ const { embeddedState } = useOpenfortCore();
13206
+ const { setRoute, triggerResize } = useOpenfort();
13207
+ const [recoveryError, setRecoveryError] = useState(null);
13208
+ const { createWallet, isWalletRecoveryOTPEnabled, requestWalletRecoverOTP } = useWallets();
13209
+ const [shouldCreateWallet, setShouldCreateWallet] = useState(false);
13210
+ const [needsOTP, setNeedsOTP] = useState(false);
13211
+ const [otpResponse, setOtpResponse] = useState(null);
13212
+ const [otpStatus, setOtpStatus] = useState('idle');
13213
+ const [error, setError] = useState(false);
13214
+ const handleCompleteOtp = async (otp) => {
13215
+ setOtpStatus('loading');
13216
+ const response = await createWallet({
13217
+ recovery: {
13218
+ recoveryMethod: RecoveryMethod.AUTOMATIC,
13219
+ otpCode: otp,
13220
+ },
13292
13221
  });
13293
- setRequiresEmailVerification(false);
13222
+ if (response.error) {
13223
+ setOtpStatus('error');
13224
+ setError(response.error.message || 'There was an error verifying the OTP');
13225
+ logger.log('Error verifying OTP for wallet recovery', response.error);
13226
+ setTimeout(() => {
13227
+ setOtpStatus('idle');
13228
+ setError(false);
13229
+ }, 1000);
13230
+ }
13231
+ else {
13232
+ setOtpStatus('success');
13233
+ // setTimeout(() => {
13234
+ // setRoute(routes.CONNECTED_SUCCESS)
13235
+ // }, 1000)
13236
+ }
13237
+ };
13238
+ useEffect(() => {
13239
+ // To ensure the wallet is created only once
13240
+ if (shouldCreateWallet) {
13241
+ (async () => {
13242
+ logger.log('Creating wallet Automatic recover');
13243
+ const response = await createWallet();
13244
+ if (response.isOTPRequired && isWalletRecoveryOTPEnabled) {
13245
+ const response = await requestWalletRecoverOTP();
13246
+ setNeedsOTP(true);
13247
+ setOtpResponse(response);
13248
+ if (response.error) {
13249
+ logger.log('Error requesting OTP for wallet recovery', response.error);
13250
+ setRecoveryError(response.error);
13251
+ }
13252
+ }
13253
+ else if (response.error) {
13254
+ logger.log('Error creating wallet', response.error);
13255
+ setRecoveryError(response.error);
13256
+ }
13257
+ triggerResize();
13258
+ })();
13259
+ }
13260
+ }, [shouldCreateWallet]);
13261
+ const [canSendOtp, setCanSendOtp] = useState(true);
13262
+ useEffect(() => {
13263
+ if (embeddedState === EmbeddedState.EMBEDDED_SIGNER_NOT_CONFIGURED) {
13264
+ setShouldCreateWallet(true);
13265
+ }
13266
+ }, [embeddedState]);
13267
+ const handleResendClick = useCallback(() => {
13268
+ setOtpStatus('send-otp');
13269
+ setCanSendOtp(false);
13294
13270
  }, []);
13295
- const { tryUseWallet } = useConnectToWalletPostAuth();
13296
- const signInEmail = useCallback(async (options) => {
13297
- var _a, _b, _c;
13298
- try {
13299
- setStatus({
13300
- status: 'loading',
13301
- });
13302
- setRequiresEmailVerification(false);
13303
- if (!options.email || !options.password) {
13304
- const error = new OpenfortError('Email and password are required', OpenfortReactErrorType.VALIDATION_ERROR);
13305
- setStatus({
13306
- status: 'error',
13307
- error,
13308
- });
13309
- return onError({
13310
- hookOptions,
13311
- options,
13312
- error,
13313
- });
13314
- }
13315
- if (!isValidEmail(options.email)) {
13316
- const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
13317
- setStatus({
13318
- status: 'error',
13319
- error,
13320
- });
13321
- return onError({
13322
- hookOptions,
13323
- options,
13324
- error,
13325
- });
13326
- }
13327
- const result = await client.auth.logInWithEmailPassword({
13328
- email: options.email,
13329
- password: options.password,
13330
- });
13331
- if ('action' in result) {
13332
- setStatus({
13333
- status: 'awaiting-input',
13334
- });
13335
- client.auth.requestEmailVerification({
13336
- email: options.email,
13337
- redirectUrl: buildCallbackUrl({
13338
- email: options.email,
13339
- callbackUrl: (_a = options.emailVerificationRedirectTo) !== null && _a !== void 0 ? _a : hookOptions === null || hookOptions === void 0 ? void 0 : hookOptions.emailVerificationRedirectTo,
13340
- provider: 'email',
13341
- isOpen,
13342
- }),
13343
- });
13344
- setRequiresEmailVerification(true);
13345
- return onSuccess({
13346
- data: { requiresEmailVerification: true },
13347
- hookOptions,
13348
- options,
13349
- });
13350
- }
13351
- else {
13352
- const { wallet } = await tryUseWallet({
13353
- logoutOnError: (_b = options.logoutOnError) !== null && _b !== void 0 ? _b : hookOptions.logoutOnError,
13354
- recoverWalletAutomatically: (_c = options.recoverWalletAutomatically) !== null && _c !== void 0 ? _c : hookOptions.recoverWalletAutomatically,
13355
- });
13356
- setStatus({
13357
- status: 'success',
13358
- });
13359
- const user = result.user;
13360
- await updateUser();
13361
- return onSuccess({
13362
- data: { user, wallet },
13363
- hookOptions,
13364
- options,
13271
+ const isResendDisabled = !canSendOtp || otpStatus === 'sending-otp' || otpStatus === 'send-otp';
13272
+ const sendButtonText = useMemo(() => {
13273
+ if (!canSendOtp)
13274
+ return 'Code Sent!';
13275
+ if (otpStatus === 'sending-otp')
13276
+ return 'Sending...';
13277
+ return 'Resend Code';
13278
+ }, [canSendOtp, otpStatus]);
13279
+ if (needsOTP && isWalletRecoveryOTPEnabled) {
13280
+ if ((!(otpResponse === null || otpResponse === void 0 ? void 0 : otpResponse.email) && !(otpResponse === null || otpResponse === void 0 ? void 0 : otpResponse.phone)) || ((_a = otpResponse.email) === null || _a === void 0 ? void 0 : _a.includes('@openfort.anonymous'))) {
13281
+ return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(Loader, { isError: true, description: 'You cannot create a wallet without authentication, please link email or phone to continue.', header: 'Cannot create wallet.' }), jsx(Button, { onClick: () => setRoute(routes.PROVIDERS), children: "Add an authentication method" })] }));
13282
+ }
13283
+ return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(ModalHeading, { children: "Enter your code" }), jsx(FloatingGraphic, { height: "100px", marginTop: "8px", marginBottom: "10px", logoCenter: {
13284
+ logo: (otpResponse === null || otpResponse === void 0 ? void 0 : otpResponse.sentTo) === 'phone' ? jsx(PhoneIcon, {}) : jsx(EmailIcon, {}),
13285
+ } }), jsxs(ModalBody, { children: [jsxs(Body$1, { children: ["Please check ", jsx("b", { children: (otpResponse === null || otpResponse === void 0 ? void 0 : otpResponse.sentTo) === 'phone' ? otpResponse === null || otpResponse === void 0 ? void 0 : otpResponse.phone : otpResponse === null || otpResponse === void 0 ? void 0 : otpResponse.email }), " and enter your code below."] }), jsx(OtpInputStandalone, { length: 9, scale: "80%", onComplete: handleCompleteOtp, isLoading: otpStatus === 'loading', isError: otpStatus === 'error', isSuccess: otpStatus === 'success' }), jsxs(ResultContainer$1, { children: [otpStatus === 'success' && jsx(ModalBody, { "$valid": true, children: "Code verified successfully!" }), otpStatus === 'error' && jsx(ModalBody, { "$error": true, children: error || 'Invalid code. Please try again.' })] }), jsxs(FooterTextButton$1, { children: ["Didn't receive the code?", ' ', jsx(FooterButtonText$1, { type: "button", onClick: handleResendClick, disabled: isResendDisabled, children: sendButtonText })] })] })] }));
13286
+ }
13287
+ return (jsx(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: jsx(Loader, { isError: !!recoveryError, header: recoveryError ? 'Error creating wallet.' : `Creating wallet...`, description: recoveryError ? recoveryError.message : undefined }) }));
13288
+ };
13289
+ const CreateWalletPasskeyRecovery = ({ onChangeMethod, onBack, logoutOnBack, }) => {
13290
+ const { triggerResize } = useOpenfort();
13291
+ const { createWallet, error: recoveryError } = useWallets();
13292
+ const [shouldCreateWallet, setShouldCreateWallet] = useState(false);
13293
+ const { embeddedState } = useOpenfortCore();
13294
+ useEffect(() => {
13295
+ // To ensure the wallet is created only once
13296
+ if (shouldCreateWallet) {
13297
+ (async () => {
13298
+ logger.log('Creating wallet passkey recovery');
13299
+ const response = await createWallet({
13300
+ recovery: {
13301
+ recoveryMethod: RecoveryMethod.PASSKEY,
13302
+ },
13365
13303
  });
13366
- }
13304
+ if (response.error) {
13305
+ logger.log('Error creating wallet', response.error);
13306
+ setShouldCreateWallet(false);
13307
+ }
13308
+ })();
13367
13309
  }
13368
- catch (e) {
13369
- const error = new OpenfortError('Failed to login with email and password', OpenfortReactErrorType.AUTHENTICATION_ERROR, { error: e });
13370
- setStatus({
13371
- status: 'error',
13372
- error: error,
13373
- });
13374
- return onError({
13375
- hookOptions,
13376
- options,
13377
- error: error,
13378
- });
13310
+ }, [shouldCreateWallet]);
13311
+ useEffect(() => {
13312
+ if (embeddedState === EmbeddedState.EMBEDDED_SIGNER_NOT_CONFIGURED) {
13313
+ setShouldCreateWallet(true);
13379
13314
  }
13380
- }, [client, setStatus, updateUser, hookOptions]);
13381
- const requestResetPassword = useCallback(async (options) => {
13382
- var _a;
13383
- try {
13384
- if (!isValidEmail(options.email)) {
13385
- const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
13386
- setStatus({
13387
- status: 'error',
13388
- error,
13389
- });
13390
- return onError({
13391
- hookOptions,
13392
- options,
13393
- error,
13394
- });
13395
- }
13396
- setStatus({
13397
- status: 'loading',
13398
- });
13399
- setRequiresEmailVerification(false);
13400
- await client.auth.requestResetPassword({
13401
- email: options.email,
13402
- redirectUrl: buildCallbackUrl({
13403
- email: options.email,
13404
- callbackUrl: (_a = options.emailVerificationRedirectTo) !== null && _a !== void 0 ? _a : hookOptions === null || hookOptions === void 0 ? void 0 : hookOptions.emailVerificationRedirectTo,
13405
- provider: 'password',
13406
- isOpen,
13407
- }),
13408
- });
13409
- setStatus({
13410
- status: 'success',
13411
- });
13412
- setRequiresEmailVerification(true);
13413
- return onSuccess({
13414
- data: { requiresEmailVerification: true },
13415
- hookOptions,
13416
- options,
13417
- });
13315
+ }, [embeddedState]);
13316
+ useEffect(() => {
13317
+ if (recoveryError)
13318
+ triggerResize();
13319
+ }, [recoveryError]);
13320
+ return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(Loader, { icon: jsx(FingerPrintIcon, {}), isError: !!recoveryError, header: recoveryError ? 'Invalid passkey.' : 'Creating wallet with passkey...', description: recoveryError ? 'There was an error creating your passkey. Please try again.' : undefined, onRetry: () => setShouldCreateWallet(true) }), jsx(OtherMethod, { currentMethod: RecoveryMethod.PASSKEY, onChangeMethod: onChangeMethod })] }));
13321
+ };
13322
+ const CreateWalletPasswordRecovery = ({ onChangeMethod, onBack, logoutOnBack, }) => {
13323
+ const [recoveryPhrase, setRecoveryPhrase] = useState('');
13324
+ const [recoveryError, setRecoveryError] = useState(false);
13325
+ const { triggerResize } = useOpenfort();
13326
+ const [showPasswordIsTooWeakError, setShowPasswordIsTooWeakError] = useState(false);
13327
+ const [loading, setLoading] = useState(false);
13328
+ const { createWallet } = useWallets();
13329
+ const handleSubmit = async () => {
13330
+ if (getPasswordStrength(recoveryPhrase) < MEDIUM_SCORE_THRESHOLD) {
13331
+ setShowPasswordIsTooWeakError(true);
13332
+ return;
13418
13333
  }
13419
- catch (e) {
13420
- const error = new OpenfortError('Failed to reset password', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
13421
- error: e,
13422
- });
13423
- setStatus({
13424
- status: 'error',
13425
- error,
13426
- });
13427
- return onError({
13428
- hookOptions,
13429
- options,
13430
- error: error,
13334
+ setLoading(true);
13335
+ const { error } = await createWallet({
13336
+ recovery: {
13337
+ recoveryMethod: RecoveryMethod.PASSWORD,
13338
+ password: recoveryPhrase,
13339
+ },
13340
+ });
13341
+ setLoading(false);
13342
+ if (error) {
13343
+ setRecoveryError(error.message || 'There was an error recovering your account');
13344
+ }
13345
+ else {
13346
+ logger.log('Recovery success');
13347
+ }
13348
+ };
13349
+ useEffect(() => {
13350
+ if (recoveryError)
13351
+ triggerResize();
13352
+ }, [recoveryError]);
13353
+ return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(FloatingGraphic, { height: "80px", logoCenter: {
13354
+ logo: jsx(KeyIcon, {}),
13355
+ size: '1.2',
13356
+ }, logoTopLeft: {
13357
+ logo: jsx(ShieldIcon, {}),
13358
+ size: '0.75',
13359
+ }, logoBottomRight: {
13360
+ logo: jsx(LockIcon, {}),
13361
+ size: '0.5',
13362
+ } }), jsx(ModalHeading, { children: "Secure your wallet" }), jsxs(ModalBody, { style: { textAlign: 'center' }, children: [jsx(FitText, { children: "Set a password for your wallet." }), jsxs("form", { onSubmit: (e) => {
13363
+ e.preventDefault();
13364
+ handleSubmit();
13365
+ }, children: [jsx(Input, { value: recoveryPhrase, onChange: (e) => {
13366
+ if (showPasswordIsTooWeakError)
13367
+ setShowPasswordIsTooWeakError(false);
13368
+ setRecoveryPhrase(e.target.value);
13369
+ }, type: "password", placeholder: "Enter your password", autoComplete: "off" }), jsx(PasswordStrengthIndicator, { password: recoveryPhrase, showPasswordIsTooWeakError: showPasswordIsTooWeakError }), jsx(TickList, { items: ['You will use this password to access your wallet', "Make sure it's strong and memorable"] }), recoveryError && (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, children: jsx(ModalBody, { style: { height: 24, marginTop: 12 }, "$error": true, children: jsx(FitText, { children: recoveryError }) }) }, recoveryError)), jsx(Button, { onClick: handleSubmit, waiting: loading, disabled: loading, children: "Create wallet" })] }), jsx(OtherMethod, { currentMethod: RecoveryMethod.PASSWORD, onChangeMethod: onChangeMethod })] })] }));
13370
+ };
13371
+ const ChooseRecoveryMethod = ({ onChangeMethod, onBack, logoutOnBack, }) => {
13372
+ return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(ModalHeading, { children: "Choose a recovery method" }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.PASSKEY), children: [jsx(ProviderLabel, { children: "Passkey" }), jsx(ProviderIcon$1, { children: jsx(FingerPrintIcon, {}) })] }) }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.PASSWORD), children: [jsx(ProviderLabel, { children: "Password" }), jsx(ProviderIcon$1, { children: jsx(KeyIcon, {}) })] }) }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.AUTOMATIC), children: [jsx(ProviderLabel, { children: "Automatic" }), jsx(ProviderIcon$1, { children: jsx(LockIcon, {}) })] }) })] }));
13373
+ };
13374
+ const CreateEmbeddedWallet = ({ onBack, logoutOnBack }) => {
13375
+ const { uiConfig, triggerResize } = useOpenfort();
13376
+ const [userSelectedMethod, setUserSelectedMethod] = useState(null);
13377
+ useEffect(() => {
13378
+ triggerResize();
13379
+ }, [userSelectedMethod]);
13380
+ const method = userSelectedMethod !== null && userSelectedMethod !== void 0 ? userSelectedMethod : uiConfig.walletRecovery.defaultMethod;
13381
+ switch (method) {
13382
+ case RecoveryMethod.PASSWORD:
13383
+ return (jsx(CreateWalletPasswordRecovery, { onChangeMethod: setUserSelectedMethod, onBack: onBack, logoutOnBack: logoutOnBack }));
13384
+ case RecoveryMethod.AUTOMATIC:
13385
+ return jsx(CreateWalletAutomaticRecovery, { onBack: onBack, logoutOnBack: logoutOnBack });
13386
+ case RecoveryMethod.PASSKEY:
13387
+ return (jsx(CreateWalletPasskeyRecovery, { onChangeMethod: setUserSelectedMethod, onBack: onBack, logoutOnBack: logoutOnBack }));
13388
+ case 'other':
13389
+ return (jsx(ChooseRecoveryMethod, { onChangeMethod: setUserSelectedMethod, onBack: () => {
13390
+ setUserSelectedMethod(null);
13391
+ }, logoutOnBack: logoutOnBack }));
13392
+ default:
13393
+ logger.error(`Unsupported recovery method: ${userSelectedMethod}${uiConfig.walletRecovery.defaultMethod}`);
13394
+ return null;
13395
+ }
13396
+ };
13397
+ const CreateOrConnectWallet = () => {
13398
+ const [showCreateEmbeddedWallet, setShowCreateEmbeddedWallet] = useState(false);
13399
+ const { setRoute } = useOpenfort();
13400
+ if (showCreateEmbeddedWallet)
13401
+ return jsx(CreateEmbeddedWallet, { onBack: () => setShowCreateEmbeddedWallet(false), logoutOnBack: false });
13402
+ return (jsxs(PageContent, { onBack: routes.PROVIDERS, logoutOnBack: true, children: [jsx(ModalHeading, { children: "Choose an option" }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => setShowCreateEmbeddedWallet(true), children: [jsx(ProviderLabel, { children: "Create Wallet" }), jsx(ProviderIcon$1, { children: jsx(PlusIcon, {}) })] }) }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => {
13403
+ setRoute({ route: routes.CONNECTORS, connectType: 'link' });
13404
+ }, children: [jsx(ProviderLabel, { children: "Connect Wallet" }), jsx(ProviderIcon$1, { children: jsx(Logos.OtherWallets, {}) })] }) })] }));
13405
+ };
13406
+ const CreateWallet = () => {
13407
+ const { uiConfig, walletConfig, setRoute } = useOpenfort();
13408
+ const { user } = useOpenfortCore();
13409
+ const { isConnected } = useAccount();
13410
+ useEffect(() => {
13411
+ if (isConnected && user)
13412
+ setRoute(routes.CONNECTED_SUCCESS);
13413
+ }, [isConnected, user]);
13414
+ if (uiConfig.linkWalletOnSignUp === LinkWalletOnSignUpOption.OPTIONAL) {
13415
+ return jsx(CreateOrConnectWallet, {});
13416
+ }
13417
+ if (uiConfig.linkWalletOnSignUp === LinkWalletOnSignUpOption.REQUIRED ||
13418
+ (!walletConfig && uiConfig.linkWalletOnSignUp !== LinkWalletOnSignUpOption.DISABLED)) {
13419
+ return jsx(Connectors, { logoutOnBack: true });
13420
+ }
13421
+ return jsx(CreateEmbeddedWallet, { onBack: routes.PROVIDERS, logoutOnBack: true });
13422
+ };
13423
+
13424
+ function useWindowSize() {
13425
+ const [windowSize, setWindowSize] = useState({
13426
+ width: 0,
13427
+ height: 0,
13428
+ });
13429
+ useEffect(() => {
13430
+ function handleResize() {
13431
+ setWindowSize({
13432
+ width: window.innerWidth,
13433
+ height: window.innerHeight,
13431
13434
  });
13432
13435
  }
13433
- }, [client, setStatus, updateUser, hookOptions]);
13434
- const resetPassword = useCallback(async (options) => {
13435
- try {
13436
- if (!isValidEmail(options.email)) {
13437
- const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
13438
- setStatus({
13439
- status: 'error',
13440
- error,
13441
- });
13442
- return onError({
13443
- hookOptions,
13444
- options,
13445
- error,
13446
- });
13436
+ window.addEventListener('resize', handleResize);
13437
+ handleResize();
13438
+ return () => window.removeEventListener('resize', handleResize);
13439
+ }, []);
13440
+ return windowSize;
13441
+ }
13442
+
13443
+ const generateMatrix = (value, errorCorrectionLevel) => {
13444
+ const arr = Array.prototype.slice.call(QRCodeUtil.create(value, { errorCorrectionLevel }).modules.data, 0);
13445
+ const sqrt = Math.sqrt(arr.length);
13446
+ return arr.reduce((rows, key, index) => (index % sqrt === 0 ? rows.push([key]) : rows[rows.length - 1].push(key)) && rows, []);
13447
+ };
13448
+ function QRCode({ ecl = 'M', size: sizeProp = 200, uri, clearArea = false, image, imageBackground = 'transparent', }) {
13449
+ const logoSize = clearArea ? 76 : 0;
13450
+ const size = sizeProp - 10 * 2;
13451
+ const dots = useMemo(() => {
13452
+ const dots = [];
13453
+ const matrix = generateMatrix(uri, ecl);
13454
+ const cellSize = size / matrix.length;
13455
+ const qrList = [
13456
+ { x: 0, y: 0 },
13457
+ { x: 1, y: 0 },
13458
+ { x: 0, y: 1 },
13459
+ ];
13460
+ qrList.forEach(({ x, y }) => {
13461
+ const x1 = (matrix.length - 7) * cellSize * x;
13462
+ const y1 = (matrix.length - 7) * cellSize * y;
13463
+ for (let i = 0; i < 3; i++) {
13464
+ dots.push(jsx("rect", { fill: i % 2 !== 0 ? 'var(--ck-qr-background, var(--ck-body-background))' : 'var(--ck-qr-dot-color)', rx: (i - 2) * -5 + (i === 0 ? 2 : 3), ry: (i - 2) * -5 + (i === 0 ? 2 : 3), width: cellSize * (7 - i * 2), height: cellSize * (7 - i * 2), x: x1 + cellSize * i, y: y1 + cellSize * i }, `${i}-${x}-${y}`));
13447
13465
  }
13448
- setStatus({
13449
- status: 'loading',
13450
- });
13451
- setRequiresEmailVerification(false);
13452
- await client.auth.resetPassword({
13453
- password: options.password,
13454
- token: options.state,
13455
- });
13456
- setStatus({
13457
- status: 'success',
13458
- });
13459
- setRequiresEmailVerification(true);
13460
- return onSuccess({
13461
- data: { requiresEmailVerification: true },
13462
- hookOptions,
13463
- options,
13464
- });
13466
+ });
13467
+ if (image) {
13468
+ const x1 = (matrix.length - 7) * cellSize * 1;
13469
+ const y1 = (matrix.length - 7) * cellSize * 1;
13470
+ dots.push(jsxs(Fragment, { children: [jsx("rect", { fill: imageBackground, rx: (0 - 2) * -5 + 2, ry: (0 - 2) * -5 + 2, width: cellSize * (7 - 0 * 2), height: cellSize * (7 - 0 * 2), x: x1 + cellSize * 0, y: y1 + cellSize * 0 }), jsx("foreignObject", { width: cellSize * (7 - 0 * 2), height: cellSize * (7 - 0 * 2), x: x1 + cellSize * 0, y: y1 + cellSize * 0, children: jsx("div", { style: { borderRadius: (0 - 2) * -5 + 2, overflow: 'hidden' }, children: image }) })] }));
13465
13471
  }
13466
- catch (e) {
13467
- const error = new OpenfortError('Failed to reset password', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
13468
- error: e,
13469
- });
13470
- setStatus({
13471
- status: 'error',
13472
- error,
13473
- });
13474
- return onError({
13475
- hookOptions,
13476
- options,
13477
- error: error,
13472
+ const clearArenaSize = Math.floor((logoSize + 25) / cellSize);
13473
+ const matrixMiddleStart = matrix.length / 2 - clearArenaSize / 2;
13474
+ const matrixMiddleEnd = matrix.length / 2 + clearArenaSize / 2 - 1;
13475
+ matrix.forEach((row, i) => {
13476
+ row.forEach((_, j) => {
13477
+ if (matrix[i][j]) {
13478
+ // Do not render dots under position squares
13479
+ if (!((i < 7 && j < 7) || (i > matrix.length - 8 && j < 7) || (i < 7 && j > matrix.length - 8))) {
13480
+ //if (image && i > matrix.length - 9 && j > matrix.length - 9) return;
13481
+ if (image ||
13482
+ !(i > matrixMiddleStart && i < matrixMiddleEnd && j > matrixMiddleStart && j < matrixMiddleEnd)) {
13483
+ dots.push(jsx("circle", { cx: i * cellSize + cellSize / 2, cy: j * cellSize + cellSize / 2, fill: "var(--ck-qr-dot-color)", r: cellSize / 3 }, `qr-dot-${i}-${j}`));
13484
+ }
13485
+ }
13486
+ }
13478
13487
  });
13479
- }
13480
- }, [client, setStatus, updateUser, hookOptions]);
13481
- const signUpEmail = useCallback(async (options) => {
13488
+ });
13489
+ return dots;
13490
+ }, [ecl, size, uri]);
13491
+ return (jsxs("svg", { height: size, width: size, viewBox: `0 0 ${size} ${size}`, style: {
13492
+ width: size,
13493
+ height: size,
13494
+ }, children: [jsx("title", { children: "QR Code" }), jsx("rect", { fill: "transparent", height: size, width: size }), dots] }));
13495
+ }
13496
+
13497
+ const QRCodeContainer = styled(motion.div) `
13498
+ z-index: 3;
13499
+ position: relative;
13500
+ overflow: hidden;
13501
+ height: 0;
13502
+ padding-bottom: 100% !important;
13503
+ display: flex;
13504
+ align-items: center;
13505
+ justify-content: center;
13506
+ margin: 1px 0 2px;
13507
+ border-radius: var(--ck-qr-border-radius, 24px);
13508
+ background: var(--ck-qr-background, transparent);
13509
+ box-shadow: 0 0 0 1px var(--ck-qr-border-color);
13510
+ backface-visibility: hidden;
13511
+ svg {
13512
+ display: block;
13513
+ max-width: 100%;
13514
+ width: 100%;
13515
+ height: auto;
13516
+ }
13517
+ `;
13518
+ const QRCodeContent = styled(motion.div) `
13519
+ position: absolute;
13520
+ inset: 13px;
13521
+ svg {
13522
+ width: 100% !important;
13523
+ height: auto !important;
13524
+ }
13525
+ `;
13526
+ const PlaceholderKeyframes = keyframes `
13527
+ 0%{ background-position: 100% 0; }
13528
+ 100%{ background-position: -100% 0; }
13529
+ `;
13530
+ const QRPlaceholder = styled(motion.div) `
13531
+ --color: var(--ck-qr-dot-color);
13532
+ --bg: var(--ck-qr-background, var(--ck-body-background));
13533
+
13534
+ position: absolute;
13535
+ inset: 0;
13536
+ display: flex;
13537
+ align-items: center;
13538
+ justify-content: center;
13539
+ > div {
13540
+ z-index: 4;
13541
+ position: relative;
13542
+ width: 28%;
13543
+ height: 28%;
13544
+ border-radius: 20px;
13545
+ background: var(--bg);
13546
+ box-shadow: 0 0 0 7px var(--bg);
13547
+ }
13548
+ > span {
13549
+ z-index: 4;
13550
+ position: absolute;
13551
+ background: var(--color);
13552
+ border-radius: 12px;
13553
+ width: 13.25%;
13554
+ height: 13.25%;
13555
+ box-shadow: 0 0 0 4px var(--bg);
13556
+ &:before {
13557
+ content: '';
13558
+ position: absolute;
13559
+ inset: 9px;
13560
+ border-radius: 3px;
13561
+ box-shadow: 0 0 0 4px var(--bg);
13562
+ }
13563
+ &:nth-child(1) {
13564
+ top: 0;
13565
+ left: 0;
13566
+ }
13567
+ &:nth-child(2) {
13568
+ top: 0;
13569
+ right: 0;
13570
+ }
13571
+ &:nth-child(3) {
13572
+ bottom: 0;
13573
+ left: 0;
13574
+ }
13575
+ }
13576
+ &:before {
13577
+ z-index: 3;
13578
+ content: '';
13579
+ position: absolute;
13580
+ inset: 0;
13581
+ background: repeat;
13582
+ background-size: 1.888% 1.888%;
13583
+ background-image: radial-gradient(var(--color) 41%, transparent 41%);
13584
+ }
13585
+ &:after {
13586
+ z-index: 5;
13587
+ content: '';
13588
+ position: absolute;
13589
+ inset: 0;
13590
+ transform: scale(1.5) rotate(45deg);
13591
+ background-image: linear-gradient(
13592
+ 90deg,
13593
+ rgba(255, 255, 255, 0) 50%,
13594
+ rgba(255, 255, 255, 1),
13595
+ rgba(255, 255, 255, 0)
13596
+ );
13597
+ background-size: 200% 100%;
13598
+ animation: ${PlaceholderKeyframes} 1000ms linear infinite both;
13599
+ }
13600
+ `;
13601
+ const LogoContainer$1 = styled(motion.div) `
13602
+ z-index: 6;
13603
+ position: absolute;
13604
+ top: 0;
13605
+ left: 0;
13606
+ width: 100%;
13607
+ height: 100%;
13608
+ transform: translateY(50%) scale(0.9999); // Shifting fix
13609
+ `;
13610
+ const LogoIcon = styled(motion.div) `
13611
+ z-index: 6;
13612
+ position: absolute;
13613
+ left: 50%;
13614
+ overflow: hidden;
13615
+
13616
+ transform: translate(-50%, -50%) scale(0.9999); // Shifting fix
13617
+
13618
+ svg {
13619
+ display: block;
13620
+ position: relative;
13621
+ width: 100%;
13622
+ height: 100%;
13623
+ }
13624
+
13625
+ ${(props) => props.$wcLogo
13626
+ ? css `
13627
+ width: 29%;
13628
+ height: 20.5%;
13629
+ `
13630
+ : css `
13631
+ width: 28%;
13632
+ height: 28%;
13633
+ border-radius: 17px;
13634
+ &:before {
13635
+ pointer-events: none;
13636
+ z-index: 2;
13637
+ content: '';
13638
+ position: absolute;
13639
+ inset: 0;
13640
+ border-radius: inherit;
13641
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.02);
13642
+ }
13643
+ `}
13644
+ `;
13645
+
13646
+ function CustomQRCode({ value, image, imageBackground, imagePosition = 'center', tooltipMessage }) {
13647
+ const windowSize = useWindowSize();
13648
+ const Logo = windowSize.width > 920 && tooltipMessage ? (jsx(Tooltip, { xOffset: 139, yOffset: 5, delay: 0.1, message: tooltipMessage, children: image })) : (image);
13649
+ return (jsx(QRCodeContainer, { children: jsxs(QRCodeContent, { children: [image && (jsx(LogoContainer$1, { children: jsx(LogoIcon, { "$wcLogo": imagePosition !== 'center', style: {
13650
+ background: imagePosition === 'center' ? imageBackground : undefined,
13651
+ }, children: Logo }) })), jsx(AnimatePresence, { initial: false, children: value ? (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, position: 'absolute', inset: [0, 0] }, transition: {
13652
+ duration: 0.2,
13653
+ }, children: jsx(QRCode, { uri: value, size: 288, ecl: "M", clearArea: !!(imagePosition === 'center' && image) }) }, value)) : (jsxs(QRPlaceholder, { initial: { opacity: 0.1 }, animate: { opacity: 0.1 }, exit: { opacity: 0, position: 'absolute', inset: [0, 0] }, transition: {
13654
+ duration: 0.2,
13655
+ }, children: [jsx("span", {}), jsx("span", {}), jsx("span", {}), jsx("div", {})] })) })] }) }));
13656
+ }
13657
+ CustomQRCode.displayName = 'CustomQRCode';
13658
+
13659
+ const DownloadApp = () => {
13660
+ var _a, _b, _c;
13661
+ const context = useOpenfort();
13662
+ const wallet = useWallet(context.connector.id);
13663
+ const locales = useLocales({
13664
+ CONNECTORNAME: wallet === null || wallet === void 0 ? void 0 : wallet.name,
13665
+ });
13666
+ if (!wallet)
13667
+ return jsx(Fragment, { children: "Wallet not found" });
13668
+ const downloads = {
13669
+ ios: (_a = wallet.downloadUrls) === null || _a === void 0 ? void 0 : _a.ios,
13670
+ android: (_b = wallet.downloadUrls) === null || _b === void 0 ? void 0 : _b.android,
13671
+ redirect: (_c = wallet.downloadUrls) === null || _c === void 0 ? void 0 : _c.download,
13672
+ };
13673
+ const bodycopy = downloads.ios && downloads.android
13674
+ ? locales.downloadAppScreen_iosAndroid
13675
+ : downloads.ios
13676
+ ? locales.downloadAppScreen_ios
13677
+ : locales.downloadAppScreen_android;
13678
+ return (jsx(PageContent, { children: jsxs(ModalContent, { style: { paddingBottom: 4, gap: 14 }, children: [downloads.redirect && jsx(CustomQRCode, { value: downloads.redirect }), !downloads.redirect && jsx(Fragment, { children: "No download link available" }), jsx(ModalBody, { style: { fontSize: 15, lineHeight: '20px', padding: '0 12px' }, children: bodycopy })] }) }));
13679
+ };
13680
+
13681
+ const isValidEmail = (email) => {
13682
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
13683
+ return emailRegex.test(email);
13684
+ };
13685
+
13686
+ const buildCallbackUrl = ({ email, callbackUrl, provider, isOpen, }) => {
13687
+ if (callbackUrl && !callbackUrl.startsWith('http')) {
13688
+ callbackUrl = `${window.location.origin}${callbackUrl.startsWith('/') ? '' : '/'}${callbackUrl}`;
13689
+ }
13690
+ const redirectUrl = new URL(callbackUrl || window.location.origin);
13691
+ redirectUrl.searchParams.append('openfortAuthProvider', provider);
13692
+ if (email) {
13693
+ redirectUrl.searchParams.append('email', email);
13694
+ }
13695
+ if (isOpen) {
13696
+ redirectUrl.searchParams.append('openfortEmailVerificationUI', 'true');
13697
+ }
13698
+ return redirectUrl.toString();
13699
+ };
13700
+
13701
+ /**
13702
+ * Hook for email-based authentication operations
13703
+ *
13704
+ * This hook manages email authentication flows including sign-in, sign-up, password reset,
13705
+ * email verification, and email linking. It handles both password and passwordless authentication
13706
+ * and automatically manages wallet connection after successful authentication.
13707
+ *
13708
+ * @param hookOptions - Optional configuration with callback functions and authentication options
13709
+ * @returns Current authentication state with email auth actions
13710
+ *
13711
+ * @example
13712
+ * ```tsx
13713
+ * const emailAuth = useEmailAuth({
13714
+ * onSignInEmailSuccess: (result) => console.log('Signed in:', result.user),
13715
+ * onSignInEmailError: (error) => console.error('Sign-in failed:', error),
13716
+ * emailVerificationRedirectTo: 'https://yourapp.com/verify',
13717
+ * recoverWalletAutomatically: true,
13718
+ * });
13719
+ *
13720
+ * // Sign up with email and password
13721
+ * await emailAuth.signUpEmail({
13722
+ * email: 'user@example.com',
13723
+ * password: 'securePassword123',
13724
+ * name: 'John Doe',
13725
+ * });
13726
+ *
13727
+ * // Sign in with email and password
13728
+ * await emailAuth.signInEmail({
13729
+ * email: 'user@example.com',
13730
+ * password: 'securePassword123',
13731
+ * });
13732
+ *
13733
+ * // Request password reset
13734
+ * await emailAuth.requestResetPassword({
13735
+ * email: 'user@example.com',
13736
+ * });
13737
+ *
13738
+ * // Reset password with state token
13739
+ * await emailAuth.resetPassword({
13740
+ * email: 'user@example.com',
13741
+ * password: 'newPassword123',
13742
+ * state: 'reset-token-from-email',
13743
+ * });
13744
+ *
13745
+ * // Verify email with state token
13746
+ * await emailAuth.verifyEmail({
13747
+ * email: 'user@example.com',
13748
+ * state: 'verification-token-from-email',
13749
+ * });
13750
+ *
13751
+ * // Link email to existing authenticated account
13752
+ * await emailAuth.linkEmail({
13753
+ * email: 'secondary@example.com',
13754
+ * password: 'password123',
13755
+ * });
13756
+ *
13757
+ * // Check authentication state
13758
+ * if (emailAuth.isLoading) {
13759
+ * console.log('Processing authentication...');
13760
+ * } else if (emailAuth.isError) {
13761
+ * console.error('Authentication error:', emailAuth.error);
13762
+ * } else if (emailAuth.isSuccess) {
13763
+ * console.log('Authentication successful');
13764
+ * }
13765
+ *
13766
+ * // Handle email verification requirement
13767
+ * if (emailAuth.requiresEmailVerification) {
13768
+ * console.log('Please check your email to verify your account');
13769
+ * }
13770
+ * ```
13771
+ */
13772
+ const useEmailAuth = (hookOptions = {}) => {
13773
+ const { client, updateUser } = useOpenfortCore();
13774
+ const { isOpen } = useUI();
13775
+ const [requiresEmailVerification, setRequiresEmailVerification] = useState(false);
13776
+ const [status, setStatus] = useState({
13777
+ status: 'idle',
13778
+ });
13779
+ const reset = useCallback(() => {
13780
+ setStatus({
13781
+ status: 'idle',
13782
+ });
13783
+ setRequiresEmailVerification(false);
13784
+ }, []);
13785
+ const { tryUseWallet } = useConnectToWalletPostAuth();
13786
+ const signInEmail = useCallback(async (options) => {
13482
13787
  var _a, _b, _c;
13483
13788
  try {
13484
- if (!options.email || !options.password) {
13485
- const error = new OpenfortError('Email and password are required', OpenfortReactErrorType.VALIDATION_ERROR);
13486
- setStatus({
13487
- status: 'error',
13488
- error,
13489
- });
13490
- return onError({
13491
- hookOptions,
13492
- options,
13493
- error,
13494
- });
13495
- }
13496
- if (!isValidEmail(options.email)) {
13497
- const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
13498
- setStatus({
13499
- status: 'error',
13500
- error,
13501
- });
13502
- return onError({
13503
- hookOptions,
13504
- options,
13505
- error,
13506
- });
13507
- }
13508
13789
  setStatus({
13509
13790
  status: 'loading',
13510
13791
  });
13511
13792
  setRequiresEmailVerification(false);
13512
- const result = await client.auth.signUpWithEmailPassword({
13513
- email: options.email,
13514
- password: options.password,
13515
- callbackURL: buildCallbackUrl({
13516
- email: options.email,
13517
- callbackUrl: (_a = options.emailVerificationRedirectTo) !== null && _a !== void 0 ? _a : hookOptions === null || hookOptions === void 0 ? void 0 : hookOptions.emailVerificationRedirectTo,
13518
- provider: 'email',
13519
- isOpen,
13520
- }),
13521
- ...(options.name && { name: options.name }),
13522
- });
13523
- // TODO: TMP FIX
13524
- if ('token' in result && result.token !== null) {
13525
- const { wallet } = await tryUseWallet({
13526
- logoutOnError: (_b = options.logoutOnError) !== null && _b !== void 0 ? _b : hookOptions.logoutOnError,
13527
- recoverWalletAutomatically: (_c = options.recoverWalletAutomatically) !== null && _c !== void 0 ? _c : hookOptions.recoverWalletAutomatically,
13528
- });
13529
- setStatus({
13530
- status: 'success',
13531
- });
13532
- const user = result.user;
13533
- await updateUser(user);
13534
- return onSuccess({
13535
- data: { user, wallet },
13536
- hookOptions,
13537
- options,
13538
- });
13539
- }
13540
- else {
13541
- setStatus({
13542
- status: 'awaiting-input',
13543
- });
13544
- setRequiresEmailVerification(true);
13545
- return onSuccess({
13546
- data: { requiresEmailVerification: true },
13547
- hookOptions,
13548
- options,
13549
- });
13550
- }
13551
- }
13552
- catch (e) {
13553
- const error = new OpenfortError('Failed to login with email and password', OpenfortReactErrorType.AUTHENTICATION_ERROR, { error: e });
13554
- setStatus({
13555
- status: 'error',
13556
- error,
13557
- });
13558
- return onError({
13559
- hookOptions,
13560
- options,
13561
- error: error,
13562
- });
13563
- }
13564
- }, [client, setStatus, updateUser, hookOptions, isOpen]);
13565
- const linkEmail = useCallback(async (options) => {
13566
- var _a;
13567
- try {
13568
- if (!isValidEmail(options.email)) {
13569
- const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
13793
+ if (!options.email || !options.password) {
13794
+ const error = new OpenfortError('Email and password are required', OpenfortReactErrorType.VALIDATION_ERROR);
13570
13795
  setStatus({
13571
13796
  status: 'error',
13572
13797
  error,
@@ -13577,11 +13802,8 @@ const useEmailAuth = (hookOptions = {}) => {
13577
13802
  error,
13578
13803
  });
13579
13804
  }
13580
- await client.validateAndRefreshToken();
13581
- const authToken = await client.getAccessToken();
13582
- if (!authToken) {
13583
- logger.log('No token found');
13584
- const error = new OpenfortError('No token found', OpenfortReactErrorType.AUTHENTICATION_ERROR);
13805
+ if (!isValidEmail(options.email)) {
13806
+ const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
13585
13807
  setStatus({
13586
13808
  status: 'error',
13587
13809
  error,
@@ -13592,29 +13814,49 @@ const useEmailAuth = (hookOptions = {}) => {
13592
13814
  error,
13593
13815
  });
13594
13816
  }
13595
- await client.auth.addEmail({
13596
- // name: options.name || '',
13817
+ const result = await client.auth.logInWithEmailPassword({
13597
13818
  email: options.email,
13598
- // password: options.password,
13599
- // method: 'password',
13600
- callbackURL: buildCallbackUrl({
13601
- callbackUrl: (_a = options.emailVerificationRedirectTo) !== null && _a !== void 0 ? _a : hookOptions === null || hookOptions === void 0 ? void 0 : hookOptions.emailVerificationRedirectTo,
13602
- email: options.email,
13603
- provider: 'email',
13604
- isOpen,
13605
- }),
13606
- });
13607
- logger.log('Email linked successfully');
13608
- return onSuccess({
13609
- data: {},
13610
- hookOptions,
13611
- options,
13819
+ password: options.password,
13612
13820
  });
13821
+ if ('action' in result) {
13822
+ setStatus({
13823
+ status: 'awaiting-input',
13824
+ });
13825
+ client.auth.requestEmailVerification({
13826
+ email: options.email,
13827
+ redirectUrl: buildCallbackUrl({
13828
+ email: options.email,
13829
+ callbackUrl: (_a = options.emailVerificationRedirectTo) !== null && _a !== void 0 ? _a : hookOptions === null || hookOptions === void 0 ? void 0 : hookOptions.emailVerificationRedirectTo,
13830
+ provider: 'email',
13831
+ isOpen,
13832
+ }),
13833
+ });
13834
+ setRequiresEmailVerification(true);
13835
+ return onSuccess({
13836
+ data: { requiresEmailVerification: true },
13837
+ hookOptions,
13838
+ options,
13839
+ });
13840
+ }
13841
+ else {
13842
+ const { wallet } = await tryUseWallet({
13843
+ logoutOnError: (_b = options.logoutOnError) !== null && _b !== void 0 ? _b : hookOptions.logoutOnError,
13844
+ recoverWalletAutomatically: (_c = options.recoverWalletAutomatically) !== null && _c !== void 0 ? _c : hookOptions.recoverWalletAutomatically,
13845
+ });
13846
+ setStatus({
13847
+ status: 'success',
13848
+ });
13849
+ const user = result.user;
13850
+ await updateUser();
13851
+ return onSuccess({
13852
+ data: { user, wallet },
13853
+ hookOptions,
13854
+ options,
13855
+ });
13856
+ }
13613
13857
  }
13614
13858
  catch (e) {
13615
- const error = new OpenfortError('Failed to link email', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
13616
- error: e,
13617
- });
13859
+ const error = new OpenfortError('Failed to login with email and password', OpenfortReactErrorType.AUTHENTICATION_ERROR, { error: e });
13618
13860
  setStatus({
13619
13861
  status: 'error',
13620
13862
  error: error,
@@ -13625,11 +13867,9 @@ const useEmailAuth = (hookOptions = {}) => {
13625
13867
  error: error,
13626
13868
  });
13627
13869
  }
13628
- }, [client, setStatus, updateUser, hookOptions, isOpen]);
13629
- const verifyEmail = useCallback(async (options) => {
13630
- setStatus({
13631
- status: 'loading',
13632
- });
13870
+ }, [client, setStatus, updateUser, hookOptions]);
13871
+ const requestResetPassword = useCallback(async (options) => {
13872
+ var _a;
13633
13873
  try {
13634
13874
  if (!isValidEmail(options.email)) {
13635
13875
  const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
@@ -13643,199 +13883,46 @@ const useEmailAuth = (hookOptions = {}) => {
13643
13883
  error,
13644
13884
  });
13645
13885
  }
13646
- await client.auth.verifyEmail({
13647
- token: options.state,
13886
+ setStatus({
13887
+ status: 'loading',
13888
+ });
13889
+ setRequiresEmailVerification(false);
13890
+ await client.auth.requestResetPassword({
13891
+ email: options.email,
13892
+ redirectUrl: buildCallbackUrl({
13893
+ email: options.email,
13894
+ callbackUrl: (_a = options.emailVerificationRedirectTo) !== null && _a !== void 0 ? _a : hookOptions === null || hookOptions === void 0 ? void 0 : hookOptions.emailVerificationRedirectTo,
13895
+ provider: 'password',
13896
+ isOpen,
13897
+ }),
13648
13898
  });
13649
13899
  setStatus({
13650
13900
  status: 'success',
13651
13901
  });
13902
+ setRequiresEmailVerification(true);
13652
13903
  return onSuccess({
13904
+ data: { requiresEmailVerification: true },
13653
13905
  hookOptions,
13654
13906
  options,
13655
- data: {
13656
- email: options.email,
13657
- },
13658
13907
  });
13659
13908
  }
13660
13909
  catch (e) {
13661
- const error = new OpenfortError('Failed to verify email', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
13910
+ const error = new OpenfortError('Failed to reset password', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
13662
13911
  error: e,
13663
- });
13664
- setStatus({
13665
- status: 'error',
13666
- error,
13667
- });
13668
- logger.log('Error verifying email', e);
13669
- return onError({
13670
- hookOptions,
13671
- options,
13672
- error,
13673
- });
13674
- }
13675
- }, [client, hookOptions]);
13676
- return {
13677
- signInEmail,
13678
- signUpEmail,
13679
- verifyEmail,
13680
- linkEmail,
13681
- requestResetPassword,
13682
- resetPassword,
13683
- reset,
13684
- ...mapStatus(status),
13685
- requiresEmailVerification,
13686
- isAwaitingInput: status.status === 'awaiting-input',
13687
- };
13688
- };
13689
-
13690
- const FooterContainer = styled.span `
13691
- padding: 12px 4px 0px 0px;
13692
- display: flex;
13693
- align-items: center;
13694
- justify-content: center;
13695
- gap: 6px;
13696
- line-height: 1rem;
13697
- color: var(--ck-body-disclaimer-color, var(--ck-body-color-muted, inherit));
13698
-
13699
- & button {
13700
- color: var(--ck-body-disclaimer-link-color, inherit);
13701
- font-weight: var(--ck-body-disclaimer-font-weight, 400);
13702
- text-decoration: none;
13703
- transition: color 200ms ease;
13704
- &:hover {
13705
- color: var(--ck-body-disclaimer-link-hover-color, inherit);
13706
- }
13707
- }
13708
- `;
13709
-
13710
- // TODO: Localize
13711
- const textVariants$1 = {
13712
- initial: {
13713
- opacity: 0,
13714
- },
13715
- animate: {
13716
- opacity: 1,
13717
- transition: {
13718
- duration: 0.3,
13719
- ease: [0.25, 1, 0.5, 1],
13720
- },
13721
- },
13722
- exit: {
13723
- position: 'absolute',
13724
- opacity: 0,
13725
- transition: {
13726
- duration: 0,
13727
- },
13728
- },
13729
- };
13730
- const EmailLogin = () => {
13731
- const [password, setPassword] = React.useState('');
13732
- const { setRoute, triggerResize, setEmailInput: setEmail, emailInput: email, previousRoute } = useOpenfort();
13733
- const [isRegister, setIsRegister] = React.useState(false);
13734
- const { signUpEmail, signInEmail, error: loginError, isLoading: loginLoading, } = useEmailAuth({
13735
- recoverWalletAutomatically: false,
13736
- });
13737
- const handleSubmit = async () => {
13738
- if (isRegister) {
13739
- return handleSignUp();
13740
- }
13741
- setIsRegister(false);
13742
- setTimeout(() => {
13743
- triggerResize();
13744
- });
13745
- const { error, requiresEmailVerification } = await signInEmail({
13746
- email,
13747
- password,
13748
- });
13749
- logger.log('SIGN IN RESPONSE', { error, requiresEmailVerification });
13750
- if (!error) {
13751
- if (requiresEmailVerification) {
13752
- setRoute(routes.EMAIL_VERIFICATION);
13753
- }
13754
- else {
13755
- setRoute(routes.LOAD_WALLETS);
13756
- }
13757
- }
13758
- else {
13759
- setTimeout(() => {
13760
- triggerResize();
13761
- }, 100);
13762
- }
13763
- };
13764
- const handleSignUp = async () => {
13765
- setIsRegister(true);
13766
- setTimeout(() => {
13767
- triggerResize();
13768
- });
13769
- const { error, requiresEmailVerification } = await signUpEmail({
13770
- email,
13771
- password,
13772
- });
13773
- logger.log('SIGN UP RESPONSE', { error, requiresEmailVerification });
13774
- if (!error) {
13775
- if (requiresEmailVerification) {
13776
- setRoute(routes.EMAIL_VERIFICATION);
13777
- }
13778
- else {
13779
- setEmail('');
13780
- setRoute(routes.LOAD_WALLETS);
13781
- }
13782
- }
13783
- else {
13784
- setTimeout(() => {
13785
- triggerResize();
13786
- }, 100);
13787
- }
13788
- };
13789
- const handleToggle = () => {
13790
- setIsRegister((prev) => !prev);
13791
- };
13792
- const errorMessage = loginError
13793
- ? loginError.message === 'Unauthorized'
13794
- ? 'Invalid email or password'
13795
- : loginError.message
13796
- : null;
13797
- const onBack = useMemo(() => {
13798
- if ((previousRoute === null || previousRoute === void 0 ? void 0 : previousRoute.route) === routes.EMAIL_VERIFICATION)
13799
- return routes.PROVIDERS;
13800
- return 'back';
13801
- }, [previousRoute]);
13802
- return (jsxs(PageContent, { onBack: onBack, children: [jsxs("form", { onSubmit: (e) => {
13803
- e.preventDefault();
13804
- handleSubmit();
13805
- }, noValidate: true, children: [jsx(Input, { style: { marginTop: 0 }, value: email, onChange: (e) => setEmail(e.target.value), type: "email", placeholder: "Enter your email", disabled: loginLoading }), jsx(Input, { value: password, onChange: (e) => setPassword(e.target.value), type: "password", placeholder: "Enter your password", disabled: loginLoading, autoFocus: true }), jsx(ModalBody, { style: { marginTop: 12 }, "$error": !!loginError, children: jsx(AnimatePresence, { initial: false, children: jsxs(motion.div, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants$1, children: [jsx(FitText, { maxFontSize: 80, children: jsx("span", { style: { textAlign: 'center', color: 'var(--color-error)', marginRight: '4px' }, children: errorMessage }) }, loginError ? 'text-error' : 'text-no-error'), jsx(FitText, { maxFontSize: 80, children: jsx(TextLinkButton, { type: "button", onClick: () => {
13806
- setRoute(routes.FORGOT_PASSWORD);
13807
- }, children: "Forgot password?" }) })] }, loginError ? 'error' : 'no-error') }) }), jsx(Button, { onClick: handleSubmit, disabled: loginLoading, waiting: loginLoading, children: jsx(AnimatePresence, { initial: false, children: loginLoading ? (jsx(TextContainer, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants$1, children: isRegister ? 'Signing up...' : 'Logging in...' }, "connectedText")) : isRegister ? (jsx(TextContainer, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants$1, children: "Sign up" }, "connectedText")) : (jsx(TextContainer, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants$1, children: "Sign in" }, "connectedText")) }) })] }), jsxs(FooterContainer, { children: ["or", jsx("button", { type: "button", onClick: handleToggle, disabled: loginLoading, children: isRegister ? 'Sign in' : 'Sign up' })] })] }));
13808
- };
13809
-
13810
- const useEmailOtpAuth = (hookOptions = {}) => {
13811
- const { client, updateUser } = useOpenfortCore();
13812
- const [status, setStatus] = useState({
13813
- status: 'idle',
13814
- });
13815
- const reset = useCallback(() => {
13816
- setStatus({
13817
- status: 'idle',
13818
- });
13819
- }, []);
13820
- const { tryUseWallet } = useConnectToWalletPostAuth();
13821
- const signInEmailOtp = useCallback(async (options) => {
13822
- var _a, _b;
13823
- try {
13912
+ });
13824
13913
  setStatus({
13825
- status: 'loading',
13914
+ status: 'error',
13915
+ error,
13826
13916
  });
13827
- if (!options.email || !options.otp) {
13828
- const error = new OpenfortError('Email and OTP are required', OpenfortReactErrorType.VALIDATION_ERROR);
13829
- setStatus({
13830
- status: 'error',
13831
- error,
13832
- });
13833
- return onError({
13834
- hookOptions,
13835
- options,
13836
- error,
13837
- });
13838
- }
13917
+ return onError({
13918
+ hookOptions,
13919
+ options,
13920
+ error: error,
13921
+ });
13922
+ }
13923
+ }, [client, setStatus, updateUser, hookOptions]);
13924
+ const resetPassword = useCallback(async (options) => {
13925
+ try {
13839
13926
  if (!isValidEmail(options.email)) {
13840
13927
  const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
13841
13928
  setStatus({
@@ -13848,32 +13935,31 @@ const useEmailOtpAuth = (hookOptions = {}) => {
13848
13935
  error,
13849
13936
  });
13850
13937
  }
13851
- const result = await client.auth.logInWithEmailOtp({
13852
- email: options.email,
13853
- otp: options.otp,
13938
+ setStatus({
13939
+ status: 'loading',
13854
13940
  });
13855
- const { wallet } = await tryUseWallet({
13856
- logoutOnError: (_a = options.logoutOnError) !== null && _a !== void 0 ? _a : hookOptions.logoutOnError,
13857
- recoverWalletAutomatically: (_b = options.recoverWalletAutomatically) !== null && _b !== void 0 ? _b : hookOptions.recoverWalletAutomatically,
13941
+ setRequiresEmailVerification(false);
13942
+ await client.auth.resetPassword({
13943
+ password: options.password,
13944
+ token: options.state,
13858
13945
  });
13859
13946
  setStatus({
13860
13947
  status: 'success',
13861
13948
  });
13862
- const user = result.user;
13863
- await updateUser();
13949
+ setRequiresEmailVerification(true);
13864
13950
  return onSuccess({
13865
- data: { user, wallet },
13951
+ data: { requiresEmailVerification: true },
13866
13952
  hookOptions,
13867
13953
  options,
13868
13954
  });
13869
13955
  }
13870
13956
  catch (e) {
13871
- const error = new OpenfortError('Failed to login with email OTP', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
13957
+ const error = new OpenfortError('Failed to reset password', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
13872
13958
  error: e,
13873
13959
  });
13874
13960
  setStatus({
13875
13961
  status: 'error',
13876
- error: error,
13962
+ error,
13877
13963
  });
13878
13964
  return onError({
13879
13965
  hookOptions,
@@ -13882,13 +13968,11 @@ const useEmailOtpAuth = (hookOptions = {}) => {
13882
13968
  });
13883
13969
  }
13884
13970
  }, [client, setStatus, updateUser, hookOptions]);
13885
- const requestEmailOtp = useCallback(async (options) => {
13971
+ const signUpEmail = useCallback(async (options) => {
13972
+ var _a, _b, _c;
13886
13973
  try {
13887
- setStatus({
13888
- status: 'requesting',
13889
- });
13890
- if (!options.email) {
13891
- const error = new OpenfortError('Email is required', OpenfortReactErrorType.VALIDATION_ERROR);
13974
+ if (!options.email || !options.password) {
13975
+ const error = new OpenfortError('Email and password are required', OpenfortReactErrorType.VALIDATION_ERROR);
13892
13976
  setStatus({
13893
13977
  status: 'error',
13894
13978
  error,
@@ -13911,12 +13995,106 @@ const useEmailOtpAuth = (hookOptions = {}) => {
13911
13995
  error,
13912
13996
  });
13913
13997
  }
13914
- await client.auth.requestEmailOtp({
13998
+ setStatus({
13999
+ status: 'loading',
14000
+ });
14001
+ setRequiresEmailVerification(false);
14002
+ const result = await client.auth.signUpWithEmailPassword({
13915
14003
  email: options.email,
14004
+ password: options.password,
14005
+ callbackURL: buildCallbackUrl({
14006
+ email: options.email,
14007
+ callbackUrl: (_a = options.emailVerificationRedirectTo) !== null && _a !== void 0 ? _a : hookOptions === null || hookOptions === void 0 ? void 0 : hookOptions.emailVerificationRedirectTo,
14008
+ provider: 'email',
14009
+ isOpen,
14010
+ }),
14011
+ ...(options.name && { name: options.name }),
13916
14012
  });
14013
+ // TODO: TMP FIX
14014
+ if ('token' in result && result.token !== null) {
14015
+ const { wallet } = await tryUseWallet({
14016
+ logoutOnError: (_b = options.logoutOnError) !== null && _b !== void 0 ? _b : hookOptions.logoutOnError,
14017
+ recoverWalletAutomatically: (_c = options.recoverWalletAutomatically) !== null && _c !== void 0 ? _c : hookOptions.recoverWalletAutomatically,
14018
+ });
14019
+ setStatus({
14020
+ status: 'success',
14021
+ });
14022
+ const user = result.user;
14023
+ await updateUser(user);
14024
+ return onSuccess({
14025
+ data: { user, wallet },
14026
+ hookOptions,
14027
+ options,
14028
+ });
14029
+ }
14030
+ else {
14031
+ setStatus({
14032
+ status: 'awaiting-input',
14033
+ });
14034
+ setRequiresEmailVerification(true);
14035
+ return onSuccess({
14036
+ data: { requiresEmailVerification: true },
14037
+ hookOptions,
14038
+ options,
14039
+ });
14040
+ }
14041
+ }
14042
+ catch (e) {
14043
+ const error = new OpenfortError('Failed to login with email and password', OpenfortReactErrorType.AUTHENTICATION_ERROR, { error: e });
13917
14044
  setStatus({
13918
- status: 'success',
14045
+ status: 'error',
14046
+ error,
14047
+ });
14048
+ return onError({
14049
+ hookOptions,
14050
+ options,
14051
+ error: error,
14052
+ });
14053
+ }
14054
+ }, [client, setStatus, updateUser, hookOptions, isOpen]);
14055
+ const linkEmail = useCallback(async (options) => {
14056
+ var _a;
14057
+ try {
14058
+ if (!isValidEmail(options.email)) {
14059
+ const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
14060
+ setStatus({
14061
+ status: 'error',
14062
+ error,
14063
+ });
14064
+ return onError({
14065
+ hookOptions,
14066
+ options,
14067
+ error,
14068
+ });
14069
+ }
14070
+ await client.validateAndRefreshToken();
14071
+ const authToken = await client.getAccessToken();
14072
+ if (!authToken) {
14073
+ logger.log('No token found');
14074
+ const error = new OpenfortError('No token found', OpenfortReactErrorType.AUTHENTICATION_ERROR);
14075
+ setStatus({
14076
+ status: 'error',
14077
+ error,
14078
+ });
14079
+ return onError({
14080
+ hookOptions,
14081
+ options,
14082
+ error,
14083
+ });
14084
+ }
14085
+ await client.auth.addEmail({
14086
+ // name: options.name || '',
14087
+ email: options.email,
14088
+ // password: options.password,
14089
+ // method: 'password',
14090
+ callbackURL: buildCallbackUrl({
14091
+ callbackUrl: (_a = options.emailVerificationRedirectTo) !== null && _a !== void 0 ? _a : hookOptions === null || hookOptions === void 0 ? void 0 : hookOptions.emailVerificationRedirectTo,
14092
+ email: options.email,
14093
+ provider: 'email',
14094
+ isOpen,
14095
+ }),
13919
14096
  });
14097
+ logger.log('Email linked successfully');
13920
14098
  return onSuccess({
13921
14099
  data: {},
13922
14100
  hookOptions,
@@ -13924,7 +14102,7 @@ const useEmailOtpAuth = (hookOptions = {}) => {
13924
14102
  });
13925
14103
  }
13926
14104
  catch (e) {
13927
- const error = new OpenfortError('Failed to request email OTP', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
14105
+ const error = new OpenfortError('Failed to link email', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
13928
14106
  error: e,
13929
14107
  });
13930
14108
  setStatus({
@@ -13937,298 +14115,328 @@ const useEmailOtpAuth = (hookOptions = {}) => {
13937
14115
  error: error,
13938
14116
  });
13939
14117
  }
13940
- }, [client, setStatus, updateUser, hookOptions]);
13941
- return {
13942
- signInEmailOtp,
13943
- requestEmailOtp,
13944
- reset,
13945
- isRequesting: status.status === 'requesting',
13946
- ...mapStatus(status),
13947
- isAwaitingInput: status.status === 'awaiting-input',
13948
- };
13949
- };
13950
-
13951
- const caretBlink = keyframes `
13952
- 0%, 70%, 100% { opacity: 1; }
13953
- 20%, 50% { opacity: 0; }
13954
- `;
13955
- const OtpContainer = styled.div `
13956
- display: flex;
13957
- align-items: center;
13958
- justify-content: center;
13959
-
13960
- --border: ${({ showBorder }) => (showBorder ? 'var(--ck-body-color-muted)' : 'transparent')};
13961
- `;
13962
- const pulse = keyframes `
13963
- 0% {
13964
- opacity: 100%;
13965
- }
13966
- 50% {
13967
- opacity: 40%;
13968
- }
13969
- 100% {
13970
- opacity: 100%;
13971
- }
13972
- `;
13973
- const dist = 2;
13974
- const shakeKeyframes = keyframes `
13975
- 0%{ transform:none; }
13976
- 25%{ transform:translateX(${dist}px); }
13977
- 50%{ transform:translateX(-${dist}px); }
13978
- 75%{ transform:translateX(${dist}px); }
13979
- 100%{ transform:none; }
13980
- `;
13981
- const keyframeSuccess = keyframes `
13982
- 0% { transform: scale(1); }
13983
- 50% { transform: scale(1.1); }
13984
- 100% { transform: scale(1); }
13985
- `;
13986
- const OTPGroup = styled.div `
13987
- display: flex;
13988
-
13989
- outline-width: 3px;
13990
- outline-style: solid;
13991
- border-radius: 0.375rem;
13992
- transition: outline-color 0.3s, border-radius .5s;
13993
-
13994
- outline-color: ${({ isError, isSuccess }) => {
13995
- if (isError)
13996
- return 'var(--ck-body-color-danger)';
13997
- if (isSuccess)
13998
- return 'var(--ck-body-color-valid)';
13999
- return 'transparent';
14000
- }};
14001
-
14002
- ${({ isLoading }) => isLoading &&
14003
- css `
14004
- animation: ${pulse} 1s ease-in-out infinite;
14005
- `}
14006
-
14007
- ${({ isError }) => isError &&
14008
- css `
14009
- animation: ${shakeKeyframes} 220ms ease-out both;
14010
- `}
14011
-
14012
- ${({ isSuccess }) => isSuccess &&
14013
- css `
14014
- border-radius: 3rem;
14015
- min-width: 3.5rem;
14016
- ${OTPSlotWrapper} {
14017
- width: 0;
14018
- border: 0;
14019
- transition: width .5s, border .5s;
14020
- }
14021
- animation: ${keyframeSuccess} 220ms ease-out both;
14022
- animation-delay: 250ms;
14023
- `}
14024
- `;
14025
- const OTPSlotWrapper = styled.div `
14026
- position: relative;
14027
- width: 2.5rem;
14028
- height: 3.5rem;
14029
- font-size: 2rem;
14118
+ }, [client, setStatus, updateUser, hookOptions, isOpen]);
14119
+ const verifyEmail = useCallback(async (options) => {
14120
+ setStatus({
14121
+ status: 'loading',
14122
+ });
14123
+ try {
14124
+ if (!isValidEmail(options.email)) {
14125
+ const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
14126
+ setStatus({
14127
+ status: 'error',
14128
+ error,
14129
+ });
14130
+ return onError({
14131
+ hookOptions,
14132
+ options,
14133
+ error,
14134
+ });
14135
+ }
14136
+ await client.auth.verifyEmail({
14137
+ token: options.state,
14138
+ });
14139
+ setStatus({
14140
+ status: 'success',
14141
+ });
14142
+ return onSuccess({
14143
+ hookOptions,
14144
+ options,
14145
+ data: {
14146
+ email: options.email,
14147
+ },
14148
+ });
14149
+ }
14150
+ catch (e) {
14151
+ const error = new OpenfortError('Failed to verify email', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
14152
+ error: e,
14153
+ });
14154
+ setStatus({
14155
+ status: 'error',
14156
+ error,
14157
+ });
14158
+ logger.log('Error verifying email', e);
14159
+ return onError({
14160
+ hookOptions,
14161
+ options,
14162
+ error,
14163
+ });
14164
+ }
14165
+ }, [client, hookOptions]);
14166
+ return {
14167
+ signInEmail,
14168
+ signUpEmail,
14169
+ verifyEmail,
14170
+ linkEmail,
14171
+ requestResetPassword,
14172
+ resetPassword,
14173
+ reset,
14174
+ ...mapStatus(status),
14175
+ requiresEmailVerification,
14176
+ isAwaitingInput: status.status === 'awaiting-input',
14177
+ };
14178
+ };
14030
14179
 
14180
+ const FooterContainer = styled.span `
14181
+ padding: 12px 4px 0px 0px;
14031
14182
  display: flex;
14032
14183
  align-items: center;
14033
14184
  justify-content: center;
14185
+ gap: 6px;
14186
+ line-height: 1rem;
14187
+ color: var(--ck-body-disclaimer-color, var(--ck-body-color-muted, inherit));
14034
14188
 
14035
- transition: all 0.3s;
14036
-
14037
- border-top: 1px solid var(--border);
14038
- border-bottom: 1px solid var(--border);
14039
- border-right: 0.5px solid var(--border);
14040
- border-left: 0.5px solid var(--border);
14041
-
14042
- &:first-child {
14043
- border-left: 1px solid var(--border);
14044
- border-radius: 0.375rem 0 0 0.375rem;
14045
- }
14046
-
14047
- &:last-child {
14048
- border-radius: 0 0.375rem 0.375rem 0;
14189
+ & button {
14190
+ color: var(--ck-body-disclaimer-link-color, inherit);
14191
+ font-weight: var(--ck-body-disclaimer-font-weight, 400);
14192
+ text-decoration: none;
14193
+ transition: color 200ms ease;
14194
+ &:hover {
14195
+ color: var(--ck-body-disclaimer-link-hover-color, inherit);
14196
+ }
14049
14197
  }
14050
-
14051
- outline: ${({ isActive }) => (isActive ? '2px solid var(--ck-connectbutton-color)' : '0')};
14052
- z-index: ${({ isActive }) => (isActive ? 1 : 0)};
14053
- outline-offset: 0;
14054
-
14055
- cursor: text;
14056
- color: var(--ck-body-color);
14057
- `;
14058
- const OTPNumberValue = styled.div `
14059
- opacity: ${({ $hide }) => ($hide ? 0 : 1)};
14060
- transition: opacity 0.3s;
14061
- `;
14062
- const OTPHiddenInput = styled.input `
14063
- position: absolute;
14064
- inset: 0;
14065
- opacity: 0;
14066
- cursor: text;
14067
- caret-color: transparent; /* Hide native caret */
14068
- `;
14069
- const FakeCaretWrapper = styled.div `
14070
- position: absolute;
14071
- inset: 0;
14072
- pointer-events: none;
14073
-
14074
- display: flex;
14075
- align-items: center;
14076
- justify-content: center;
14077
-
14078
- animation: ${caretBlink} 1.2s ease-out infinite;
14079
- `;
14080
- const CaretBar = styled.div `
14081
- width: 1px;
14082
- height: 2rem;
14083
- background: var(--ck-body-color);
14084
- `;
14085
- const keyframeWrapper = keyframes `
14086
- 0% { transform: scale(0); }
14087
- 100% { transform: scale(1); }
14088
- `;
14089
- const SuccessTickWrapper = styled.div `
14090
- position: absolute;
14091
- inset: 5px;
14092
- display: flex;
14093
- animation: ${keyframeWrapper} 200ms ease-out both;
14094
- animation-delay: 200ms;
14095
- color: var(--ck-body-color-valid);
14096
14198
  `;
14097
14199
 
14098
- function FakeCaret() {
14099
- return (jsx(FakeCaretWrapper, { children: jsx(CaretBar, {}) }));
14100
- }
14101
- function OtpInputStandalone({ length = 6, onChange, onComplete, isLoading, isError, isSuccess, }) {
14102
- const [values, setValues] = useState(Array(length).fill(''));
14103
- const [activeIndex, setActiveIndex] = useState(0);
14104
- const canEdit = !isLoading && !isError && !isSuccess;
14105
- const inputsRef = useRef([]);
14106
- const handleInput = (index, char) => {
14107
- var _a;
14108
- if (!char.match(/^[0-9]$/))
14109
- return;
14110
- if (!canEdit)
14111
- return;
14112
- const newValues = [...values];
14113
- newValues[index] = char;
14114
- setValues(newValues);
14115
- onChange === null || onChange === void 0 ? void 0 : onChange(newValues.join(''));
14116
- // Move cursor to next box
14117
- if (index < length - 1) {
14118
- setActiveIndex(index + 1);
14119
- (_a = inputsRef.current[index + 1]) === null || _a === void 0 ? void 0 : _a.focus();
14200
+ // TODO: Localize
14201
+ const textVariants$1 = {
14202
+ initial: {
14203
+ opacity: 0,
14204
+ },
14205
+ animate: {
14206
+ opacity: 1,
14207
+ transition: {
14208
+ duration: 0.3,
14209
+ ease: [0.25, 1, 0.5, 1],
14210
+ },
14211
+ },
14212
+ exit: {
14213
+ position: 'absolute',
14214
+ opacity: 0,
14215
+ transition: {
14216
+ duration: 0,
14217
+ },
14218
+ },
14219
+ };
14220
+ const EmailLogin = () => {
14221
+ const [password, setPassword] = React.useState('');
14222
+ const { setRoute, triggerResize, setEmailInput: setEmail, emailInput: email, previousRoute } = useOpenfort();
14223
+ const [isRegister, setIsRegister] = React.useState(false);
14224
+ const { signUpEmail, signInEmail, error: loginError, isLoading: loginLoading, } = useEmailAuth({
14225
+ recoverWalletAutomatically: false,
14226
+ });
14227
+ const handleSubmit = async () => {
14228
+ if (isRegister) {
14229
+ return handleSignUp();
14120
14230
  }
14121
- };
14122
- useEffect(() => {
14123
- if (values.every((v) => v !== '')) {
14124
- onComplete === null || onComplete === void 0 ? void 0 : onComplete(values.join(''));
14231
+ setIsRegister(false);
14232
+ setTimeout(() => {
14233
+ triggerResize();
14234
+ });
14235
+ const { error, requiresEmailVerification } = await signInEmail({
14236
+ email,
14237
+ password,
14238
+ });
14239
+ logger.log('SIGN IN RESPONSE', { error, requiresEmailVerification });
14240
+ if (!error) {
14241
+ if (requiresEmailVerification) {
14242
+ setRoute(routes.EMAIL_VERIFICATION);
14243
+ }
14244
+ else {
14245
+ setRoute(routes.LOAD_WALLETS);
14246
+ }
14125
14247
  }
14126
- }, [values]);
14127
- const handleBackspace = (index) => {
14128
- var _a;
14129
- const newValues = [...values];
14130
- if (newValues[index] === '') {
14131
- if (index > 0) {
14132
- // Move back
14133
- setActiveIndex(index - 1);
14134
- (_a = inputsRef.current[index - 1]) === null || _a === void 0 ? void 0 : _a.focus();
14248
+ else {
14249
+ setTimeout(() => {
14250
+ triggerResize();
14251
+ }, 100);
14252
+ }
14253
+ };
14254
+ const handleSignUp = async () => {
14255
+ setIsRegister(true);
14256
+ setTimeout(() => {
14257
+ triggerResize();
14258
+ });
14259
+ const { error, requiresEmailVerification } = await signUpEmail({
14260
+ email,
14261
+ password,
14262
+ });
14263
+ logger.log('SIGN UP RESPONSE', { error, requiresEmailVerification });
14264
+ if (!error) {
14265
+ if (requiresEmailVerification) {
14266
+ setRoute(routes.EMAIL_VERIFICATION);
14267
+ }
14268
+ else {
14269
+ setEmail('');
14270
+ setRoute(routes.LOAD_WALLETS);
14135
14271
  }
14136
- newValues[index - 1] = '';
14137
14272
  }
14138
14273
  else {
14139
- newValues[index] = '';
14274
+ setTimeout(() => {
14275
+ triggerResize();
14276
+ }, 100);
14140
14277
  }
14141
- setValues(newValues);
14142
- onChange === null || onChange === void 0 ? void 0 : onChange(newValues.join(''));
14143
14278
  };
14144
- const handlePaste = (e) => {
14145
- var _a;
14146
- const pasted = e.clipboardData.getData('text').replace(/\D/g, '');
14147
- if (!pasted)
14148
- return;
14149
- const arr = pasted.substring(0, length).split('');
14150
- const newValues = [...values];
14151
- arr.forEach((char, i) => {
14152
- newValues[i] = char;
14279
+ const handleToggle = () => {
14280
+ setIsRegister((prev) => !prev);
14281
+ };
14282
+ const errorMessage = loginError
14283
+ ? loginError.message === 'Unauthorized'
14284
+ ? 'Invalid email or password'
14285
+ : loginError.message
14286
+ : null;
14287
+ const onBack = useMemo(() => {
14288
+ if ((previousRoute === null || previousRoute === void 0 ? void 0 : previousRoute.route) === routes.EMAIL_VERIFICATION)
14289
+ return routes.PROVIDERS;
14290
+ return 'back';
14291
+ }, [previousRoute]);
14292
+ return (jsxs(PageContent, { onBack: onBack, children: [jsxs("form", { onSubmit: (e) => {
14293
+ e.preventDefault();
14294
+ handleSubmit();
14295
+ }, noValidate: true, children: [jsx(Input, { style: { marginTop: 0 }, value: email, onChange: (e) => setEmail(e.target.value), type: "email", placeholder: "Enter your email", disabled: loginLoading }), jsx(Input, { value: password, onChange: (e) => setPassword(e.target.value), type: "password", placeholder: "Enter your password", disabled: loginLoading, autoFocus: true }), jsx(ModalBody, { style: { marginTop: 12 }, "$error": !!loginError, children: jsx(AnimatePresence, { initial: false, children: jsxs(motion.div, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants$1, children: [jsx(FitText, { maxFontSize: 80, children: jsx("span", { style: { textAlign: 'center', color: 'var(--color-error)', marginRight: '4px' }, children: errorMessage }) }, loginError ? 'text-error' : 'text-no-error'), jsx(FitText, { maxFontSize: 80, children: jsx(TextLinkButton, { type: "button", onClick: () => {
14296
+ setRoute(routes.FORGOT_PASSWORD);
14297
+ }, children: "Forgot password?" }) })] }, loginError ? 'error' : 'no-error') }) }), jsx(Button, { onClick: handleSubmit, disabled: loginLoading, waiting: loginLoading, children: jsx(AnimatePresence, { initial: false, children: loginLoading ? (jsx(TextContainer, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants$1, children: isRegister ? 'Signing up...' : 'Logging in...' }, "connectedText")) : isRegister ? (jsx(TextContainer, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants$1, children: "Sign up" }, "connectedText")) : (jsx(TextContainer, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants$1, children: "Sign in" }, "connectedText")) }) })] }), jsxs(FooterContainer, { children: ["or", jsx("button", { type: "button", onClick: handleToggle, disabled: loginLoading, children: isRegister ? 'Sign in' : 'Sign up' })] })] }));
14298
+ };
14299
+
14300
+ const useEmailOtpAuth = (hookOptions = {}) => {
14301
+ const { client, updateUser } = useOpenfortCore();
14302
+ const [status, setStatus] = useState({
14303
+ status: 'idle',
14304
+ });
14305
+ const reset = useCallback(() => {
14306
+ setStatus({
14307
+ status: 'idle',
14153
14308
  });
14154
- setValues(newValues);
14155
- onChange === null || onChange === void 0 ? void 0 : onChange(newValues.join(''));
14156
- const finalIndex = Math.min(arr.length - 1, length - 1);
14157
- setActiveIndex(finalIndex);
14158
- (_a = inputsRef.current[finalIndex]) === null || _a === void 0 ? void 0 : _a.focus();
14159
- };
14160
- const handleFocus = (i) => {
14309
+ }, []);
14310
+ const { tryUseWallet } = useConnectToWalletPostAuth();
14311
+ const signInEmailOtp = useCallback(async (options) => {
14161
14312
  var _a, _b;
14162
- if (activeIndex !== -1) {
14163
- setActiveIndex(i);
14164
- return;
14313
+ try {
14314
+ setStatus({
14315
+ status: 'loading',
14316
+ });
14317
+ if (!options.email || !options.otp) {
14318
+ const error = new OpenfortError('Email and OTP are required', OpenfortReactErrorType.VALIDATION_ERROR);
14319
+ setStatus({
14320
+ status: 'error',
14321
+ error,
14322
+ });
14323
+ return onError({
14324
+ hookOptions,
14325
+ options,
14326
+ error,
14327
+ });
14328
+ }
14329
+ if (!isValidEmail(options.email)) {
14330
+ const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
14331
+ setStatus({
14332
+ status: 'error',
14333
+ error,
14334
+ });
14335
+ return onError({
14336
+ hookOptions,
14337
+ options,
14338
+ error,
14339
+ });
14340
+ }
14341
+ const result = await client.auth.logInWithEmailOtp({
14342
+ email: options.email,
14343
+ otp: options.otp,
14344
+ });
14345
+ const { wallet } = await tryUseWallet({
14346
+ logoutOnError: (_a = options.logoutOnError) !== null && _a !== void 0 ? _a : hookOptions.logoutOnError,
14347
+ recoverWalletAutomatically: (_b = options.recoverWalletAutomatically) !== null && _b !== void 0 ? _b : hookOptions.recoverWalletAutomatically,
14348
+ });
14349
+ setStatus({
14350
+ status: 'success',
14351
+ });
14352
+ const user = result.user;
14353
+ await updateUser();
14354
+ return onSuccess({
14355
+ data: { user, wallet },
14356
+ hookOptions,
14357
+ options,
14358
+ });
14165
14359
  }
14166
- const firstEmptyIndex = values.indexOf('');
14167
- if (firstEmptyIndex !== -1) {
14168
- setActiveIndex(firstEmptyIndex);
14169
- (_a = inputsRef.current[firstEmptyIndex]) === null || _a === void 0 ? void 0 : _a.focus();
14360
+ catch (e) {
14361
+ const error = new OpenfortError('Failed to login with email OTP', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
14362
+ error: e,
14363
+ });
14364
+ setStatus({
14365
+ status: 'error',
14366
+ error: error,
14367
+ });
14368
+ return onError({
14369
+ hookOptions,
14370
+ options,
14371
+ error: error,
14372
+ });
14170
14373
  }
14171
- else {
14172
- setActiveIndex(length - 1);
14173
- (_b = inputsRef.current[length - 1]) === null || _b === void 0 ? void 0 : _b.focus();
14374
+ }, [client, setStatus, updateUser, hookOptions]);
14375
+ const requestEmailOtp = useCallback(async (options) => {
14376
+ try {
14377
+ setStatus({
14378
+ status: 'requesting',
14379
+ });
14380
+ if (!options.email) {
14381
+ const error = new OpenfortError('Email is required', OpenfortReactErrorType.VALIDATION_ERROR);
14382
+ setStatus({
14383
+ status: 'error',
14384
+ error,
14385
+ });
14386
+ return onError({
14387
+ hookOptions,
14388
+ options,
14389
+ error,
14390
+ });
14391
+ }
14392
+ if (!isValidEmail(options.email)) {
14393
+ const error = new OpenfortError('Invalid email', OpenfortReactErrorType.VALIDATION_ERROR);
14394
+ setStatus({
14395
+ status: 'error',
14396
+ error,
14397
+ });
14398
+ return onError({
14399
+ hookOptions,
14400
+ options,
14401
+ error,
14402
+ });
14403
+ }
14404
+ await client.auth.requestEmailOtp({
14405
+ email: options.email,
14406
+ });
14407
+ setStatus({
14408
+ status: 'success',
14409
+ });
14410
+ return onSuccess({
14411
+ data: {},
14412
+ hookOptions,
14413
+ options,
14414
+ });
14174
14415
  }
14175
- };
14176
- useEffect(() => {
14177
- var _a;
14178
- if (!isError) {
14179
- setValues(Array(length).fill(''));
14180
- setActiveIndex(0);
14181
- (_a = inputsRef.current[0]) === null || _a === void 0 ? void 0 : _a.focus();
14416
+ catch (e) {
14417
+ const error = new OpenfortError('Failed to request email OTP', OpenfortReactErrorType.AUTHENTICATION_ERROR, {
14418
+ error: e,
14419
+ });
14420
+ setStatus({
14421
+ status: 'error',
14422
+ error: error,
14423
+ });
14424
+ return onError({
14425
+ hookOptions,
14426
+ options,
14427
+ error: error,
14428
+ });
14182
14429
  }
14183
- }, [isError]);
14184
- return (jsx(OtpContainer, { showBorder: !isSuccess, children: jsxs(OTPGroup, { isError: isError, isSuccess: isSuccess, isLoading: isLoading, children: [values.slice(0, 6).map((value, idx) => {
14185
- const index = idx;
14186
- return (jsxs(OTPSlotWrapper, { isActive: canEdit && activeIndex === index, children: [jsx(OTPHiddenInput, { ref: (el) => {
14187
- inputsRef.current[index] = el;
14188
- }, value: "", inputMode: "numeric", onBlur: () => setActiveIndex(-1), autoComplete: "one-time-code", autoFocus: index === 0, onFocus: () => handleFocus(index), onPaste: handlePaste, onKeyDown: (e) => {
14189
- if (!canEdit)
14190
- return;
14191
- if (e.key === 'Backspace') {
14192
- e.preventDefault();
14193
- handleBackspace(index);
14194
- }
14195
- }, onChange: (e) => handleInput(index, e.target.value) }), value && jsx(OTPNumberValue, { "$hide": isSuccess, children: value }), !value && activeIndex === index && jsx(FakeCaret, {})] }, index));
14196
- }), isSuccess && (jsx(SuccessTickWrapper, { children: jsx(TickIcon, { width: '100%', height: '100%' }) }))] }) }));
14197
- }
14198
-
14199
- const Body$1 = styled.p `
14200
- color: var(--ck-body-color);
14201
- text-align: center;
14202
- margin-bottom: 16px;
14203
- `;
14204
- const ResultContainer$1 = styled.div `
14205
- margin-top: 16px;
14206
- height: 24px;
14207
- text-align: center;
14208
- `;
14209
- const FooterButtonText$1 = styled.button `
14210
- background: none;
14211
- border: none;
14212
- cursor: pointer;
14213
- padding: 0;
14214
- color: var(--ck-body-color-muted);
14215
- transition: color 0.3s;
14216
-
14217
- &:disabled {
14218
- color: var(--ck-body-color-muted) !important;
14219
- cursor: not-allowed;
14220
- }
14221
- `;
14222
- const FooterTextButton$1 = styled.p `
14223
- color: var(--ck-body-color-muted);
14224
- text-align: center;
14225
- margin-top: 16px;
14226
- &:hover {
14227
- ${FooterButtonText$1} {
14228
- color: var(--ck-body-color);
14229
- }
14230
- }
14231
- `;
14430
+ }, [client, setStatus, updateUser, hookOptions]);
14431
+ return {
14432
+ signInEmailOtp,
14433
+ requestEmailOtp,
14434
+ reset,
14435
+ isRequesting: status.status === 'requesting',
14436
+ ...mapStatus(status),
14437
+ isAwaitingInput: status.status === 'awaiting-input',
14438
+ };
14439
+ };
14232
14440
 
14233
14441
  const RESEND_COOLDOWN_MS$1 = 10000;
14234
14442
  const SUCCESS_REDIRECT_DELAY_MS$1 = 2000;
@@ -14600,7 +14808,7 @@ const LinkEmail = () => {
14600
14808
  return (jsxs(PageContent, { children: [jsx(ModalHeading, { children: "Link your email" }), jsxs("form", { onSubmit: (e) => {
14601
14809
  e.preventDefault();
14602
14810
  handleSubmit();
14603
- }, children: [jsx(Input, { style: { marginTop: 0 }, value: email, onChange: (e) => setEmail(e.target.value), type: "email", placeholder: "Enter your email", disabled: loginLoading }), loginError && (jsx(ModalBody, { style: { height: 24, marginTop: 12 }, "$error": true, children: jsx(FitText, { children: loginError }) })), jsx(Button, { onClick: handleSubmit, disabled: loginLoading, waiting: loginLoading, children: jsx(AnimatePresence, { initial: false, children: loginLoading ? (jsx(TextContainer, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants, children: "Linking email..." }, "connectedText")) : (jsx(TextContainer, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants, children: "Link email" }, "connectedText")) }) })] })] }));
14811
+ }, children: [jsx(Input, { style: { marginTop: 0 }, value: email, onChange: (e) => setEmail(e.target.value), type: "email", placeholder: "Enter your email", disabled: loginLoading }), loginError && (jsx(ModalBody, { style: { height: 24, marginTop: 12 }, "$error": true, children: loginError })), jsx(Button, { onClick: handleSubmit, disabled: loginLoading, waiting: loginLoading, children: jsx(AnimatePresence, { initial: false, children: loginLoading ? (jsx(TextContainer, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants, children: "Linking email..." }, "connectedText")) : (jsx(TextContainer, { initial: 'initial', animate: 'animate', exit: 'exit', variants: textVariants, children: "Link email" }, "connectedText")) }) })] })] }));
14604
14812
  };
14605
14813
 
14606
14814
  const getProviderName = (provider) => {
@@ -15861,16 +16069,25 @@ const RecoverPasskeyWallet = ({ wallet, onBack, logoutOnBack, }) => {
15861
16069
  };
15862
16070
  const RecoverAutomaticWallet = ({ walletAddress, onBack, logoutOnBack, }) => {
15863
16071
  const { embeddedState } = useOpenfortCore();
15864
- const { setActiveWallet } = useWallets();
16072
+ const { setActiveWallet, isWalletRecoveryOTPEnabled, requestWalletRecoverOTP } = useWallets();
15865
16073
  const { setRoute } = useOpenfort();
15866
16074
  const [error, setError] = useState(false);
16075
+ const [needsOTP, setNeedsOTP] = useState(false);
16076
+ const [otpResponse, setOtpResponse] = useState(null);
16077
+ const [otpStatus, setOtpStatus] = useState('idle');
15867
16078
  const recoverWallet = useCallback(async () => {
15868
16079
  if (embeddedState === EmbeddedState.EMBEDDED_SIGNER_NOT_CONFIGURED) {
15869
16080
  logger.log('Automatically recovering wallet', walletAddress);
15870
16081
  const response = await setActiveWallet({
15871
16082
  walletId: embeddedWalletId,
16083
+ address: walletAddress,
15872
16084
  });
15873
- if (response.error) {
16085
+ if (response.isOTPRequired && isWalletRecoveryOTPEnabled) {
16086
+ const response = await requestWalletRecoverOTP();
16087
+ setNeedsOTP(true);
16088
+ setOtpResponse(response);
16089
+ }
16090
+ else if (response.error) {
15874
16091
  setError(response.error.message || 'There was an error recovering your account');
15875
16092
  logger.log('Error recovering wallet', response.error);
15876
16093
  }
@@ -15878,7 +16095,7 @@ const RecoverAutomaticWallet = ({ walletAddress, onBack, logoutOnBack, }) => {
15878
16095
  setRoute(routes.CONNECTED_SUCCESS);
15879
16096
  }
15880
16097
  }
15881
- }, [walletAddress, setActiveWallet, setRoute]);
16098
+ }, [walletAddress, setActiveWallet, setRoute, isWalletRecoveryOTPEnabled, requestWalletRecoverOTP]);
15882
16099
  const shouldRecoverWallet = useRef(false);
15883
16100
  useEffect(() => {
15884
16101
  if (shouldRecoverWallet.current)
@@ -15886,6 +16103,67 @@ const RecoverAutomaticWallet = ({ walletAddress, onBack, logoutOnBack, }) => {
15886
16103
  shouldRecoverWallet.current = true;
15887
16104
  recoverWallet();
15888
16105
  }, []);
16106
+ const handleCompleteOtp = async (otp) => {
16107
+ setOtpStatus('loading');
16108
+ const response = await setActiveWallet({
16109
+ walletId: embeddedWalletId,
16110
+ recovery: {
16111
+ recoveryMethod: RecoveryMethod.AUTOMATIC,
16112
+ otpCode: otp,
16113
+ },
16114
+ address: walletAddress,
16115
+ });
16116
+ if (response.error) {
16117
+ setOtpStatus('error');
16118
+ setError(response.error.message || 'There was an error verifying the OTP');
16119
+ logger.log('Error verifying OTP for wallet recovery', response.error);
16120
+ setTimeout(() => {
16121
+ setOtpStatus('idle');
16122
+ setError(false);
16123
+ }, 1000);
16124
+ }
16125
+ else {
16126
+ setOtpStatus('success');
16127
+ setTimeout(() => {
16128
+ setRoute(routes.CONNECTED_SUCCESS);
16129
+ }, 1000);
16130
+ }
16131
+ };
16132
+ const ensFallbackConfig = useEnsFallbackConfig();
16133
+ const { data: ensName } = useEnsName({
16134
+ chainId: 1,
16135
+ address: walletAddress,
16136
+ config: ensFallbackConfig,
16137
+ });
16138
+ const walletDisplay = ensName !== null && ensName !== void 0 ? ensName : truncateEthAddress(walletAddress);
16139
+ const [canSendOtp, setCanSendOtp] = useState(true);
16140
+ // Handle resend cooldown
16141
+ useEffect(() => {
16142
+ if (canSendOtp)
16143
+ return;
16144
+ const timerId = setTimeout(() => {
16145
+ setCanSendOtp(true);
16146
+ }, 10000);
16147
+ return () => clearTimeout(timerId);
16148
+ }, [canSendOtp]);
16149
+ const handleResendClick = useCallback(() => {
16150
+ setOtpStatus('send-otp');
16151
+ setCanSendOtp(false);
16152
+ }, []);
16153
+ const isResendDisabled = !canSendOtp || otpStatus === 'sending-otp' || otpStatus === 'send-otp';
16154
+ // Memoize button text to avoid recalculation
16155
+ const sendButtonText = useMemo(() => {
16156
+ if (!canSendOtp)
16157
+ return 'Code Sent!';
16158
+ if (otpStatus === 'sending-otp')
16159
+ return 'Sending...';
16160
+ return 'Resend Code';
16161
+ }, [canSendOtp, otpStatus]);
16162
+ if (needsOTP && isWalletRecoveryOTPEnabled) {
16163
+ return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(ModalHeading, { children: "Enter your code" }), jsx(FloatingGraphic, { height: "100px", marginTop: "8px", marginBottom: "10px", logoCenter: {
16164
+ logo: (otpResponse === null || otpResponse === void 0 ? void 0 : otpResponse.sentTo) === 'phone' ? jsx(PhoneIcon, {}) : jsx(EmailIcon, {}),
16165
+ } }), jsxs(ModalBody, { children: [jsxs(Body$1, { children: ["Recovering wallet ", jsx(CopyText, { value: walletAddress, children: walletDisplay }), "Please check ", jsx("b", { children: (otpResponse === null || otpResponse === void 0 ? void 0 : otpResponse.sentTo) === 'phone' ? otpResponse === null || otpResponse === void 0 ? void 0 : otpResponse.phone : otpResponse === null || otpResponse === void 0 ? void 0 : otpResponse.email }), " and enter your code below."] }), jsx(OtpInputStandalone, { length: 9, scale: "80%", onComplete: handleCompleteOtp, isLoading: otpStatus === 'loading', isError: otpStatus === 'error', isSuccess: otpStatus === 'success' }), jsxs(ResultContainer$1, { children: [otpStatus === 'success' && jsx(ModalBody, { "$valid": true, children: "Code verified successfully!" }), otpStatus === 'error' && jsx(ModalBody, { "$error": true, children: error || 'Invalid code. Please try again.' })] }), jsxs(FooterTextButton$1, { children: ["Didn't receive the code?", ' ', jsx(FooterButtonText$1, { type: "button", onClick: handleResendClick, disabled: isResendDisabled, children: sendButtonText })] })] })] }));
16166
+ }
15889
16167
  if (error) {
15890
16168
  return (jsx(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: jsx(ModalBody, { style: { textAlign: 'center' }, "$error": true, children: jsx(FitText, { children: error }) }) }));
15891
16169
  }
@@ -18390,6 +18668,10 @@ const OpenfortProvider = ({ children, uiConfig, onConnect, onDisconnect, debugMo
18390
18668
  })), children, jsx(ConnectModal, { lang: ckLang, theme: ckTheme, mode: (_e = safeUiConfig.mode) !== null && _e !== void 0 ? _e : ckMode, customTheme: ckCustomTheme })] }) }));
18391
18669
  };
18392
18670
 
18671
+ const PageLayout = ({ children, width, header }) => {
18672
+ return (jsx(PageContent, { width: width, header: header, onBack: null, children: children }));
18673
+ };
18674
+
18393
18675
  /**
18394
18676
  * Hook for OAuth-based authentication operations
18395
18677
  *
@@ -19305,5 +19587,5 @@ const wallets = Object.keys(walletConfigs).reduce((acc, key) => {
19305
19587
  return acc;
19306
19588
  }, {});
19307
19589
 
19308
- export { UIAuthProvider as AuthProvider, Avatar, Chain as ChainIcon, LinkWalletOnSignUpOption, OPENFORT_VERSION, OpenfortButton, OpenfortError, OpenfortReactErrorType as OpenfortErrorType, OpenfortProvider, embeddedWalletId, defaultConfig as getDefaultConfig, defaultConnectors as getDefaultConnectors, use7702Authorization, useAuthCallback, useChainIsSupported, useChains, useConnectWithSiwe, useEmailAuth, useEmailOtpAuth, useGrantPermissions, useGuestAuth, useOAuth, useOpenfortCore as useOpenfort, usePhoneOtpAuth, useRevokePermissions, useSignOut, useUI, useUser, useWalletAssets, useWalletAuth, useWallets, wallets };
19590
+ export { UIAuthProvider as AuthProvider, Avatar, Chain as ChainIcon, LinkWalletOnSignUpOption, OPENFORT_VERSION, OpenfortButton, OpenfortError, OpenfortReactErrorType as OpenfortErrorType, OpenfortProvider, PageLayout, embeddedWalletId, defaultConfig as getDefaultConfig, defaultConnectors as getDefaultConnectors, use7702Authorization, useAuthCallback, useChainIsSupported, useChains, useConnectWithSiwe, useEmailAuth, useEmailOtpAuth, useGrantPermissions, useGuestAuth, useOAuth, useOpenfortCore as useOpenfort, usePhoneOtpAuth, useRevokePermissions, useSignOut, useUI, useUser, useWalletAssets, useWalletAuth, useWallets, wallets };
19309
19591
  //# sourceMappingURL=index.es.js.map