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
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