Skip to content

Commit

Permalink
Merge pull request #458 from Alfredoeb9/separate-vesting-schedule
Browse files Browse the repository at this point in the history
fix: separated vestingSchedule
  • Loading branch information
dahal committed Jul 25, 2024
2 parents be6fb3b + fc94413 commit e769873
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
Warnings:
- You are about to drop the column `vestingSchedule` on the `Option` table. All the data in the column will be lost.
- You are about to drop the column `vestingSchedule` on the `Share` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Option" DROP COLUMN "vestingSchedule";

-- AlterTable
ALTER TABLE "Share" DROP COLUMN "vestingSchedule";

-- DropEnum
DROP TYPE "VestingScheduleEnum";
24 changes: 6 additions & 18 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -669,16 +669,6 @@ enum SecuritiesStatusEnum {
PENDING
}

enum VestingScheduleEnum {
VESTING_0_0_0 // Immediate vesting
VESTING_0_0_1 // 1 year cliff with no vesting
VESTING_4_1_0 // 4 years vesting every month with no cliff
VESTING_4_1_1 // 4 years vesting every month with 1 year cliff
VESTING_4_3_1 // 4 years vesting every 3 months with 1 year cliff
VESTING_4_6_1 // 4 years vesting every 6 months with 1 year cliff
VESTING_4_12_1 // 4 years vesting every year with 1 year cliff
}

