@zerodev/wallet-react-ui 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +69 -10
  2. package/dist/_types/auth/authStoreSlice.d.ts +3 -4
  3. package/dist/_types/auth/authStoreSlice.d.ts.map +1 -1
  4. package/dist/_types/auth/components/WalletGridSheet/index.d.ts +15 -0
  5. package/dist/_types/auth/components/WalletGridSheet/index.d.ts.map +1 -0
  6. package/dist/_types/auth/hooks/useAuth.d.ts +0 -2
  7. package/dist/_types/auth/hooks/useAuth.d.ts.map +1 -1
  8. package/dist/_types/auth/index.d.ts +9 -1
  9. package/dist/_types/auth/index.d.ts.map +1 -1
  10. package/dist/_types/auth/pages/OtpInput.d.ts.map +1 -1
  11. package/dist/_types/auth/pages/SignUp/Email.d.ts +4 -0
  12. package/dist/_types/auth/pages/SignUp/Email.d.ts.map +1 -0
  13. package/dist/_types/auth/pages/SignUp/Google.d.ts +3 -0
  14. package/dist/_types/auth/pages/SignUp/Google.d.ts.map +1 -0
  15. package/dist/_types/auth/pages/SignUp/InstalledWallets.d.ts +12 -0
  16. package/dist/_types/auth/pages/SignUp/InstalledWallets.d.ts.map +1 -0
  17. package/dist/_types/auth/pages/SignUp/MoreWallets.d.ts +5 -0
  18. package/dist/_types/auth/pages/SignUp/MoreWallets.d.ts.map +1 -0
  19. package/dist/_types/auth/pages/SignUp/Passkey.d.ts +3 -0
  20. package/dist/_types/auth/pages/SignUp/Passkey.d.ts.map +1 -0
  21. package/dist/_types/auth/pages/SignUp/Wallet.d.ts +7 -0
  22. package/dist/_types/auth/pages/SignUp/Wallet.d.ts.map +1 -0
  23. package/dist/_types/auth/pages/SignUp/context.d.ts +30 -0
  24. package/dist/_types/auth/pages/SignUp/context.d.ts.map +1 -0
  25. package/dist/_types/auth/pages/SignUp/index.d.ts +37 -0
  26. package/dist/_types/auth/pages/SignUp/index.d.ts.map +1 -0
  27. package/dist/_types/auth/pages/Verifying.d.ts.map +1 -1
  28. package/dist/_types/auth/types.d.ts +2 -10
  29. package/dist/_types/auth/types.d.ts.map +1 -1
  30. package/dist/_types/auth/utils/isCancellationError.d.ts +2 -0
  31. package/dist/_types/auth/utils/isCancellationError.d.ts.map +1 -0
  32. package/dist/_types/auth/walletGuide.d.ts +108 -0
  33. package/dist/_types/auth/walletGuide.d.ts.map +1 -0
  34. package/dist/_types/connector.d.ts +3 -13
  35. package/dist/_types/connector.d.ts.map +1 -1
  36. package/dist/_types/index.d.ts +5 -3
  37. package/dist/_types/index.d.ts.map +1 -1
  38. package/dist/_types/store.d.ts +1 -6
  39. package/dist/_types/store.d.ts.map +1 -1
  40. package/dist/index.cjs +3 -3
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.mjs +810 -576
  43. package/dist/index.mjs.map +1 -1
  44. package/dist/styles.css +1 -1
  45. package/package.json +3 -3
  46. package/src/auth/authStoreSlice.ts +7 -13
  47. package/src/auth/components/WalletGridSheet/index.tsx +77 -0
  48. package/src/auth/hooks/useAuth.ts +0 -5
  49. package/src/auth/index.tsx +19 -20
  50. package/src/auth/pages/OtpInput.tsx +1 -4
  51. package/src/auth/pages/SignUp/Email.tsx +86 -0
  52. package/src/auth/pages/SignUp/Google.tsx +43 -0
  53. package/src/auth/pages/SignUp/InstalledWallets.tsx +95 -0
  54. package/src/auth/pages/SignUp/MoreWallets.tsx +105 -0
  55. package/src/auth/pages/SignUp/Passkey.tsx +56 -0
  56. package/src/auth/pages/SignUp/Wallet.tsx +81 -0
  57. package/src/auth/pages/SignUp/context.tsx +46 -0
  58. package/src/auth/pages/SignUp/index.tsx +164 -0
  59. package/src/auth/pages/Verifying.tsx +3 -10
  60. package/src/auth/types.ts +1 -11
  61. package/src/auth/utils/isCancellationError.ts +10 -0
  62. package/src/auth/walletGuide.ts +132 -0
  63. package/src/connector.ts +12 -28
  64. package/src/index.ts +4 -3
  65. package/src/store.ts +1 -8
  66. package/dist/_types/auth/pages/SignUp.d.ts +0 -2
  67. package/dist/_types/auth/pages/SignUp.d.ts.map +0 -1
  68. package/dist/_types/auth/pages/WalletSelection.d.ts +0 -2
  69. package/dist/_types/auth/pages/WalletSelection.d.ts.map +0 -1
  70. package/src/auth/pages/SignUp.tsx +0 -311
  71. package/src/auth/pages/WalletSelection.tsx +0 -61
