From 6b7c3fb5a2619381081025f4928798ce4a81677b Mon Sep 17 00:00:00 2001 From: Rohit Sah Date: Mon, 25 Sep 2023 15:12:33 +0545 Subject: [PATCH] feat(admin): :sparkles: added kyc form with document uploads --- apps/admin/next.config.js | 2 +- .../src/components/pages/settings/KYC.tsx | 371 ++++++++++++++++++ .../components/ui/FileUploadInputField.tsx | 96 +++++ apps/admin/src/generated/graphql.ts | 218 ++++++++++ .../kyc/requestKYCVerification.graphql | 19 + .../admin/src/graphql/query/tenantKYC.graphql | 19 + apps/admin/src/pages/dashboard/settings.tsx | 5 +- apps/admin/src/pages/test.tsx | 17 + apps/admin/src/server/uploadthing.ts | 5 +- apps/api/src/data-source.ts | 2 + apps/api/src/entities/Tenant.ts | 4 + apps/api/src/entities/TenantKyc.ts | 93 +++++ apps/api/src/resolvers/GqlObjets/TenantKYC.ts | 34 ++ apps/api/src/resolvers/tenant.ts | 37 ++ apps/storefront/src/generated/graphql.ts | 44 ++- 15 files changed, 957 insertions(+), 9 deletions(-) create mode 100644 apps/admin/src/components/pages/settings/KYC.tsx create mode 100644 apps/admin/src/components/ui/FileUploadInputField.tsx create mode 100644 apps/admin/src/graphql/mutation/kyc/requestKYCVerification.graphql create mode 100644 apps/admin/src/graphql/query/tenantKYC.graphql create mode 100644 apps/admin/src/pages/test.tsx create mode 100644 apps/api/src/entities/TenantKyc.ts create mode 100644 apps/api/src/resolvers/GqlObjets/TenantKYC.ts diff --git a/apps/admin/next.config.js b/apps/admin/next.config.js index 615d4bc..83911ae 100644 --- a/apps/admin/next.config.js +++ b/apps/admin/next.config.js @@ -2,7 +2,7 @@ const nextConfig = { reactStrictMode: true, images: { - domains: ["images.unsplash.com"], + domains: ["images.unsplash.com", "utfs.io"], }, }; diff --git a/apps/admin/src/components/pages/settings/KYC.tsx b/apps/admin/src/components/pages/settings/KYC.tsx new file mode 100644 index 0000000..651c8cd --- /dev/null +++ b/apps/admin/src/components/pages/settings/KYC.tsx @@ -0,0 +1,371 @@ +/* eslint-disable no-nested-ternary */ +/* eslint-disable @typescript-eslint/naming-convention */ +import { + Card, + Stack, + StackDivider, + HStack, + Button, + VStack, + useToast, + Alert, + AlertIcon, + Box, + AlertTitle, + AlertDescription, +} from "@chakra-ui/react"; +import { useForm } from "react-hook-form"; +import * as Yup from "yup"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { + useRequestKycVerficationMutation, + useTenantKycQuery, +} from "@/generated/graphql"; +import PageLoader from "@/components/shared/PageLoader"; +import Result from "@/components/shared/Result"; +import InputField from "@/components/ui/InputField"; +import { capitalize } from "@/utils/helpers"; +import ConfirmationModal from "@/components/helpers/ConfirmationModal"; +import FileUploadInputField from "@/components/ui/FileUploadInputField"; +import FieldGroup from "./FieldGroup"; +import { BRAND_NAME } from "../../../../constants"; + +interface KYCFormValues { + name: string; + address: string; + phone_number: string; + pan_number: string; + bank_name: string; + bank_branch: string; + account_number: string; + account_name: string; + registration_document: string; + pan_document: string; +} + +const KYCFormSchema = Yup.object({ + name: Yup.string().required("Required"), + address: Yup.string().required("Required"), + phone_number: Yup.string().required("Required"), + pan_number: Yup.string().required("Required"), + bank_name: Yup.string().required("Required"), + bank_branch: Yup.string().required("Required"), + account_number: Yup.string().required("Required"), + account_name: Yup.string().required("Required"), + registration_document: Yup.string().url().required("Required"), + pan_document: Yup.string().url().required("Required"), +}); + +const TenantKYC = () => { + const toast = useToast(); + const { + register: registerKYC, + handleSubmit: handleKYCSubmit, + formState: { errors: kycErrors, touchedFields: kycTouchedFields }, + reset: kycReset, + setValue: kycSetValue, + watch, + } = useForm({ + defaultValues: { + name: "", + address: "", + phone_number: "", + pan_number: "", + bank_name: "", + bank_branch: "", + account_number: "", + account_name: "", + registration_document: "", + pan_document: "", + }, + resolver: yupResolver(KYCFormSchema), + }); + + const { data, loading, error } = useTenantKycQuery({ + onCompleted: (kycData) => { + kycReset({ + name: kycData?.tenantKYC?.name ?? "", + address: kycData?.tenantKYC?.address ?? "", + phone_number: kycData?.tenantKYC?.phone_number ?? "", + pan_number: kycData?.tenantKYC?.pan_number ?? "", + bank_name: kycData?.tenantKYC?.bank_name ?? "", + bank_branch: kycData?.tenantKYC?.bank_branch ?? "", + account_number: kycData?.tenantKYC?.account_number ?? "", + account_name: kycData?.tenantKYC?.account_name ?? "", + registration_document: kycData?.tenantKYC?.registration_document ?? "", + pan_document: kycData?.tenantKYC?.pan_document ?? "", + }); + }, + }); + + const [requestKYCVerification] = useRequestKycVerficationMutation({ + refetchQueries: ["TenantKYC"], + onCompleted: () => { + toast({ + title: "KYC Verification Rquested", + status: "success", + duration: 5000, + isClosable: true, + }); + }, + onError: (kycMutateError) => { + toast({ + title: "KYC Verification Request Failed", + description: kycMutateError.message, + status: "error", + duration: 5000, + isClosable: true, + }); + }, + }); + + const { registration_document, pan_document } = watch(); + + if (loading) return ; + + if (error) { + return ( + + ); + } + + const { status, text, canSubmitDirectly } = kycStatusToText( + data?.tenantKYC?.status ?? "NOT_SUBMITTED" + ); + + return ( + + } spacing="6"> + +
{ + requestKYCVerification({ + variables: { + options: values, + }, + }); + })} + > + + + + + + {capitalize( + (data?.tenantKYC?.status ?? "NOT_SUBMITTED").replace( + "_", + " " + ) + )} + + {text} + + + + + + + + + + + + + + + + + + + + + { + kycSetValue("registration_document", ""); + }} + onUploadComplete={(res) => { + kycSetValue("registration_document", res?.[0].url ?? ""); + }} + value={registration_document} + /> + { + kycSetValue("pan_document", ""); + }} + onUploadComplete={(res) => { + kycSetValue("pan_document", res?.[0].url ?? ""); + }} + value={pan_document} + /> + + {canSubmitDirectly ? ( + + ) : ( + {}} + headerText="KYC Verification Re-Request" + bodyText={`Your KYC is already verified. Are you sure you want to re-request it? ⚠️ You'll lose the verified status`} + > + Re-request KYC Verification + + )} + +
+
+
+
+ ); +}; + +export default TenantKYC; + +const kycStatusToText = ( + status: string +): { + canSubmitDirectly: boolean; + status: "info" | "warning" | "success" | "error" | "loading" | undefined; + text: string; +} => { + switch (status) { + case "VERIFIED": + return { + canSubmitDirectly: false, + status: "success", + text: "KYC is successfully verified. You have access to all the perks. Resubmitting this form will remove your verified status.", + }; + case "IN_PROGRESS": + return { + canSubmitDirectly: false, + status: "warning", + text: "We are verifying your KYC. You will get the green tick in no time. Resubmitting this form will push your request to the back of the queue.", + }; + case "FAILED": + return { + canSubmitDirectly: true, + status: "error", + text: "There was an issue with you verification request. Please retry submitting.", + }; + default: + return { + canSubmitDirectly: true, + status: "info", + text: "Submit you KYC Verification Request to get verified and get access to all the perks.", + }; + } +}; diff --git a/apps/admin/src/components/ui/FileUploadInputField.tsx b/apps/admin/src/components/ui/FileUploadInputField.tsx new file mode 100644 index 0000000..f751277 --- /dev/null +++ b/apps/admin/src/components/ui/FileUploadInputField.tsx @@ -0,0 +1,96 @@ +import { InputHTMLAttributes } from "react"; +import { + BoxProps, + FormControl, + FormErrorMessage, + FormLabel, + HStack, + IconButton, + type IconButtonProps, + useToast, +} from "@chakra-ui/react"; +import { FieldError } from "react-hook-form"; +import Image from "next/image"; +import { AiOutlineClose } from "react-icons/ai"; +import { type UploadFileResponse } from "uploadthing/client"; +import { UploadButton } from "@/utils/uploadthing"; + +type FileUploadInputFieldProps = Omit & + Omit, "size"> & { + size?: "lg" | "md" | "sm" | "xs"; + name: string; + label: string; + error: FieldError | undefined; + showErrorMessage?: boolean; + showLabel?: boolean; + value: string; + onUploadComplete: (res: UploadFileResponse[] | undefined) => void; + onResetField: () => void; + resetIconProps?: IconButtonProps; + }; + +const FileUploadInputField = (props: FileUploadInputFieldProps) => { + const toast = useToast(); + + const { + error, + showErrorMessage, + showLabel, + name, + label, + value, + onUploadComplete, + onResetField, + resetIconProps, + } = props; + + return ( + + + {label} + + {showErrorMessage && error?.message} + + + {value ? ( + + Pan/VAT Document + } + aria-label="Delete Image" + onClick={onResetField} + {...resetIconProps} + /> + + ) : ( + { + toast({ + title: "KYC Document Upload Failed", + description: uploadError.message, + status: "error", + duration: 5000, + isClosable: true, + }); + }} + /> + )} + + ); +}; + +FileUploadInputField.defaultProps = { + showErrorMessage: true, + showLabel: true, + size: "md", + resetIconProps: {}, +}; + +export default FileUploadInputField; diff --git a/apps/admin/src/generated/graphql.ts b/apps/admin/src/generated/graphql.ts index d8f02cb..c3b156b 100644 --- a/apps/admin/src/generated/graphql.ts +++ b/apps/admin/src/generated/graphql.ts @@ -255,6 +255,7 @@ export type Mutation = { logout: Scalars["Boolean"]; register: UserResponse; removeFromFavourite: Scalars["Boolean"]; + requestKYCVerfication: TenantKyc; resendVerificationEmail: Scalars["Boolean"]; resolveByCustomer: Scalars["Boolean"]; updateAddress: Address; @@ -403,6 +404,10 @@ export type MutationRemoveFromFavouriteArgs = { productId: Scalars["Int"]; }; +export type MutationRequestKycVerficationArgs = { + options: TenantKycInput; +}; + export type MutationResendVerificationEmailArgs = { email: Scalars["String"]; }; @@ -718,6 +723,7 @@ export type Query = { staffs?: Maybe>; tenantCategories: Array; tenantContacts?: Maybe; + tenantKYC?: Maybe; userByEmail: UserDataResponse; variants: Array; verifyDomain: VerifyDomainResponse; @@ -877,6 +883,38 @@ export type TenantContactInput = { whatsapp?: InputMaybe; }; +export type TenantKycInput = { + account_name: Scalars["String"]; + account_number: Scalars["String"]; + address: Scalars["String"]; + bank_branch: Scalars["String"]; + bank_name: Scalars["String"]; + name: Scalars["String"]; + pan_document: Scalars["String"]; + pan_number: Scalars["String"]; + phone_number: Scalars["String"]; + registration_document: Scalars["String"]; +}; + +export type TenantKyc = { + __typename?: "TenantKyc"; + account_name: Scalars["String"]; + account_number: Scalars["String"]; + address: Scalars["String"]; + bank_branch: Scalars["String"]; + bank_name: Scalars["String"]; + created_at: Scalars["String"]; + id: Scalars["Int"]; + name: Scalars["String"]; + pan_document: Scalars["String"]; + pan_number: Scalars["String"]; + phone_number: Scalars["String"]; + registration_document: Scalars["String"]; + status: Scalars["String"]; + tenantId: Scalars["Int"]; + updated_at: Scalars["String"]; +}; + export type UpdateCategoryInput = { desc?: InputMaybe; identifier?: InputMaybe; @@ -1094,6 +1132,32 @@ export type UpdateSubDomainMutation = { }; }; +export type RequestKycVerficationMutationVariables = Exact<{ + options: TenantKycInput; +}>; + +export type RequestKycVerficationMutation = { + __typename?: "Mutation"; + requestKYCVerfication: { + __typename?: "TenantKyc"; + id: number; + name: string; + address: string; + phone_number: string; + pan_number: string; + bank_name: string; + bank_branch: string; + account_number: string; + account_name: string; + registration_document: string; + pan_document: string; + status: string; + tenantId: number; + created_at: string; + updated_at: string; + }; +}; + export type CreateShippingMethodMutationVariables = Exact<{ price: Scalars["Int"]; dispatch_in: Scalars["Int"]; @@ -1822,6 +1886,30 @@ export type TenantContactsQuery = { } | null; }; +export type TenantKycQueryVariables = Exact<{ [key: string]: never }>; + +export type TenantKycQuery = { + __typename?: "Query"; + tenantKYC?: { + __typename?: "TenantKyc"; + id: number; + name: string; + address: string; + phone_number: string; + pan_number: string; + bank_name: string; + bank_branch: string; + account_number: string; + account_name: string; + registration_document: string; + pan_document: string; + status: string; + tenantId: number; + created_at: string; + updated_at: string; + } | null; +}; + export type UserByEmailQueryVariables = Exact<{ email: Scalars["String"]; }>; @@ -2063,6 +2151,70 @@ export type UpdateSubDomainMutationOptions = Apollo.BaseMutationOptions< UpdateSubDomainMutation, UpdateSubDomainMutationVariables >; +export const RequestKycVerficationDocument = gql` + mutation RequestKYCVerfication($options: TenantKYCInput!) { + requestKYCVerfication(options: $options) { + id + name + address + phone_number + pan_number + bank_name + bank_branch + account_number + account_name + registration_document + pan_document + status + tenantId + created_at + updated_at + } + } +`; +export type RequestKycVerficationMutationFn = Apollo.MutationFunction< + RequestKycVerficationMutation, + RequestKycVerficationMutationVariables +>; + +/** + * __useRequestKycVerficationMutation__ + * + * To run a mutation, you first call `useRequestKycVerficationMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useRequestKycVerficationMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [requestKycVerficationMutation, { data, loading, error }] = useRequestKycVerficationMutation({ + * variables: { + * options: // value for 'options' + * }, + * }); + */ +export function useRequestKycVerficationMutation( + baseOptions?: Apollo.MutationHookOptions< + RequestKycVerficationMutation, + RequestKycVerficationMutationVariables + > +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useMutation< + RequestKycVerficationMutation, + RequestKycVerficationMutationVariables + >(RequestKycVerficationDocument, options); +} +export type RequestKycVerficationMutationHookResult = ReturnType< + typeof useRequestKycVerficationMutation +>; +export type RequestKycVerficationMutationResult = + Apollo.MutationResult; +export type RequestKycVerficationMutationOptions = Apollo.BaseMutationOptions< + RequestKycVerficationMutation, + RequestKycVerficationMutationVariables +>; export const CreateShippingMethodDocument = gql` mutation CreateShippingMethod( $price: Int! @@ -3608,6 +3760,72 @@ export type TenantContactsQueryResult = Apollo.QueryResult< TenantContactsQuery, TenantContactsQueryVariables >; +export const TenantKycDocument = gql` + query TenantKYC { + tenantKYC { + id + name + address + phone_number + pan_number + bank_name + bank_branch + account_number + account_name + registration_document + pan_document + status + tenantId + created_at + updated_at + } + } +`; + +/** + * __useTenantKycQuery__ + * + * To run a query within a React component, call `useTenantKycQuery` and pass it any options that fit your needs. + * When your component renders, `useTenantKycQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useTenantKycQuery({ + * variables: { + * }, + * }); + */ +export function useTenantKycQuery( + baseOptions?: Apollo.QueryHookOptions +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useQuery( + TenantKycDocument, + options + ); +} +export function useTenantKycLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions< + TenantKycQuery, + TenantKycQueryVariables + > +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useLazyQuery( + TenantKycDocument, + options + ); +} +export type TenantKycQueryHookResult = ReturnType; +export type TenantKycLazyQueryHookResult = ReturnType< + typeof useTenantKycLazyQuery +>; +export type TenantKycQueryResult = Apollo.QueryResult< + TenantKycQuery, + TenantKycQueryVariables +>; export const UserByEmailDocument = gql` query UserByEmail($email: String!) { userByEmail(email: $email) { diff --git a/apps/admin/src/graphql/mutation/kyc/requestKYCVerification.graphql b/apps/admin/src/graphql/mutation/kyc/requestKYCVerification.graphql new file mode 100644 index 0000000..48527a6 --- /dev/null +++ b/apps/admin/src/graphql/mutation/kyc/requestKYCVerification.graphql @@ -0,0 +1,19 @@ +mutation RequestKYCVerfication($options: TenantKYCInput!) { + requestKYCVerfication(options: $options) { + id + name + address + phone_number + pan_number + bank_name + bank_branch + account_number + account_name + registration_document + pan_document + status + tenantId + created_at + updated_at + } +} diff --git a/apps/admin/src/graphql/query/tenantKYC.graphql b/apps/admin/src/graphql/query/tenantKYC.graphql new file mode 100644 index 0000000..0dfaae6 --- /dev/null +++ b/apps/admin/src/graphql/query/tenantKYC.graphql @@ -0,0 +1,19 @@ +query TenantKYC { + tenantKYC { + id + name + address + phone_number + pan_number + bank_name + bank_branch + account_number + account_name + registration_document + pan_document + status + tenantId + created_at + updated_at + } +} diff --git a/apps/admin/src/pages/dashboard/settings.tsx b/apps/admin/src/pages/dashboard/settings.tsx index b412f89..389f59b 100644 --- a/apps/admin/src/pages/dashboard/settings.tsx +++ b/apps/admin/src/pages/dashboard/settings.tsx @@ -17,6 +17,7 @@ import StoreDetails from "@/components/pages/settings/StoreDetails"; import DeliveryOptions from "@/components/pages/settings/DeliveryOptions"; import Domains from "@/components/pages/settings/Domains"; import StoreContacts from "@/components/pages/settings/Contact"; +import TenantKYC from "@/components/pages/settings/KYC"; const SETTING_TABS = [ { @@ -27,12 +28,12 @@ const SETTING_TABS = [ { icon: BiSupport, heading: "Contact", - component: () => , + component: StoreContacts, }, { icon: FiUserCheck, heading: "KYC", - component: () =>

KYC Page

, + component: TenantKYC, }, { icon: CiDeliveryTruck, diff --git a/apps/admin/src/pages/test.tsx b/apps/admin/src/pages/test.tsx new file mode 100644 index 0000000..38bdff5 --- /dev/null +++ b/apps/admin/src/pages/test.tsx @@ -0,0 +1,17 @@ +import { UploadDropzone } from "@/utils/uploadthing"; + +const TestPage = () => ( + { + // Do something with the response + console.log("Files: ", res); + }} + onUploadError={(error: Error) => { + // Do something with the error. + alert(`ERROR! ${error.message}`); + }} + /> +); + +export default TestPage; diff --git a/apps/admin/src/server/uploadthing.ts b/apps/admin/src/server/uploadthing.ts index 5c2a23d..3a37a68 100644 --- a/apps/admin/src/server/uploadthing.ts +++ b/apps/admin/src/server/uploadthing.ts @@ -5,10 +5,9 @@ const f = createUploadthing(); export const ourFileRouter = { // Router for uploading documents kycDocumentUploader: f({ - image: { maxFileCount: 5 }, - pdf: { maxFileCount: 5 }, + image: { maxFileCount: 1 }, }).onUploadComplete(async ({ file }) => { - console.log("file url", file.url); + console.log("KYC Document Uploded", file.name, file.url); }), } satisfies FileRouter; diff --git a/apps/api/src/data-source.ts b/apps/api/src/data-source.ts index de8fac4..a1262a2 100644 --- a/apps/api/src/data-source.ts +++ b/apps/api/src/data-source.ts @@ -29,6 +29,7 @@ import { IssueComment } from "./entities/IssueComment"; import { Staff } from "./entities/Staff"; import { TenantCategory } from "./entities/TenantCategory"; import { TenantContact } from "./entities/TenantContant"; +import { TenantKyc } from "./entities/TenantKyc"; export const AppDataSource = new DataSource({ type: "postgres", @@ -64,6 +65,7 @@ export const AppDataSource = new DataSource({ Staff, TenantCategory, TenantContact, + TenantKyc, ], migrations: ["dist/migration/**/*.js"], subscribers: [], diff --git a/apps/api/src/entities/Tenant.ts b/apps/api/src/entities/Tenant.ts index ac77a97..2b629f0 100644 --- a/apps/api/src/entities/Tenant.ts +++ b/apps/api/src/entities/Tenant.ts @@ -19,6 +19,7 @@ import { ProductCategory } from "./ProductCategory"; import { UserDataResponse } from "../resolvers/GqlObjets/User"; import { ShippingMethod } from "./ShippingMethod"; import { TenantContact } from "./TenantContant"; +import { TenantKyc } from "./TenantKyc"; @ObjectType() @Entity() @@ -104,6 +105,9 @@ export class Tenant extends BaseEntity { @OneToOne(() => TenantContact, (contact) => contact.tenant) contact!: TenantContact; + @OneToOne(() => TenantKyc, (kyc) => kyc.tenant) + kyc!: TenantKyc; + @Field(() => String) @CreateDateColumn() created_at = new Date(); diff --git a/apps/api/src/entities/TenantKyc.ts b/apps/api/src/entities/TenantKyc.ts new file mode 100644 index 0000000..cdab1f8 --- /dev/null +++ b/apps/api/src/entities/TenantKyc.ts @@ -0,0 +1,93 @@ +import { Field, Int, ObjectType } from "type-graphql"; +import { + Entity, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + BaseEntity, + Column, + JoinColumn, + OneToOne, +} from "typeorm"; +import { Tenant } from "./Tenant"; + +export const POSSIBLE_KYC_STATUS = [ + "NOT_SUBMITTED", + "IN_PROGRESS", + "VERIFIED", + "FAILED", +]; + +export type KYCStatus = "NOT_SUBMITTED" | "IN_PROGRESS" | "VERIFIED" | "FAILED"; + +@ObjectType() +@Entity() +export class TenantKyc extends BaseEntity { + @Field(() => Int) + @PrimaryGeneratedColumn() + id!: number; + + @Field(() => String) + @Column() + name!: string; + + @Field(() => String) + @Column() + address!: string; + + @Field(() => String) + @Column() + phone_number!: string; + + @Field(() => String) + @Column() + pan_number!: string; + + @Field(() => String) + @Column() + bank_name!: string; + + @Field(() => String) + @Column() + bank_branch!: string; + + @Field(() => String) + @Column() + account_number!: string; + + @Field(() => String) + @Column() + account_name!: string; + + @Field(() => String) + @Column() + registration_document!: string; + + @Field(() => String) + @Column() + pan_document!: string; + + @Field(() => String) + @Column({ + type: "enum", + enum: POSSIBLE_KYC_STATUS, + default: "NOT_SUBMITTED", + }) + status!: KYCStatus; + + @Field(() => Int) + @Column() + tenantId!: number; + + @OneToOne(() => Tenant, (tenant) => tenant.contact) + @JoinColumn() + tenant!: Tenant; + + @Field(() => String) + @CreateDateColumn() + created_at = new Date(); + + @Field(() => String) + @UpdateDateColumn() + updated_at = new Date(); +} diff --git a/apps/api/src/resolvers/GqlObjets/TenantKYC.ts b/apps/api/src/resolvers/GqlObjets/TenantKYC.ts new file mode 100644 index 0000000..36aa0e7 --- /dev/null +++ b/apps/api/src/resolvers/GqlObjets/TenantKYC.ts @@ -0,0 +1,34 @@ +import { Field, InputType } from "type-graphql"; + +@InputType() +export class TenantKYCInput { + @Field(() => String) + name!: string; + + @Field(() => String) + address!: string; + + @Field(() => String) + phone_number!: string; + + @Field(() => String) + pan_number!: string; + + @Field(() => String) + bank_name!: string; + + @Field(() => String) + bank_branch!: string; + + @Field(() => String) + account_number!: string; + + @Field(() => String) + account_name!: string; + + @Field(() => String) + registration_document!: string; + + @Field(() => String) + pan_document!: string; +} diff --git a/apps/api/src/resolvers/tenant.ts b/apps/api/src/resolvers/tenant.ts index b0c4939..596bb3c 100644 --- a/apps/api/src/resolvers/tenant.ts +++ b/apps/api/src/resolvers/tenant.ts @@ -12,6 +12,8 @@ import { import { MyContext } from "../types"; import { TenantContact } from "../entities/TenantContant"; import { TenantContactInput } from "./GqlObjets/TenantContact"; +import { TenantKyc } from "../entities/TenantKyc"; +import { TenantKYCInput } from "./GqlObjets/TenantKYC"; @Resolver() export class TenantResolver { @@ -86,4 +88,39 @@ export class TenantResolver { }, }); } + + @Query(() => TenantKyc, { nullable: true }) + @UseMiddleware(isVerified) + tenantKYC(@Ctx() { req }: MyContext): Promise { + return TenantKyc.findOne({ + where: { + tenantId: req.session.tenantId, + }, + }); + } + + @Mutation(() => TenantKyc) + @UseMiddleware(isVerified) + async requestKYCVerfication( + @Ctx() { req }: MyContext, + @Arg("options", () => TenantKYCInput) options: TenantKYCInput + ): Promise { + await TenantKyc.upsert( + { + ...options, + tenantId: req.session.tenantId, + status: "IN_PROGRESS", + }, + { + conflictPaths: ["tenantId"], + skipUpdateIfNoValuesChanged: true, + } + ); + + return TenantKyc.findOneOrFail({ + where: { + tenantId: req.session.tenantId, + }, + }); + } } diff --git a/apps/storefront/src/generated/graphql.ts b/apps/storefront/src/generated/graphql.ts index bff6dec..cb64e66 100644 --- a/apps/storefront/src/generated/graphql.ts +++ b/apps/storefront/src/generated/graphql.ts @@ -255,6 +255,7 @@ export type Mutation = { logout: Scalars["Boolean"]; register: UserResponse; removeFromFavourite: Scalars["Boolean"]; + requestKYCVerfication: TenantKyc; resendVerificationEmail: Scalars["Boolean"]; resolveByCustomer: Scalars["Boolean"]; updateAddress: Address; @@ -403,6 +404,10 @@ export type MutationRemoveFromFavouriteArgs = { productId: Scalars["Int"]; }; +export type MutationRequestKycVerficationArgs = { + options: TenantKycInput; +}; + export type MutationResendVerificationEmailArgs = { email: Scalars["String"]; }; @@ -717,7 +722,8 @@ export type Query = { shippingmethodsByTenant: Array; staffs?: Maybe>; tenantCategories: Array; - tenantContacts: TenantContact; + tenantContacts?: Maybe; + tenantKYC?: Maybe; userByEmail: UserDataResponse; variants: Array; verifyDomain: VerifyDomainResponse; @@ -854,7 +860,7 @@ export type TenantContact = { instagram?: Maybe; ncell?: Maybe; ntc?: Maybe; - primary: Scalars["String"]; + primary?: Maybe; secondary?: Maybe; tenantId: Scalars["Int"]; tiktok?: Maybe; @@ -869,7 +875,7 @@ export type TenantContactInput = { instagram?: InputMaybe; ncell?: InputMaybe; ntc?: InputMaybe; - primary: Scalars["String"]; + primary?: InputMaybe; secondary?: InputMaybe; tiktok?: InputMaybe; twitter?: InputMaybe; @@ -877,6 +883,38 @@ export type TenantContactInput = { whatsapp?: InputMaybe; }; +export type TenantKycInput = { + account_name: Scalars["String"]; + account_number: Scalars["String"]; + address: Scalars["String"]; + bank_branch: Scalars["String"]; + bank_name: Scalars["String"]; + name: Scalars["String"]; + pan_document: Scalars["String"]; + pan_number: Scalars["String"]; + phone_number: Scalars["String"]; + registration_document: Scalars["String"]; +}; + +export type TenantKyc = { + __typename?: "TenantKyc"; + account_name: Scalars["String"]; + account_number: Scalars["String"]; + address: Scalars["String"]; + bank_branch: Scalars["String"]; + bank_name: Scalars["String"]; + created_at: Scalars["String"]; + id: Scalars["Int"]; + name: Scalars["String"]; + pan_document: Scalars["String"]; + pan_number: Scalars["String"]; + phone_number: Scalars["String"]; + registration_document: Scalars["String"]; + status: Scalars["String"]; + tenantId: Scalars["Int"]; + updated_at: Scalars["String"]; +}; + export type UpdateCategoryInput = { desc?: InputMaybe; identifier?: InputMaybe;