Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions src/components/Global/PostSignupActionManager/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { useEffect, useState } from 'react'
import ActionModal from '../ActionModal'
import { POST_SIGNUP_ACTIONS } from './post-signup-action.consts'
import { type IconName } from '../Icons/Icon'
import { useAuth } from '@/context/authContext'
import { isUserKycVerified } from '@/constants/kyc.consts'

export const PostSignupActionManager = ({
onActionModalVisibilityChange,
Expand All @@ -23,11 +21,10 @@ export const PostSignupActionManager = ({
action: () => void
} | null>(null)
const router = useRouter()
const { user } = useAuth()

const checkClaimModalAfterKYC = () => {
const checkForPostSignupAction = () => {
const redirectUrl = getRedirectUrl()
if (isUserKycVerified(user?.user) && redirectUrl) {
if (redirectUrl) {
const matchedAction = POST_SIGNUP_ACTIONS.find((action) => action.pathPattern.test(redirectUrl))
if (matchedAction) {
setActionConfig({
Expand All @@ -44,8 +41,8 @@ export const PostSignupActionManager = ({
}

useEffect(() => {
checkClaimModalAfterKYC()
}, [router, user])
checkForPostSignupAction()
}, [router])

useEffect(() => {
onActionModalVisibilityChange(showModal)
Expand Down
63 changes: 40 additions & 23 deletions src/hooks/useZeroDev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,35 +129,52 @@ export const useZeroDev = () => {
}
}

// login function
const handleLogin = async () => {
dispatch(zerodevActions.setIsLoggingIn(true))
try {
const passkeyServerHeaders: Record<string, string> = {}
// TODO: Consider implementing conditional mediation (WebAuthn autofill) for login.
// Currently, discoverable credential requests cause conflicts on Samsung devices with
// dual passkey providers (Google Credential Manager + Samsung Pass). Conditional mediation
// surfaces passkeys from ALL providers in the autofill UI, bypassing provider priority issues.
// See: https://web.dev/articles/passkey-form-autofill
const _attemptLogin = async () => {
const passkeyServerHeaders: Record<string, string> = {}

if (user?.user?.username) {
passkeyServerHeaders['x-username'] = user.user.username
}
if (user?.user?.username) {
passkeyServerHeaders['x-username'] = user.user.username
}

const webAuthnKey = await toWebAuthnKey({
passkeyName: '[]',
passkeyServerUrl: consts.PASSKEY_SERVER_URL as string,
mode: WebAuthnMode.Login,
passkeyServerHeaders,
rpID: window.location.hostname.replace(/^www\./, ''),
})
const webAuthnKey = await toWebAuthnKey({
passkeyName: '[]',
passkeyServerUrl: consts.PASSKEY_SERVER_URL as string,
mode: WebAuthnMode.Login,
passkeyServerHeaders,
rpID: window.location.hostname.replace(/^www\./, ''),
})

setWebAuthnKey(webAuthnKey)
saveToCookie(WEB_AUTHN_COOKIE_KEY, webAuthnKey, 90)
setWebAuthnKey(webAuthnKey)
saveToCookie(WEB_AUTHN_COOKIE_KEY, webAuthnKey, 90)
}

const handleLogin = async () => {
dispatch(zerodevActions.setIsLoggingIn(true))
try {
await _attemptLogin()
} catch (e) {
const error = e as Error
if (error.name === 'NotAllowedError') {
// User cancelled - no state was saved, just let them retry
dispatch(zerodevActions.setIsLoggingIn(false))
throw new PasskeyError(
'Login was canceled or no passkey found. Please try again or register.',
'LOGIN_CANCELED'
)
// On devices with multiple passkey providers (e.g. Samsung with Google + Samsung Pass),
// the first provider may intercept and report "no passkeys" while the actual passkey
// lives in the other provider. Auto-retry once to give the second provider a chance.
console.warn('[useZeroDev] Login NotAllowedError, retrying once for multi-provider devices')
try {
await _attemptLogin()
return // retry succeeded
} catch (retryError) {
// retry also failed — throw user-facing error
dispatch(zerodevActions.setIsLoggingIn(false))
throw new PasskeyError(
'Login was canceled or no passkey found. Please try again or register.',
'LOGIN_CANCELED'
)
}
}

// Other login errors - clear any stale state
Expand Down
Loading