Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wallet/frontend): replace rapyd with gatehub for KYC #1530

Closed
wants to merge 15 commits into from
Closed
1 change: 0 additions & 1 deletion docker/prod/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ services:
args:
PORT: ${WALLET_FRONTEND_PORT}
COOKIE_NAME: ${WALLET_BACKEND_COOKIE_NAME}
NEXT_PUBLIC_USE_TEST_KYC_DATA: ${WALLET_FRONTEND_USE_TEST_KYC_DATA}
NEXT_PUBLIC_BACKEND_URL: ${WALLET_FRONTEND_BACKEND_URL}
NEXT_PUBLIC_OPEN_PAYMENTS_HOST: ${WALLET_FRONTEND_OPEN_PAYMENTS_HOST}
NEXT_PUBLIC_AUTH_HOST: ${WALLET_FRONTEND_AUTH_HOST}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type PersonalSettingsFormProps = {
user: UserResponse
}

// TODO: Can these details be updated by the user when switching to GateHub?
export const PersonalSettingsForm = ({ user }: PersonalSettingsFormProps) => {
const [isReadOnly, setIsReadOnly] = useState(true)
const { isChangePassword, setIsChangePassword } = usePasswordContext()
Expand Down
160 changes: 25 additions & 135 deletions packages/wallet/frontend/src/lib/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import {
type ErrorResponse,
type SuccessResponse
} from '../httpClient'
import { ACCEPTED_IMAGE_TYPES } from '@/utils/constants'
import { SelectOption } from '@/ui/forms/Select'
import { IFRAME_TYPE, IframeResponse } from '@wallet/shared'
import {
UserResponse,
ValidTokenResponse,
Expand All @@ -16,66 +15,14 @@ import {
loginSchema
} from '@wallet/shared'

export const personalDetailsSchema = z.object({
firstName: z.string().min(1, { message: 'First name is required' }),
lastName: z.string().min(1, { message: 'Last name is required' }),
country: z.object({
value: z.string().length(2),
label: z.string().min(1)
}),
city: z.string().min(1, { message: 'City is required' }),
address: z.string().min(1, { message: 'Address is required' }),
zip: z.string().min(1, { message: 'ZIP code is required' })
})

export const profileSchema = z.object({
firstName: z.string().min(1, { message: 'First name is required' }),
lastName: z.string().min(1, { message: 'Last name is required' })
})

export const verifyIdentitySchema = z
.object({
documentType: z.string({
invalid_type_error: 'Please select an ID Type'
}),
frontSideImage: z
.string()
.min(1, { message: 'Front side of ID is required' }),
frontSideImageType: z.string(),
backSideImage: z.string().optional(),
backSideImageType: z.string().optional(),
faceImage: z.string().min(1, { message: 'A selfie image is required' }),
faceImageType: z.string()
})
.superRefine(
({ frontSideImageType, faceImageType, backSideImageType }, ctx) => {
if (!ACCEPTED_IMAGE_TYPES.includes(frontSideImageType)) {
ctx.addIssue({
code: 'custom',
message: `Image must be 'jpeg' or 'png'`,
path: ['frontSideImage']
})
}
if (!ACCEPTED_IMAGE_TYPES.includes(faceImageType)) {
ctx.addIssue({
code: 'custom',
message: `Image must be 'jpeg' or 'png'`,
path: ['faceImage']
})
}
if (
backSideImageType &&
backSideImageType?.length > 0 &&
!ACCEPTED_IMAGE_TYPES.includes(backSideImageType)
) {
ctx.addIssue({
code: 'custom',
message: `Image must be 'jpeg' or 'png'`,
path: ['backSideImage']
})
}
}
)
export const forgotPasswordSchema = z.object({
email: z.string().email({ message: 'Email is required' })
})

export const resetPasswordSchema = z
.object({
Expand Down Expand Up @@ -137,12 +84,6 @@ export const changePasswordSchema = z
}
})

export type Document = {
type: string
name: string
isBackRequired: boolean
}

type SignUpArgs = z.infer<typeof signUpSchema>
type SignUpError = ErrorResponse<SignUpArgs | undefined>
type SignUpResponse = SuccessResponse | SignUpError
Expand Down Expand Up @@ -179,14 +120,6 @@ type VerifyEmailResponse = SuccessResponse | VerifyEmailError
type MeResult = SuccessResponse<UserResponse>
type MeResponse = MeResult | ErrorResponse

type CreateWalletArgs = z.infer<typeof personalDetailsSchema>
type CreateWalletError = ErrorResponse<CreateWalletArgs | undefined>
type CreateWalletResponse = SuccessResponse | CreateWalletError

type VerifyIdentityArgs = z.infer<typeof verifyIdentitySchema>
type VerifyIdentityError = ErrorResponse<VerifyIdentityArgs | undefined>
type VerifyIdentityResponse = SuccessResponse | VerifyIdentityError

type ProfileArgs = z.infer<typeof profileSchema>
type ProfileError = ErrorResponse<ProfileArgs | undefined>
type ProfileResponse = SuccessResponse | ProfileError
Expand All @@ -195,6 +128,9 @@ type ChangePasswordArgs = z.infer<typeof changePasswordSchema>
type ChangePasswordError = ErrorResponse<ChangePasswordArgs | undefined>
type ChangePasswordResponse = SuccessResponse | ChangePasswordError

type GetGateHubIframeSrcResult = SuccessResponse<IframeResponse>
type GetGateHubIframeSrcResponse = GetGateHubIframeSrcResult | ErrorResponse

interface UserService {
signUp: (args: SignUpArgs) => Promise<SignUpResponse>
login: (args: LoginArgs) => Promise<LoginResponse>
Expand All @@ -204,15 +140,15 @@ interface UserService {
checkToken: (token: string, cookies?: string) => Promise<CheckTokenResponse>
verifyEmail: (args: VerifyEmailArgs) => Promise<VerifyEmailResponse>
me: (cookies?: string) => Promise<MeResponse>
createWallet: (args: CreateWalletArgs) => Promise<CreateWalletResponse>
verifyIdentity: (args: VerifyIdentityArgs) => Promise<VerifyIdentityResponse>
updateProfile: (args: ProfileArgs) => Promise<ProfileResponse>
getDocuments: (cookies?: string) => Promise<Document[]>
getCountries: (cookies?: string) => Promise<SelectOption[]>
changePassword: (args: ChangePasswordArgs) => Promise<ChangePasswordResponse>
resendVerifyEmail: (
args: ResendVerificationEmailArgs
) => Promise<ResendVerificationEmailResponse>
getGateHubIframeSrc: (
type: IFRAME_TYPE,
cookies?: string
) => Promise<GetGateHubIframeSrcResponse>
}

const createUserService = (): UserService => ({
Expand Down Expand Up @@ -355,99 +291,53 @@ const createUserService = (): UserService => ({
}
},

async createWallet(args) {
try {
const response = await httpClient
.post('wallet', {
json: {
...args,
country: args.country.value
}
})
.json<SuccessResponse>()
return response
} catch (error) {
return getError<CreateWalletArgs>(
error,
'Something went wrong while trying to create your wallet. Please try again.'
)
}
},

async verifyIdentity(args) {
async updateProfile(args) {
try {
const response = await httpClient
.post('verify', {
.post('updateProfile', {
json: args
})
.json<SuccessResponse>()
return response
} catch (error) {
return getError<VerifyIdentityArgs>(
return getError<ProfileArgs>(
error,
'Something went wrong while verifying your ID. Please try again.'
'Something went wrong while updating your profile. Please try again.'
)
}
},

async updateProfile(args) {
async changePassword(args) {
try {
const response = await httpClient
.post('updateProfile', {
.patch('change-password', {
json: args
})
.json<SuccessResponse>()
return response
} catch (error) {
return getError<ProfileArgs>(
return getError<ChangePasswordArgs>(
error,
'Something went wrong while updating your profile. Please try again.'
'Something went wrong while changing your password. Please try again.'
)
}
},

async getDocuments(cookies) {
try {
const response = await httpClient
.get('documents', {
headers: {
...(cookies ? { Cookie: cookies } : {})
}
})
.json<SuccessResponse<Document[]>>()
return response?.result ?? []
} catch (error) {
return []
}
},

async getCountries(cookies) {
async getGateHubIframeSrc(type, cookies) {
try {
const response = await httpClient
.get('countries', {
.get(`iframe-urls/${type}`, {
headers: {
...(cookies ? { Cookie: cookies } : {})
}
})
.json<SuccessResponse<SelectOption[]>>()
return response?.result ?? []
} catch (error) {
return []
}
},

async changePassword(args) {
try {
const response = await httpClient
.patch('change-password', {
json: args
})
.json<SuccessResponse>()
.json<GetGateHubIframeSrcResult>()
return response
} catch (error) {
return getError<ChangePasswordArgs>(
return getError(
error,
'Something went wrong while changing your password. Please try again.'
// TODO: Better error message
'Something went wrong. Please try again.'
)
}
}
Expand Down
17 changes: 10 additions & 7 deletions packages/wallet/frontend/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const isPublicPath = (path: string) => {

const publicPaths = ['/auth*']

// TODO: Update middleware for the new KYC
// We might want to showcase the users that the identity verification is in
// progress and probably do not let them perform any action (sending money, etc).
export async function middleware(req: NextRequest) {
const isPublic = isPublicPath(req.nextUrl.pathname)
const nextPage = req.nextUrl.searchParams.get('next')
Expand Down Expand Up @@ -41,13 +44,13 @@ export async function middleware(req: NextRequest) {

// If KYC is completed and the user tries to navigate to the page, redirect
// to homepage.
if (
!response.result?.needsIDProof &&
!response.result?.needsWallet &&
req.nextUrl.pathname.startsWith('/kyc')
) {
return NextResponse.redirect(new URL('/', req.url))
}
// if (
// !response.result?.needsIDProof &&
// !response.result?.needsWallet &&
// req.nextUrl.pathname.startsWith('/kyc')
// ) {
// return NextResponse.redirect(new URL('/', req.url))
// }

if (isPublic) {
return NextResponse.redirect(new URL('/', req.url))
Expand Down
Loading
Loading