@@ -0,0 +1,105 @@
1
+ import { ListItem, ListItemChevron, ListItemIcon } from '@zerodev/react-ui'
2
+ import { useState } from 'react'
3
+ import { useConnect, useConnectors } from 'wagmi'
4
+ import {
5
+ WalletGridSheet,
6
+ type WalletTileData,
7
+ } from '../../components/WalletGridSheet'
8
+ import { useAuth } from '../../hooks/useAuth'
9
+ import { isCancellationError } from '../../utils/isCancellationError'
10
+ import { matchesWallet, WALLET_GUIDE } from '../../walletGuide'
11
+ import { useReportPending, useSignUpContext } from './context'
12
+
13
+ /** "More wallets" row — opens the wallet grid sheet. */
14
+ export function SignUpMoreWallets({
15
+ title = 'More wallets',
16
+ }: {
17
+ title?: string
18
+ }) {
19
+ const { goToStep } = useAuth()
20
+ const { authPending, guardAgreement, setError } = useSignUpContext()
21
+ const [open, setOpen] = useState(false)
22
+ const connectors = useConnectors()
23
+ const { mutate: connect, isPending } = useConnect()
24
+ useReportPending(isPending)
25
+
26
+ // Our own connector is the embedded wallet, and walletConnect-type
27
+ // connectors are the mobile transport rather than a wallet — neither
28
+ // belongs in the grid.
29
+ const walletConnectors = connectors.filter(
30
+ (c) => c.id !== 'zerodev-wallet' && c.type !== 'walletConnect',
31
+ )
32
+
33
+ const startConnect = (connector: (typeof connectors)[number]) => {
34
+ if (authPending) return
35
+ if (!guardAgreement()) return
36
+ setOpen(false)
37
+ setError(null)
38
+ connect(
39
+ { connector },
40
+ {
41
+ // The external wallet is now the active wagmi connection — the
42
+ // embedded-wallet flow is done, so close it.
43
+ onSuccess: () => goToStep(null),
44
+ onError: (err) => {
45
+ if (!isCancellationError(err)) {
46
+ setError(err instanceof Error ? err.message : String(err))
47
+ }
48
+ },
49
+ },
50
+ )
51
+ }
52
+
53
+ // Claimed by a connector → connect it; otherwise the tile opens the
54
+ // vendor download page.
55
+ const guideTiles: WalletTileData[] = WALLET_GUIDE.map((wallet) => {
56
+ const installed = walletConnectors.find((c) => matchesWallet(c, wallet))
57
+ return {
58
+ key: wallet.id,
59
+ name: wallet.name,
60
+ icon: wallet.icon,
61
+ onSelect: () => {
62
+ if (installed) {
63
+ startConnect(installed)
64
+ return
65
+ }
66
+ setOpen(false)
67
+ window.open(wallet.downloadUrl, '_blank', 'noopener,noreferrer')
68
+ },
69
+ }
70
+ })
71
+
72
+ // Live connectors we have no guide entry for still get a tile — nothing
73
+ // installed is ever hidden.
74
+ const connectorTiles: WalletTileData[] = walletConnectors
75
+ .filter((c) => !WALLET_GUIDE.some((wallet) => matchesWallet(c, wallet)))
76
+ .map((connector) => ({
77
+ key: connector.uid,
78
+ name: connector.name,
79
+ icon: connector.icon,
80
+ onSelect: () => startConnect(connector),
81
+ }))
82
+
83
+ const handleClick = () => {
84
+ if (authPending) return
85
+ if (!guardAgreement()) return
86
+ setOpen(true)
87
+ }
88
+
89
+ return (
90
+ <>
91
+ <ListItem
92
+ icon={<ListItemIcon name="walletOutline" className="zd:w-5 zd:h-5" />}
93
+ title={title}
94
+ trailing={<ListItemChevron />}
95
+ disabled={authPending}
96
+ onClick={handleClick}
97
+ />
98
+ <WalletGridSheet
99
+ open={open}
100
+ onOpenChange={setOpen}
101
+ tiles={[...guideTiles, ...connectorTiles]}
102
+ />
103
+ </>
104
+ )
105
+ }
@@ -0,0 +1,56 @@
1
+ import { Button } from '@zerodev/react-ui'
2
+ import { useLoginPasskey, useRegisterPasskey } from '@zerodev/wallet-react'
3
+ import { useAuth } from '../../hooks/useAuth'
4
+ import { isCancellationError } from '../../utils/isCancellationError'
5
+ import { useReportPending, useSignUpContext } from './context'
6
+
7
+ /** "Create a passkey" + "Log in with passkey" buttons. */
8
+ export function SignUpPasskey() {
9
+ const { goToStep } = useAuth()
10
+ const { authPending, guardAgreement, setError } = useSignUpContext()
11
+
12
+ const mutation = {
13
+ onSuccess: () => {
14
+ goToStep('authenticated')
15
+ },
16
+ onError: (err: unknown) => {
17
+ if (!isCancellationError(err)) {
18
+ setError(err instanceof Error ? err.message : String(err))
19
+ }
20
+ },
21
+ }
22
+ const { mutate: registerPasskey, isPending: isRegisterPending } =
23
+ useRegisterPasskey({ mutation })
24
+ const { mutate: loginPasskey, isPending: isLoginPending } = useLoginPasskey({
25
+ mutation,
26
+ })
27
+ useReportPending(isRegisterPending || isLoginPending)
28
+
29
+ const guarded = (run: () => void) => () => {
30
+ if (authPending) return
31
+ if (!guardAgreement()) return
32
+ setError(null)
33
+ run()
34
+ }
35
+
36
+ return (
37
+ <>
38
+ <Button
39
+ action="secondary"
40
+ text="Create a passkey"
41
+ iconName="key"
42
+ trailIcon
43
+ disabled={authPending}
44
+ onClick={guarded(() => registerPasskey())}
45
+ />
46
+ <Button
47
+ action="secondary"
48
+ text="Log in with passkey"
49
+ iconName="key"
50
+ trailIcon
51
+ disabled={authPending}
52
+ onClick={guarded(() => loginPasskey())}
53
+ />
54
+ </>
55
+ )
56
+ }
@@ -0,0 +1,81 @@
1
+ import { Badge, ListItem, ListItemChevron } from '@zerodev/react-ui'
2
+ import { useLayoutEffect } from 'react'
3
+ import { useConnect, useConnectors } from 'wagmi'
4
+ import { useAuth } from '../../hooks/useAuth'
5
+ import { isCancellationError } from '../../utils/isCancellationError'
6
+ import { matchesWallet, WALLET_GUIDE, type WalletId } from '../../walletGuide'
7
+ import { useReportPending, useSignUpContext } from './context'
8
+
9
+ /** Dedicated row for a single guide wallet: connects it directly when a live
10
+ * connector claims it, and is a download link for the vendor page otherwise. */
11
+ export function SignUpWallet({ walletId }: { walletId: WalletId }) {
12
+ const wallet = WALLET_GUIDE.find((w) => w.id === walletId)
13
+ if (!wallet) {
14
+ // Unreachable for TS consumers; catches raw-JS typos.
15
+ throw new Error(
16
+ `Unknown walletId "${walletId}". Valid ids: ${WALLET_GUIDE.map((w) => w.id).join(', ')}`,
17
+ )
18
+ }
19
+
20
+ const { goToStep } = useAuth()
21
+ const { authPending, guardAgreement, setError, registerWallet } =
22
+ useSignUpContext()
23
+ const connectors = useConnectors()
24
+ const { mutate: connect, isPending } = useConnect()
25
+ useReportPending(isPending)
26
+ useLayoutEffect(() => registerWallet(wallet.id), [registerWallet, wallet.id])
27
+
28
+ // Same claim rule as the MoreWallets grid: a 6963 announcement or a
29
+ // configured SDK connector both make the row connectable. Only an
30
+ // announcement (id === rdns) proves a live extension and earns the badge.
31
+ const installed = connectors.find((c) => matchesWallet(c, wallet))
32
+ const isAnnounced =
33
+ !!wallet.rdns && connectors.some((c) => c.id === wallet.rdns)
34
+
35
+ const shared = {
36
+ title: wallet.name,
37
+ icon: <img src={wallet.icon} alt="" className="zd:w-6 zd:h-6" />,
38
+ trailing: <ListItemChevron />,
39
+ }
40
+
41
+ if (!installed) {
42
+ return (
43
+ <ListItem {...shared} asChild>
44
+ {/* biome-ignore lint/a11y/useAnchorContent: the row layout (incl. the title text) is injected into the anchor via Slot */}
45
+ <a
46
+ href={wallet.downloadUrl}
47
+ target="_blank"
48
+ rel="noopener noreferrer"
49
+ />
50
+ </ListItem>
51
+ )
52
+ }
53
+
54
+ const handleClick = () => {
55
+ if (authPending) return
56
+ if (!guardAgreement()) return
57
+ setError(null)
58
+ connect(
59
+ { connector: installed },
60
+ {
61
+ // The external wallet is now the active wagmi connection — the
62
+ // embedded-wallet flow is done, so close it (mirrors SignUp.MoreWallets).
63
+ onSuccess: () => goToStep(null),
64
+ onError: (err) => {
65
+ if (!isCancellationError(err)) {
66
+ setError(err instanceof Error ? err.message : String(err))
67
+ }
68
+ },
69
+ },
70
+ )
71
+ }
72
+
73
+ return (
74
+ <ListItem
75
+ {...shared}
76
+ {...(isAnnounced && { subtitle: <Badge text="INSTALLED" /> })}
77
+ disabled={authPending}
78
+ onClick={handleClick}
79
+ />
80
+ )
81
+ }
@@ -0,0 +1,46 @@
1
+ import { createContext, useContext, useEffect } from 'react'
2
+ import type { EmailAuthMethod } from '../../types'
3
+
4
+ export type SignUpContextValue = {
5
+ /** True while any method's auth attempt is in flight — used to disable
6
+ * sibling methods so two flows can't run at once. */
7
+ authPending: boolean
8
+ setAuthPending: (pending: boolean) => void
9
+ /** Which email verification flow the Email unit runs. Set on the root
10
+ * (`<SignUp emailAuthMethod=…>`); already resolved to its default here. */
11
+ emailAuthMethod: EmailAuthMethod
12
+ /** True when the terms checkbox is required but unchecked. For passive
13
+ * disabled styling; use `guardAgreement` before starting an attempt. */
14
+ needsAgreement: boolean
15
+ /** Call before starting an auth attempt: highlights the terms checkbox and
16
+ * returns false when agreement is required but missing. */
17
+ guardAgreement: () => boolean
18
+ setError: (message: string | null) => void
19
+ /** Guide ids of every mounted `SignUp.Wallet` pin. Discovery units skip
20
+ * these so a pinned wallet isn't listed twice. */
21
+ registeredWallets: readonly string[]
22
+ /** Returns the unregister function. */
23
+ registerWallet: (walletId: string) => () => void
24
+ }
25
+
26
+ export const SignUpContext = createContext<SignUpContextValue | null>(null)
27
+
28
+ export function useSignUpContext(): SignUpContextValue {
29
+ const ctx = useContext(SignUpContext)
30
+ if (!ctx) {
31
+ throw new Error('SignUp.* components must be rendered inside <SignUp>')
32
+ }
33
+ return ctx
34
+ }
35
+
36
+ /** Mirror a method's in-flight state into the shared pending flag.
37
+ * Clears on unmount so a removed unit can't leave the page locked.
38
+ * One flag, not per-unit: methods are mutually exclusive, so at most one
39
+ * unit reports `true` at a time. */
40
+ export function useReportPending(pending: boolean) {
41
+ const { setAuthPending } = useSignUpContext()
42
+ useEffect(() => {
43
+ setAuthPending(pending)
44
+ return () => setAuthPending(false)
45
+ }, [pending, setAuthPending])
46
+ }
@@ -0,0 +1,164 @@
1
+ import { Button, Text } from '@zerodev/react-ui'
2
+ import { type ReactNode, useCallback, useState } from 'react'
3
+ import { SignUpFooter } from '../../../shared/components/SignUpFooter'
4
+ import { BlobAnimation } from '../../components/BlobAnimation'
5
+ import type { EmailAuthMethod } from '../../types'
6
+ import { SignUpContext } from './context'
7
+ import { SignUpEmail } from './Email'
8
+ import { SignUpGoogle } from './Google'
9
+ import { SignUpInstalledWallets } from './InstalledWallets'
10
+ import { SignUpMoreWallets } from './MoreWallets'
11
+ import { SignUpPasskey } from './Passkey'
12
+ import { SignUpWallet } from './Wallet'
13
+
14
+ type SignUpRootProps = {
15
+ children: ReactNode
16
+ /** Enable the consent gate: linked from the footer checkbox, and every
17
+ * method is blocked until the user agrees when either URL is set. */
18
+ termsAndConditionsUrl?: string | undefined
19
+ privacyPolicyUrl?: string | undefined
20
+ /** Which email verification flow the Email unit runs. */
21
+ emailAuthMethod?: EmailAuthMethod | undefined
22
+ }
23
+
24
+ function SignUpRoot({
25
+ children,
26
+ termsAndConditionsUrl,
27
+ privacyPolicyUrl,
28
+ emailAuthMethod = 'magicLink',
29
+ }: SignUpRootProps) {
30
+ const [agreedToTerms, setAgreedToTerms] = useState(false)
31
+ const [highlightAgreement, setHighlightAgreement] = useState(false)
32
+ const [error, setError] = useState<string | null>(null)
33
+ // One flag for the whole page: methods are mutually exclusive, so at most
34
+ // one unit is in flight at a time. Known accepted edge: a unit unmounting
35
+ // mid-flight (conditional composition) clears a sibling's lock.
36
+ const [authPending, setAuthPending] = useState(false)
37
+ // Which wallets have a pinned `SignUp.Wallet` row mounted. A plain list of
38
+ // registrants: register appends, cleanup removes one occurrence.
39
+ const [registeredWallets, setRegisteredWallets] = useState<readonly string[]>(
40
+ [],
41
+ )
42
+
43
+ const registerWallet = useCallback((walletId: string) => {
44
+ setRegisteredWallets((prev) => [...prev, walletId])
45
+ return () => {
46
+ setRegisteredWallets((prev) => {
47
+ const next = [...prev]
48
+ next.splice(next.indexOf(walletId), 1)
49
+ return next
50
+ })
51
+ }
52
+ }, [])
53
+
54
+ const requiresAgreement = !!(termsAndConditionsUrl || privacyPolicyUrl)
55
+ const needsAgreement = requiresAgreement && !agreedToTerms
56
+
57
+ const guardAgreement = () => {
58
+ if (!needsAgreement) return true
59
+ setHighlightAgreement(true)
60
+ return false
61
+ }
62
+
63
+ return (
64
+ <SignUpContext.Provider
65
+ value={{
66
+ authPending,
67
+ emailAuthMethod,
68
+ setAuthPending,
69
+ needsAgreement,
70
+ guardAgreement,
71
+ setError,
72
+ registeredWallets,
73
+ registerWallet,
74
+ }}
75
+ >
76
+ {error !== null && (
77
+ <div className="zd:flex zd:items-center zd:justify-center zd:h-full zd:min-h-100">
78
+ <div className="zd:flex zd:flex-col zd:gap-4 zd:max-w-md">
79
+ <Text className="zd:text-h2 zd:text-center">Error occurred</Text>
80
+ <Text className="zd:text-center zd:text-red-500">{error}</Text>
81
+ <Button
82
+ action="primary"
83
+ text="Try again"
84
+ onClick={() => setError(null)}
85
+ />
86
+ </div>
87
+ </div>
88
+ )}
89
+ {/* Kept mounted (hidden) during the error takeover so unit state — the
90
+ typed email, pending flags — survives "Try again".
91
+ `-mx-4` cancels Screen's scroll-container padding so this page
92
+ owns its own; inner blocks re-add `zd:px-4` where they need it. */}
93
+ <div
94
+ className={`zd:-mx-4 zd:flex-1 zd:flex zd:flex-col zd:justify-between zd:pb-4 zd:overflow-y-auto zd:overflow-x-hidden${
95
+ error !== null ? ' zd:hidden' : ''
96
+ }`}
97
+ >
98
+ <div className="zd:flex-1 zd:flex zd:flex-col zd:justify-center">
99
+ <div className="zd:px-4 zd:flex zd:flex-col zd:items-center">
100
+ <div className="zd:w-full zd:px-16 zd:py-4">
101
+ <BlobAnimation className="zd:w-full zd:pointer-events-none zd:select-none" />
102
+ </div>
103
+ <Text className="zd:text-h2 zd:text-center">
104
+ Continue to your wallet
105
+ </Text>
106
+ <Text className="zd:mt-2 zd:text-center zd:text-greyScale/50">
107
+ Choose a sign-in method to proceed
108
+ </Text>
109
+ </div>
110
+ <div className="zd:mt-6 zd:mb-4 zd:px-4 zd:flex zd:flex-col zd:gap-2">
111
+ {children}
112
+ </div>
113
+ </div>
114
+ <div className="zd:px-4">
115
+ <SignUpFooter
116
+ termsAndConditionsUrl={termsAndConditionsUrl}
117
+ privacyPolicyUrl={privacyPolicyUrl}
118
+ agreedToTerms={agreedToTerms}
119
+ setAgreedToTerms={(agreed) => {
120
+ setAgreedToTerms(agreed)
121
+ if (agreed) setHighlightAgreement(false)
122
+ }}
123
+ highlight={highlightAgreement}
124
+ />
125
+ </div>
126
+ </div>
127
+ </SignUpContext.Provider>
128
+ )
129
+ }
130
+
131
+ function SignUpDivider({ label = 'or' }: { label?: string }) {
132
+ return (
133
+ <div className="zd:-mx-4 zd:my-2 zd:flex zd:items-center zd:gap-3">
134
+ <div className="zd:h-px zd:flex-1 zd:bg-greyScale/30" />
135
+ <Text className="zd:text-body3">{label}</Text>
136
+ <div className="zd:h-px zd:flex-1 zd:bg-greyScale/30" />
137
+ </div>
138
+ )
139
+ }
140
+
141
+ /** The canonical sign-up page. Takes the root's own props (the consent-gate
142
+ * URLs) and forwards them — per-unit config (e.g. the email method) still
143
+ * means composing the units yourself. */
144
+ function SignUpDefault(props: Omit<SignUpRootProps, 'children'>) {
145
+ return (
146
+ <SignUpRoot {...props}>
147
+ <SignUpPasskey />
148
+ <SignUpDivider />
149
+ <SignUpGoogle />
150
+ <SignUpEmail />
151
+ </SignUpRoot>
152
+ )
153
+ }
154
+
155
+ export const SignUp = Object.assign(SignUpRoot, {
156
+ Default: SignUpDefault,
157
+ Passkey: SignUpPasskey,
158
+ Google: SignUpGoogle,
159
+ Email: SignUpEmail,
160
+ Wallet: SignUpWallet,
161
+ InstalledWallets: SignUpInstalledWallets,
162
+ MoreWallets: SignUpMoreWallets,
163
+ Divider: SignUpDivider,
164
+ })
@@ -11,13 +11,8 @@ function getCodeFromUrl(): string | null {
11
11
  }
