Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: separated vestingSchedule #458

Merged
merged 11 commits into from
Jul 25, 2024
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
118 changes: 95 additions & 23 deletions src/components/securities/options/steps/vesting-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { Input } from "@/components/ui/input";
import {
StepperModalFooter,
StepperPrev,
useStepper,
} from "@/components/ui/stepper";
import { VestingSchedule } from "@/lib/vesting";
import { VestingScheduleEnum } from "@/prisma/enums";
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,89 @@ 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="cliffYears"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
<FormItem>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<FormLabel>Cliff Years</FormLabel>
</TooltipTrigger>
<TooltipContent>
<p>Vesting starts after</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<div>
<FormControl>
<NumericFormat
thousandSeparator
allowedDecimalSeparators={["%"]}
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="vestingYears"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
<FormItem>

<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<FormLabel>Vesting Years</FormLabel>
</TooltipTrigger>
<TooltipContent>
<p>Vesting starts after</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<div>
<FormControl>
<NumericFormat
thousandSeparator
allowedDecimalSeparators={["%"]}
decimalScale={0}
{...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
124 changes: 98 additions & 26 deletions src/components/securities/shares/steps/general-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { Input } from "@/components/ui/input";
import {
MultiSelector,
Expand All @@ -26,17 +32,15 @@ import {
StepperPrev,
useStepper,
} from "@/components/ui/stepper";
import { VestingSchedule } from "@/lib/vesting";
import {
SecuritiesStatusEnum,
ShareLegendsEnum,
VestingScheduleEnum,
} 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 { useForm, UseFormReturn } from "react-hook-form";
import { NumericFormat } from "react-number-format";
import { z } from "zod";

Expand All @@ -58,7 +62,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 +77,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 +96,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 @@ -252,23 +256,91 @@ export const GeneralDetails = ({ shareClasses = [] }: GeneralDetailsProps) => {
/>
</div>
</div>

<div className="flex items-center gap-4">
<div className="flex-1">
<FormField
control={form.control}
name="cliffYears"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
<FormItem>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<FormLabel>Cliff Years</FormLabel>
</TooltipTrigger>
<TooltipContent>
<p>Vesting starts after</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<div>
<FormControl>
<NumericFormat
thousandSeparator
allowedDecimalSeparators={["%"]}
decimalScale={0}
{...rest}
customInput={Input}
onValueChange={(values) => {
const { floatValue } = values;
onChange(floatValue);
}}
/>
</FormControl>
</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>
)}
/>
<FormMessage className="text-xs font-light" />
</FormItem>
)
}}
/>
</div>

<div className="flex-1">
<FormField
control={form.control}
name="vestingYears"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
<FormItem>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<FormLabel>Vesting Years</FormLabel>
</TooltipTrigger>
<TooltipContent>
<p>Vesting starts after</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>

<div>
<FormControl>
<NumericFormat
thousandSeparator
allowedDecimalSeparators={["%"]}
decimalScale={0}
{...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
Loading