Skip to content

Commit 6769506

Browse files
authored
feat: create overview page - UI (#85)
* chore: seperate empty overview page to its own component * feat: add card component * feat: render top overview and donut chart placeholders * feat: wrapping up overview ui page
1 parent 0c5af8f commit 6769506

File tree

9 files changed

+1870
-217
lines changed

9 files changed

+1870
-217
lines changed

package-lock.json

Lines changed: 1408 additions & 195 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"email:preview": "email preview ./src/emails"
2020
},
2121
"dependencies": {
22+
"@ant-design/plots": "^2.1.12",
2223
"@aws-sdk/client-s3": "^3.490.0",
2324
"@aws-sdk/s3-request-presigner": "^3.490.0",
2425
"@hookform/resolvers": "^3.3.4",

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

Lines changed: 125 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,140 @@
11
"use client";
22

3-
import Link from "next/link";
43
import { useParams } from "next/navigation";
54
import { useSession } from "next-auth/react";
6-
import { Button } from "@/components/ui/button";
7-
import EmptyState from "@/components/shared/empty-state";
8-
import { RiPieChartFill, RiArrowRightLine } from "@remixicon/react";
5+
import EmptyOverview from "@/components/overview/empty";
6+
import DonutCard from "@/components/overview/donut-card";
7+
import OverviewCard from "@/components/overview/top-card";
8+
import ActivitiesCard from "@/components/overview/activities-card";
99

1010
const OverviewPage = () => {
1111
const params = useParams<{ publicId: string }>();
1212
const { data } = useSession();
1313
const firstName = data?.user.name?.split(" ")[0];
14-
const companyPublicId = params.publicId;
14+
const publicCompanyId = params.publicId;
15+
16+
const byShareClasses = [
17+
{
18+
key: "Common - 53%",
19+
value: 53,
20+
},
21+
22+
{
23+
key: "Preferred - 10%",
24+
value: 10,
25+
},
26+
27+
// {
28+
// key: "Preferred (Series A) - 23%",
29+
// value: 15,
30+
// },
31+
32+
// {
33+
// key: "Preferred (Convertible note) - 7%",
34+
// value: 7,
35+
// },
36+
37+
{
38+
key: "Stock Plan - 15%",
39+
value: 15,
40+
},
41+
];
42+
43+
const byStakeholders = [
44+
{
45+
key: "John Doe",
46+
value: 27,
47+
},
48+
49+
{
50+
key: "Jane Doe",
51+
value: 25,
52+
},
53+
54+
// {
55+
// key: "Others",
56+
// value: 18,
57+
// },
58+
59+
{
60+
key: "Equity Plan",
61+
value: 15,
62+
},
63+
64+
{
65+
key: "Acme Ventures",
66+
value: 10,
67+
},
68+
69+
{
70+
key: "Jane Doe",
71+
value: 5,
72+
},
73+
];
1574

1675
return (
1776
<>
18-
<EmptyState
19-
icon={<RiPieChartFill />}
20-
title={`Welcome to OpenCap${firstName && `, ${firstName}`} 👋`}
21-
subtitle={
22-
<span className="text-muted-foreground">
23-
We will get you setup with your Captable in no time.
24-
</span>
25-
}
26-
>
27-
<Button size="lg">
28-
<Link href={`/${companyPublicId}/stakeholders`}>
29-
Let{`'`}s get started
30-
<RiArrowRightLine className="ml-5 inline-block h-4 w-5" />
31-
</Link>
32-
</Button>
33-
</EmptyState>
77+
{/* <EmptyOverview firstName={firstName} publicCompanyId={publicCompanyId} /> */}
78+
79+
<header>
80+
<h3 className="font-medium">Overview</h3>
81+
<p className="text-sm text-muted-foreground">
82+
View your company{`'`}s captable overview
83+
</p>
84+
</header>
85+
86+
<div className="grid max-h-[500px] gap-8 md:grid-cols-12">
87+
<div className="sm:col-span-12 md:col-span-6 lg:col-span-8">
88+
{/* Overview */}
89+
<section className="mt-6">
90+
<div className="grid grid-cols-2 gap-8 md:grid-cols-2 lg:grid-cols-3">
91+
<OverviewCard
92+
title="Amount raised"
93+
amount={10000000}
94+
prefix="$"
95+
/>
96+
<OverviewCard title="Diluted shares" amount={11567010} />
97+
<OverviewCard title="Stakeholders" amount={28} format={false} />
98+
</div>
99+
</section>
100+
101+
{/* Donut charts */}
102+
<section className="mt-6">
103+
<div className="grid h-fit gap-4 sm:grid-cols-2 xl:grid-cols-2 ">
104+
<DonutCard
105+
title={
106+
<>
107+
<span className="text-xs text-muted-foreground">
108+
Ownerships by{" "}
109+
</span>
110+
<span className="text-md font-semibold text-primary">
111+
Stakeholders
112+
</span>
113+
</>
114+
}
115+
data={byStakeholders}
116+
/>
117+
<DonutCard
118+
title={
119+
<>
120+
<span className="text-xs text-muted-foreground">
121+
Ownerships by{" "}
122+
</span>
123+
<span className="text-md font-semibold text-primary">
124+
Share Class
125+
</span>
126+
</>
127+
}
128+
data={byShareClasses}
129+
/>
130+
</div>
131+
</section>
132+
</div>
133+
134+
<div className="mt-6 sm:col-span-12 md:col-span-6 lg:col-span-4">
135+
<ActivitiesCard className="border-none bg-transparent shadow-none" />
136+
</div>
137+
</div>
34138
</>
35139
);
36140
};

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const StakeholdersPage = async () => {
1313

1414
return (
1515
<div className="flex flex-col gap-y-3">
16-
<div className="flex items-center justify-between gap-y-3 ">
16+
<div className="flex items-center justify-between gap-y-3 ">
1717
<div className="gap-y-3">
1818
<h3 className="font-medium">Stakeholders</h3>
1919
<p className="text-sm text-muted-foreground">
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { RiUser4Fill } from "@remixicon/react";
2+
import {
3+
Card,
4+
CardDescription,
5+
CardHeader,
6+
CardContent,
7+
} from "@/components/ui/card";
8+
9+
type Props = {
10+
title?: string;
11+
className: string;
12+
};
13+
14+
const DonutCard = ({ title, className }: Props) => {
15+
const activity = [
16+
{
17+
id: 1,
18+
action: "safe.created",
19+
actor: {
20+
id: "xxxxx",
21+
type: "user",
22+
},
23+
target: {
24+
id: "xxxxx",
25+
type: "user",
26+
},
27+
28+
summary: "Jane Doe created a safe '[Draft] - SAFE for Y Combinator'",
29+
date: "28 days ago",
30+
},
31+
{
32+
id: 2,
33+
action: "document.uploaded",
34+
actor: {
35+
id: "xxxxx",
36+
type: "user",
37+
},
38+
target: {
39+
id: "xxxxx",
40+
type: "user",
41+
},
42+
43+
summary: "Jane Doe uploaded a document 'Certificate of Incorporation'",
44+
date: "28 days ago",
45+
},
46+
{
47+
id: 3,
48+
action: "user.invited",
49+
actor: {
50+
id: "xxxxx",
51+
type: "user",
52+
},
53+
target: {
54+
id: "xxxxx",
55+
type: "user",
56+
},
57+
58+
summary: "Jane Doe accepted the invitation to join the company",
59+
date: "29 days ago",
60+
},
61+
{
62+
id: 4,
63+
action: "user.invited",
64+
actor: {
65+
id: "xxxxx",
66+
type: "user",
67+
},
68+
target: {
69+
id: "xxxxx",
70+
type: "user",
71+
},
72+
73+
summary: "John Doe invited an admin Jane Doe to join the company",
74+
date: "30 days ago",
75+
},
76+
];
77+
78+
return (
79+
<Card className={className}>
80+
<CardHeader className="pt-0">
81+
<CardDescription className="text-md font-bold text-primary">
82+
Activities
83+
</CardDescription>
84+
</CardHeader>
85+
86+
<CardContent>
87+
<ul role="list" className="-mb-8">
88+
{activity.map((activityItem, activityItemIdx) => (
89+
<li key={activityItem.id}>
90+
<div className="relative pb-8">
91+
{activityItemIdx !== activity.length - 1 ? (
92+
<span
93+
className="absolute left-5 top-5 -ml-px h-full w-0.5 bg-gray-200"
94+
aria-hidden="true"
95+
/>
96+
) : null}
97+
<div className="relative flex items-start space-x-3">
98+
<>
99+
<div>
100+
<div className="relative px-1">
101+
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-teal-100 ring-8 ring-white">
102+
<RiUser4Fill
103+
className="h-5 w-5 text-teal-500"
104+
aria-hidden="true"
105+
/>
106+
</div>
107+
</div>
108+
</div>
109+
<div className="min-w-0 flex-1 py-1.5">
110+
<div className="text-sm text-gray-500">
111+
<span className="font-medium text-gray-900">
112+
{activityItem.summary}
113+
</span>{" "}
114+
<br />
115+
<span className="whitespace-nowrap">
116+
{activityItem.date}
117+
</span>
118+
</div>
119+
</div>
120+
</>
121+
</div>
122+
</div>
123+
</li>
124+
))}
125+
</ul>
126+
127+
<div className="mt-6">
128+
<button
129+
type="button"
130+
className="flex w-full items-center justify-center rounded-md border border-gray-300 bg-white py-1 text-sm text-gray-700 hover:bg-gray-50"
131+
>
132+
View all activity
133+
</button>
134+
</div>
135+
</CardContent>
136+
</Card>
137+
);
138+
};
139+
140+
export default DonutCard;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
Card,
3+
CardDescription,
4+
CardContent,
5+
CardHeader,
6+
} from "@/components/ui/card";
7+
8+
import { Pie } from "@ant-design/plots";
9+
import React from "react";
10+
11+
type Props = {
12+
title: string | React.ReactNode;
13+
data: { key: string; value: number }[];
14+
};
15+
16+
const DonutCard = ({ title, data }: Props) => {
17+
const config = {
18+
data,
19+
appendPadding: 10,
20+
angleField: "value",
21+
colorField: "key",
22+
radius: 1,
23+
innerRadius: 0.6,
24+
};
25+
26+
return (
27+
<Card>
28+
<CardHeader>
29+
<CardDescription>{title}</CardDescription>
30+
</CardHeader>
31+
32+
<CardContent className="-p-5 h-72">
33+
<Pie {...config} />
34+
</CardContent>
35+
</Card>
36+
);
37+
};
38+
39+
export default DonutCard;

src/components/overview/empty.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Link from "next/link";
2+
import { Button } from "@/components/ui/button";
3+
import EmptyState from "@/components/shared/empty-state";
4+
import { RiPieChartFill, RiArrowRightLine } from "@remixicon/react";
5+
6+
type EmptyOverviewProps = {
7+
firstName: string | undefined;
8+
publicCompanyId: string;
9+
};
10+
11+
const EmptyOverview = ({ firstName, publicCompanyId }: EmptyOverviewProps) => {
12+
return (
13+
<EmptyState
14+
icon={<RiPieChartFill />}
15+
title={`Welcome to OpenCap${firstName && `, ${firstName}`} 👋`}
16+
subtitle={
17+
<span className="text-muted-foreground">
18+
We will get you setup with your Captable in no time.
19+
</span>
20+
}
21+
>
22+
<Button size="lg">
23+
<Link href={`/${publicCompanyId}/stakeholders`}>
24+
Let{`'`}s get started
25+
<RiArrowRightLine className="ml-5 inline-block h-4 w-5" />
26+
</Link>
27+
</Button>
28+
</EmptyState>
29+
);
30+
};
31+
32+
export default EmptyOverview;

0 commit comments

Comments
 (0)