12
12
 
13
13
  export function Verifying() {
14
- const {
15
- otpId,
16
- otpEncryptionTargetBundle,
17
- goToStep,
18
- clearOtpSession,
19
- config,
20
- } = useAuth()
14
+ const { otpId, otpEncryptionTargetBundle, goToStep, clearOtpSession } =
15
+ useAuth()
21
16
  const [code] = useState<string | null>(getCodeFromUrl)
22
17
  const [error, setError] = useState<Error | null>(null)
23
18
 
@@ -26,14 +21,12 @@ export function Verifying() {
26
21
  const { mutate: verifyMagicLink, isPending: isVerificationLoading } =
27
22
  useVerifyMagicLink({
28
23
  mutation: {
29
- onSuccess: async () => {
24
+ onSuccess: () => {
30
25
  clearOtpSession()
31
26
  goToStep('authenticated')
32
- config?.onSuccess?.()
33
27
  },
34
28
  onError: (err) => {
35
29
  setError(err)
36
- config?.onError?.(err)
37
30
  },
38
31
  },
39
32
  })
package/src/auth/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type AuthMethod = 'email' | 'google' | 'passkey'
1
+ export type AuthMethod = 'email' | 'google' | 'passkey' | 'external-wallet'
2
2
 
3
3
  export type AuthStep =
4
4
  | 'sign-up'
@@ -7,17 +7,7 @@ export type AuthStep =
7
7
  | 'verifying-otp'
8
8
  | 'passkey-prompt'
9
9
  | 'oauth-in-progress'
10
- | 'wallet-selection'
11
10
  | 'authenticated'
12
11
  | 'error'
13
12
 
14
13
  export type EmailAuthMethod = 'magicLink' | 'otp'
15
-
16
- export interface AuthConfig {
17
- enabledMethods: AuthMethod[]
18
- emailAuthMethod?: EmailAuthMethod
19
- termsAndConditionsUrl?: string
20
- privacyPolicyUrl?: string
21
- onSuccess?: () => void
22
- onError?: (error: unknown) => void
23
- }
@@ -0,0 +1,10 @@
1
+ export function isCancellationError(err: unknown): boolean {
2
+ if (!(err instanceof Error)) return false
3
+ // WebAuthn / passkey
4
+ if (err.name === 'AbortError' || err.name === 'NotAllowedError') return true
5
+ // EIP-1193 user rejection (external-wallet connect), via viem
6
+ if (err.name === 'UserRejectedRequestError') return true
7
+ // OAuth (existing logic, message-based)
8
+ const msg = err.message.toLowerCase()
9
+ return msg.includes('oauth popup was closed')
10
+ }