@volr/react-ui 0.1.120 → 0.1.122

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/dist/index.d.cts CHANGED
@@ -61,6 +61,7 @@ declare const VolrUIProvider: React$1.FC<VolrUIProviderProps>;
61
61
  /**
62
62
  * Passkey Enrollment View Component
63
63
  * Modern passkey setup with device-specific biometric icons
64
+ * Includes cross-domain migration detection and guidance
64
65
  */
65
66
  interface PasskeyEnrollViewProps {
66
67
  onComplete: () => void;
@@ -194,6 +195,19 @@ declare const en: {
194
195
  readonly default: "Please use this device's biometric.";
195
196
  readonly note: "Using other devices or apps may not work.";
196
197
  };
198
+ readonly migration: {
199
+ readonly title: "Set up passkey for this site";
200
+ readonly description: "Your wallet was created on {{sourceDomain}}. To use it here, you need to set up a new passkey for this site.";
201
+ readonly descriptionGeneric: "Your wallet was created on a different site. To use it here, you need to set up a new passkey for this site.";
202
+ readonly currentDomain: "Current site";
203
+ readonly sourceDomain: "Original site";
204
+ readonly benefits: "Your wallet address and balance will remain the same.";
205
+ readonly cta: "Set up passkey";
206
+ readonly later: "Do it later";
207
+ readonly inProgress: "Setting up...";
208
+ readonly success: "Passkey set up successfully!";
209
+ readonly error: "Failed to set up passkey. Please try again.";
210
+ };
197
211
  };
198
212
  readonly success: {
199
213
  readonly title: "Success!";
package/dist/index.d.ts CHANGED
@@ -61,6 +61,7 @@ declare const VolrUIProvider: React$1.FC<VolrUIProviderProps>;
61
61
  /**
62
62
  * Passkey Enrollment View Component
63
63
  * Modern passkey setup with device-specific biometric icons
64
+ * Includes cross-domain migration detection and guidance
64
65
  */
65
66
  interface PasskeyEnrollViewProps {
66
67
  onComplete: () => void;
@@ -194,6 +195,19 @@ declare const en: {
194
195
  readonly default: "Please use this device's biometric.";
195
196
  readonly note: "Using other devices or apps may not work.";
196
197
  };
198
+ readonly migration: {
199
+ readonly title: "Set up passkey for this site";
200
+ readonly description: "Your wallet was created on {{sourceDomain}}. To use it here, you need to set up a new passkey for this site.";
201
+ readonly descriptionGeneric: "Your wallet was created on a different site. To use it here, you need to set up a new passkey for this site.";
202
+ readonly currentDomain: "Current site";
203
+ readonly sourceDomain: "Original site";
204
+ readonly benefits: "Your wallet address and balance will remain the same.";
205
+ readonly cta: "Set up passkey";
206
+ readonly later: "Do it later";
207
+ readonly inProgress: "Setting up...";
208
+ readonly success: "Passkey set up successfully!";
209
+ readonly error: "Failed to set up passkey. Please try again.";
210
+ };
197
211
  };
198
212
  readonly success: {
199
213
  readonly title: "Success!";
package/dist/index.js CHANGED
@@ -570,6 +570,19 @@ var en = {
570
570
  windows: "Please use your phone via QR code. (Windows Hello is not supported)",
571
571
  default: "Please use this device's biometric.",
572
572
  note: "Using other devices or apps may not work."
573
+ },
574
+ migration: {
575
+ title: "Set up passkey for this site",
576
+ description: "Your wallet was created on {{sourceDomain}}. To use it here, you need to set up a new passkey for this site.",
577
+ descriptionGeneric: "Your wallet was created on a different site. To use it here, you need to set up a new passkey for this site.",
578
+ currentDomain: "Current site",
579
+ sourceDomain: "Original site",
580
+ benefits: "Your wallet address and balance will remain the same.",
581
+ cta: "Set up passkey",
582
+ later: "Do it later",
583
+ inProgress: "Setting up...",
584
+ success: "Passkey set up successfully!",
585
+ error: "Failed to set up passkey. Please try again."
573
586
  }
574
587
  },
575
588
  success: {
@@ -799,6 +812,19 @@ var ko = {
799
812
  windows: "QR \uCF54\uB4DC\uB85C \uD734\uB300\uD3F0\uC744 \uC0AC\uC6A9\uD574\uC8FC\uC138\uC694. (Windows Hello\uB294 \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4)",
800
813
  default: "\uC774 \uAE30\uAE30\uC758 \uC0DD\uCCB4 \uC778\uC99D\uC744 \uC0AC\uC6A9\uD574\uC8FC\uC138\uC694.",
801
814
  note: "\uB2E4\uB978 \uAE30\uAE30\uB098 \uC571 \uC0AC\uC6A9 \uC2DC \uB3D9\uC791\uD558\uC9C0 \uC54A\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
815
+ },
816
+ migration: {
817
+ title: "\uC774 \uC0AC\uC774\uD2B8\uC6A9 \uD328\uC2A4\uD0A4 \uC124\uC815",
818
+ description: "\uC9C0\uAC11\uC774 {{sourceDomain}}\uC5D0\uC11C \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774 \uC0AC\uC774\uD2B8\uC5D0\uC11C \uC0AC\uC6A9\uD558\uB824\uBA74 \uC0C8 \uD328\uC2A4\uD0A4\uB97C \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.",
819
+ descriptionGeneric: "\uC9C0\uAC11\uC774 \uB2E4\uB978 \uC0AC\uC774\uD2B8\uC5D0\uC11C \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774 \uC0AC\uC774\uD2B8\uC5D0\uC11C \uC0AC\uC6A9\uD558\uB824\uBA74 \uC0C8 \uD328\uC2A4\uD0A4\uB97C \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.",
820
+ currentDomain: "\uD604\uC7AC \uC0AC\uC774\uD2B8",
821
+ sourceDomain: "\uC6D0\uBCF8 \uC0AC\uC774\uD2B8",
822
+ benefits: "\uC9C0\uAC11 \uC8FC\uC18C\uC640 \uC794\uC561\uC740 \uADF8\uB300\uB85C \uC720\uC9C0\uB429\uB2C8\uB2E4.",
823
+ cta: "\uD328\uC2A4\uD0A4 \uC124\uC815\uD558\uAE30",
824
+ later: "\uB098\uC911\uC5D0 \uD558\uAE30",
825
+ inProgress: "\uC124\uC815 \uC911...",
826
+ success: "\uD328\uC2A4\uD0A4 \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!",
827
+ error: "\uD328\uC2A4\uD0A4 \uC124\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."
802
828
  }
803
829
  },
804
830
  success: {
@@ -1858,6 +1884,84 @@ function PasskeyCompatibilityScreen({
1858
1884
  ] })
1859
1885
  ] });
