Skip to content

Commit 69cf85d

Browse files
authored
Merge pull request #331 from captableinc/feat/create-share-page
feat: Allocate shares to stakeholders
2 parents b000aac + 21427cf commit 69cf85d

File tree

31 files changed

+2086
-44
lines changed

31 files changed

+2086
-44
lines changed

biome.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
".next",
88
"dist",
99
"public/pdf.worker.min.js",
10-
"./prisma/enums.ts"
10+
"./prisma/enums.ts",
11+
"./src/components/ui/simple-multi-select.tsx"
1112
]
1213
},
1314
"linter": {
@@ -25,7 +26,8 @@
2526
".next",
2627
"dist",
2728
"public/pdf.worker.min.js",
28-
"./prisma/enums.ts"
29+
"./prisma/enums.ts",
30+
"./src/components/ui/simple-multi-select.tsx"
2931
]
3032
},
3133
"formatter": {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
Warnings:
3+
4+
- A unique constraint covering the columns `[companyId,certificateId]` on the table `Share` will be added. If there are existing duplicate values, this will fail.
5+
6+
*/
7+
-- CreateIndex
8+
CREATE UNIQUE INDEX "Share_companyId_certificateId_key" ON "Share"("companyId", "certificateId");

prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ model Share {
659659
createdAt DateTime @default(now())
660660
updatedAt DateTime @updatedAt
661661
662+
@@unique([companyId, certificateId])
662663
@@index([companyId])
663664
@@index([shareClassId])
664665
@@index([stakeholderId])

src/app/(authenticated)/(dashboard)/[publicId]/securities/options/page.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,7 @@ const OptionsPage = async () => {
2424
>
2525
<OptionModal
2626
title="Create an option"
27-
subtitle={
28-
<Tldr
29-
message="Please fill in the details to create an option. If you need help, click the link below."
30-
cta={{
31-
label: "Learn more",
32-
href: "https://captable.inc/help/stakeholder-options",
33-
}}
34-
/>
35-
}
27+
subtitle="Please fill in the details to create an option."
3628
trigger={
3729
<Button size="lg">
3830
<RiAddFill className="mr-2 h-5 w-5" />

src/app/(authenticated)/(dashboard)/[publicId]/securities/shares/page.tsx

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,69 @@
11
import EmptyState from "@/components/common/empty-state";
2+
import Tldr from "@/components/common/tldr";
3+
import { ShareModal } from "@/components/securities/shares/share-modal";
4+
import ShareTable from "@/components/securities/shares/share-table";
25
import { Button } from "@/components/ui/button";
3-
import { RiPieChartFill } from "@remixicon/react";
4-
import { type Metadata } from "next";
6+
import { Card } from "@/components/ui/card";
7+
import { api } from "@/trpc/server";
8+
import { RiAddFill, RiPieChartFill } from "@remixicon/react";
9+
import type { Metadata } from "next";
510

611
export const metadata: Metadata = {
7-
title: "Cap table",
12+
title: "Captable | Shares",
813
};
914

10-
const SharesPage = () => {
15+
const SharesPage = async () => {
16+
const shares = await api.securities.getShares.query();
17+
18+
if (shares?.data?.length === 0) {
19+
return (
20+
<EmptyState
21+
icon={<RiPieChartFill />}
22+
title="You have not issued any shares"
23+
subtitle="Please click the button below to start issuing shares."
24+
>
25+
<ShareModal
26+
size="4xl"
27+
title="Create a share"
28+
subtitle="Please fill in the details to create and issue a share."
29+
trigger={
30+
<Button size="lg">
31+
<RiAddFill className="mr-2 h-5 w-5" />
32+
Create a share
33+
</Button>
34+
}
35+
/>
36+
</EmptyState>
37+
);
38+
}
39+
1140
return (
12-
<EmptyState
13-
icon={<RiPieChartFill />}
14-
title="Work in progress."
15-
subtitle="This page is not yet available."
16-
>
17-
<Button size="lg">Coming soon...</Button>
18-
</EmptyState>
41+
<div className="flex flex-col gap-y-3">
42+
<div className="flex items-center justify-between gap-y-3 ">
43+
<div className="gap-y-3">
44+
<h3 className="font-medium">Shares</h3>
45+
<p className="mt-1 text-sm text-muted-foreground">
46+
Issue shares to stakeholders
47+
</p>
48+
</div>
49+
<div>
50+
<ShareModal
51+
size="4xl"
52+
title="Create a share"
53+
subtitle="Please fill in the details to create and issue a share."
54+
trigger={
55+
<Button>
56+
<RiAddFill className="mr-2 h-5 w-5" />
57+
Create a share
58+
</Button>
59+
}
60+
/>
61+
</div>
62+
</div>
63+
<Card className="mx-auto mt-3 w-[28rem] sm:w-[38rem] md:w-full">
64+
<ShareTable shares={shares.data} />
65+
</Card>
66+
</div>
1967
);
2068
};
2169

src/app/(authenticated)/(dashboard)/[publicId]/settings/company/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CompanyForm } from "@/components/onboarding/company-form";
22
import { api } from "@/trpc/server";
3-
import { type Metadata } from "next";
3+
import type { Metadata } from "next";
44

55
export const metadata: Metadata = {
66
title: "Company",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { cn } from "@/lib/utils";
2+
3+
export interface ISVGProps extends React.SVGProps<SVGSVGElement> {
4+
size?: number;
5+
className?: string;
6+
}
7+
8+
export const LoadingSpinner = ({
9+
size = 24,
10+
className,
11+
...props
12+
}: ISVGProps) => {
13+
return (
14+
<svg
15+
xmlns="http://www.w3.org/2000/svg"
16+
width={size}
17+
height={size}
18+
{...props}
19+
viewBox="0 0 24 24"
20+
fill="none"
21+
stroke="currentColor"
22+
strokeWidth="2"
23+
strokeLinecap="round"
24+
strokeLinejoin="round"
25+
className={cn("animate-spin", className)}
26+
>
27+
<title>Loading</title>
28+
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
29+
</svg>
30+
);
31+
};

src/components/onboarding/company-form.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export const CompanyForm = ({ type, data }: CompanyFormProps) => {
8282
state: data?.company.state ?? "",
8383
streetAddress: data?.company.streetAddress ?? "",
8484
zipcode: data?.company.zipcode ?? "",
85+
country: data?.company.country ?? "",
8586
},
8687
},
8788
});

src/components/securities/options/steps/vesting-details.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"use client";
22

3-
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
43
import { Button } from "@/components/ui/button";
54
import {
65
Form,
@@ -31,6 +30,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
3130
import { useForm } from "react-hook-form";
3231
import { NumericFormat } from "react-number-format";
3332
import { z } from "zod";
33+
import { EmptySelect } from "../../shared/EmptySelect";
3434

3535
const formSchema = z.object({
3636
equityPlanId: z.string(),
@@ -46,20 +46,6 @@ interface VestingDetailsProps {
4646
equityPlans: RouterOutputs["equityPlan"]["getPlans"];
4747
}
4848

49-
interface EmptySelectProps {
50-
title: string;
51-
description: string;
52-
}
53-
54-
function EmptySelect({ title, description }: EmptySelectProps) {
55-
return (
56-
<Alert variant="destructive">
57-
<AlertTitle>{title}</AlertTitle>
58-
<AlertDescription>{description}</AlertDescription>
59-
</Alert>
60-
);
61-
}
62-
6349
export const VestingDetails = (props: VestingDetailsProps) => {
6450
const { stakeholders, equityPlans } = props;
6551

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
2+
3+
interface EmptySelectProps {
4+
title: string;
5+
description: string;
6+
}
7+
8+
export function EmptySelect({ title, description }: EmptySelectProps) {
9+
return (
10+
<Alert variant="destructive">
11+
<AlertTitle>{title}</AlertTitle>
12+
<AlertDescription>{description}</AlertDescription>
13+
</Alert>
14+
);
15+
}

0 commit comments

Comments
 (0)