From 20a48cc778d3d374dc31e13d1949df1600839c62 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 12 Aug 2024 15:28:54 +0530 Subject: [PATCH 01/48] feat: add convertible note schema --- src/server/api/schema/convertible-note.ts | 61 +++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/server/api/schema/convertible-note.ts diff --git a/src/server/api/schema/convertible-note.ts b/src/server/api/schema/convertible-note.ts new file mode 100644 index 000000000..102196873 --- /dev/null +++ b/src/server/api/schema/convertible-note.ts @@ -0,0 +1,61 @@ +import { + ConvertibleInterestAccrualEnum, + ConvertibleInterestMethodEnum, + ConvertibleInterestPaymentScheduleEnum, + ConvertibleStatusEnum, + ConvertibleTypeEnum, +} from "@/prisma/enums"; +import { z } from "@hono/zod-openapi"; + +const ConvertibleStatus = Object.values(ConvertibleStatusEnum) as [ + ConvertibleStatusEnum, + ...ConvertibleStatusEnum[], +]; + +const ConvertibleType = Object.values(ConvertibleTypeEnum) as [ + ConvertibleTypeEnum, + ...ConvertibleTypeEnum[], +]; + +const ConvertibleInterestMethod = Object.values( + ConvertibleInterestMethodEnum, +) as [ConvertibleInterestMethodEnum, ...ConvertibleInterestMethodEnum[]]; + +const ConvertibleInterestPaymentSchedule = Object.values( + ConvertibleInterestPaymentScheduleEnum, +) as [ + ConvertibleInterestPaymentScheduleEnum, + ...ConvertibleInterestPaymentScheduleEnum[], +]; + +const ConvertibleInterestAccrual = Object.values( + ConvertibleInterestAccrualEnum, +) as [ConvertibleInterestAccrualEnum, ...ConvertibleInterestAccrualEnum[]]; + +export const ConvertibleNoteSchema = z + .object({ + id: z.string().cuid(), + publicId: z.string(), + capital: z.number(), + conversionCap: z.number().nullable(), + discountRate: z.number().nullable(), + mfn: z.boolean().nullable(), + additionalTerms: z.string().nullable(), + interestRate: z.number().nullable(), + stakeholderId: z.string(), + companyId: z.string(), + issueDate: z.coerce.date(), + boardApprovalDate: z.coerce.date(), + + status: z.enum(ConvertibleStatus), + type: z.enum(ConvertibleType), + interestMethod: z.enum(ConvertibleInterestMethod).nullable(), + interestAccrual: z.enum(ConvertibleInterestPaymentSchedule).nullable(), + interestPaymentSchedule: z.enum(ConvertibleInterestAccrual).nullable(), + + createdAt: z.coerce.date(), + updatedAt: z.coerce.date(), + }) + .openapi("Convertible Note"); + +export type TConvertibleNoteSchema = z.infer; From e51b1fb7928a77502a7949b2f98e1dbb2852f1e5 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 12 Aug 2024 16:56:54 +0530 Subject: [PATCH 02/48] feat: add register --- src/server/api/index.ts | 2 ++ src/server/api/routes/convertible-note/index.ts | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 src/server/api/routes/convertible-note/index.ts diff --git a/src/server/api/index.ts b/src/server/api/index.ts index 7e919c248..4fe496cfc 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -1,6 +1,7 @@ import { PublicAPI } from "./hono"; import { middlewareServices } from "./middlewares/services"; import { registerCompanyRoutes } from "./routes/company"; +import { registerConvertibleNotesRoutes } from "./routes/convertible-note"; import { registerShareRoutes } from "./routes/share"; import { registerStakeholderRoutes } from "./routes/stakeholder"; @@ -12,5 +13,6 @@ api.use("*", middlewareServices()); registerCompanyRoutes(api); registerShareRoutes(api); registerStakeholderRoutes(api); +registerConvertibleNotesRoutes(api); export default api; diff --git a/src/server/api/routes/convertible-note/index.ts b/src/server/api/routes/convertible-note/index.ts new file mode 100644 index 000000000..38ab9f820 --- /dev/null +++ b/src/server/api/routes/convertible-note/index.ts @@ -0,0 +1,3 @@ +import type { PublicAPI } from "@/server/api/hono"; + +export const registerConvertibleNotesRoutes = (api: PublicAPI) => {}; From f18f246aacf41177adac00af30eb4aa39df7cb6d Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 12 Aug 2024 17:24:38 +0530 Subject: [PATCH 03/48] feat: add create convertible note --- .../api/routes/convertible-note/create.ts | 80 +++++++++++++++++++ .../api/routes/convertible-note/index.ts | 6 +- src/server/api/schema/convertible-note.ts | 18 ++++- 3 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 src/server/api/routes/convertible-note/create.ts diff --git a/src/server/api/routes/convertible-note/create.ts b/src/server/api/routes/convertible-note/create.ts new file mode 100644 index 000000000..dbfbec363 --- /dev/null +++ b/src/server/api/routes/convertible-note/create.ts @@ -0,0 +1,80 @@ +import { generatePublicId } from "@/common/id"; + +import { z } from "@hono/zod-openapi"; +import { + ConvertibleNoteSchema, + CreateConvertibleNotesSchema, +} from "../../schema/convertible-note"; +import { authMiddleware, withAuthApiV1 } from "../../utils/endpoint-creator"; +("../../utils/endpoint-creator"); + +const ResponseSchema = z.object({ + message: z.string(), + data: ConvertibleNoteSchema, +}); + +const ParamsSchema = z.object({ + companyId: z.string().openapi({ + param: { + name: "companyId", + in: "path", + }, + description: "Company ID", + type: "string", + example: "clxwbok580000i7nge8nm1ry0", + }), +}); + +export const create = withAuthApiV1 + .createRoute({ + method: "post", + path: "/v1/{companyId}/convertible-notes", + summary: "Create convertible notes", + description: "Add convertible notes to a company.", + tags: ["Convertible Notes"], + middleware: [authMiddleware()], + request: { + params: ParamsSchema, + body: { + content: { + "application/json": { + schema: CreateConvertibleNotesSchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: + "Confirmation of convertible notes created with relevant details.", + }, + }, + }) + .handler(async (c) => { + const { db } = c.get("services"); + const { membership } = c.get("session"); + const body = c.req.valid("json"); + + const note = await db.convertibleNote.create({ + data: { + ...body, + publicId: generatePublicId(), + companyId: membership.companyId, + }, + }); + + const data: z.infer["data"] = note; + + return c.json( + { + data, + message: "Stakeholders successfully created.", + }, + 200, + ); + }); diff --git a/src/server/api/routes/convertible-note/index.ts b/src/server/api/routes/convertible-note/index.ts index 38ab9f820..18fd93d6e 100644 --- a/src/server/api/routes/convertible-note/index.ts +++ b/src/server/api/routes/convertible-note/index.ts @@ -1,3 +1,7 @@ import type { PublicAPI } from "@/server/api/hono"; -export const registerConvertibleNotesRoutes = (api: PublicAPI) => {}; +import { create } from "./create"; + +export const registerConvertibleNotesRoutes = (api: PublicAPI) => { + api.openapi(create.route, create.handler); +}; diff --git a/src/server/api/schema/convertible-note.ts b/src/server/api/schema/convertible-note.ts index 102196873..5c17c5d0a 100644 --- a/src/server/api/schema/convertible-note.ts +++ b/src/server/api/schema/convertible-note.ts @@ -50,8 +50,10 @@ export const ConvertibleNoteSchema = z status: z.enum(ConvertibleStatus), type: z.enum(ConvertibleType), interestMethod: z.enum(ConvertibleInterestMethod).nullable(), - interestAccrual: z.enum(ConvertibleInterestPaymentSchedule).nullable(), - interestPaymentSchedule: z.enum(ConvertibleInterestAccrual).nullable(), + interestAccrual: z.enum(ConvertibleInterestAccrual).nullable(), + interestPaymentSchedule: z + .enum(ConvertibleInterestPaymentSchedule) + .nullable(), createdAt: z.coerce.date(), updatedAt: z.coerce.date(), @@ -59,3 +61,15 @@ export const ConvertibleNoteSchema = z .openapi("Convertible Note"); export type TConvertibleNoteSchema = z.infer; + +export const CreateConvertibleNotesSchema = ConvertibleNoteSchema.omit({ + id: true, + publicId: true, + createdAt: true, + updatedAt: true, + companyId: true, +}); + +export type TCreateConvertibleNotesSchema = z.infer< + typeof CreateConvertibleNotesSchema +>; From 09ba976b993f5d9a7e2acb3850ded3463dd5d569 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 12 Aug 2024 21:20:46 +0530 Subject: [PATCH 04/48] feat: add basic modal --- .../add-convertible-notes-button.tsx | 18 +++++++++++++ .../add-convertible-notes-modal.tsx | 25 +++++++++++++++++++ .../fundraise/convertible-notes/page.tsx | 9 ++++--- src/components/modals/index.ts | 3 +++ .../api/routes/convertible-note/create.ts | 2 +- 5 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-button.tsx create mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-modal.tsx diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-button.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-button.tsx new file mode 100644 index 000000000..82e3027c7 --- /dev/null +++ b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-button.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { pushModal } from "@/components/modals"; +import { Button } from "@/components/ui/button"; + +export function AddConvertibleNotesButton() { + return ( + + ); +} diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-modal.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-modal.tsx new file mode 100644 index 000000000..460a2ab1e --- /dev/null +++ b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-modal.tsx @@ -0,0 +1,25 @@ +import { + StepperModal, + StepperModalContent, + type StepperModalProps, + StepperStep, +} from "@/components/ui/stepper"; + +export function AddConvertibleNotesModal( + props: Omit, +) { + return ( + + + +
hello
+
+
+ + +
hello
+
+
+
+ ); +} diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/page.tsx index 3690e43e0..340e5eb81 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/page.tsx @@ -1,7 +1,8 @@ import EmptyState from "@/components/common/empty-state"; -import { Button } from "@/components/ui/button"; + import { RiPieChartFill } from "@remixicon/react"; import type { Metadata } from "next"; +import { AddConvertibleNotesButton } from "./components/add-convertible-notes-button"; export const metadata: Metadata = { title: "Convertible notes", @@ -11,10 +12,10 @@ const ConvertibleNotesPage = () => { return ( } - title="Work in progress." - subtitle="This page is not yet available." + title="Create and manage convertible notes." + subtitle="Please click the button for creating convertible notes." > - + ); }; diff --git a/src/components/modals/index.ts b/src/components/modals/index.ts index c05179f74..d5b668eb4 100644 --- a/src/components/modals/index.ts +++ b/src/components/modals/index.ts @@ -18,6 +18,7 @@ import { UpdateSingleStakeholderModal } from "./stakeholder/update-stakeholder-m import { TeamMemberModal } from "./team-member/team-member-modal"; import { WipModal } from "./wip-modal"; +import { AddConvertibleNotesModal } from "@/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-modal"; import { AddEsignDocumentModal } from "./esign-doc"; import { InvestorModal } from "./investor/add-investor-modal"; @@ -43,5 +44,7 @@ export const { pushModal, popModal, ModalProvider } = createPushModal({ NewSafeModal, ExistingSafeModal, InvestorModal, + + AddConvertibleNotes: AddConvertibleNotesModal, }, }); diff --git a/src/server/api/routes/convertible-note/create.ts b/src/server/api/routes/convertible-note/create.ts index dbfbec363..9c1377a8d 100644 --- a/src/server/api/routes/convertible-note/create.ts +++ b/src/server/api/routes/convertible-note/create.ts @@ -73,7 +73,7 @@ export const create = withAuthApiV1 return c.json( { data, - message: "Stakeholders successfully created.", + message: "Convertible note successfully created.", }, 200, ); From 1c47e68f4b99adee5fc6b39ad01e4ec324e36b93 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Tue, 13 Aug 2024 00:38:36 +0530 Subject: [PATCH 05/48] chore: fix schema --- src/server/api/schema/convertible-note.ts | 43 +++++------------------ src/server/api/schema/shares.ts | 13 ++----- src/server/api/schema/stakeholder.ts | 12 ++----- 3 files changed, 13 insertions(+), 55 deletions(-) diff --git a/src/server/api/schema/convertible-note.ts b/src/server/api/schema/convertible-note.ts index 5c17c5d0a..d227426b5 100644 --- a/src/server/api/schema/convertible-note.ts +++ b/src/server/api/schema/convertible-note.ts @@ -7,31 +7,6 @@ import { } from "@/prisma/enums"; import { z } from "@hono/zod-openapi"; -const ConvertibleStatus = Object.values(ConvertibleStatusEnum) as [ - ConvertibleStatusEnum, - ...ConvertibleStatusEnum[], -]; - -const ConvertibleType = Object.values(ConvertibleTypeEnum) as [ - ConvertibleTypeEnum, - ...ConvertibleTypeEnum[], -]; - -const ConvertibleInterestMethod = Object.values( - ConvertibleInterestMethodEnum, -) as [ConvertibleInterestMethodEnum, ...ConvertibleInterestMethodEnum[]]; - -const ConvertibleInterestPaymentSchedule = Object.values( - ConvertibleInterestPaymentScheduleEnum, -) as [ - ConvertibleInterestPaymentScheduleEnum, - ...ConvertibleInterestPaymentScheduleEnum[], -]; - -const ConvertibleInterestAccrual = Object.values( - ConvertibleInterestAccrualEnum, -) as [ConvertibleInterestAccrualEnum, ...ConvertibleInterestAccrualEnum[]]; - export const ConvertibleNoteSchema = z .object({ id: z.string().cuid(), @@ -44,19 +19,19 @@ export const ConvertibleNoteSchema = z interestRate: z.number().nullable(), stakeholderId: z.string(), companyId: z.string(), - issueDate: z.coerce.date(), - boardApprovalDate: z.coerce.date(), + issueDate: z.string().date(), + boardApprovalDate: z.string().date(), - status: z.enum(ConvertibleStatus), - type: z.enum(ConvertibleType), - interestMethod: z.enum(ConvertibleInterestMethod).nullable(), - interestAccrual: z.enum(ConvertibleInterestAccrual).nullable(), + status: z.nativeEnum(ConvertibleStatusEnum), + type: z.nativeEnum(ConvertibleTypeEnum), + interestMethod: z.nativeEnum(ConvertibleInterestMethodEnum).nullable(), + interestAccrual: z.nativeEnum(ConvertibleInterestAccrualEnum).nullable(), interestPaymentSchedule: z - .enum(ConvertibleInterestPaymentSchedule) + .nativeEnum(ConvertibleInterestPaymentScheduleEnum) .nullable(), - createdAt: z.coerce.date(), - updatedAt: z.coerce.date(), + createdAt: z.string().date(), + updatedAt: z.string().date(), }) .openapi("Convertible Note"); diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts index a8401eef6..83f79a47c 100644 --- a/src/server/api/schema/shares.ts +++ b/src/server/api/schema/shares.ts @@ -1,15 +1,6 @@ import { SecuritiesStatusEnum, ShareLegendsEnum } from "@/prisma/enums"; import { z } from "@hono/zod-openapi"; -const ShareLegendsArr = Object.values(ShareLegendsEnum) as [ - ShareLegendsEnum, - ...ShareLegendsEnum[], -]; -const SecuritiesStatusArr = Object.values(SecuritiesStatusEnum) as [ - SecuritiesStatusEnum, - ...SecuritiesStatusEnum[], -]; - export const ShareSchema = z .object({ id: z.string().cuid().nullish().openapi({ @@ -17,7 +8,7 @@ export const ShareSchema = z example: "clyvb2s8d0000f1ngd72y2cxw", }), - status: z.enum(SecuritiesStatusArr).openapi({ + status: z.nativeEnum(SecuritiesStatusEnum).openapi({ description: "Security Status", example: "DRAFT", }), @@ -67,7 +58,7 @@ export const ShareSchema = z }), companyLegends: z - .enum(ShareLegendsArr) + .nativeEnum(ShareLegendsEnum) .array() .openapi({ description: "Company Legends", diff --git a/src/server/api/schema/stakeholder.ts b/src/server/api/schema/stakeholder.ts index 854ff7470..853fc06b8 100644 --- a/src/server/api/schema/stakeholder.ts +++ b/src/server/api/schema/stakeholder.ts @@ -4,14 +4,6 @@ import { } from "@/prisma/enums"; import { z } from "@hono/zod-openapi"; -const StakeholderTypeArray = Object.values(StakeholderTypeEnum) as [ - StakeholderTypeEnum, - ...StakeholderTypeEnum[], -]; -const StakeholderRelationshipArray = Object.values( - StakeholderRelationshipEnum, -) as [StakeholderRelationshipEnum, ...StakeholderRelationshipEnum[]]; - export const StakeholderSchema = z .object({ id: z.string().cuid().openapi({ @@ -34,12 +26,12 @@ export const StakeholderSchema = z example: "ACME Corp", }), - stakeholderType: z.enum(StakeholderTypeArray).openapi({ + stakeholderType: z.nativeEnum(StakeholderTypeEnum).openapi({ description: "Stakeholder type", example: "INDIVIDUAL", }), - currentRelationship: z.enum(StakeholderRelationshipArray).openapi({ + currentRelationship: z.nativeEnum(StakeholderRelationshipEnum).openapi({ description: "Current relationship with the company", example: "EMPLOYEE", }), From d426ee765ea6c2f83e2223a8ae74bfda1d69d392 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Tue, 13 Aug 2024 00:38:47 +0530 Subject: [PATCH 06/48] chore: fix formatting --- src/server/api/routes/convertible-note/create.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/server/api/routes/convertible-note/create.ts b/src/server/api/routes/convertible-note/create.ts index 9c1377a8d..93ff5118a 100644 --- a/src/server/api/routes/convertible-note/create.ts +++ b/src/server/api/routes/convertible-note/create.ts @@ -68,7 +68,13 @@ export const create = withAuthApiV1 }, }); - const data: z.infer["data"] = note; + const data: z.infer["data"] = { + ...note, + updatedAt: note.updatedAt.toISOString(), + createdAt: note.createdAt.toISOString(), + issueDate: note.issueDate.toISOString(), + boardApprovalDate: note.boardApprovalDate.toISOString(), + }; return c.json( { From fba122b6e95676d6e085271ae0496103b8076d72 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Tue, 13 Aug 2024 00:44:17 +0530 Subject: [PATCH 07/48] feat: add basic fields --- .../components/add-convertible-notes-form.tsx | 182 ++++++++++++++++++ .../add-convertible-notes-modal.tsx | 3 +- 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-form.tsx diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-form.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-form.tsx new file mode 100644 index 000000000..2573b966b --- /dev/null +++ b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/convertible-notes/components/add-convertible-notes-form.tsx @@ -0,0 +1,182 @@ +"use client"; + +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import { + ConvertibleInterestAccrualEnum, + ConvertibleInterestMethodEnum, + ConvertibleInterestPaymentScheduleEnum, + type ConvertibleTypeEnum, +} from "@/prisma/enums"; + +const InterestMethodOption: Record = { + COMPOUND: "Compound", + SIMPLE: "Simple", +}; + +const TypeOption: Record = { + CCD: "Compulsory Convertible Debenture", + NOTE: "Simple Convertible note", + OCD: "Optionally Convertible Debenture", +}; + +const InterestPaymentScheduleOption: Record< + ConvertibleInterestPaymentScheduleEnum, + string +> = { + DEFERRED: "Deferred", + PAY_AT_MATURITY: "Pay-At-Maturity", +}; + +const InterestAccrualOption: Record = { + ANNUALLY: "Annually", + CONTINUOUSLY: "Continuously", + DAILY: "Daily", + MONTHLY: "Monthly", + SEMI_ANNUALLY: "Semi-Annually", + YEARLY: "Yearly", +}; + +const Schema = z.object({ + capital: z.number(), + issueDate: z.string().date(), + boardApprovalDate: z.string().date(), + additionalTerms: z.string().optional(), + conversionCap: z.number().optional(), + discountRate: z.number().optional(), + interestRate: z.number().optional(), + interestMethod: z.nativeEnum(ConvertibleInterestMethodEnum).optional(), + interestAccrual: z.nativeEnum(ConvertibleInterestAccrualEnum).optional(), + interestPaymentSchedule: z + .nativeEnum(ConvertibleInterestPaymentScheduleEnum) + .optional(), +}); + +type TSchema = z.infer; + +export function AddConvertibleNotesForm() { + const form = useForm({ + resolver: zodResolver(Schema), + }); + + const onSubmit = (_data: TSchema) => {}; + + return ( +
+ +
+ ( + + Capital + + + + + + )} + /> + + ( + + Conversion capital + + + + + + )} + /> + + ( + + Discount Rate + + + + + + )} + /> + + ( + + Interest Rate + + + + + + )} + /> + + ( + + Issue date + + + + + + )} + /> + ( + + Board approval date + + + + + + )} + /> + +
+ ( + + Additional Terms + +