enum ShareLegendsEnum {
US_SECURITIES_ACT // US Securities Act of 1933
SALE_AND_ROFR // Sale and Right of first refusal
Expand All @@ -697,9 +687,8 @@ model Share {
debtCancelled Float? // Amount of debt cancelled
otherContributions Float? // Other contributions
cliffYears Int @default(0) // 0 means immediate vesting, 1 means vesting starts after 1 year
vestingYears Int @default(0) // 0 means immediate vesting, 1 means vesting over 1 year
vestingSchedule VestingScheduleEnum
cliffYears Int @default(0) // 0 means immediate vesting, 1 means vesting starts after 1 year
vestingYears Int @default(0) // 0 means immediate vesting, 1 means vesting over 1 year
companyLegends ShareLegendsEnum[] @default([])
Expand Down Expand Up @@ -748,11 +737,10 @@ model Option {
quantity Int
exercisePrice Float
type OptionTypeEnum
status OptionStatusEnum @default(DRAFT)
cliffYears Int @default(0) // 0 means immediate vesting, 1 means vesting starts after 1 year
vestingYears Int @default(0) // 0 means immediate vesting, 1 means vesting over 1 year
vestingSchedule VestingScheduleEnum
type OptionTypeEnum
status OptionStatusEnum @default(DRAFT)
cliffYears Int @default(0) // 0 means immediate vesting, 1 means vesting starts after 1 year
vestingYears Int @default(0) // 0 means immediate vesting, 1 means vesting over 1 year
issueDate DateTime
expirationDate DateTime
Expand Down
119 changes: 96 additions & 23 deletions src/components/securities/options/steps/vesting-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ import {
StepperPrev,
useStepper,
} from "@/components/ui/stepper";
import { VestingSchedule } from "@/lib/vesting";
import { VestingScheduleEnum } from "@/prisma/enums";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useStockOptionFormValues } from "@/providers/stock-option-form-provider";
import type { RouterOutputs } from "@/trpc/shared";
import { zodResolver } from "@hookform/resolvers/zod";
Expand All @@ -28,7 +32,8 @@ import { EmptySelect } from "../../shared/EmptySelect";

const formSchema = z.object({
equityPlanId: z.string(),
vestingSchedule: z.nativeEnum(VestingScheduleEnum),
cliffYears: z.coerce.number().min(0),
vestingYears: z.coerce.number().min(0),
exercisePrice: z.coerce.number(),
stakeholderId: z.string(),
});
Expand All @@ -54,10 +59,10 @@ export const VestingDetails = (props: VestingDetailsProps) => {

const disabled = !stakeholders?.length && !equityPlans?.length;

const vestingSchedileOpts = Object.keys(VestingSchedule).map((vKey) => ({
value: vKey,
label: VestingSchedule[vKey] || "",
}));
// const vestingSchedileOpts = Object.keys(VestingSchedule).map((vKey) => ({
// value: vKey,
// label: VestingSchedule[vKey] || "",
// }));

const equityPlansOpts = equityPlans?.map(({ id, name }) => ({
value: id,
Expand All @@ -76,22 +81,90 @@ export const VestingDetails = (props: VestingDetailsProps) => {
className="flex flex-col gap-y-4"
>
<div className="grid gap-4">
<FormField
control={form.control}
name="vestingSchedule"
render={({ field }) => (
<FormItem>
<FormLabel>Vesting schedule</FormLabel>
<div>
<LinearCombobox
options={vestingSchedileOpts}
onValueChange={(option) => field.onChange(option.value)}
/>
</div>
<FormMessage className="text-xs font-light" />
</FormItem>
)}
/>
<div className="flex items-center gap-4">
<div className="flex-1">
<FormField
control={form.control}
name="vestingYears"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
<FormItem>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<FormLabel>Vesting</FormLabel>
</TooltipTrigger>
<TooltipContent>
<p>Vesting starts over</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<div>
<FormControl>
<NumericFormat
thousandSeparator
allowedDecimalSeparators={["%"]}
suffix={field.value > 1 ? " years" : " year"}
decimalScale={0}
{...rest}
customInput={Input}
onValueChange={(values) => {
const { floatValue } = values;
onChange(floatValue);
}}
/>
</FormControl>
</div>
<FormMessage className="text-xs font-light" />
</FormItem>
);
}}
/>
</div>

<div className="flex-1">
<FormField
control={form.control}
name="cliffYears"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
<FormItem>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<FormLabel>Cliff</FormLabel>
</TooltipTrigger>
<TooltipContent>
<p>Vesting starts after</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<div>
<FormControl>
<NumericFormat
thousandSeparator
allowedDecimalSeparators={["%"]}
decimalScale={0}
suffix={field.value > 1 ? " years" : " year"}
{...rest}
customInput={Input}
onValueChange={(values) => {
const { floatValue } = values;
onChange(floatValue);
}}
/>
</FormControl>
</div>

<FormMessage className="text-xs font-light" />
</FormItem>
);
}}
/>
</div>
</div>

{equityPlans?.length ? (
<FormField
Expand Down
129 changes: 100 additions & 29 deletions src/components/securities/shares/steps/general-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,18 @@ import {
StepperPrev,
useStepper,
} from "@/components/ui/stepper";
import { VestingSchedule } from "@/lib/vesting";
import {
SecuritiesStatusEnum,
ShareLegendsEnum,
VestingScheduleEnum,
} from "@/prisma/enums";
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { SecuritiesStatusEnum, ShareLegendsEnum } from "@/prisma/enums";
import { useAddShareFormValues } from "@/providers/add-share-form-provider";
import type { RouterOutputs } from "@/trpc/shared";
import { zodResolver } from "@hookform/resolvers/zod";
import { RiAddFill } from "@remixicon/react";
import { useForm } from "react-hook-form";
import { type UseFormReturn, useForm } from "react-hook-form";
import { NumericFormat } from "react-number-format";
import { z } from "zod";

Expand All @@ -58,7 +59,8 @@ const formSchema = z.object({
certificateId: z.string(),
status: z.nativeEnum(SecuritiesStatusEnum),
quantity: z.coerce.number().min(0),
vestingSchedule: z.nativeEnum(VestingScheduleEnum),
cliffYears: z.coerce.number().min(0),
vestingYears: z.coerce.number().min(0),
companyLegends: z.nativeEnum(ShareLegendsEnum).array(),
pricePerShare: z.coerce.number().min(0),
});
Expand All @@ -72,14 +74,13 @@ interface GeneralDetailsProps {
}

export const GeneralDetails = ({ shareClasses = [] }: GeneralDetailsProps) => {
const form = useForm<TFormSchema>({
const form: UseFormReturn<TFormSchema> = useForm<TFormSchema>({
resolver: zodResolver(formSchema),
});
const { next } = useStepper();
const { setValue } = useAddShareFormValues();

const status = Object.values(SecuritiesStatusEnum);
const vestingSchedule = Object.values(VestingScheduleEnum);
const companyLegends = Object.values(ShareLegendsEnum);

const handleSubmit = (data: TFormSchema) => {
Expand All @@ -92,10 +93,10 @@ export const GeneralDetails = ({ shareClasses = [] }: GeneralDetailsProps) => {
label: share.name,
}));

const vestingScheduleOpts = vestingSchedule.map((vs) => ({
value: vs,
label: VestingSchedule[vs] || "",
}));
// const vestingScheduleOpts = vestingSchedule.map((vs) => ({
// value: vs,
// label: VestingSchedule[vs] || "",
// }));

const statusOpts = status.map((s) => ({
value: s,
Expand Down Expand Up @@ -253,22 +254,92 @@ export const GeneralDetails = ({ shareClasses = [] }: GeneralDetailsProps) => {
</div>
</div>

<FormField
control={form.control}
name="vestingSchedule"
render={({ field }) => (
<FormItem>
<FormLabel>Vesting schedule</FormLabel>
<div>
<LinearCombobox
options={vestingScheduleOpts}
onValueChange={(option) => field.onChange(option.value)}
/>
</div>
<FormMessage className="text-xs font-light" />
</FormItem>
)}
/>
<div className="flex items-center gap-4">
<div className="flex-1">
<FormField
control={form.control}
name="vestingYears"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
<FormItem>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<FormLabel>Vesting</FormLabel>
</TooltipTrigger>
<TooltipContent>
<p>Vesting starts over</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>

<div>
<FormControl>
<NumericFormat
thousandSeparator
allowedDecimalSeparators={["%"]}
decimalScale={0}
suffix={field.value > 1 ? " years" : " year"}
{...rest}
customInput={Input}
onValueChange={(values) => {
const { floatValue } = values;
onChange(floatValue);
}}
/>
</FormControl>
</div>

<FormMessage className="text-xs font-light" />
</FormItem>
);
}}
/>
</div>

<div className="flex-1">
<FormField
control={form.control}
name="cliffYears"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
<FormItem>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<FormLabel>Cliff</FormLabel>
</TooltipTrigger>
<TooltipContent>
<p>Vesting starts after</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<div>
<FormControl>
<NumericFormat
thousandSeparator
allowedDecimalSeparators={["%"]}
decimalScale={0}
suffix={field.value > 1 ? " years" : " year"}
{...rest}
customInput={Input}
onValueChange={(values) => {
const { floatValue } = values;
onChange(floatValue);
}}
/>
</FormControl>
</div>

<FormMessage className="text-xs font-light" />
</FormItem>
);
}}
/>
</div>
</div>

<FormField
control={form.control}
Expand Down
2 changes: 1 addition & 1 deletion src/components/update/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { DropdownButton } from "@/components/ui/dropdown-button";
import { api } from "@/trpc/react";
import type { Block } from "@blocknote/core";
import type { Block, PartialBlock } from "@blocknote/core";
import type { Update } from "@prisma/client";
import { RiArrowDownSLine } from "@remixicon/react";
import Link from "next/link";
Expand Down
Loading

0 comments on commit e769873

Please sign in to comment.