1860
1886
  }
1887
+ function PasskeyMigrationView({
1888
+ sourcePasskey,
1889
+ currentDomain,
1890
+ onMigrate,
1891
+ onSkip,
1892
+ onError,
1893
+ isOpen = true,
1894
+ wrapInModal = true
1895
+ }) {
1896
+ const { t } = useI18n();
1897
+ const [isMigrating, setIsMigrating] = useState(false);
1898
+ const [error, setError] = useState(null);
1899
+ const biometricType = getBiometricType();
1900
+ const handleMigrate = async () => {
1901
+ try {
1902
+ setIsMigrating(true);
1903
+ setError(null);
1904
+ await onMigrate();
1905
+ } catch (err) {
1906
+ const errorMessage = err instanceof Error ? err.message : String(err);
1907
+ setError(t("passkey.migration.error"));
1908
+ if (onError) {
1909
+ onError(err instanceof Error ? err : new Error(errorMessage));
1910
+ }
1911
+ } finally {
1912
+ setIsMigrating(false);
1913
+ }
1914
+ };
1915
+ const content = /* @__PURE__ */ jsxs("div", { children: [
1916
+ /* @__PURE__ */ jsx("p", { className: "volr:text-xl volr:font-semibold volr:mb-4", children: t("passkey.migration.title") }),
1917
+ /* @__PURE__ */ jsx("div", { className: "volr:my-6 volr:flex volr:justify-center", children: /* @__PURE__ */ jsx(BiometricIcon, { type: biometricType, size: 48 }) }),
1918
+ /* @__PURE__ */ jsx("p", { className: "volr:text-sm volr:mb-4 volr:text-center volr-text-secondary", children: sourcePasskey.rpId ? t("passkey.migration.description").replace(
1919
+ "{{sourceDomain}}",
1920
+ sourcePasskey.rpId
1921
+ ) : t("passkey.migration.descriptionGeneric") }),
1922
+ /* @__PURE__ */ jsxs("div", { className: "volr:mb-4 volr:p-3 volr:rounded-lg volr:border volr:border-slate-200 volr:bg-slate-50", children: [
1923
+ /* @__PURE__ */ jsxs("div", { className: "volr:flex volr:justify-between volr:items-center volr:text-sm volr:mb-2", children: [
1924
+ /* @__PURE__ */ jsx("span", { className: "volr-text-secondary", children: t("passkey.migration.sourceDomain") }),
1925
+ /* @__PURE__ */ jsx("span", { className: "volr:font-mono volr:text-xs", children: sourcePasskey.rpId })
1926
+ ] }),
1927
+ /* @__PURE__ */ jsxs("div", { className: "volr:flex volr:justify-between volr:items-center volr:text-sm", children: [
1928
+ /* @__PURE__ */ jsx("span", { className: "volr-text-secondary", children: t("passkey.migration.currentDomain") }),
1929
+ /* @__PURE__ */ jsx("span", { className: "volr:font-mono volr:text-xs", children: currentDomain })
1930
+ ] })
1931
+ ] }),
1932
+ /* @__PURE__ */ jsx("div", { className: "volr:mb-6 volr:p-3 volr:rounded-lg volr-hint", children: /* @__PURE__ */ jsxs("p", { className: "volr:text-sm volr:flex volr:items-start volr:gap-2", children: [
1933
+ /* @__PURE__ */ jsx("span", { className: "volr:text-base", children: "\u2713" }),
1934
+ /* @__PURE__ */ jsx("span", { children: t("passkey.migration.benefits") })
1935
+ ] }) }),
1936
+ error && /* @__PURE__ */ jsx("div", { className: "volr:mb-4 volr:p-3 volr:rounded-lg volr:border volr:text-sm volr:text-left volr-error", children: /* @__PURE__ */ jsx("span", { children: error }) }),
1937
+ /* @__PURE__ */ jsxs("div", { className: "volr:flex volr:flex-col volr:gap-3", children: [
1938
+ /* @__PURE__ */ jsx(
1939
+ Button,
1940
+ {
1941
+ variant: "primary",
1942
+ fullWidth: true,
1943
+ onClick: handleMigrate,
1944
+ disabled: isMigrating,
1945
+ children: isMigrating ? t("passkey.migration.inProgress") : t("passkey.migration.cta")
1946
+ }
1947
+ ),
1948
+ onSkip && /* @__PURE__ */ jsx(
1949
+ Button,
1950
+ {
1951
+ variant: "ghost",
1952
+ fullWidth: true,
1953
+ onClick: onSkip,
1954
+ disabled: isMigrating,
1955
+ children: t("passkey.migration.later")
1956
+ }
1957
+ )
1958
+ ] })
1959
+ ] });
1960
+ if (!wrapInModal) {
1961
+ return content;
1962
+ }
1963
+ return /* @__PURE__ */ jsx(Modal, { open: isOpen, onOpenChange: (open) => !open && onSkip?.(), children: content });
1964
+ }
1861
1965
  function PasskeyEnrollView({
1862
1966
  onComplete,
1863
1967
  onError,
@@ -1886,14 +1990,34 @@ function PasskeyEnrollView({
1886
1990
  [compatibility.platform]
1887
1991
  );
1888
1992
  const hasPasskey = user?.keyStorageType === "passkey";
1993
+ const currentDomain = useMemo(() => {
1994
+ if (typeof window === "undefined") return "localhost";
1995
+ return window.location.hostname;
1996
+ }, []);
1997
+ const migrationInfo = useMemo(() => {
1998
+ if (!user?.registeredPasskeys || user.registeredPasskeys.length === 0) {
1999
+ return { needsMigration: false, sourcePasskey: null };
2000
+ }
2001
+ const hasPasskeyOnCurrentDomain = user.registeredPasskeys.some(
2002
+ (pk) => pk.rpId === currentDomain
2003
+ );
2004
+ if (hasPasskeyOnCurrentDomain) {
2005
+ return { needsMigration: false, sourcePasskey: null };
2006
+ }
2007
+ const sourcePasskey = user.registeredPasskeys[0];
2008
+ return { needsMigration: true, sourcePasskey };
2009
+ }, [user?.registeredPasskeys, currentDomain]);
1889
2010
  useEffect(() => {
1890
2011
  console.log("[PasskeyEnrollView] User state:", {
1891
2012
  user,
1892
2013
  keyStorageType: user?.keyStorageType,
1893
2014
  evmAddress: user?.evmAddress,
1894
- hasPasskey
2015
+ hasPasskey,
2016
+ registeredPasskeys: user?.registeredPasskeys,
2017
+ currentDomain,
2018
+ migrationInfo
1895
2019
  });
1896
- }, [user, hasPasskey]);
2020
+ }, [user, hasPasskey, currentDomain, migrationInfo]);
1897
2021
  useEffect(() => {
1898
2022
  if (hasPasskey && !user?.evmAddress && !isRefreshing) {
1899
2023
  const refreshUserData = async () => {
@@ -2031,6 +2155,23 @@ function PasskeyEnrollView({
2031
2155
  }
2032
2156
  return /* @__PURE__ */ jsx(Modal, { open: isOpen, onOpenChange: (open) => !open && onLogout?.(), children: compatibilityContent });
2033
2157
  }
2158
+ if (migrationInfo.needsMigration && migrationInfo.sourcePasskey) {
2159
+ const handleMigration = async () => {
2160
+ await handleEnroll();
2161
+ };
2162
+ return /* @__PURE__ */ jsx(
2163
+ PasskeyMigrationView,
2164
+ {
2165
+ sourcePasskey: migrationInfo.sourcePasskey,
2166
+ currentDomain,
2167
+ onMigrate: handleMigration,
2168
+ onSkip: handleLogout,
2169
+ onError,
2170
+ isOpen,
2171
+ wrapInModal
2172
+ }
2173
+ );
2174
+ }
2034
2175
  if (hasPasskey) {
2035
2176
  const handleClose = () => {
2036
2177
  if (onClose) {
@@ -2944,6 +3085,7 @@ function SiweLoginScreen({
2944
3085
  }
2945
3086
  function SigninModal({ isOpen, onClose, onError }) {
2946
3087
  const { logout, user } = useVolrContext();
3088
+ const { client } = useInternalAuth();
2947
3089
  const { appName, branding } = useVolrUI();
2948
3090
  const { requestEmailCode, verifyEmailCode, handleSocialLogin } = useVolrLogin();
2949
3091
  const [currentScreen, setCurrentScreen] = useState("method-select");
@@ -2976,13 +3118,43 @@ function SigninModal({ isOpen, onClose, onError }) {
2976
3118
  const handleCodeSubmit = async (code) => {
2977
3119
  const result = await verifyEmailCode(email, code);
2978
3120
  if (result.keyStorageType) {
3121
+ try {
3122
+ const refreshed = await client.post(
3123
+ "/auth/refresh",
3124
+ {}
3125
+ );
3126
+ const currentRpId = typeof window !== "undefined" ? window.location.hostname : "localhost";
3127
+ const passkeys = refreshed.user?.registeredPasskeys ?? [];
3128
+ const hasCurrent = passkeys.some((p) => p.rpId === currentRpId);
3129
+ const needsMigration = passkeys.length > 0 && !hasCurrent;
3130
+ if (result.keyStorageType === "passkey" && needsMigration) {
3131
+ setCurrentScreen("passkey-setup");
3132
+ return;
3133
+ }
3134
+ } catch {
3135
+ }
2979
3136
  onClose();
2980
3137
  return;
2981
3138
  }
2982
3139
  setCurrentScreen("passkey-setup");
2983
3140
  };
2984
- const handleSiweSuccess = (data) => {
3141
+ const handleSiweSuccess = async (data) => {
2985
3142
  if (data.keyStorageType) {
3143
+ try {
3144
+ const refreshed = await client.post(
3145
+ "/auth/refresh",
3146
+ {}
3147
+ );
3148
+ const currentRpId = typeof window !== "undefined" ? window.location.hostname : "localhost";
3149
+ const passkeys = refreshed.user?.registeredPasskeys ?? [];
3150
+ const hasCurrent = passkeys.some((p) => p.rpId === currentRpId);
3151
+ const needsMigration = passkeys.length > 0 && !hasCurrent;
3152
+ if (data.keyStorageType === "passkey" && needsMigration) {
3153
+ setCurrentScreen("passkey-setup");
3154
+ return;
3155
+ }
3156
+ } catch {
3157
+ }
2986
3158
  onClose();
2987
3159
  return;
2988
3160
  }
@@ -6820,8 +6992,16 @@ function OAuthCallbackHandler({
6820
6992
  const { isLoading, error } = useVolrAuthCallback({
6821
6993
  onSuccess: (resultUser) => {
6822
6994
  console.log("[OAuthCallbackHandler] Login successful:", resultUser.email);
6995
+ const currentRpId = typeof window !== "undefined" ? window.location.hostname : "localhost";
6996
+ const passkeys = resultUser.registeredPasskeys ?? [];
6997
+ const hasCurrent = passkeys.some((p) => p.rpId === currentRpId);
6998
+ const needsMigration = passkeys.length > 0 && !hasCurrent;
6823
6999
  if (!resultUser.keyStorageType) {
6824
7000
  onShowOnboarding();
7001
+ return;
7002
+ }
7003
+ if (resultUser.keyStorageType === "passkey" && needsMigration) {
7004
+ onShowOnboarding();
6825
7005
  }
6826
7006
  },
6827
7007
  onError: (err) => {