Skip to content

Commit

Permalink
feat: Replaced generic error toast with custom error stages (#32)
Browse files Browse the repository at this point in the history
Co-authored-by: Paolo D'Amico <[email protected]>
  • Loading branch information
maxpetretta and paolodamico authored Jan 4, 2023
1 parent d6f1b56 commit 35ca61b
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 14 deletions.
19 changes: 13 additions & 6 deletions idkit/src/components/IDKitWidget/States/EnterPhoneState.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { motion } from 'framer-motion'
import { classNames } from '@/lib/utils'
import useIDKitStore from '@/store/idkit'
import { ERROR_TITLES } from './ErrorState'
import { DEFAULT_COPY } from '@/types/config'
import * as Toast from '@radix-ui/react-toast'
import type { IDKitStore } from '@/store/idkit'
Expand All @@ -9,7 +10,7 @@ import WorldIDIcon from '@/components/WorldIDIcon'
import { XMarkIcon } from '@heroicons/react/20/solid'
import { isRequestCodeError, requestCode } from '@/services/phone'
import { getTelemetryId, telemetryPhoneTyped } from '@/lib/telemetry'
import { ErrorCodes, IDKITStage, PhoneVerificationChannel } from '@/types'
import { ErrorCodes, IDKITStage, PhoneRequestErrorCodes, PhoneVerificationChannel } from '@/types'

const getParams = ({
processing,
Expand All @@ -36,13 +37,18 @@ const getParams = ({
setProcessing(false)
setStage(IDKITStage.ENTER_CODE)
} catch (error) {
console.error(error)
setProcessing(false)
if (isRequestCodeError(error) && error.code !== 'server_error') {
setErrorState({ code: ErrorCodes.GENERIC_ERROR })
console.error(error)
let message: string | undefined = undefined
if (isRequestCodeError(error)) {
message = (Object.values(PhoneRequestErrorCodes).includes(error.code) && error.detail) || undefined
if (error.code !== PhoneRequestErrorCodes.TIMEOUT) {
setStage(IDKITStage.ERROR)
}
} else {
setStage(IDKITStage.ERROR)
}
setErrorState({ code: ErrorCodes.PHONE_OTP_REQUEST_ERROR, message })
}
},
onResetErrorState: () => {
Expand All @@ -51,7 +57,7 @@ const getParams = ({
})

const EnterPhoneState = () => {
const { copy, phoneNumber, processing, errorState, onResetErrorState, useWorldID, onSubmit } =
const { copy, phoneNumber, processing, useWorldID, onSubmit, errorState, onResetErrorState } =
useIDKitStore(getParams)

return (
Expand All @@ -68,7 +74,8 @@ const EnterPhoneState = () => {
transition={{ duration: 0.3 }}
>
<Toast.Title className="text-xs font-medium text-red-600">
Something went wrong. Please try again.
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
{errorState?.message || 'Unable to send code. Please try again.'}
</Toast.Title>
<Toast.Action altText="Close">
<XMarkIcon className="h-4 w-4" />
Expand Down
10 changes: 8 additions & 2 deletions idkit/src/components/IDKitWidget/States/ErrorState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { XMarkIcon } from '@heroicons/react/20/solid'

const getParams = ({ retryFlow, errorState }: IDKitStore) => ({ retryFlow, errorState })

const ERROR_TITLES: Record<ErrorCodes, string> = {
export const ERROR_TITLES: Record<ErrorCodes, string> = {
[ErrorCodes.GENERIC_ERROR]: 'Something went wrong',
[ErrorCodes.INVALID_CODE]: 'Invalid code',
[ErrorCodes.PHONE_OTP_REQUEST_ERROR]: 'We could not send you a code',
[ErrorCodes.REJECTED_BY_HOST_APP]: 'Verification declined by app',
}

Expand All @@ -29,7 +30,7 @@ const ErrorState = () => {
</p>
<p className="mt-2 text-center text-lg text-gray-400">
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
{errorState?.message || 'Please try to verify again in a moment'}
{errorState?.message || 'Please try again in a moment.'}
</p>
</div>
<div className="flex justify-center">
Expand All @@ -41,6 +42,11 @@ const ErrorState = () => {
Try Again
</button>
</div>
<div>
<p className="mt-4 text-xs text-gray-400">
If you are the app owner, check the console for further details.
</p>
</div>
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion idkit/src/components/PhoneInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const PhoneInput = ({ disabled, onSubmit }: { disabled?: boolean; onSubmit?: ()
onChange={e => setPhoneNumber(e.target.value)}
className="block w-full rounded-md border-transparent bg-transparent pl-6 focus:border-transparent focus:ring-transparent dark:text-white sm:text-sm"
disabled={disabled}
onKeyDown={e => e.key === 'Enter' && onSubmit?.()}
onKeyDown={e => e.key === 'Enter' && void onSubmit?.()}
/>
</div>
</Fragment>
Expand Down
9 changes: 5 additions & 4 deletions idkit/src/services/phone.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PhoneSignalProof, PhoneVerificationChannel } from '@/types'
import type { PhoneRequestErrorCodes, PhoneSignalProof, PhoneVerificationChannel } from '@/types'

const API_BASE_URL = 'https://developer.worldcoin.org/api/v1'

Expand Down Expand Up @@ -41,15 +41,16 @@ export async function verifyCode(
}

interface RequestCodeError {
code: 'max_attempts' | 'server_error' | 'timeout'
details: string
code: PhoneRequestErrorCodes
detail: string
}

export function isRequestCodeError(error: unknown): error is RequestCodeError {
return (
typeof error === 'object' &&
error !== null &&
Object.prototype.hasOwnProperty.call(error as Record<string, unknown>, 'code')
Object.prototype.hasOwnProperty.call(error as Record<string, unknown>, 'code') &&
Object.prototype.hasOwnProperty.call(error as Record<string, unknown>, 'detail')
)
}

Expand Down
4 changes: 3 additions & 1 deletion idkit/src/store/idkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const useIDKitStore = create<IDKitStore>()((set, get) => ({
phoneNumber: '',
autoClose: false,
errorState: null,
errorTitle: '',
errorDetail: '',
processing: false,
successCallbacks: {},
stage: IDKITStage.ENTER_PHONE,
Expand All @@ -52,7 +54,7 @@ const useIDKitStore = create<IDKitStore>()((set, get) => ({
setErrorState: errorState => set({ errorState }),
setPhoneNumber: phoneNumber => set({ phoneNumber }),
setProcessing: (processing: boolean) => set({ processing }),
retryFlow: () => set({ stage: IDKITStage.ENTER_PHONE, phoneNumber: '' }),
retryFlow: () => set({ stage: IDKITStage.ENTER_PHONE, phoneNumber: '', errorState: null }),
addSuccessCallback: (cb: CallbackFn, source: ConfigSource) => {
set(state => {
state.successCallbacks[source] = cb
Expand Down
8 changes: 8 additions & 0 deletions idkit/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface IErrorState {

export enum ErrorCodes {
GENERIC_ERROR = 'GENERIC_ERROR',
PHONE_OTP_REQUEST_ERROR = 'PHONE_OTP_REQUEST_ERROR',
INVALID_CODE = 'INVALID_CODE', // OTP code is invalid
REJECTED_BY_HOST_APP = 'REJECTED_BY_HOST_APP', // Host app rejected the verification request
}
Expand All @@ -53,3 +54,10 @@ export enum PhoneVerificationChannel {
SMS = 'sms',
Call = 'call',
}

export enum PhoneRequestErrorCodes {
MAX_ATTEMPTS = 'max_attempts',
TIMEOUT = 'timeout',
UNSUPPORTED_COUNTRY = 'unsupported_country',
SERVER_ERROR = 'server_error',
}

0 comments on commit 35ca61b

Please sign in to comment.