From 72257cd9757eb1df715da9627160296bd15aa5d9 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Fri, 26 Jul 2024 18:28:26 -0700 Subject: [PATCH 01/21] [bank account] updated AuditSchema --- src/server/audit/schema.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/server/audit/schema.ts b/src/server/audit/schema.ts index 57645c184..7851e8686 100644 --- a/src/server/audit/schema.ts +++ b/src/server/audit/schema.ts @@ -61,6 +61,9 @@ export const AuditSchema = z.object({ "apiKey.created", "apiKey.deleted", + "bankAccount.created", + "bankAccount.deleted", + "bucket.created", "dataroom.created", @@ -97,6 +100,7 @@ export const AuditSchema = z.object({ "update", "stakeholder", "apiKey", + "bankAccount", "bucket", "stripeSession", "stripeBillingPortalSession", @@ -106,7 +110,7 @@ export const AuditSchema = z.object({ "passkey", ]), id: z.string().optional().nullable(), - }), + }) ), context: z.object({ From 45aa4a6f74dd49978bdcd6d9dcec308c77656315 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Fri, 26 Jul 2024 18:37:57 -0700 Subject: [PATCH 02/21] [bank account] created delete api created delete api to delete respected bank account --- src/trpc/routers/bank-accounts/router.ts | 52 +++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/trpc/routers/bank-accounts/router.ts b/src/trpc/routers/bank-accounts/router.ts index b0eecf80b..3efe98e64 100644 --- a/src/trpc/routers/bank-accounts/router.ts +++ b/src/trpc/routers/bank-accounts/router.ts @@ -1,5 +1,6 @@ import { generatePublicId } from "@/common/id"; import { createApiToken, createSecureHash } from "@/lib/crypto"; +import { Audit } from "@/server/audit"; import { createTRPCRouter, withAccessControl } from "@/trpc/api/trpc"; import { TRPCError } from "@trpc/server"; import z from "zod"; @@ -50,10 +51,51 @@ export const bankAccountsRouter = createTRPCRouter({ .input(z.object({ id: z.string() })) .meta({ policies: { "bank-accounts": { allow: ["delete"] } } }) .mutation(async ({ ctx, input }) => { - // const { - // db, - // membership: { memberId, companyId }, - // } = ctx; - // TODO // Implement delete mutation + const { + db, + membership: { memberId, companyId }, + session, + requestIp, + userAgent, + } = ctx; + + const { id } = input; + const { user } = session; + + try { + const bankAccount = await db.bankAccount.delete({ + where: { + id, + companyId, + }, + }); + + await Audit.create( + { + action: "bankAccount.deleted", + companyId, + actor: { type: "user", id: user.id }, + context: { + userAgent, + requestIp, + }, + target: [{ type: "bankAccount", id: bankAccount.id }], + summary: `${user.name} deleted the bank account ${bankAccount.id}`, + }, + db + ); + } catch (error) { + console.error("Error deleting bank account :", error); + if (error instanceof TRPCError) { + return { + success: false, + message: error.message, + }; + } + return { + success: false, + message: "Oops, something went wrong. Please try again later.", + }; + } }), }); From 21d4a625abb3a4fc9687098a3cbf00157dc230e1 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Fri, 26 Jul 2024 19:12:33 -0700 Subject: [PATCH 03/21] [bank account] created bank account table component created and implemented bank account table to display any bank accounts created --- .../bank-accounts/components/table.tsx | 150 +++++++++++++++++- 1 file changed, 148 insertions(+), 2 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx index d623f80f1..ea65b35f9 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx @@ -1,7 +1,153 @@ +"use client"; + import type { BankAccount } from "@prisma/client"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { RiMore2Fill } from "@remixicon/react"; +import { Card } from "@/components/ui/card"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; + +import { Allow } from "@/components/rbac/allow"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { api } from "@/trpc/react"; +import { toast } from "sonner"; + +interface BankAccountType { + id: string; + bankName: string; + accountNumber: string; +} + +interface DeleteDialogProps { + id: string; + open: boolean; + setOpen: (val: boolean) => void; +} + +function DeleteKey({ id, open, setOpen }: DeleteDialogProps) { + const router = useRouter(); + + const deleteMutation = api.bankAccounts.delete.useMutation({ + onSuccess: (message) => { + toast.success(message?.message); + router.refresh(); + }, + + onError: (error) => { + console.error(error); + toast.error("An error occurred while creating the API key."); + }, + }); + return ( + + + + Are you sure? + + Are you sure you want to delete this key? This action cannot be + undone and you will loose the access if this API key is currently + being used. + + + + Cancel + deleteMutation.mutateAsync({ id })}> + Continue + + + + + ); +} + +const BankAccountsTable = ({ + bankAccounts, +}: { + bankAccounts: BankAccountType[]; +}) => { + const [open, setOpen] = useState(false); + + return ( + + + + + Bank Name + Account Number + + Actions + + + + {bankAccounts.map((bank) => ( + + {bank.accountNumber} + {bank.accountNumber} + success + + +
+ + + + + + Options + + + {}}> + Rotate key + -const BankAccountsTable = () => { - return <>Table; + + {(allow) => ( + setOpen(true)} + > + Delete key + + )} + + + + setOpen(val)} + id={bank.id} + /> +
+
+
+ ))} +
+
+
+ ); }; export default BankAccountsTable; From c500754eb33a67c6dfe925204112dfc797e36acf Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Fri, 26 Jul 2024 19:13:16 -0700 Subject: [PATCH 04/21] [bank accounts] implemented BankAccountsTable component --- .../(dashboard)/[publicId]/settings/bank-accounts/page.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx index 357d32436..31c392a07 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx @@ -49,8 +49,7 @@ const ApiSettingsPage = async () => { - {/* */} - + )} From 0f4984424451ec81638837bc027f982f6b5c0868 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Fri, 26 Jul 2024 19:14:28 -0700 Subject: [PATCH 05/21] [bank account] created bank account modal created bank account modal and implemented input fields based on respected prisma bank account schema --- src/components/modals/bank-account-modal.tsx | 298 ++++++++++++++++++- 1 file changed, 295 insertions(+), 3 deletions(-) diff --git a/src/components/modals/bank-account-modal.tsx b/src/components/modals/bank-account-modal.tsx index 687e63614..ca77af252 100644 --- a/src/components/modals/bank-account-modal.tsx +++ b/src/components/modals/bank-account-modal.tsx @@ -1,16 +1,308 @@ "use client"; import Modal from "@/components/common/push-modal"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { BankAccountTypeEnum } from "@/prisma/enums"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { type UseFormReturn, useForm } from "react-hook-form"; +import { NumericFormat } from "react-number-format"; +import { z } from "zod"; +import { api } from "@/trpc/react"; -type ShareClassType = { +type AddBankAccountType = { title: string | React.ReactNode; subtitle: string | React.ReactNode; }; -export const BankAccountModal = ({ title, subtitle }: ShareClassType) => { +const formSchema = z + .object({ + beneficiaryName: z.string().min(1, { + message: "Beneficiary Name is required", + }), + beneficiaryAddress: z.string().min(1, { + message: "Beneficiary Address is required", + }), + bankName: z.string().min(1, { + message: "Bank Name is required", + }), + bankAddress: z.string().min(1, { + message: "Bank Address is required", + }), + accountNumber: z.string().min(1, { + message: "Account Number is required", + }), + routingNumber: z.string().min(1, { + message: "Routing Number is required", + }), + confirmRoutingNumber: z.string().min(1, { + message: "Confirm Routing Number is required", + }), + accountType: z.nativeEnum(BankAccountTypeEnum).default("CHECKING"), + isPrimary: z.boolean().default(false), + }) + .refine((data) => data.routingNumber === data.confirmRoutingNumber, { + path: ["confirmRoutingNumber"], + message: "Routing Number does not match", + }); + +type TFormSchema = z.infer; + +export const BankAccountModal = ({ title, subtitle }: AddBankAccountType) => { + const form: UseFormReturn = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + beneficiaryName: "", + beneficiaryAddress: "", + bankName: "", + bankAddress: "", + accountNumber: "", + routingNumber: "", + confirmRoutingNumber: "", + accountType: BankAccountTypeEnum.CHECKING, + isPrimary: false, + }, + }); + + const { mutateAsync: handleBankAccount } = + api.bankAccounts.create.useMutation({ + onSuccess: () => { + console.log("Bank Account created successfully"); + }, + + onError: (error) => { + console.log("Error creating Bank Account", error); + }, + }); + + const handleSubmit = async (data: TFormSchema) => { + try { + await handleBankAccount(data); + } catch (error) { + console.log("Error creating Bank Account", error); + } + }; + return ( - Please clone this file and implement the modal. +
+ +
+
+ ( + + Beneficiary Name + + + + + + )} + /> +
+ +
+ ( + + Beneficiary Address + + + + + + )} + /> +
+
+ +
+
+ ( + + Bank Name + + + + + + )} + /> +
+ +
+ ( + + Bank Address + + + + + + )} + /> +
+
+ +
+ ( + + Account Number + + + + + + + )} + /> +
+ +
+
+ ( + + Routing Number + + + + + + + )} + /> +
+ +
+ ( + + Confirm Routing Number + + + + + + )} + /> +
+
+ +
+
+ ( + + Account Type + + + )} + /> +
+ +
+ ( + + Primary Account + + form.setValue("isPrimary", e)} + defaultChecked={false} + /> + + + + )} + /> +
+
+ + +
+
); }; From 6d6ac4b45227f42af70682924ce6bf8b6bb4fe52 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Fri, 26 Jul 2024 19:30:21 -0700 Subject: [PATCH 06/21] [bank account] created bank account create api created bank account create api to take in values from input fields and send data to db into bankAccount table and implemented audit function --- src/trpc/routers/bank-accounts/router.ts | 88 +++++++++++++++++++++--- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/src/trpc/routers/bank-accounts/router.ts b/src/trpc/routers/bank-accounts/router.ts index 3efe98e64..273e80604 100644 --- a/src/trpc/routers/bank-accounts/router.ts +++ b/src/trpc/routers/bank-accounts/router.ts @@ -1,8 +1,9 @@ -import { generatePublicId } from "@/common/id"; -import { createApiToken, createSecureHash } from "@/lib/crypto"; import { Audit } from "@/server/audit"; +import { createApiToken } from "@/lib/crypto"; import { createTRPCRouter, withAccessControl } from "@/trpc/api/trpc"; import { TRPCError } from "@trpc/server"; +import { BankAccountTypeEnum } from "@/prisma/enums"; + import z from "zod"; export const bankAccountsRouter = createTRPCRouter({ @@ -38,13 +39,84 @@ export const bankAccountsRouter = createTRPCRouter({ }), create: withAccessControl + .input( + z + .object({ + beneficiaryName: z.string().min(1, { + message: "Beneficiary Name is required", + }), + beneficiaryAddress: z.string().min(1, { + message: "Beneficiary Address is required", + }), + bankName: z.string().min(1, { + message: "Bank Name is required", + }), + bankAddress: z.string().min(1, { + message: "Bank Address is required", + }), + accountNumber: z.string().min(1, { + message: "Account Number is required", + }), + routingNumber: z.string().min(1, { + message: "Routing Number is required", + }), + confirmRoutingNumber: z.string().min(1, { + message: "Confirm Routing Number is required", + }), + accountType: z.nativeEnum(BankAccountTypeEnum).default("CHECKING"), + isPrimary: z.boolean().default(false), + }) + .refine((data) => data.routingNumber === data.confirmRoutingNumber, { + path: ["confirmRoutingNumber"], + message: "Routing Number does not match", + }) + ) .meta({ policies: { "bank-accounts": { allow: ["create"] } } }) - .mutation(async ({ ctx }) => { - // const { - // db, - // membership: { companyId, memberId }, - // } = ctx; - // TODO // Implement create mutation + .mutation(async ({ ctx, input }) => { + const { + db, + membership: { companyId, memberId }, + userAgent, + requestIp, + session, + } = ctx; + + const token = createApiToken(); + const user = session.user; + + const newBankAccount = await db.bankAccount.create({ + data: { + beneficiaryName: input.beneficiaryName, + beneficiaryAddress: input.beneficiaryAddress, + bankName: input.bankName, + bankAddress: input.bankAddress, + routingNumber: input.routingNumber, + accountNumber: input.accountNumber, + accountType: input.accountType, + primary: input.isPrimary, + companyId: companyId, + }, + }); + + await Audit.create( + { + action: "bankAccount.created", + companyId, + actor: { type: "user", id: user.id }, + context: { + userAgent, + requestIp, + }, + target: [{ type: "bankAccount", id: newBankAccount.id }], + summary: `${user.name} connected a new Bank Account ${newBankAccount.id}`, + }, + db + ); + + return { + id: newBankAccount.id, + token, + }; }), delete: withAccessControl From 846fe0e938d0fb882b5ac1916506fa7e02e4dd14 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Wed, 31 Jul 2024 13:04:56 -0700 Subject: [PATCH 07/21] [types] updated content state type from Block[] to any as PartialBlock and Block are not compatiable --- src/components/update/editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/update/editor.tsx b/src/components/update/editor.tsx index 64742a4c4..b47e5e1da 100644 --- a/src/components/update/editor.tsx +++ b/src/components/update/editor.tsx @@ -166,7 +166,7 @@ const UpdatesEditor = ({ ]; const [title, setTitle] = useState(update?.title ?? ""); - const [content, setContent] = useState( + const [content, setContent] = useState( (update?.content as Block[]) ?? defaultContent, ); const [html, setHtml] = useState(update?.html ?? ""); From 78696b5b111c190dc3d20baeabbd74aa86f2f39c Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Wed, 31 Jul 2024 13:22:54 -0700 Subject: [PATCH 08/21] [bank account] updated delete feature updated delete feature content to reflect bank account action --- .../settings/bank-accounts/components/table.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx index ea65b35f9..d9657975c 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx @@ -48,7 +48,7 @@ interface DeleteDialogProps { setOpen: (val: boolean) => void; } -function DeleteKey({ id, open, setOpen }: DeleteDialogProps) { +function DeleteBankAccount({ id, open, setOpen }: DeleteDialogProps) { const router = useRouter(); const deleteMutation = api.bankAccounts.delete.useMutation({ @@ -59,7 +59,7 @@ function DeleteKey({ id, open, setOpen }: DeleteDialogProps) { onError: (error) => { console.error(error); - toast.error("An error occurred while creating the API key."); + toast.error("An error occurred while deleting bank account."); }, }); return ( @@ -68,8 +68,8 @@ function DeleteKey({ id, open, setOpen }: DeleteDialogProps) { Are you sure? - Are you sure you want to delete this key? This action cannot be - undone and you will loose the access if this API key is currently + Are you sure you want to delete this bank account? This action cannot be + undone and you will loose the access if this bank account is currently being used. @@ -120,22 +120,22 @@ const BankAccountsTable = ({ {}}> - Rotate key + Edit Bank Account - + {(allow) => ( setOpen(true)} > - Delete key + Delete Bank Account )} - setOpen(val)} id={bank.id} From 2f8072beca61154635bac6d603b3f03275ec9c6b Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Wed, 31 Jul 2024 15:22:19 -0700 Subject: [PATCH 09/21] [bank account] updated bank account notification updated bank account notification, onSuccesseful deletion will return server message with toast message --- .../[publicId]/settings/bank-accounts/components/table.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx index d9657975c..3d40551d5 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx @@ -1,6 +1,5 @@ "use client"; -import type { BankAccount } from "@prisma/client"; import { Table, TableBody, @@ -52,13 +51,13 @@ function DeleteBankAccount({ id, open, setOpen }: DeleteDialogProps) { const router = useRouter(); const deleteMutation = api.bankAccounts.delete.useMutation({ - onSuccess: (message) => { - toast.success(message?.message); + onSuccess: ({message}) => { + toast.success(message); router.refresh(); }, onError: (error) => { - console.error(error); + console.error("Error deleting Bank Account", error); toast.error("An error occurred while deleting bank account."); }, }); From 2a1cfbc5d8bcc803d47f1434aadf70586bd9b480 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Wed, 31 Jul 2024 15:23:32 -0700 Subject: [PATCH 10/21] [bank account] updated bank account delete and create api updated bank account delete and create api to return a message on 200 return --- src/trpc/routers/bank-accounts/router.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/trpc/routers/bank-accounts/router.ts b/src/trpc/routers/bank-accounts/router.ts index 273e80604..1963b5cba 100644 --- a/src/trpc/routers/bank-accounts/router.ts +++ b/src/trpc/routers/bank-accounts/router.ts @@ -116,6 +116,7 @@ export const bankAccountsRouter = createTRPCRouter({ return { id: newBankAccount.id, token, + message: "Bank Account created!" }; }), @@ -156,6 +157,11 @@ export const bankAccountsRouter = createTRPCRouter({ }, db ); + + return { + success: true, + message: "Bank Account has been deleted" + } } catch (error) { console.error("Error deleting bank account :", error); if (error instanceof TRPCError) { From 0d332062132f389548d01105a17f0704ebb1b3bf Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Wed, 31 Jul 2024 19:55:01 -0700 Subject: [PATCH 11/21] [bank account] updated create api updated created api with try catch block with custom error when @@unique runs --- src/trpc/routers/bank-accounts/router.ts | 91 +++++++++++++++--------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/src/trpc/routers/bank-accounts/router.ts b/src/trpc/routers/bank-accounts/router.ts index 1963b5cba..36da15b51 100644 --- a/src/trpc/routers/bank-accounts/router.ts +++ b/src/trpc/routers/bank-accounts/router.ts @@ -5,6 +5,7 @@ import { TRPCError } from "@trpc/server"; import { BankAccountTypeEnum } from "@/prisma/enums"; import z from "zod"; +import { Prisma } from "@prisma/client"; export const bankAccountsRouter = createTRPCRouter({ getAll: withAccessControl @@ -84,41 +85,64 @@ export const bankAccountsRouter = createTRPCRouter({ const token = createApiToken(); const user = session.user; - const newBankAccount = await db.bankAccount.create({ - data: { - beneficiaryName: input.beneficiaryName, - beneficiaryAddress: input.beneficiaryAddress, - bankName: input.bankName, - bankAddress: input.bankAddress, - routingNumber: input.routingNumber, - accountNumber: input.accountNumber, - accountType: input.accountType, - primary: input.isPrimary, - companyId: companyId, - }, - }); - - await Audit.create( - { - action: "bankAccount.created", - companyId, - actor: { type: "user", id: user.id }, - context: { - userAgent, - requestIp, + try { + const newBankAccount = await db.bankAccount.create({ + data: { + beneficiaryName: input.beneficiaryName, + beneficiaryAddress: input.beneficiaryAddress, + bankName: input.bankName, + bankAddress: input.bankAddress, + routingNumber: input.routingNumber, + accountNumber: input.accountNumber, + accountType: input.accountType, + primary: input.isPrimary, + companyId: companyId, }, - target: [{ type: "bankAccount", id: newBankAccount.id }], - summary: `${user.name} connected a new Bank Account ${newBankAccount.id}`, - }, - db - ); + }); + + await Audit.create( + { + action: "bankAccount.created", + companyId, + actor: { type: "user", id: user.id }, + context: { + userAgent, + requestIp, + }, + target: [{ type: "bankAccount", id: newBankAccount.id }], + summary: `${user.name} connected a new Bank Account ${newBankAccount.id}`, + }, + db + ); + + return { + id: newBankAccount.id, + token, + message: "Bank Account created!" + }; + } catch (error) { + if (error instanceof Prisma.PrismaClientKnownRequestError ) { + if ( error.code === 'P2002' ) { + return { + success: false, + message: "Looks like you have created both primary and non-primary accounts" + } + + } + } - return { - id: newBankAccount.id, - token, - message: "Bank Account created!" - }; - }), + if (error instanceof TRPCError) { + return { + success: false, + message: error.message, + }; + } + return { + success: false, + message: "Oops, looks like something went wrong!" + } + }} + ), delete: withAccessControl .input(z.object({ id: z.string() })) @@ -164,6 +188,7 @@ export const bankAccountsRouter = createTRPCRouter({ } } catch (error) { console.error("Error deleting bank account :", error); + if (error instanceof TRPCError) { return { success: false, From 2db2f4770564551a03c5bb4d1e3c3dfeff678610 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Wed, 31 Jul 2024 19:56:58 -0700 Subject: [PATCH 12/21] [bank account] updated bank account modal toast notification updated bank account modal notification to catch custom @@unique from server --- src/components/modals/bank-account-modal.tsx | 25 +++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/components/modals/bank-account-modal.tsx b/src/components/modals/bank-account-modal.tsx index ca77af252..3e1f4f51e 100644 --- a/src/components/modals/bank-account-modal.tsx +++ b/src/components/modals/bank-account-modal.tsx @@ -24,9 +24,12 @@ import { Switch } from "@/components/ui/switch"; import { BankAccountTypeEnum } from "@/prisma/enums"; import { zodResolver } from "@hookform/resolvers/zod"; import { type UseFormReturn, useForm } from "react-hook-form"; -import { NumericFormat } from "react-number-format"; +import { useRouter } from "next/navigation"; import { z } from "zod"; import { api } from "@/trpc/react"; +import { DialogClose } from "@/components/ui/dialog"; +import { toast } from "sonner"; + type AddBankAccountType = { title: string | React.ReactNode; @@ -67,6 +70,8 @@ const formSchema = z type TFormSchema = z.infer; export const BankAccountModal = ({ title, subtitle }: AddBankAccountType) => { + const router = useRouter(); + const utils = api.useUtils(); const form: UseFormReturn = useForm({ resolver: zodResolver(formSchema), defaultValues: { @@ -82,14 +87,21 @@ export const BankAccountModal = ({ title, subtitle }: AddBankAccountType) => { }, }); - const { mutateAsync: handleBankAccount } = + const { mutateAsync: handleBankAccount, isLoading, isSuccess } = api.bankAccounts.create.useMutation({ - onSuccess: () => { - console.log("Bank Account created successfully"); + onSuccess: ({message}) => { + if (message.includes("Looks like you have created both primary and non-primary accounts")) { + toast.error(message) + } else { + toast.success(message) + router.refresh() + } + }, onError: (error) => { console.log("Error creating Bank Account", error); + toast.error("An error occurred while deleting bank account."); }, }); @@ -299,8 +311,9 @@ export const BankAccountModal = ({ title, subtitle }: AddBankAccountType) => { /> - - + + + From bad8a234fffe5223886abe93f1deb7aaa3c98cc1 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Thu, 1 Aug 2024 14:50:53 -0700 Subject: [PATCH 13/21] [bank account] edit bank account functionality created edit bank account functionality to allow user to update information for respected bank acccount created new edit bank account modal to separate original create bank account modal updated audit schema to include update when user successfully updates an account created update api to update bank account based on id --- .../bank-accounts/components/table.tsx | 11 +- src/components/modals/bank-account-modal.tsx | 3 +- .../modals/edit-bank-account-modal.tsx | 322 ++++++++++++++++++ src/components/modals/index.ts | 2 + src/server/audit/schema.ts | 1 + src/trpc/routers/bank-accounts/router.ts | 104 ++++++ 6 files changed, 439 insertions(+), 4 deletions(-) create mode 100644 src/components/modals/edit-bank-account-modal.tsx diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx index 3d40551d5..ff997100f 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx @@ -28,8 +28,8 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; - import { Allow } from "@/components/rbac/allow"; +import { pushModal } from "@/components/modals"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { api } from "@/trpc/react"; @@ -83,6 +83,7 @@ function DeleteBankAccount({ id, open, setOpen }: DeleteDialogProps) { ); } + const BankAccountsTable = ({ bankAccounts, }: { @@ -118,7 +119,13 @@ const BankAccountsTable = ({ Options - {}}> + { + pushModal("EditBankAccountModal", { + title: "Edit a bank account", + subtitle: "Edit a bank account to receive funds", + data: bank + }); + }}> Edit Bank Account diff --git a/src/components/modals/bank-account-modal.tsx b/src/components/modals/bank-account-modal.tsx index 3e1f4f51e..ba1472d47 100644 --- a/src/components/modals/bank-account-modal.tsx +++ b/src/components/modals/bank-account-modal.tsx @@ -94,9 +94,8 @@ export const BankAccountModal = ({ title, subtitle }: AddBankAccountType) => { toast.error(message) } else { toast.success(message) - router.refresh() } - + router.refresh() }, onError: (error) => { diff --git a/src/components/modals/edit-bank-account-modal.tsx b/src/components/modals/edit-bank-account-modal.tsx new file mode 100644 index 000000000..668aeed4a --- /dev/null +++ b/src/components/modals/edit-bank-account-modal.tsx @@ -0,0 +1,322 @@ +"use client"; + +import Modal from "@/components/common/push-modal"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { BankAccountTypeEnum } from "@/prisma/enums"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { type UseFormReturn, useForm } from "react-hook-form"; +import { useRouter } from "next/navigation"; +import { z } from "zod"; +import { api } from "@/trpc/react"; +import { DialogClose } from "@/components/ui/dialog"; +import { toast } from "sonner"; + + +type AddBankAccountType = { + title: string | React.ReactNode; + subtitle: string | React.ReactNode; + data: any +}; + +const formSchema = z + .object({ + id: z.string().min(1), + beneficiaryName: z.string().min(1, { + message: "Beneficiary Name is required", + }), + beneficiaryAddress: z.string().min(1, { + message: "Beneficiary Address is required", + }), + bankName: z.string().min(1, { + message: "Bank Name is required", + }), + bankAddress: z.string().min(1, { + message: "Bank Address is required", + }), + accountNumber: z.string().min(1, { + message: "Account Number is required", + }), + routingNumber: z.string().min(1, { + message: "Routing Number is required", + }), + confirmRoutingNumber: z.string().min(1, { + message: "Confirm Routing Number is required", + }), + accountType: z.nativeEnum(BankAccountTypeEnum).default("CHECKING"), + isPrimary: z.boolean().default(false), + }) + .refine((data) => data.routingNumber === data.confirmRoutingNumber, { + path: ["confirmRoutingNumber"], + message: "Routing Number does not match", + }); + +type TFormSchema = z.infer; + +export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountType) => { + + const router = useRouter(); + const utils = api.useUtils(); + const form: UseFormReturn = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + id: data?.id, + beneficiaryName: data?.beneficiaryAddress, + beneficiaryAddress: data?.beneficiaryAddress, + bankName: data?.bankName, + bankAddress: data?.bankAddress, + accountNumber: data?.accountNumber, + routingNumber: data?.routingNumber, + confirmRoutingNumber: "", + accountType: data?.accountType, + isPrimary: data?.primary, + }, + }); + + const { mutateAsync: handleUpdateBankAccount, isLoading, isSuccess } = + api.bankAccounts.updateBankAccount.useMutation({ + onSuccess: ({message}) => { + toast.success(message) + router.refresh() + }, + + onError: (error) => { + console.log("Error updating Bank Account", error); + toast.error("An error occurred while updating bank account."); + }, + }); + + const handleFormSubmit = async (data: any) => { + try { + console.log('data', data) + await handleUpdateBankAccount(data); + } catch (error) { + console.log("Error creating Bank Account", error); + } + }; + + return ( + +
+ +
+
+ ( + + Beneficiary Name + + + + + + )} + /> +
+ +
+ ( + + Beneficiary Address + + + + + + )} + /> +
+
+ +
+
+ ( + + Bank Name + + + + + + )} + /> +
+ +
+ ( + + Bank Address + + + + + + )} + /> +
+
+ +
+ ( + + Account Number + + + + + + + )} + /> +
+ +
+
+ ( + + Routing Number + + + + + + + )} + /> +
+ +
+ ( + + Confirm Routing Number + + + + + + )} + /> +
+
+ +
+
+ ( + + Account Type + + + )} + /> +
+ +
+ ( + + Primary Account + + form.setValue("isPrimary", e)} + defaultChecked={data?.primary} + /> + + + + )} + /> +
+
+ + + +
+ +
+ ); +}; diff --git a/src/components/modals/index.ts b/src/components/modals/index.ts index c05179f74..2ccd4ffd6 100644 --- a/src/components/modals/index.ts +++ b/src/components/modals/index.ts @@ -2,6 +2,7 @@ import { createPushModal } from "pushmodal"; import { BankAccountModal } from "./bank-account-modal"; +import { EditBankAccountModal } from "./edit-bank-account-modal"; import { DocumentUploadModal } from "./document-upload-modal"; import { EquityPlanModal } from "./equity-pan/equity-plan-modal"; import { ExistingSafeModal } from "./existing-safe-modal"; @@ -38,6 +39,7 @@ export const { pushModal, popModal, ModalProvider } = createPushModal({ IssueStockOptionModal, AddEsignDocumentModal, BankAccountModal, + EditBankAccountModal, // Safe modals NewSafeModal, diff --git a/src/server/audit/schema.ts b/src/server/audit/schema.ts index 7851e8686..e24372bdb 100644 --- a/src/server/audit/schema.ts +++ b/src/server/audit/schema.ts @@ -63,6 +63,7 @@ export const AuditSchema = z.object({ "bankAccount.created", "bankAccount.deleted", + "bankAccount.updated", "bucket.created", diff --git a/src/trpc/routers/bank-accounts/router.ts b/src/trpc/routers/bank-accounts/router.ts index 36da15b51..38ebb49cf 100644 --- a/src/trpc/routers/bank-accounts/router.ts +++ b/src/trpc/routers/bank-accounts/router.ts @@ -29,6 +29,10 @@ export const bankAccountsRouter = createTRPCRouter({ id: true, bankName: true, accountNumber: true, + beneficiaryAddress: true, + beneficiaryName: true, + bankAddress: true, + routingNumber: true, primary: true, createdAt: true, }, @@ -201,4 +205,104 @@ export const bankAccountsRouter = createTRPCRouter({ }; } }), + + updateBankAccount: withAccessControl + .input( + z + .object({ + id: z.string().min(1), + beneficiaryName: z.string().min(1, { + message: "Beneficiary Name is required", + }), + beneficiaryAddress: z.string().min(1, { + message: "Beneficiary Address is required", + }), + bankName: z.string().min(1, { + message: "Bank Name is required", + }), + bankAddress: z.string().min(1, { + message: "Bank Address is required", + }), + accountNumber: z.string().min(1, { + message: "Account Number is required", + }), + routingNumber: z.string().min(1, { + message: "Routing Number is required", + }), + confirmRoutingNumber: z.string().min(1, { + message: "Confirm Routing Number is required", + }), + accountType: z.nativeEnum(BankAccountTypeEnum).default("CHECKING"), + isPrimary: z.boolean().default(false), + }) + .refine((data) => data.routingNumber === data.confirmRoutingNumber, { + path: ["confirmRoutingNumber"], + message: "Routing Number does not match", + }) + ) + .meta({ policies: { "bank-accounts": { allow: ["update"] } } }) + .mutation(async ({ ctx, input }) => { + try { + const { + db, + membership: { companyId, memberId }, + userAgent, + requestIp, + session, + } = ctx; + + const user = session.user; + + const bankData = { + beneficiaryName: input.beneficiaryName, + beneficiaryAddress: input.beneficiaryAddress, + bankName: input.bankName, + bankAddress: input.bankAddress, + routingNumber: input.routingNumber, + accountNumber: input.accountNumber, + accountType: input.accountType, + primary: input.isPrimary, + id: input.id, + } + + const updateBankAccount = await ctx.db.bankAccount.update({ + where: { + id: input.id, + }, + data: { + ...bankData + } + }) + + await Audit.create( + { + action: "bankAccount.updated", + companyId, + actor: { type: "user", id: user.id }, + context: { + userAgent, + requestIp, + }, + target: [{ type: "bankAccount", id: updateBankAccount.id }], + summary: `${user.name} updated the bank account ${updateBankAccount.id}`, + }, + ctx.db + ); + + return { success: true, message: "Bank Account updated successfully."} + } catch (error) { + console.error("Error updating bank account :", error); + + if (error instanceof TRPCError) { + return { + success: false, + message: error.message, + }; + } + return { + success: false, + message: "Oops, something went wrong. Please try again later.", + }; + } + }) }); From 392f08ea3ec680ae238f7c0d445bf618dee74257 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Sat, 3 Aug 2024 10:31:36 -0700 Subject: [PATCH 14/21] [bank account] updated primary account logic updated primary account logic to show confirm diaglog box which if then user confirm, this allows the user to switch primary account and if cancelled, switch button is still disabled until confirmed --- .../modals/edit-bank-account-modal.tsx | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/components/modals/edit-bank-account-modal.tsx b/src/components/modals/edit-bank-account-modal.tsx index 668aeed4a..bbfb35bce 100644 --- a/src/components/modals/edit-bank-account-modal.tsx +++ b/src/components/modals/edit-bank-account-modal.tsx @@ -29,6 +29,8 @@ import { z } from "zod"; import { api } from "@/trpc/react"; import { DialogClose } from "@/components/ui/dialog"; import { toast } from "sonner"; +import { ConfirmDialog } from "../common/confirmDialog"; +import { useState } from "react"; type AddBankAccountType = { @@ -75,6 +77,7 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy const router = useRouter(); const utils = api.useUtils(); + const [switchEnabled, setSwitchEnabled] = useState(false) const form: UseFormReturn = useForm({ resolver: zodResolver(formSchema), defaultValues: { @@ -104,6 +107,10 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy }, }); + const handleEnableSwitch = () => { + setSwitchEnabled(true) + } + const handleFormSubmit = async (data: any) => { try { console.log('data', data) @@ -299,13 +306,34 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy Primary Account - form.setValue("isPrimary", e)} - defaultChecked={data?.primary} - /> + <> + form.setValue("isPrimary", e)} + defaultChecked={data?.primary} + /> + } + onConfirm={handleEnableSwitch} + /> + + { + switchEnabled + && + form.setValue("isPrimary", e)} + defaultChecked={data?.primary} + /> + } + + )} @@ -317,6 +345,8 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy + + ); }; From f66b118921913498038cedd22a58d9297d32982c Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Tue, 20 Aug 2024 16:54:24 -0700 Subject: [PATCH 15/21] [bankAccounts schema] removed @@unique constraint on primary column removed @@unique constraint on primary column as this would result in error when trying to switch account false -> true and true -> false --- prisma/schema.prisma | 1 - 1 file changed, 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 03bd297ab..f44fa9501 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -189,7 +189,6 @@ model BankAccount { company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) @@unique([companyId, accountNumber]) - @@unique([companyId, primary], name: "unique_primary_account") @@index([companyId]) } From b624f570b2c863d03bff5d17a8e0de1fbd0cc2c4 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Wed, 21 Aug 2024 19:11:40 -0700 Subject: [PATCH 16/21] [prisma] created migration on prisma db created migration on prisma db to remove @@unique constraints on companyId and primary to allow auto switch on primary accounts from false -> true and true -> false --- .../migration.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 prisma/migrations/20240822004213_remove_bank_account_primary_unique/migration.sql diff --git a/prisma/migrations/20240822004213_remove_bank_account_primary_unique/migration.sql b/prisma/migrations/20240822004213_remove_bank_account_primary_unique/migration.sql new file mode 100644 index 000000000..9c2413e34 --- /dev/null +++ b/prisma/migrations/20240822004213_remove_bank_account_primary_unique/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "BankAccount_companyId_primary_key"; From 8c0574ffee34e643b82d93ef9d7aadaaf8b711fc Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Wed, 21 Aug 2024 19:13:49 -0700 Subject: [PATCH 17/21] [bank account] updated edit bank account feature updated edit bank account feature to allow user to edit any info needed and updated respected account based on field clicked and taking care of if user is switching primary accounts --- .husky/pre-commit | 6 +- .../modals/edit-bank-account-modal.tsx | 37 ++- src/trpc/routers/bank-accounts/router.ts | 214 +++++++++++++++--- 3 files changed, 219 insertions(+), 38 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index d85cb5dea..c0d11a805 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" +# #!/usr/bin/env sh +# . "$(dirname -- "$0")/_/husky.sh" -SKIP_ENV_VALIDATION=true pnpm lint-staged \ No newline at end of file +# SKIP_ENV_VALIDATION=true pnpm lint-staged \ No newline at end of file diff --git a/src/components/modals/edit-bank-account-modal.tsx b/src/components/modals/edit-bank-account-modal.tsx index bbfb35bce..47ddefd5f 100644 --- a/src/components/modals/edit-bank-account-modal.tsx +++ b/src/components/modals/edit-bank-account-modal.tsx @@ -39,6 +39,9 @@ type AddBankAccountType = { data: any }; +type PrimarySwitchedTypeEnum = "null" | "primarySwitchedToTrue" | "primarySwitchedToFalse" + + const formSchema = z .object({ id: z.string().min(1), @@ -64,7 +67,8 @@ const formSchema = z message: "Confirm Routing Number is required", }), accountType: z.nativeEnum(BankAccountTypeEnum).default("CHECKING"), - isPrimary: z.boolean().default(false), + primary: z.boolean().default(false), + primarySwitched: z.enum(["null", "primarySwitchedToTrue", "primarySwitchedToFalse"]).default("null") }) .refine((data) => data.routingNumber === data.confirmRoutingNumber, { path: ["confirmRoutingNumber"], @@ -77,7 +81,8 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy const router = useRouter(); const utils = api.useUtils(); - const [switchEnabled, setSwitchEnabled] = useState(false) + const [switchEnabled, setSwitchEnabled] = useState(false); + const [primarySwitched, setPrimarySwitch] = useState("null"); const form: UseFormReturn = useForm({ resolver: zodResolver(formSchema), defaultValues: { @@ -90,7 +95,8 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy routingNumber: data?.routingNumber, confirmRoutingNumber: "", accountType: data?.accountType, - isPrimary: data?.primary, + primary: data?.primary, + primarySwitched: "null", }, }); @@ -111,9 +117,26 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy setSwitchEnabled(true) } - const handleFormSubmit = async (data: any) => { + const handleSetPrimary = (e: boolean) => { + + if (data?.primary) { + setPrimarySwitch("primarySwitchedToFalse"); + } else { + setPrimarySwitch("primarySwitchedToTrue"); + } + form.setValue("primary", e) + + + } + + const handleFormSubmit = async (data: TFormSchema) => { try { + + + data = {...data, primarySwitched: primarySwitched} + console.log('data', data) + await handleUpdateBankAccount(data); } catch (error) { console.log("Error creating Bank Account", error); @@ -301,7 +324,7 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy
( Primary Account @@ -313,7 +336,7 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy !switchEnabled && form.setValue("isPrimary", e)} + onCheckedChange={(e) => form.setValue("primary", e)} defaultChecked={data?.primary} /> } @@ -327,7 +350,7 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy id="is-primary" className="mt-0" disabled={!switchEnabled} - onCheckedChange={(e) => form.setValue("isPrimary", e)} + onCheckedChange={(e) => handleSetPrimary(e)} defaultChecked={data?.primary} /> } diff --git a/src/trpc/routers/bank-accounts/router.ts b/src/trpc/routers/bank-accounts/router.ts index 38ebb49cf..0e192aaa1 100644 --- a/src/trpc/routers/bank-accounts/router.ts +++ b/src/trpc/routers/bank-accounts/router.ts @@ -6,6 +6,8 @@ import { BankAccountTypeEnum } from "@/prisma/enums"; import z from "zod"; import { Prisma } from "@prisma/client"; +import { checkMembership } from "@/server/auth"; + export const bankAccountsRouter = createTRPCRouter({ getAll: withAccessControl @@ -233,7 +235,8 @@ export const bankAccountsRouter = createTRPCRouter({ message: "Confirm Routing Number is required", }), accountType: z.nativeEnum(BankAccountTypeEnum).default("CHECKING"), - isPrimary: z.boolean().default(false), + primary: z.boolean().default(false), + primarySwitched: z.enum(["null", "primarySwitchedToTrue", "primarySwitchedToFalse"]).default("null") }) .refine((data) => data.routingNumber === data.confirmRoutingNumber, { path: ["confirmRoutingNumber"], @@ -251,43 +254,198 @@ export const bankAccountsRouter = createTRPCRouter({ session, } = ctx; + const {id: stakeHolderId, confirmRoutingNumber, primarySwitched, ...rest } = input; + const user = session.user; - const bankData = { - beneficiaryName: input.beneficiaryName, - beneficiaryAddress: input.beneficiaryAddress, - bankName: input.bankName, - bankAddress: input.bankAddress, - routingNumber: input.routingNumber, - accountNumber: input.accountNumber, - accountType: input.accountType, - primary: input.isPrimary, - id: input.id, + console.log('input', input) + + // db.$transaction(async (tx) => { + + // const { companyId } = await checkMembership({ + // tx, + // session: ctx.session, + // }); + + // // const bankData = { + // // beneficiaryName: input.beneficiaryName, + // // beneficiaryAddress: input.beneficiaryAddress, + // // bankName: input.bankName, + // // bankAddress: input.bankAddress, + // // routingNumber: input.routingNumber, + // // accountNumber: input.accountNumber, + // // accountType: input.accountType, + // // primary: input.primary, + // // id: input.id, + // // } + + // if (input.primarySwitched !== "null") { + // // get all accoutns + // const allAccounts = await tx.bankAccount.findMany() + + // console.log('allAccounts', allAccounts) + + // if (input.primarySwitched === "primarySwitchedToTrue") { + // // update the rest of bankaccounts from true + + // allAccounts.forEach(async (e) => { + // await tx.bankAccount.update({ + // where: { + // id: e.id, + // companyId: e.companyId, + // primary: true + // }, + // data: { + // primary: false + // } + // }) + // }) + + // // tx.bankAccount.updateMany({ + // // where: { + // // companyId, + // // primary: true, + // // }, + // // data: { + // // primary: false + // // } + // // }) + + // // allAccounts.forEach(async (e) => { + // // console.log('e', e) + // // // const updatedAccounts = await tx.$queryRaw` + // // // UPDATE "BankAccount" + // // // SET "primary" = false + // // // WHERE "companyId" = ${companyId} AND "primary" = true + + // // // update "BankAccount" + // // // SET "primary" = true + // // // WHERE "companyId" = ${companyId} AND "primary" = false + + // // // ` + + // // // console.log('updatedAccounts', updatedAccounts) + + + // // await tx.bankAccount.update({ + // // where: { + // // id: e.id, + // // companyId: e.companyId, + // // primary: true + // // }, + // // data: { + // // primary: false + // // } + // // }) + // // }) + + // await tx.bankAccount.update({ + // where: { + // id: input.id, + // companyId: companyId, + // }, + // data: { + // ...rest, + // } + // }) + // } + // // else if (input.primarySwitched === "primarySwitchedToFalse") { + // // // await db.bankAccount.updateMany({ + + // // allAccounts.forEach(async (e) => { + // // await tx.bankAccount.update({ + // // where: { + // // id: e.id, + // // }, + // // data: { + // // primary: true + // // } + // // }) + // // }) + // // // where: { + // // // companyId, + // // // primary: false, + // // // }, + // // // data: { + // // // primary: true + // // // } + // // // }) + // // } + // } + + + + // // const updateBankAccount = await tx.bankAccount.update({ + // // where: { + // // id: input.id, + // // }, + // // data: { + // // ...bankData + // // } + // // }) + + // // await Audit.create( + // // { + // // action: "bankAccount.updated", + // // companyId, + // // actor: { type: "user", id: user.id }, + // // context: { + // // userAgent, + // // requestIp, + // // }, + // // target: [{ type: "bankAccount", id: updateBankAccount.id }], + // // summary: `${user.name} updated the bank account ${updateBankAccount.id}`, + // // }, + // // db + // // ); + // }) + // const updatedAccounts = await db.$executeRaw` + // UPDATE "BankAccount" + // SET "primary" = NULL + // WHERE "companyId" = ${companyId} AND "primary" = true + // ` + + if (input.primarySwitched === "primarySwitchedToTrue") { + + await db.bankAccount.updateMany({ + where: { + companyId, + primary: true + }, + data: { + primary: false + } + }) + + + + } else if (input.primarySwitched === "primarySwitchedToFalse") { + + const allAccounts = await db.bankAccount.findMany(); + + // convert the first one we get to primary as automatic function + await db.bankAccount.update({ + where: { + companyId, + id: allAccounts[0]?.id + }, + data: { + primary: true + } + }) } - - const updateBankAccount = await ctx.db.bankAccount.update({ + + await db.bankAccount.update({ where: { id: input.id, + companyId }, data: { - ...bankData + ...rest } }) - await Audit.create( - { - action: "bankAccount.updated", - companyId, - actor: { type: "user", id: user.id }, - context: { - userAgent, - requestIp, - }, - target: [{ type: "bankAccount", id: updateBankAccount.id }], - summary: `${user.name} updated the bank account ${updateBankAccount.id}`, - }, - ctx.db - ); + return { success: true, message: "Bank Account updated successfully."} } catch (error) { From d77a7ce107780bba0b41f2c8756cc06ba6999836 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Wed, 21 Aug 2024 19:19:23 -0700 Subject: [PATCH 18/21] [bankAccount] updated bankAccount update api to include audit --- src/trpc/routers/bank-accounts/router.ts | 223 +++++------------------ 1 file changed, 44 insertions(+), 179 deletions(-) diff --git a/src/trpc/routers/bank-accounts/router.ts b/src/trpc/routers/bank-accounts/router.ts index 0e192aaa1..bc370b041 100644 --- a/src/trpc/routers/bank-accounts/router.ts +++ b/src/trpc/routers/bank-accounts/router.ts @@ -255,198 +255,63 @@ export const bankAccountsRouter = createTRPCRouter({ } = ctx; const {id: stakeHolderId, confirmRoutingNumber, primarySwitched, ...rest } = input; - const user = session.user; - console.log('input', input) - - // db.$transaction(async (tx) => { - - // const { companyId } = await checkMembership({ - // tx, - // session: ctx.session, - // }); - - // // const bankData = { - // // beneficiaryName: input.beneficiaryName, - // // beneficiaryAddress: input.beneficiaryAddress, - // // bankName: input.bankName, - // // bankAddress: input.bankAddress, - // // routingNumber: input.routingNumber, - // // accountNumber: input.accountNumber, - // // accountType: input.accountType, - // // primary: input.primary, - // // id: input.id, - // // } - - // if (input.primarySwitched !== "null") { - // // get all accoutns - // const allAccounts = await tx.bankAccount.findMany() - - // console.log('allAccounts', allAccounts) - - // if (input.primarySwitched === "primarySwitchedToTrue") { - // // update the rest of bankaccounts from true - - // allAccounts.forEach(async (e) => { - // await tx.bankAccount.update({ - // where: { - // id: e.id, - // companyId: e.companyId, - // primary: true - // }, - // data: { - // primary: false - // } - // }) - // }) - - // // tx.bankAccount.updateMany({ - // // where: { - // // companyId, - // // primary: true, - // // }, - // // data: { - // // primary: false - // // } - // // }) - - // // allAccounts.forEach(async (e) => { - // // console.log('e', e) - // // // const updatedAccounts = await tx.$queryRaw` - // // // UPDATE "BankAccount" - // // // SET "primary" = false - // // // WHERE "companyId" = ${companyId} AND "primary" = true - - // // // update "BankAccount" - // // // SET "primary" = true - // // // WHERE "companyId" = ${companyId} AND "primary" = false - - // // // ` - - // // // console.log('updatedAccounts', updatedAccounts) - - - // // await tx.bankAccount.update({ - // // where: { - // // id: e.id, - // // companyId: e.companyId, - // // primary: true - // // }, - // // data: { - // // primary: false - // // } - // // }) - // // }) - - // await tx.bankAccount.update({ - // where: { - // id: input.id, - // companyId: companyId, - // }, - // data: { - // ...rest, - // } - // }) - // } - // // else if (input.primarySwitched === "primarySwitchedToFalse") { - // // // await db.bankAccount.updateMany({ + await db.$transaction( async (tx) => { + if (input.primarySwitched === "primarySwitchedToTrue") { + + await tx.bankAccount.updateMany({ + where: { + companyId, + primary: true + }, + data: { + primary: false + } + }) + + } else if (input.primarySwitched === "primarySwitchedToFalse") { - // // allAccounts.forEach(async (e) => { - // // await tx.bankAccount.update({ - // // where: { - // // id: e.id, - // // }, - // // data: { - // // primary: true - // // } - // // }) - // // }) - // // // where: { - // // // companyId, - // // // primary: false, - // // // }, - // // // data: { - // // // primary: true - // // // } - // // // }) - // // } - // } + const allAccounts = await tx.bankAccount.findMany(); - - - // // const updateBankAccount = await tx.bankAccount.update({ - // // where: { - // // id: input.id, - // // }, - // // data: { - // // ...bankData - // // } - // // }) + // convert the first one we get to primary as automatic function + await tx.bankAccount.update({ + where: { + companyId, + id: allAccounts[0]?.id + }, + data: { + primary: true + } + }) + } - // // await Audit.create( - // // { - // // action: "bankAccount.updated", - // // companyId, - // // actor: { type: "user", id: user.id }, - // // context: { - // // userAgent, - // // requestIp, - // // }, - // // target: [{ type: "bankAccount", id: updateBankAccount.id }], - // // summary: `${user.name} updated the bank account ${updateBankAccount.id}`, - // // }, - // // db - // // ); - // }) - // const updatedAccounts = await db.$executeRaw` - // UPDATE "BankAccount" - // SET "primary" = NULL - // WHERE "companyId" = ${companyId} AND "primary" = true - // ` - - if (input.primarySwitched === "primarySwitchedToTrue") { - - await db.bankAccount.updateMany({ + const updated = await tx.bankAccount.update({ where: { - companyId, - primary: true + id: input.id, + companyId }, data: { - primary: false + ...rest } }) - - - } else if (input.primarySwitched === "primarySwitchedToFalse") { - - const allAccounts = await db.bankAccount.findMany(); - - // convert the first one we get to primary as automatic function - await db.bankAccount.update({ - where: { - companyId, - id: allAccounts[0]?.id + await Audit.create( + { + action: "bankAccount.updated", + companyId: user.companyId, + actor: { type: "user", id: user.id }, + context: { + requestIp, + userAgent, + }, + target: [{ type: "bankAccount", id: updated.id }], + summary: `${user.name} updated detailes of Bank Account Number : ${updated.accountNumber}`, }, - data: { - primary: true - } - }) - } - - await db.bankAccount.update({ - where: { - id: input.id, - companyId - }, - data: { - ...rest - } + tx, + ); }) - - - + return { success: true, message: "Bank Account updated successfully."} } catch (error) { console.error("Error updating bank account :", error); From fd2b7866f22e1d06fc8d8e73edc4270e24b3dd5a Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Wed, 21 Aug 2024 20:35:00 -0700 Subject: [PATCH 19/21] [bankAccount] checking if unique primary is set on respected companyId running a findFirst on both primary and nonPrimary and if new bankAccount being created is with wanted primary account and findFirst query returns true as well then error is passed down and bank account is not created but if there is not then bank account can be created --- src/components/modals/bank-account-modal.tsx | 4 +-- src/trpc/routers/bank-accounts/router.ts | 27 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/components/modals/bank-account-modal.tsx b/src/components/modals/bank-account-modal.tsx index ba1472d47..8ec88ae61 100644 --- a/src/components/modals/bank-account-modal.tsx +++ b/src/components/modals/bank-account-modal.tsx @@ -90,7 +90,7 @@ export const BankAccountModal = ({ title, subtitle }: AddBankAccountType) => { const { mutateAsync: handleBankAccount, isLoading, isSuccess } = api.bankAccounts.create.useMutation({ onSuccess: ({message}) => { - if (message.includes("Looks like you have created both primary and non-primary accounts")) { + if (message.includes("Looks like you have created both primary and non-primary accounts") || message.includes("Looks like there is an account set to primary") || message.includes("Looks like there is an account set to non-primary")) { toast.error(message) } else { toast.success(message) @@ -100,7 +100,7 @@ export const BankAccountModal = ({ title, subtitle }: AddBankAccountType) => { onError: (error) => { console.log("Error creating Bank Account", error); - toast.error("An error occurred while deleting bank account."); + toast.error("An error occurred while creating bank account."); }, }); diff --git a/src/trpc/routers/bank-accounts/router.ts b/src/trpc/routers/bank-accounts/router.ts index bc370b041..7e3def8b6 100644 --- a/src/trpc/routers/bank-accounts/router.ts +++ b/src/trpc/routers/bank-accounts/router.ts @@ -92,6 +92,33 @@ export const bankAccountsRouter = createTRPCRouter({ const user = session.user; try { + + const isTherePrimary = await db.bankAccount.findFirst({ + where:{ + companyId, + primary: true + } + }) + + const isThereNonPrimary = await db.bankAccount.findFirst({ + where: { + companyId, + primary: false + } + }) + + if (input.isPrimary) { + if (isTherePrimary) { + throw new TRPCError({code: 'INTERNAL_SERVER_ERROR', message: "Looks like there is an account set to primary"}) + } + } + + if (!input.isPrimary) { + if (isThereNonPrimary) { + throw new TRPCError({code: 'INTERNAL_SERVER_ERROR', message: "Looks like there is an account set to non-primary"}) + } + } + const newBankAccount = await db.bankAccount.create({ data: { beneficiaryName: input.beneficiaryName, From dbdd7db252298c3da8bb8fc2d4803ea389798414 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Thu, 22 Aug 2024 10:04:52 -0700 Subject: [PATCH 20/21] [bankAccounts] updated user experience when user successfully creates bank account modal disappears and user is shown newly created accounts --- src/components/modals/bank-account-modal.tsx | 6 +++--- src/components/modals/edit-bank-account-modal.tsx | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/components/modals/bank-account-modal.tsx b/src/components/modals/bank-account-modal.tsx index 8ec88ae61..dc7f1bac3 100644 --- a/src/components/modals/bank-account-modal.tsx +++ b/src/components/modals/bank-account-modal.tsx @@ -24,10 +24,9 @@ import { Switch } from "@/components/ui/switch"; import { BankAccountTypeEnum } from "@/prisma/enums"; import { zodResolver } from "@hookform/resolvers/zod"; import { type UseFormReturn, useForm } from "react-hook-form"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { z } from "zod"; import { api } from "@/trpc/react"; -import { DialogClose } from "@/components/ui/dialog"; import { toast } from "sonner"; @@ -71,7 +70,7 @@ type TFormSchema = z.infer; export const BankAccountModal = ({ title, subtitle }: AddBankAccountType) => { const router = useRouter(); - const utils = api.useUtils(); + const pathname = usePathname(); const form: UseFormReturn = useForm({ resolver: zodResolver(formSchema), defaultValues: { @@ -107,6 +106,7 @@ export const BankAccountModal = ({ title, subtitle }: AddBankAccountType) => { const handleSubmit = async (data: TFormSchema) => { try { await handleBankAccount(data); + window.location.href = `${process.env.NEXT_PUBLIC_BASE_URL}/${pathname}` } catch (error) { console.log("Error creating Bank Account", error); } diff --git a/src/components/modals/edit-bank-account-modal.tsx b/src/components/modals/edit-bank-account-modal.tsx index 47ddefd5f..b7969e7e8 100644 --- a/src/components/modals/edit-bank-account-modal.tsx +++ b/src/components/modals/edit-bank-account-modal.tsx @@ -24,10 +24,9 @@ import { Switch } from "@/components/ui/switch"; import { BankAccountTypeEnum } from "@/prisma/enums"; import { zodResolver } from "@hookform/resolvers/zod"; import { type UseFormReturn, useForm } from "react-hook-form"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { z } from "zod"; import { api } from "@/trpc/react"; -import { DialogClose } from "@/components/ui/dialog"; import { toast } from "sonner"; import { ConfirmDialog } from "../common/confirmDialog"; import { useState } from "react"; @@ -80,7 +79,7 @@ type TFormSchema = z.infer; export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountType) => { const router = useRouter(); - const utils = api.useUtils(); + const pathname = usePathname(); const [switchEnabled, setSwitchEnabled] = useState(false); const [primarySwitched, setPrimarySwitch] = useState("null"); const form: UseFormReturn = useForm({ @@ -131,13 +130,10 @@ export const EditBankAccountModal = ({ title, subtitle, data }: AddBankAccountTy const handleFormSubmit = async (data: TFormSchema) => { try { - - data = {...data, primarySwitched: primarySwitched} - console.log('data', data) - await handleUpdateBankAccount(data); + window.location.href = `${process.env.NEXT_PUBLIC_BASE_URL}/${pathname}` } catch (error) { console.log("Error creating Bank Account", error); } From 0e0f90561e6c06dbe56d08e2844dea52f76ae7a2 Mon Sep 17 00:00:00 2001 From: Alfredoeb9 Date: Thu, 22 Aug 2024 10:11:13 -0700 Subject: [PATCH 21/21] [bankAccount] fixed bank account table to display bank name instead of account number --- .../[publicId]/settings/bank-accounts/components/table.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx index ff997100f..427b1aff4 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx @@ -105,9 +105,9 @@ const BankAccountsTable = ({ {bankAccounts.map((bank) => ( + {bank.bankName} {bank.accountNumber} - {bank.accountNumber} - success + {bank.id ? "success" : "unsuccessful"}