From 815772d0f3549b99b34a1baaac86a4c8ec96e7ff Mon Sep 17 00:00:00 2001
From: Puru D
Date: Tue, 2 Jul 2024 01:24:44 -0500
Subject: [PATCH 01/60] fix: fixing few things, and getting started with
securities endpoint
---
src/server/api/auth.ts | 11 +--
src/server/api/error.ts | 2 +-
src/server/api/index.ts | 8 ++
src/server/api/routes/_example.ts | 4 +-
src/server/api/routes/company/getMany.ts | 7 +-
src/server/api/routes/company/getOne.ts | 19 +++--
.../api/routes/company/securities/create.ts | 58 ++++++++++++++
.../routes/company/securities/deleteOne.ts | 77 +++++++++++++++++++
.../api/routes/company/securities/getMany.ts | 58 ++++++++++++++
.../api/routes/company/securities/getOne.ts | 71 +++++++++++++++++
.../api/routes/company/securities/index.ts | 16 ++++
.../api/routes/company/securities/update.ts | 71 +++++++++++++++++
src/server/api/schema/company.ts | 2 +-
src/server/api/schema/delete.ts | 13 ++++
src/server/api/schema/securities.ts | 10 +++
15 files changed, 408 insertions(+), 19 deletions(-)
create mode 100644 src/server/api/routes/company/securities/create.ts
create mode 100644 src/server/api/routes/company/securities/deleteOne.ts
create mode 100644 src/server/api/routes/company/securities/getMany.ts
create mode 100644 src/server/api/routes/company/securities/getOne.ts
create mode 100644 src/server/api/routes/company/securities/index.ts
create mode 100644 src/server/api/routes/company/securities/update.ts
create mode 100644 src/server/api/schema/delete.ts
create mode 100644 src/server/api/schema/securities.ts
diff --git a/src/server/api/auth.ts b/src/server/api/auth.ts
index 9149887e0..4d0ea14cf 100644
--- a/src/server/api/auth.ts
+++ b/src/server/api/auth.ts
@@ -1,6 +1,7 @@
import { verifySecureHash } from "@/lib/crypto";
import { ApiError } from "@/server/api/error";
import { db } from "@/server/db";
+import type { Company, Member, User } from "@prisma/client";
import type { Context } from "hono";
export const withMemberAuth = async (c: Context) => {
@@ -26,12 +27,12 @@ export const withCompanyAuth = async (c: Context) => {
const bearerToken = await getBearerToken(c);
const user = await verifyBearerToken(bearerToken);
- const member = await db.member.findFirst({
+ const member = (await db.member.findFirst({
where: {
userId: user.id,
companyId: id,
},
- });
+ })) as Member;
if (!member) {
throw new ApiError({
@@ -40,9 +41,9 @@ export const withCompanyAuth = async (c: Context) => {
});
}
- const company = await db.company.findUnique({
+ const company = (await db.company.findUnique({
where: { id },
- });
+ })) as Company;
if (!company) {
throw new ApiError({
@@ -118,5 +119,5 @@ const verifyBearerToken = async (bearerToken: string | null | undefined) => {
data: { lastUsed: new Date() },
});
- return apiKey.user;
+ return apiKey.user as User;
};
diff --git a/src/server/api/error.ts b/src/server/api/error.ts
index b8349fb1b..bcb7aa7cd 100644
--- a/src/server/api/error.ts
+++ b/src/server/api/error.ts
@@ -17,7 +17,7 @@ const ErrorContent = {
},
};
-export const ErrorResponses = {
+export const ApiErrorResponses = {
400: {
content: ErrorContent,
description: "Bad Request",
diff --git a/src/server/api/index.ts b/src/server/api/index.ts
index a6165b81d..616263ebb 100644
--- a/src/server/api/index.ts
+++ b/src/server/api/index.ts
@@ -1,9 +1,17 @@
import { PublicAPI } from "./hono";
import companyRoutes from "./routes/company";
+import securitiesRoutes from "./routes/company/securities";
+// import transactionsRoutes from "./routes/company/transaction";
export const api = PublicAPI();
// RESTful routes for company
companyRoutes(api);
+// RESTful routes for securities
+securitiesRoutes(api);
+
+// RESTful routes for transactions
+// transactionsRoutes(api);
+
export default api;
diff --git a/src/server/api/routes/_example.ts b/src/server/api/routes/_example.ts
index e2db2b88a..866ee1bc1 100644
--- a/src/server/api/routes/_example.ts
+++ b/src/server/api/routes/_example.ts
@@ -1,4 +1,4 @@
-import { ErrorResponses } from "@/server/api/error";
+import { ApiErrorResponses } from "@/server/api/error";
import type { PublicAPI } from "@/server/api/hono";
import { createRoute, z } from "@hono/zod-openapi";
import type { Context } from "hono";
@@ -41,7 +41,7 @@ const route = createRoute({
description: "Get a company by ID",
},
- ...ErrorResponses,
+ ...ApiErrorResponses,
},
});
const getOne = (app: PublicAPI) => {
diff --git a/src/server/api/routes/company/getMany.ts b/src/server/api/routes/company/getMany.ts
index 6b210e403..8c8736d38 100644
--- a/src/server/api/routes/company/getMany.ts
+++ b/src/server/api/routes/company/getMany.ts
@@ -1,5 +1,5 @@
import { withMemberAuth } from "@/server/api/auth";
-import { ErrorResponses } from "@/server/api/error";
+import { ApiErrorResponses } from "@/server/api/error";
import type { PublicAPI } from "@/server/api/hono";
import { ApiCompanySchema } from "@/server/api/schema/company";
import { db } from "@/server/db";
@@ -19,12 +19,13 @@ const route = createRoute({
}),
},
},
- description: "List companies",
+ description: "List of companies",
},
- ...ErrorResponses,
+ ...ApiErrorResponses,
},
});
+
const getMany = (app: PublicAPI) => {
app.openapi(route, async (c: Context) => {
const { membership } = await withMemberAuth(c);
diff --git a/src/server/api/routes/company/getOne.ts b/src/server/api/routes/company/getOne.ts
index 0f42f4340..ca8120368 100644
--- a/src/server/api/routes/company/getOne.ts
+++ b/src/server/api/routes/company/getOne.ts
@@ -1,8 +1,9 @@
import { withCompanyAuth } from "@/server/api/auth";
-import { ApiError, ErrorResponses } from "@/server/api/error";
+import { ApiError, ApiErrorResponses } from "@/server/api/error";
import type { PublicAPI } from "@/server/api/hono";
import { ApiCompanySchema } from "@/server/api/schema/company";
import { createRoute, z } from "@hono/zod-openapi";
+import type { Company } from "@prisma/client";
import type { Context } from "hono";
export const RequestSchema = z.object({
@@ -34,13 +35,13 @@ const route = createRoute({
description: "Get a company by ID",
},
- ...ErrorResponses,
+ ...ApiErrorResponses,
},
-});
+} as const);
+
const getOne = (app: PublicAPI) => {
app.openapi(route, async (c: Context) => {
- // biome-ignore lint/suspicious/noExplicitAny:
- const { company } = (await withCompanyAuth(c)) as { company: any };
+ const { company } = (await withCompanyAuth(c)) as { company: Company };
if (!company) {
throw new ApiError({
@@ -48,8 +49,12 @@ const getOne = (app: PublicAPI) => {
message: "Company not found",
});
}
-
- return c.json(company, 200);
+ const response = {
+ ...company,
+ logo: company.logo ?? undefined,
+ website: company.website ?? undefined,
+ };
+ return c.json(response, 200);
});
};
diff --git a/src/server/api/routes/company/securities/create.ts b/src/server/api/routes/company/securities/create.ts
new file mode 100644
index 000000000..e2e1c6495
--- /dev/null
+++ b/src/server/api/routes/company/securities/create.ts
@@ -0,0 +1,58 @@
+import { withCompanyAuth } from "@/server/api/auth";
+import { ApiError, ApiErrorResponses } from "@/server/api/error";
+import type { PublicAPI } from "@/server/api/hono";
+import { ApiSecuritiesSchema } from "@/server/api/schema/securities";
+import { createRoute, z } from "@hono/zod-openapi";
+import type { Company } from "@prisma/client";
+import type { Context } from "hono";
+
+export const RequestSchema = z.object({
+ id: z
+ .string()
+ .cuid()
+ .openapi({
+ description: "Company ID",
+ param: {
+ name: "id",
+ in: "path",
+ },
+
+ example: "cly405ci60000i7ngbiel3m5l",
+ }),
+});
+
+const route = createRoute({
+ method: "post",
+ path: "/v1/companies/:id/securities",
+ request: { params: RequestSchema },
+ responses: {
+ 200: {
+ content: {
+ "application/json": {
+ schema: ApiSecuritiesSchema,
+ },
+ },
+ description: "Create a security",
+ },
+
+ ...ApiErrorResponses,
+ },
+});
+
+const create = (app: PublicAPI) => {
+ app.openapi(route, async (c: Context) => {
+ const { company } = (await withCompanyAuth(c)) as { company: Company };
+
+ if (!company) {
+ throw new ApiError({
+ code: "NOT_FOUND",
+ message: "Company not found",
+ });
+ }
+
+ // TODO: Implement the logic to create a security
+ return c.json(company, 200);
+ });
+};
+
+export default create;
diff --git a/src/server/api/routes/company/securities/deleteOne.ts b/src/server/api/routes/company/securities/deleteOne.ts
new file mode 100644
index 000000000..24d6e8830
--- /dev/null
+++ b/src/server/api/routes/company/securities/deleteOne.ts
@@ -0,0 +1,77 @@
+import { withCompanyAuth } from "@/server/api/auth";
+import { ApiError, ApiErrorResponses } from "@/server/api/error";
+import type { PublicAPI } from "@/server/api/hono";
+import { ApiDeleteResponseSchema } from "@/server/api/schema/delete";
+import { createRoute, z } from "@hono/zod-openapi";
+import type { Company } from "@prisma/client";
+import type { Context } from "hono";
+
+export const RequestSchema = z.object({
+ id: z
+ .string()
+ .cuid()
+ .openapi({
+ description: "Company ID",
+ param: {
+ name: "id",
+ in: "path",
+ },
+
+ example: "cly405ci60000i7ngbiel3m5l",
+ }),
+
+ securityId: z
+ .string()
+ .cuid()
+ .openapi({
+ description: "Security ID",
+ param: {
+ name: "securityId",
+ in: "path",
+ },
+
+ example: "cly43q7220000i7nggrlj2a8g",
+ }),
+});
+
+const route = createRoute({
+ method: "delete",
+ path: "/v1/companies/:id/securities/:securityId",
+ request: { params: RequestSchema },
+ responses: {
+ 200: {
+ content: {
+ "application/json": {
+ schema: ApiDeleteResponseSchema,
+ },
+ },
+ description: "Delete a security by ID",
+ },
+
+ ...ApiErrorResponses,
+ },
+});
+
+const deleteOne = (app: PublicAPI) => {
+ app.openapi(route, async (c: Context) => {
+ const { company } = (await withCompanyAuth(c)) as { company: Company };
+
+ if (!company) {
+ throw new ApiError({
+ code: "NOT_FOUND",
+ message: "Company not found",
+ });
+ }
+
+ // TODO: Implement the logic to delete security by ID
+ return c.json(
+ {
+ success: true,
+ message: "Resource successfully deleted",
+ },
+ 200,
+ );
+ });
+};
+
+export default deleteOne;
diff --git a/src/server/api/routes/company/securities/getMany.ts b/src/server/api/routes/company/securities/getMany.ts
new file mode 100644
index 000000000..33b3e7992
--- /dev/null
+++ b/src/server/api/routes/company/securities/getMany.ts
@@ -0,0 +1,58 @@
+import { withCompanyAuth } from "@/server/api/auth";
+import { ApiError, ApiErrorResponses } from "@/server/api/error";
+import type { PublicAPI } from "@/server/api/hono";
+import { ApiSecuritiesSchema } from "@/server/api/schema/securities";
+import { createRoute, z } from "@hono/zod-openapi";
+import type { Company } from "@prisma/client";
+import type { Context } from "hono";
+
+export const RequestSchema = z.object({
+ id: z
+ .string()
+ .cuid()
+ .openapi({
+ description: "Security ID",
+ param: {
+ name: "id",
+ in: "path",
+ },
+
+ example: "cly405ci60000i7ngbiel3m5l",
+ }),
+});
+
+const route = createRoute({
+ method: "get",
+ path: "/v1/companies/:id/securities",
+ request: { params: RequestSchema },
+ responses: {
+ 200: {
+ content: {
+ "application/json": {
+ schema: ApiSecuritiesSchema,
+ },
+ },
+ description: "Get a security by ID",
+ },
+
+ ...ApiErrorResponses,
+ },
+});
+
+const getMany = (app: PublicAPI) => {
+ app.openapi(route, async (c: Context) => {
+ const { company } = (await withCompanyAuth(c)) as { company: Company };
+
+ if (!company) {
+ throw new ApiError({
+ code: "NOT_FOUND",
+ message: "Company not found",
+ });
+ }
+
+ // TODO: Implement the logic to get many securities
+ return c.json(company, 200);
+ });
+};
+
+export default getMany;
diff --git a/src/server/api/routes/company/securities/getOne.ts b/src/server/api/routes/company/securities/getOne.ts
new file mode 100644
index 000000000..690633a78
--- /dev/null
+++ b/src/server/api/routes/company/securities/getOne.ts
@@ -0,0 +1,71 @@
+import { withCompanyAuth } from "@/server/api/auth";
+import { ApiError, ApiErrorResponses } from "@/server/api/error";
+import type { PublicAPI } from "@/server/api/hono";
+import { ApiSecuritiesSchema } from "@/server/api/schema/securities";
+import { createRoute, z } from "@hono/zod-openapi";
+import type { Company } from "@prisma/client";
+import type { Context } from "hono";
+
+export const RequestSchema = z.object({
+ id: z
+ .string()
+ .cuid()
+ .openapi({
+ description: "Company ID",
+ param: {
+ name: "id",
+ in: "path",
+ },
+
+ example: "cly405ci60000i7ngbiel3m5l",
+ }),
+
+ securityId: z
+ .string()
+ .cuid()
+ .openapi({
+ description: "Security ID",
+ param: {
+ name: "securityId",
+ in: "path",
+ },
+
+ example: "cly43q7220000i7nggrlj2a8g",
+ }),
+});
+
+const route = createRoute({
+ method: "get",
+ path: "/v1/companies/:id/securities/:securityId",
+ request: { params: RequestSchema },
+ responses: {
+ 200: {
+ content: {
+ "application/json": {
+ schema: ApiSecuritiesSchema,
+ },
+ },
+ description: "Get a security by ID",
+ },
+
+ ...ApiErrorResponses,
+ },
+});
+
+const getOne = (app: PublicAPI) => {
+ app.openapi(route, async (c: Context) => {
+ const { company } = (await withCompanyAuth(c)) as { company: Company };
+
+ if (!company) {
+ throw new ApiError({
+ code: "NOT_FOUND",
+ message: "Company not found",
+ });
+ }
+
+ // TODO: Implement the logic to get security by ID
+ return c.json(company, 200);
+ });
+};
+
+export default getOne;
diff --git a/src/server/api/routes/company/securities/index.ts b/src/server/api/routes/company/securities/index.ts
new file mode 100644
index 000000000..7799552c9
--- /dev/null
+++ b/src/server/api/routes/company/securities/index.ts
@@ -0,0 +1,16 @@
+import type { PublicAPI } from "@/server/api/hono";
+import create from "./create";
+import deleteOne from "./deleteOne";
+import getMany from "./getMany";
+import getOne from "./getOne";
+import update from "./update";
+
+const securitiesRoutes = (api: PublicAPI) => {
+ update(api);
+ create(api);
+ getOne(api);
+ getMany(api);
+ deleteOne(api);
+};
+
+export default securitiesRoutes;
diff --git a/src/server/api/routes/company/securities/update.ts b/src/server/api/routes/company/securities/update.ts
new file mode 100644
index 000000000..914f5a366
--- /dev/null
+++ b/src/server/api/routes/company/securities/update.ts
@@ -0,0 +1,71 @@
+import { withCompanyAuth } from "@/server/api/auth";
+import { ApiError, ApiErrorResponses } from "@/server/api/error";
+import type { PublicAPI } from "@/server/api/hono";
+import { ApiSecuritiesSchema } from "@/server/api/schema/securities";
+import { createRoute, z } from "@hono/zod-openapi";
+import type { Company } from "@prisma/client";
+import type { Context } from "hono";
+
+export const RequestSchema = z.object({
+ id: z
+ .string()
+ .cuid()
+ .openapi({
+ description: "Company ID",
+ param: {
+ name: "id",
+ in: "path",
+ },
+
+ example: "cly405ci60000i7ngbiel3m5l",
+ }),
+
+ securityId: z
+ .string()
+ .cuid()
+ .openapi({
+ description: "Security ID",
+ param: {
+ name: "securityId",
+ in: "path",
+ },
+
+ example: "cly43q7220000i7nggrlj2a8g",
+ }),
+});
+
+const route = createRoute({
+ method: "put",
+ path: "/v1/companies/:id/securities/:securityId",
+ request: { params: RequestSchema },
+ responses: {
+ 200: {
+ content: {
+ "application/json": {
+ schema: ApiSecuritiesSchema,
+ },
+ },
+ description: "Create a security",
+ },
+
+ ...ApiErrorResponses,
+ },
+});
+
+const update = (app: PublicAPI) => {
+ app.openapi(route, async (c: Context) => {
+ const { company } = (await withCompanyAuth(c)) as { company: Company };
+
+ if (!company) {
+ throw new ApiError({
+ code: "NOT_FOUND",
+ message: "Company not found",
+ });
+ }
+
+ // TODO: Implement the logic to update a security
+ return c.json(company, 200);
+ });
+};
+
+export default update;
diff --git a/src/server/api/schema/company.ts b/src/server/api/schema/company.ts
index f37db4592..b73c14bfb 100644
--- a/src/server/api/schema/company.ts
+++ b/src/server/api/schema/company.ts
@@ -1,4 +1,4 @@
-import { z } from "zod";
+import { z } from "@hono/zod-openapi";
export const ApiCompanySchema = z.object({
id: z.string().cuid().openapi({
diff --git a/src/server/api/schema/delete.ts b/src/server/api/schema/delete.ts
new file mode 100644
index 000000000..eaede7bff
--- /dev/null
+++ b/src/server/api/schema/delete.ts
@@ -0,0 +1,13 @@
+import { z } from "@hono/zod-openapi";
+
+export const ApiDeleteResponseSchema = z.object({
+ success: z.boolean().openapi({
+ description: "Success",
+ example: true,
+ }),
+
+ message: z.string().openapi({
+ description: "Message",
+ example: "Resource successfully deleted",
+ }),
+});
diff --git a/src/server/api/schema/securities.ts b/src/server/api/schema/securities.ts
new file mode 100644
index 000000000..dd6fd76dd
--- /dev/null
+++ b/src/server/api/schema/securities.ts
@@ -0,0 +1,10 @@
+import { z } from "@hono/zod-openapi";
+
+export const ApiSecuritiesSchema = z.object({
+ id: z.string().cuid().openapi({
+ description: "Security ID",
+ example: "cly402ncj0000i7ng9l0v04qr",
+ }),
+});
+
+export type ApiSecuritiesType = z.infer;
From 6d7c28ac8fcc1def2d46581d4dbb608954960f80 Mon Sep 17 00:00:00 2001
From: Puru D
Date: Wed, 3 Jul 2024 01:55:23 -0500
Subject: [PATCH 02/60] feat: getting started with restful SAFE api endpoint
---
src/server/api/index.ts | 10 +++-------
.../company/{securities => safes}/create.ts | 10 +++++-----
.../company/{securities => safes}/deleteOne.ts | 10 +++++-----
.../company/{securities => safes}/getMany.ts | 12 ++++++------
.../company/{securities => safes}/getOne.ts | 16 ++++++++--------
.../company/{securities => safes}/index.ts | 4 ++--
.../company/{securities => safes}/update.ts | 16 ++++++++--------
src/server/api/schema/safes.ts | 10 ++++++++++
src/server/api/schema/securities.ts | 10 ----------
9 files changed, 47 insertions(+), 51 deletions(-)
rename src/server/api/routes/company/{securities => safes}/create.ts (82%)
rename src/server/api/routes/company/{securities => safes}/deleteOne.ts (88%)
rename src/server/api/routes/company/{securities => safes}/getMany.ts (79%)
rename src/server/api/routes/company/{securities => safes}/getOne.ts (78%)
rename src/server/api/routes/company/{securities => safes}/index.ts (78%)
rename src/server/api/routes/company/{securities => safes}/update.ts (79%)
create mode 100644 src/server/api/schema/safes.ts
delete mode 100644 src/server/api/schema/securities.ts
diff --git a/src/server/api/index.ts b/src/server/api/index.ts
index 616263ebb..aa925680c 100644
--- a/src/server/api/index.ts
+++ b/src/server/api/index.ts
@@ -1,17 +1,13 @@
import { PublicAPI } from "./hono";
import companyRoutes from "./routes/company";
-import securitiesRoutes from "./routes/company/securities";
-// import transactionsRoutes from "./routes/company/transaction";
+import safeRoutes from "./routes/company/safes";
export const api = PublicAPI();
// RESTful routes for company
companyRoutes(api);
-// RESTful routes for securities
-securitiesRoutes(api);
-
-// RESTful routes for transactions
-// transactionsRoutes(api);
+// RESTful routes for SAFEs
+safeRoutes(api);
export default api;
diff --git a/src/server/api/routes/company/securities/create.ts b/src/server/api/routes/company/safes/create.ts
similarity index 82%
rename from src/server/api/routes/company/securities/create.ts
rename to src/server/api/routes/company/safes/create.ts
index e2e1c6495..9cd4c97c0 100644
--- a/src/server/api/routes/company/securities/create.ts
+++ b/src/server/api/routes/company/safes/create.ts
@@ -1,7 +1,7 @@
import { withCompanyAuth } from "@/server/api/auth";
import { ApiError, ApiErrorResponses } from "@/server/api/error";
import type { PublicAPI } from "@/server/api/hono";
-import { ApiSecuritiesSchema } from "@/server/api/schema/securities";
+import { ApiSafesSchema } from "@/server/api/schema/safes";
import { createRoute, z } from "@hono/zod-openapi";
import type { Company } from "@prisma/client";
import type { Context } from "hono";
@@ -23,16 +23,16 @@ export const RequestSchema = z.object({
const route = createRoute({
method: "post",
- path: "/v1/companies/:id/securities",
+ path: "/v1/companies/:id/safes",
request: { params: RequestSchema },
responses: {
200: {
content: {
"application/json": {
- schema: ApiSecuritiesSchema,
+ schema: ApiSafesSchema,
},
},
- description: "Create a security",
+ description: "Create a SAFE",
},
...ApiErrorResponses,
@@ -50,7 +50,7 @@ const create = (app: PublicAPI) => {
});
}
- // TODO: Implement the logic to create a security
+ // TODO: Implement the logic to create a SAFE
return c.json(company, 200);
});
};
diff --git a/src/server/api/routes/company/securities/deleteOne.ts b/src/server/api/routes/company/safes/deleteOne.ts
similarity index 88%
rename from src/server/api/routes/company/securities/deleteOne.ts
rename to src/server/api/routes/company/safes/deleteOne.ts
index 24d6e8830..d0ea5b01d 100644
--- a/src/server/api/routes/company/securities/deleteOne.ts
+++ b/src/server/api/routes/company/safes/deleteOne.ts
@@ -20,13 +20,13 @@ export const RequestSchema = z.object({
example: "cly405ci60000i7ngbiel3m5l",
}),
- securityId: z
+ safeId: z
.string()
.cuid()
.openapi({
description: "Security ID",
param: {
- name: "securityId",
+ name: "safeId",
in: "path",
},
@@ -36,7 +36,7 @@ export const RequestSchema = z.object({
const route = createRoute({
method: "delete",
- path: "/v1/companies/:id/securities/:securityId",
+ path: "/v1/companies/:id/safes/:safeId",
request: { params: RequestSchema },
responses: {
200: {
@@ -45,7 +45,7 @@ const route = createRoute({
schema: ApiDeleteResponseSchema,
},
},
- description: "Delete a security by ID",
+ description: "Delete a SAFE by ID",
},
...ApiErrorResponses,
@@ -63,7 +63,7 @@ const deleteOne = (app: PublicAPI) => {
});
}
- // TODO: Implement the logic to delete security by ID
+ // TODO: Implement the logic to delete SAFE by ID
return c.json(
{
success: true,
diff --git a/src/server/api/routes/company/securities/getMany.ts b/src/server/api/routes/company/safes/getMany.ts
similarity index 79%
rename from src/server/api/routes/company/securities/getMany.ts
rename to src/server/api/routes/company/safes/getMany.ts
index 33b3e7992..46477a15f 100644
--- a/src/server/api/routes/company/securities/getMany.ts
+++ b/src/server/api/routes/company/safes/getMany.ts
@@ -1,7 +1,7 @@
import { withCompanyAuth } from "@/server/api/auth";
import { ApiError, ApiErrorResponses } from "@/server/api/error";
import type { PublicAPI } from "@/server/api/hono";
-import { ApiSecuritiesSchema } from "@/server/api/schema/securities";
+import { ApiSafesSchema } from "@/server/api/schema/safes";
import { createRoute, z } from "@hono/zod-openapi";
import type { Company } from "@prisma/client";
import type { Context } from "hono";
@@ -11,7 +11,7 @@ export const RequestSchema = z.object({
.string()
.cuid()
.openapi({
- description: "Security ID",
+ description: "SAFE ID",
param: {
name: "id",
in: "path",
@@ -23,16 +23,16 @@ export const RequestSchema = z.object({
const route = createRoute({
method: "get",
- path: "/v1/companies/:id/securities",
+ path: "/v1/companies/:id/safes",
request: { params: RequestSchema },
responses: {
200: {
content: {
"application/json": {
- schema: ApiSecuritiesSchema,
+ schema: ApiSafesSchema,
},
},
- description: "Get a security by ID",
+ description: "Get a SAFE by ID",
},
...ApiErrorResponses,
@@ -50,7 +50,7 @@ const getMany = (app: PublicAPI) => {
});
}
- // TODO: Implement the logic to get many securities
+ // TODO: Implement the logic to get many safes
return c.json(company, 200);
});
};
diff --git a/src/server/api/routes/company/securities/getOne.ts b/src/server/api/routes/company/safes/getOne.ts
similarity index 78%
rename from src/server/api/routes/company/securities/getOne.ts
rename to src/server/api/routes/company/safes/getOne.ts
index 690633a78..108ed8e86 100644
--- a/src/server/api/routes/company/securities/getOne.ts
+++ b/src/server/api/routes/company/safes/getOne.ts
@@ -1,7 +1,7 @@
import { withCompanyAuth } from "@/server/api/auth";
import { ApiError, ApiErrorResponses } from "@/server/api/error";
import type { PublicAPI } from "@/server/api/hono";
-import { ApiSecuritiesSchema } from "@/server/api/schema/securities";
+import { ApiSafesSchema } from "@/server/api/schema/safes";
import { createRoute, z } from "@hono/zod-openapi";
import type { Company } from "@prisma/client";
import type { Context } from "hono";
@@ -20,13 +20,13 @@ export const RequestSchema = z.object({
example: "cly405ci60000i7ngbiel3m5l",
}),
- securityId: z
+ safeId: z
.string()
.cuid()
.openapi({
- description: "Security ID",
+ description: "SAFE ID",
param: {
- name: "securityId",
+ name: "safeId",
in: "path",
},
@@ -36,16 +36,16 @@ export const RequestSchema = z.object({
const route = createRoute({
method: "get",
- path: "/v1/companies/:id/securities/:securityId",
+ path: "/v1/companies/:id/safes/:safeId",
request: { params: RequestSchema },
responses: {
200: {
content: {
"application/json": {
- schema: ApiSecuritiesSchema,
+ schema: ApiSafesSchema,
},
},
- description: "Get a security by ID",
+ description: "Get a SAFE by ID",
},
...ApiErrorResponses,
@@ -63,7 +63,7 @@ const getOne = (app: PublicAPI) => {
});
}
- // TODO: Implement the logic to get security by ID
+ // TODO: Implement the logic to get SAFE by ID
return c.json(company, 200);
});
};
diff --git a/src/server/api/routes/company/securities/index.ts b/src/server/api/routes/company/safes/index.ts
similarity index 78%
rename from src/server/api/routes/company/securities/index.ts
rename to src/server/api/routes/company/safes/index.ts
index 7799552c9..a5441abb6 100644
--- a/src/server/api/routes/company/securities/index.ts
+++ b/src/server/api/routes/company/safes/index.ts
@@ -5,7 +5,7 @@ import getMany from "./getMany";
import getOne from "./getOne";
import update from "./update";
-const securitiesRoutes = (api: PublicAPI) => {
+const safeRoutes = (api: PublicAPI) => {
update(api);
create(api);
getOne(api);
@@ -13,4 +13,4 @@ const securitiesRoutes = (api: PublicAPI) => {
deleteOne(api);
};
-export default securitiesRoutes;
+export default safeRoutes;
diff --git a/src/server/api/routes/company/securities/update.ts b/src/server/api/routes/company/safes/update.ts
similarity index 79%
rename from src/server/api/routes/company/securities/update.ts
rename to src/server/api/routes/company/safes/update.ts
index 914f5a366..8f684b77a 100644
--- a/src/server/api/routes/company/securities/update.ts
+++ b/src/server/api/routes/company/safes/update.ts
@@ -1,7 +1,7 @@
import { withCompanyAuth } from "@/server/api/auth";
import { ApiError, ApiErrorResponses } from "@/server/api/error";
import type { PublicAPI } from "@/server/api/hono";
-import { ApiSecuritiesSchema } from "@/server/api/schema/securities";
+import { ApiSafesSchema } from "@/server/api/schema/safes";
import { createRoute, z } from "@hono/zod-openapi";
import type { Company } from "@prisma/client";
import type { Context } from "hono";
@@ -20,13 +20,13 @@ export const RequestSchema = z.object({
example: "cly405ci60000i7ngbiel3m5l",
}),
- securityId: z
+ safeId: z
.string()
.cuid()
.openapi({
- description: "Security ID",
+ description: "SAFE ID",
param: {
- name: "securityId",
+ name: "safeId",
in: "path",
},
@@ -36,16 +36,16 @@ export const RequestSchema = z.object({
const route = createRoute({
method: "put",
- path: "/v1/companies/:id/securities/:securityId",
+ path: "/v1/companies/:id/safes/:safeId",
request: { params: RequestSchema },
responses: {
200: {
content: {
"application/json": {
- schema: ApiSecuritiesSchema,
+ schema: ApiSafesSchema,
},
},
- description: "Create a security",
+ description: "Create a SAFE",
},
...ApiErrorResponses,
@@ -63,7 +63,7 @@ const update = (app: PublicAPI) => {
});
}
- // TODO: Implement the logic to update a security
+ // TODO: Implement the logic to update a SAFE
return c.json(company, 200);
});
};
diff --git a/src/server/api/schema/safes.ts b/src/server/api/schema/safes.ts
new file mode 100644
index 000000000..bd337376e
--- /dev/null
+++ b/src/server/api/schema/safes.ts
@@ -0,0 +1,10 @@
+import { z } from "@hono/zod-openapi";
+
+export const ApiSafesSchema = z.object({
+ id: z.string().cuid().openapi({
+ description: "SAFE ID",
+ example: "cly402ncj0000i7ng9l0v04qr",
+ }),
+});
+
+export type ApiSafesType = z.infer;
diff --git a/src/server/api/schema/securities.ts b/src/server/api/schema/securities.ts
deleted file mode 100644
index dd6fd76dd..000000000
--- a/src/server/api/schema/securities.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { z } from "@hono/zod-openapi";
-
-export const ApiSecuritiesSchema = z.object({
- id: z.string().cuid().openapi({
- description: "Security ID",
- example: "cly402ncj0000i7ng9l0v04qr",
- }),
-});
-
-export type ApiSecuritiesType = z.infer;
From 1d432ecd6354b06b3fe70915438b97e03b1162ca Mon Sep 17 00:00:00 2001
From: Puru D
Date: Fri, 5 Jul 2024 01:20:59 -0500
Subject: [PATCH 03/60] chore: update cmd+k text
---
src/components/dashboard/navbar/command-menu.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/dashboard/navbar/command-menu.tsx b/src/components/dashboard/navbar/command-menu.tsx
index be1a5fad5..e80939457 100644
--- a/src/components/dashboard/navbar/command-menu.tsx
+++ b/src/components/dashboard/navbar/command-menu.tsx
@@ -132,7 +132,7 @@ const Pages: CommandOption[] = [
},
{
id: "issue-share",
- title: "Issue a share",
+ title: "Issue a share certificate",
onClick: () => {
pushModal("IssueShareModal", {
shouldClientFetch: true,
From 33b713dd1146b69a0a73f2035ecf2ca09fbe1789 Mon Sep 17 00:00:00 2001
From: Puru D
Date: Sat, 6 Jul 2024 23:18:58 -0500
Subject: [PATCH 04/60] WIP
---
.../migration.sql | 14 ++++++++++++++
prisma/schema.prisma | 12 ++++++------
src/components/dashboard/navbar/command-menu.tsx | 2 +-
src/components/safe/safe-actions.tsx | 2 ++
.../safe/steps/investor-details/form.tsx | 10 ----------
src/server/api/routes/company/safes/create.ts | 6 +++++-
6 files changed, 28 insertions(+), 18 deletions(-)
create mode 100644 prisma/migrations/20240705065242_update_safe_template_names/migration.sql
diff --git a/prisma/migrations/20240705065242_update_safe_template_names/migration.sql b/prisma/migrations/20240705065242_update_safe_template_names/migration.sql
new file mode 100644
index 000000000..d14d6f3ab
--- /dev/null
+++ b/prisma/migrations/20240705065242_update_safe_template_names/migration.sql
@@ -0,0 +1,14 @@
+/*
+ Warnings:
+
+ - The values [Valuation Cap, no Discount,Discount, no Valuation Cap,MFN, no Valuation Cap, no Discount,Valuation Cap, no Discount, include Pro Rata Rights,Discount, no Valuation Cap, include Pro Rata Rights,MFN, no Valuation Cap, no Discount, include Pro Rata Rights] on the enum `SafeTemplateEnum` will be removed. If these variants are still used in the database, this will fail.
+
+*/
+-- AlterEnum
+BEGIN;
+CREATE TYPE "SafeTemplateEnum_new" AS ENUM ('YC - Valuation Cap, no Discount', 'YC - Discount, no Valuation Cap', 'YC - MFN, no Valuation Cap, no Discount', 'YC - Valuation Cap, Pro Rata, no Discount', 'YC - Discount, Pro Rata, no Valuation Cap', 'YC - MFN, Pro Rata, no Valuation Cap & Discount', 'Custom');
+ALTER TABLE "Safe" ALTER COLUMN "safeTemplate" TYPE "SafeTemplateEnum_new" USING ("safeTemplate"::text::"SafeTemplateEnum_new");
+ALTER TYPE "SafeTemplateEnum" RENAME TO "SafeTemplateEnum_old";
+ALTER TYPE "SafeTemplateEnum_new" RENAME TO "SafeTemplateEnum";
+DROP TYPE "SafeTemplateEnum_old";
+COMMIT;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 8623ef40e..4e9b312b5 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -758,13 +758,13 @@ enum SafeStatusEnum {
// YC Standard Safe
enum SafeTemplateEnum {
- POST_MONEY_CAP @map("Valuation Cap, no Discount")
- POST_MONEY_DISCOUNT @map("Discount, no Valuation Cap")
- POST_MONEY_MFN @map("MFN, no Valuation Cap, no Discount")
+ POST_MONEY_CAP @map("YC - Valuation Cap, no Discount")
+ POST_MONEY_DISCOUNT @map("YC - Discount, no Valuation Cap")
+ POST_MONEY_MFN @map("YC - MFN, no Valuation Cap, no Discount")
- POST_MONEY_CAP_WITH_PRO_RATA @map("Valuation Cap, no Discount, include Pro Rata Rights")
- POST_MONEY_DISCOUNT_WITH_PRO_RATA @map("Discount, no Valuation Cap, include Pro Rata Rights")
- POST_MONEY_MFN_WITH_PRO_RATA @map("MFN, no Valuation Cap, no Discount, include Pro Rata Rights")
+ POST_MONEY_CAP_WITH_PRO_RATA @map("YC - Valuation Cap, Pro Rata, no Discount")
+ POST_MONEY_DISCOUNT_WITH_PRO_RATA @map("YC - Discount, Pro Rata, no Valuation Cap")
+ POST_MONEY_MFN_WITH_PRO_RATA @map("YC - MFN, Pro Rata, no Valuation Cap & Discount")
CUSTOM @map("Custom")
}
diff --git a/src/components/dashboard/navbar/command-menu.tsx b/src/components/dashboard/navbar/command-menu.tsx
index e80939457..eb3780e51 100644
--- a/src/components/dashboard/navbar/command-menu.tsx
+++ b/src/components/dashboard/navbar/command-menu.tsx
@@ -165,7 +165,7 @@ const Pages: CommandOption[] = [
pushModal("NewSafeModal", {
title: "Create a new SAFE agreement",
subtitle:
- "Create, sign and send a new SAFE agreement to your investors.",
+ "Create, send and send a new SAFE agreement to your investors.",
});
},
icon: RiPieChart2Fill,
diff --git a/src/components/safe/safe-actions.tsx b/src/components/safe/safe-actions.tsx
index 651c23196..3eb23069d 100644
--- a/src/components/safe/safe-actions.tsx
+++ b/src/components/safe/safe-actions.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
diff --git a/src/components/safe/steps/investor-details/form.tsx b/src/components/safe/steps/investor-details/form.tsx
index 7d00ba73b..6f832ec22 100644
--- a/src/components/safe/steps/investor-details/form.tsx
+++ b/src/components/safe/steps/investor-details/form.tsx
@@ -12,15 +12,6 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectItemStyle,
- SelectSeparator,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
import {
StepperModalFooter,
StepperPrev,
@@ -29,7 +20,6 @@ import {
import { useFormValueUpdater } from "@/providers/form-value-provider";
import type { RouterOutputs } from "@/trpc/shared";
import { zodResolver } from "@hookform/resolvers/zod";
-import * as SelectPrimitive from "@radix-ui/react-select";
import { RiAddCircleLine } from "@remixicon/react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
diff --git a/src/server/api/routes/company/safes/create.ts b/src/server/api/routes/company/safes/create.ts
index 9cd4c97c0..145897fa1 100644
--- a/src/server/api/routes/company/safes/create.ts
+++ b/src/server/api/routes/company/safes/create.ts
@@ -21,10 +21,14 @@ export const RequestSchema = z.object({
}),
});
+export const RequestJsonSchema = z.object({
+ name: z.string().min(1),
+});
+
const route = createRoute({
method: "post",
path: "/v1/companies/:id/safes",
- request: { params: RequestSchema },
+ request: { params: RequestSchema, json: RequestJsonSchema },
responses: {
200: {
content: {
From 4b57872002f63c188b9887eabbd76d47664138dc Mon Sep 17 00:00:00 2001
From: Puru D
Date: Mon, 8 Jul 2024 01:49:25 -0500
Subject: [PATCH 05/60] feat: getting started with updated safe form
---
package.json | 1 +
pnpm-lock.yaml | 184 +++++++++++++++---
prisma/schema.prisma | 6 +-
.../[publicId]/documents/esign/page.tsx | 4 +-
src/components/modals/index.ts | 6 +-
src/components/modals/safe.tsx | 35 ++++
src/components/safe/form/index.tsx | 102 ++++++++++
.../safe/form/pre-post-selector.tsx | 87 +++++++++
src/components/safe/safe-actions.tsx | 14 +-
src/components/ui/stepper.tsx | 9 +-
10 files changed, 406 insertions(+), 42 deletions(-)
create mode 100644 src/components/modals/safe.tsx
create mode 100644 src/components/safe/form/index.tsx
create mode 100644 src/components/safe/form/pre-post-selector.tsx
diff --git a/package.json b/package.json
index d52206706..2f80d7dbf 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"@aws-sdk/client-s3": "^3.577.0",
"@aws-sdk/s3-request-presigner": "^3.577.0",
"@blocknote/react": "^0.12.2",
+ "@headlessui/react": "^2.1.2",
"@hono/swagger-ui": "^0.3.0",
"@hono/zod-openapi": "^0.14.5",
"@hookform/resolvers": "^3.6.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 80688a779..9efb53c41 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,9 @@ importers:
'@blocknote/react':
specifier: ^0.12.2
version: 0.12.4(@tiptap/pm@2.4.0)(@types/react@18.3.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1)
+ '@headlessui/react':
+ specifier: ^2.1.2
+ version: 2.1.2(react-dom@18.2.0(react@18.3.1))(react@18.3.1)
'@hono/swagger-ui':
specifier: ^0.3.0
version: 0.3.0(hono@4.4.8)
@@ -1228,6 +1231,12 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
+ '@floating-ui/react-dom@2.1.1':
+ resolution: {integrity: sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
'@floating-ui/react@0.19.2':
resolution: {integrity: sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==}
peerDependencies:
@@ -1240,12 +1249,21 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
+ '@floating-ui/react@0.26.19':
+ resolution: {integrity: sha512-Jk6zITdjjIvjO/VdQFvpRaD3qPwOHH6AoDHxjhpy+oK4KFgaSP871HYWUAPdnLmx1gQ+w/pB312co3tVml+BXA==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
'@floating-ui/utils@0.2.2':
resolution: {integrity: sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==}
'@floating-ui/utils@0.2.3':
resolution: {integrity: sha512-XGndio0l5/Gvd6CLIABvsav9HHezgDFFhDfHk1bvLfr9ni8dojqLSvBbotJEjmIwNHL7vK4QzBJTdBRoB+c1ww==}
+ '@floating-ui/utils@0.2.4':
+ resolution: {integrity: sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==}
+
'@floating-ui/vue@1.0.7':
resolution: {integrity: sha512-tm9aMT9IrMzoZfzPpsoZHP7j7ULZ0p9AzCJV6i2H8sAlKe36tAnwuQLHdm7vE0SnRkHJJXuMB/gNz4gFdHLNrg==}
@@ -1261,6 +1279,13 @@ packages:
react: ^16 || ^17 || ^18
react-dom: ^16 || ^17 || ^18
+ '@headlessui/react@2.1.2':
+ resolution: {integrity: sha512-Kb3hgk9gRNRcTZktBrKdHhF3xFhYkca1Rk6e1/im2ENf83dgN54orMW0uSKTXFnUpZOUFZ+wcY05LlipwgZIFQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^18
+ react-dom: ^18
+
'@headlessui/tailwindcss@0.2.1':
resolution: {integrity: sha512-2+5+NZ+RzMyrVeCZOxdbvkUSssSxGvcUxphkIfSVLpRiKsj+/63T2TOL9dBYMXVfj/CGr6hMxSRInzXv6YY7sA==}
engines: {node: '>=10'}
@@ -2573,6 +2598,27 @@ packages:
'@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
+ '@react-aria/focus@3.17.1':
+ resolution: {integrity: sha512-FLTySoSNqX++u0nWZJPPN5etXY0WBxaIe/YuL/GTEeuqUIuC/2bJSaw5hlsM6T2yjy6Y/VAxBcKSdAFUlU6njQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+
+ '@react-aria/interactions@3.21.3':
+ resolution: {integrity: sha512-BWIuf4qCs5FreDJ9AguawLVS0lV9UU+sK4CCnbCNNmYqOWY+1+gRXCsnOM32K+oMESBxilAjdHW5n1hsMqYMpA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+
+ '@react-aria/ssr@3.9.4':
+ resolution: {integrity: sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==}
+ engines: {node: '>= 12'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+
+ '@react-aria/utils@3.24.1':
+ resolution: {integrity: sha512-O3s9qhPMd6n42x9sKeJ3lhu5V1Tlnzhu6Yk8QOvDuXf7UGuUjXf9mzfHJt1dYzID4l9Fwm8toczBzPM9t0jc8Q==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+
'@react-pdf/fns@2.2.1':
resolution: {integrity: sha512-s78aDg0vDYaijU5lLOCsUD+qinQbfOvcNeaoX9AiE7+kZzzCo6B/nX+l48cmt9OosJmvZvE9DWR9cLhrhOi2pA==}
@@ -2611,6 +2657,16 @@ packages:
'@react-pdf/types@2.5.0':
resolution: {integrity: sha512-XsVRkt0hQ60I4e3leAVt+aZR3KJCaJd179BfJHAv4F4x6Vq3yqkry8lcbUWKGKDw1j3/8sW4FsgGR41SFvsG9A==}
+ '@react-stately/utils@3.10.1':
+ resolution: {integrity: sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+
+ '@react-types/shared@3.23.1':
+ resolution: {integrity: sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+
'@remirror/core-constants@2.0.2':
resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==}
@@ -3182,6 +3238,12 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+ '@tanstack/react-virtual@3.8.1':
+ resolution: {integrity: sha512-dP5a7giEM4BQWLJ7K07ToZv8rF51mzbrBMkf0scg1QNYuFx3utnPUBPUHdzaowZhIez1K2XS78amuzD+YGRA5Q==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+
'@tanstack/table-core@8.19.2':
resolution: {integrity: sha512-KpRjhgehIhbfH78ARm/GJDXGnpdw4bCg3qas6yjWSi7czJhI/J6pWln7NHtmBkGE9ZbohiiNtLqwGzKmBfixig==}
engines: {node: '>=12'}
@@ -3192,6 +3254,9 @@ packages:
'@tanstack/virtual-core@3.7.0':
resolution: {integrity: sha512-p0CWuqn+n8iZmsL7/l0Xg7kbyIKnHNqkEJkMDOkg4x3Ni3LohszmnJY8FPhTgG7Ad9ZFGcdKmn1R1mKUGEh9Xg==}
+ '@tanstack/virtual-core@3.8.1':
+ resolution: {integrity: sha512-uNtAwenT276M9QYCjTBoHZ8X3MUeCRoGK59zPi92hMIxdfS9AyHjkDWJ94WroDxnv48UE+hIeo21BU84jKc8aQ==}
+
'@tanstack/vue-virtual@3.7.0':
resolution: {integrity: sha512-RkSrajvJpV1RdJKgZnPgzyzVVx76QjPAu+spgdAms+SZRcSbYMUKlcjusnHjhszck5ngHXSXbSBp45ycF1nlDw==}
peerDependencies:
@@ -8347,10 +8412,10 @@ snapshots:
'@aws-crypto/sha1-browser': 3.0.0
'@aws-crypto/sha256-browser': 3.0.0
'@aws-crypto/sha256-js': 3.0.0
- '@aws-sdk/client-sso-oidc': 3.577.0
- '@aws-sdk/client-sts': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)
+ '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0)
+ '@aws-sdk/client-sts': 3.577.0
'@aws-sdk/core': 3.576.0
- '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0(@aws-sdk/client-sso-oidc@3.577.0))
+ '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0)
'@aws-sdk/middleware-bucket-endpoint': 3.577.0
'@aws-sdk/middleware-expect-continue': 3.577.0
'@aws-sdk/middleware-flexible-checksums': 3.577.0
@@ -8405,13 +8470,13 @@ snapshots:
transitivePeerDependencies:
- aws-crt
- '@aws-sdk/client-sso-oidc@3.577.0':
+ '@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)':
dependencies:
'@aws-crypto/sha256-browser': 3.0.0
'@aws-crypto/sha256-js': 3.0.0
- '@aws-sdk/client-sts': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)
+ '@aws-sdk/client-sts': 3.577.0
'@aws-sdk/core': 3.576.0
- '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0(@aws-sdk/client-sso-oidc@3.577.0))
+ '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0)
'@aws-sdk/middleware-host-header': 3.577.0
'@aws-sdk/middleware-logger': 3.577.0
'@aws-sdk/middleware-recursion-detection': 3.577.0
@@ -8448,6 +8513,7 @@ snapshots:
'@smithy/util-utf8': 3.0.0
tslib: 2.6.2
transitivePeerDependencies:
+ - '@aws-sdk/client-sts'
- aws-crt
'@aws-sdk/client-sso@3.577.0':
@@ -8493,13 +8559,13 @@ snapshots:
transitivePeerDependencies:
- aws-crt
- '@aws-sdk/client-sts@3.577.0(@aws-sdk/client-sso-oidc@3.577.0)':
+ '@aws-sdk/client-sts@3.577.0':
dependencies:
'@aws-crypto/sha256-browser': 3.0.0
'@aws-crypto/sha256-js': 3.0.0
- '@aws-sdk/client-sso-oidc': 3.577.0
+ '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0)
'@aws-sdk/core': 3.576.0
- '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0(@aws-sdk/client-sso-oidc@3.577.0))
+ '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0)
'@aws-sdk/middleware-host-header': 3.577.0
'@aws-sdk/middleware-logger': 3.577.0
'@aws-sdk/middleware-recursion-detection': 3.577.0
@@ -8536,7 +8602,6 @@ snapshots:
'@smithy/util-utf8': 3.0.0
tslib: 2.6.2
transitivePeerDependencies:
- - '@aws-sdk/client-sso-oidc'
- aws-crt
'@aws-sdk/core@3.576.0':
@@ -8568,13 +8633,13 @@ snapshots:
'@smithy/util-stream': 3.0.1
tslib: 2.6.2
- '@aws-sdk/credential-provider-ini@3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0(@aws-sdk/client-sso-oidc@3.577.0))':
+ '@aws-sdk/credential-provider-ini@3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0)':
dependencies:
- '@aws-sdk/client-sts': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)
+ '@aws-sdk/client-sts': 3.577.0
'@aws-sdk/credential-provider-env': 3.577.0
'@aws-sdk/credential-provider-process': 3.577.0
'@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)
- '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.577.0(@aws-sdk/client-sso-oidc@3.577.0))
+ '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.577.0)
'@aws-sdk/types': 3.577.0
'@smithy/credential-provider-imds': 3.0.0
'@smithy/property-provider': 3.0.0
@@ -8585,14 +8650,14 @@ snapshots:
- '@aws-sdk/client-sso-oidc'
- aws-crt
- '@aws-sdk/credential-provider-node@3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0(@aws-sdk/client-sso-oidc@3.577.0))':
+ '@aws-sdk/credential-provider-node@3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0)':
dependencies:
'@aws-sdk/credential-provider-env': 3.577.0
'@aws-sdk/credential-provider-http': 3.577.0
- '@aws-sdk/credential-provider-ini': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0(@aws-sdk/client-sso-oidc@3.577.0))
+ '@aws-sdk/credential-provider-ini': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0)
'@aws-sdk/credential-provider-process': 3.577.0
'@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)
- '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.577.0(@aws-sdk/client-sso-oidc@3.577.0))
+ '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.577.0)
'@aws-sdk/types': 3.577.0
'@smithy/credential-provider-imds': 3.0.0
'@smithy/property-provider': 3.0.0
@@ -8625,9 +8690,9 @@ snapshots:
- '@aws-sdk/client-sso-oidc'
- aws-crt
- '@aws-sdk/credential-provider-web-identity@3.577.0(@aws-sdk/client-sts@3.577.0(@aws-sdk/client-sso-oidc@3.577.0))':
+ '@aws-sdk/credential-provider-web-identity@3.577.0(@aws-sdk/client-sts@3.577.0)':
dependencies:
- '@aws-sdk/client-sts': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)
+ '@aws-sdk/client-sts': 3.577.0
'@aws-sdk/types': 3.577.0
'@smithy/property-provider': 3.0.0
'@smithy/types': 3.0.0
@@ -8754,7 +8819,7 @@ snapshots:
'@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.577.0)':
dependencies:
- '@aws-sdk/client-sso-oidc': 3.577.0
+ '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0)
'@aws-sdk/types': 3.577.0
'@smithy/property-provider': 3.0.0
'@smithy/shared-ini-file-loader': 3.0.0
@@ -9441,17 +9506,17 @@ snapshots:
'@floating-ui/core@1.6.2':
dependencies:
- '@floating-ui/utils': 0.2.3
+ '@floating-ui/utils': 0.2.4
'@floating-ui/dom@1.6.3':
dependencies:
'@floating-ui/core': 1.6.2
- '@floating-ui/utils': 0.2.2
+ '@floating-ui/utils': 0.2.3
'@floating-ui/dom@1.6.5':
dependencies:
'@floating-ui/core': 1.6.2
- '@floating-ui/utils': 0.2.3
+ '@floating-ui/utils': 0.2.4
'@floating-ui/react-dom@1.3.0(react-dom@18.2.0(react@18.3.1))(react@18.3.1)':
dependencies:
@@ -9465,6 +9530,12 @@ snapshots:
react: 18.3.1
react-dom: 18.2.0(react@18.3.1)
+ '@floating-ui/react-dom@2.1.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/dom': 1.6.5
+ react: 18.3.1
+ react-dom: 18.2.0(react@18.3.1)
+
'@floating-ui/react@0.19.2(react-dom@18.2.0(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/react-dom': 1.3.0(react-dom@18.2.0(react@18.3.1))(react@18.3.1)
@@ -9481,10 +9552,20 @@ snapshots:
react-dom: 18.2.0(react@18.3.1)
tabbable: 6.2.0
+ '@floating-ui/react@0.26.19(react-dom@18.2.0(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1)
+ '@floating-ui/utils': 0.2.4
+ react: 18.3.1
+ react-dom: 18.2.0(react@18.3.1)
+ tabbable: 6.2.0
+
'@floating-ui/utils@0.2.2': {}
'@floating-ui/utils@0.2.3': {}
+ '@floating-ui/utils@0.2.4': {}
+
'@floating-ui/vue@1.0.7(vue@3.4.30(typescript@5.4.5))':
dependencies:
'@floating-ui/dom': 1.6.5
@@ -9509,6 +9590,15 @@ snapshots:
react: 18.3.1
react-dom: 18.2.0(react@18.3.1)
+ '@headlessui/react@2.1.2(react-dom@18.2.0(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/react': 0.26.19(react-dom@18.2.0(react@18.3.1))(react@18.3.1)
+ '@react-aria/focus': 3.17.1(react@18.3.1)
+ '@react-aria/interactions': 3.21.3(react@18.3.1)
+ '@tanstack/react-virtual': 3.8.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.2.0(react@18.3.1)
+
'@headlessui/tailwindcss@0.2.1(tailwindcss@3.4.3)':
dependencies:
tailwindcss: 3.4.3
@@ -10951,6 +11041,37 @@ snapshots:
'@radix-ui/rect@1.1.0': {}
+ '@react-aria/focus@3.17.1(react@18.3.1)':
+ dependencies:
+ '@react-aria/interactions': 3.21.3(react@18.3.1)
+ '@react-aria/utils': 3.24.1(react@18.3.1)
+ '@react-types/shared': 3.23.1(react@18.3.1)
+ '@swc/helpers': 0.5.11
+ clsx: 2.1.1
+ react: 18.3.1
+
+ '@react-aria/interactions@3.21.3(react@18.3.1)':
+ dependencies:
+ '@react-aria/ssr': 3.9.4(react@18.3.1)
+ '@react-aria/utils': 3.24.1(react@18.3.1)
+ '@react-types/shared': 3.23.1(react@18.3.1)
+ '@swc/helpers': 0.5.11
+ react: 18.3.1
+
+ '@react-aria/ssr@3.9.4(react@18.3.1)':
+ dependencies:
+ '@swc/helpers': 0.5.11
+ react: 18.3.1
+
+ '@react-aria/utils@3.24.1(react@18.3.1)':
+ dependencies:
+ '@react-aria/ssr': 3.9.4(react@18.3.1)
+ '@react-stately/utils': 3.10.1(react@18.3.1)
+ '@react-types/shared': 3.23.1(react@18.3.1)
+ '@swc/helpers': 0.5.11
+ clsx: 2.1.1
+ react: 18.3.1
+
'@react-pdf/fns@2.2.1':
dependencies:
'@babel/runtime': 7.24.5
@@ -11058,6 +11179,15 @@ snapshots:
'@react-pdf/types@2.5.0': {}
+ '@react-stately/utils@3.10.1(react@18.3.1)':
+ dependencies:
+ '@swc/helpers': 0.5.11
+ react: 18.3.1
+
+ '@react-types/shared@3.23.1(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+
'@remirror/core-constants@2.0.2': {}
'@remix-run/router@1.13.1': {}
@@ -11241,7 +11371,7 @@ snapshots:
'@scalar/components@0.12.4(typescript@5.4.5)':
dependencies:
- '@floating-ui/utils': 0.2.2
+ '@floating-ui/utils': 0.2.3
'@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.4.5))
'@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.4.5))
'@scalar/code-highlight': 0.0.5
@@ -12093,12 +12223,20 @@ snapshots:
react: 18.3.1
react-dom: 18.2.0(react@18.3.1)
+ '@tanstack/react-virtual@3.8.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@tanstack/virtual-core': 3.8.1
+ react: 18.3.1
+ react-dom: 18.2.0(react@18.3.1)
+
'@tanstack/table-core@8.19.2': {}
'@tanstack/virtual-core@3.5.1': {}
'@tanstack/virtual-core@3.7.0': {}
+ '@tanstack/virtual-core@3.8.1': {}
+
'@tanstack/vue-virtual@3.7.0(vue@3.4.30(typescript@5.4.5))':
dependencies:
'@tanstack/virtual-core': 3.7.0
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 4e9b312b5..cf87914f8 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -793,9 +793,9 @@ model Safe {
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
issueDate DateTime
- boardApprovalDate DateTime
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
+ boardApprovalDate DateTime?
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
@@unique([publicId, companyId])
@@index([companyId])
diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/page.tsx
index 377128fda..ea382f083 100644
--- a/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/page.tsx
+++ b/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/page.tsx
@@ -24,7 +24,7 @@ const EsignDocumentPage = async () => {
subtitle="Click the button below to upload a new document for electronic signature."
>
@@ -38,7 +38,7 @@ const EsignDocumentPage = async () => {
description="Upload, sign and send documents for electronic signatures."
action={
diff --git a/src/components/modals/index.ts b/src/components/modals/index.ts
index 78a0f44d0..f660a6188 100644
--- a/src/components/modals/index.ts
+++ b/src/components/modals/index.ts
@@ -6,6 +6,7 @@ import { ExistingSafeModal } from "./existing-safe-modal";
import { IssueShareModal } from "./issue-share-modal";
import { IssueStockOptionModal } from "./issue-stock-option-modal";
import { NewSafeModal } from "./new-safe-modal";
+import { SafeModal } from "./safe";
import { ShareClassModal } from "./share-class/share-class-modal";
import { ShareDataRoomModal } from "./share-dataroom-modal";
import { ShareUpdateModal } from "./share-update-modal";
@@ -32,8 +33,11 @@ export const { pushModal, popModal, ModalProvider } = createPushModal({
EquityPlanModal,
IssueShareModal,
IssueStockOptionModal,
+ AddEsignDocumentModal,
+
+ // Safe modals
+ SafeModal,
NewSafeModal,
ExistingSafeModal,
- AddEsignDocumentModal,
},
});
diff --git a/src/components/modals/safe.tsx b/src/components/modals/safe.tsx
new file mode 100644
index 000000000..c7ce4b3af
--- /dev/null
+++ b/src/components/modals/safe.tsx
@@ -0,0 +1,35 @@
+import Modal from "@/components/common/modal";
+import { SafeForm } from "@/components/safe/form";
+import { useState } from "react";
+
+type SafeModalProps = {
+ type: "create" | "import";
+};
+
+export const SafeModal: React.FC = ({ type }) => {
+ const [open, setOpen] = useState(true);
+
+ return (
+ {
+ setOpen(val);
+ },
+ }}
+ >
+
+
+ );
+};
diff --git a/src/components/safe/form/index.tsx b/src/components/safe/form/index.tsx
new file mode 100644
index 000000000..f082ebe6c
--- /dev/null
+++ b/src/components/safe/form/index.tsx
@@ -0,0 +1,102 @@
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+
+import { Input } from "@/components/ui/input";
+import { SafeStatusEnum, SafeTypeEnum } from "@/prisma/enums";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import { PrePostSelector } from "./pre-post-selector";
+
+type SafeFormProps = {
+ type: "create" | "import";
+};
+
+const SafeFormSchema = z.object({
+ id: z.string().optional(),
+ publicId: z.string().optional(),
+ type: z.nativeEnum(SafeTypeEnum),
+ status: z.nativeEnum(SafeStatusEnum),
+ capital: z.coerce.number(),
+ valuationCap: z.coerce.number().optional(),
+ discountRate: z.coerce.number().optional(),
+ mfn: z.boolean().default(false).optional(),
+ proRata: z.boolean().default(false).optional(),
+ issueDate: z.string().date(),
+ stakeholderId: z.string(),
+});
+
+type SafeFormType = z.infer;
+
+export const SafeForm: React.FC = ({ type }) => {
+ const form = useForm({
+ resolver: zodResolver(SafeFormSchema),
+ defaultValues: {
+ capital: 0,
+ discountRate: 0,
+ mfn: false,
+ proRata: false,
+ type: SafeTypeEnum.POST_MONEY,
+ status: SafeStatusEnum.ACTIVE,
+ issueDate: new Date().toISOString,
+ },
+ });
+
+ const handleSubmit = (data: SafeFormType) => {
+ console.log(data);
+ };
+
+ return (
+
+
+ );
+};
diff --git a/src/components/safe/form/pre-post-selector.tsx b/src/components/safe/form/pre-post-selector.tsx
new file mode 100644
index 000000000..15aa18483
--- /dev/null
+++ b/src/components/safe/form/pre-post-selector.tsx
@@ -0,0 +1,87 @@
+"use client";
+
+import { Radio, RadioGroup } from "@headlessui/react";
+import { RiCheckboxCircleFill as CheckCircleIcon } from "@remixicon/react";
+import Link from "next/link";
+import { useEffect, useState } from "react";
+
+const options = [
+ {
+ id: "POST_MONEY",
+ title: "Post-money",
+ description: "Value of the company after the investment is made.",
+ },
+ {
+ id: "PRE_MONEY",
+ title: "Pre-money",
+ description: "Value of the company before the investment is made.",
+ },
+];
+
+type SelectorProps = {
+ defaultValue: string;
+ onChange: (value: string) => void;
+};
+
+export const PrePostSelector = ({ defaultValue, onChange }: SelectorProps) => {
+ const [selected, setSelected] = useState(defaultValue);
+
+ useEffect(() => {
+ setSelected(defaultValue);
+ }, [defaultValue]);
+
+ return (
+
+ );
+};
diff --git a/src/components/safe/safe-actions.tsx b/src/components/safe/safe-actions.tsx
index 3eb23069d..83d984ccb 100644
--- a/src/components/safe/safe-actions.tsx
+++ b/src/components/safe/safe-actions.tsx
@@ -1,5 +1,6 @@
"use client";
+import { pushModal } from "@/components/modals";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
@@ -7,7 +8,6 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { RiAddFill, RiSafe2Fill, RiSafeFill } from "@remixicon/react";
-import { pushModal } from "../modals";
export function SafeActions() {
return (
@@ -27,10 +27,8 @@ export function SafeActions() {
size="sm"
type="submit"
onClick={() => {
- pushModal("NewSafeModal", {
- title: "Create a new SAFE agreement",
- subtitle:
- "Create, sign and send a new SAFE agreement to your investors.",
+ pushModal("SafeModal", {
+ type: "create",
});
}}
>
@@ -45,10 +43,8 @@ export function SafeActions() {
size="sm"
type="submit"
onClick={() => {
- pushModal("ExistingSafeModal", {
- title: "Create an existing SAFE agreement",
- subtitle:
- "Record an existing SAFE agreement to keep track of it in your captable.",
+ pushModal("SafeModal", {
+ type: "import",
});
}}
>
diff --git a/src/components/ui/stepper.tsx b/src/components/ui/stepper.tsx
index d57c510b5..7d47c0ac8 100644
--- a/src/components/ui/stepper.tsx
+++ b/src/components/ui/stepper.tsx
@@ -215,12 +215,13 @@ interface StepperModalContentProps {
}
export function StepperModalContent({ children }: StepperModalContentProps) {
- const steps = useDescendants(StepperDescendantContext);
- const { activeIndex } = useStepper();
- const currentTitle = steps?.[activeIndex]?.data?.title ?? "";
+ // const steps = useDescendants(StepperDescendantContext);
+ // const { activeIndex } = useStepper();
+ // const currentTitle = steps?.[activeIndex]?.data?.title ?? "";
+
return (
-
{currentTitle}
+ {/* {currentTitle}
*/}
{children}
);
From 6d806a2095ffdcdfadcf5a36ccf13fca161dcda7 Mon Sep 17 00:00:00 2001
From: Puru D
Date: Mon, 8 Jul 2024 03:10:17 -0500
Subject: [PATCH 06/60] WIP
---
src/components/modals/safe.tsx | 2 +-
src/components/safe/form/index.tsx | 209 ++++++++++++++++--
.../safe/form/pre-post-selector.tsx | 4 +-
3 files changed, 195 insertions(+), 20 deletions(-)
diff --git a/src/components/modals/safe.tsx b/src/components/modals/safe.tsx
index c7ce4b3af..54e285b0c 100644
--- a/src/components/modals/safe.tsx
+++ b/src/components/modals/safe.tsx
@@ -11,7 +11,7 @@ export const SafeModal: React.FC = ({ type }) => {
return (
value === "true" || value === "false", {
+ message: "Value must be a boolean",
+ })
+ .transform((value) => value === "true")
+ .default("false")
+ .optional(),
+ proRata: z
+ .string()
+ .refine((value) => value === "true" || value === "false", {
+ message: "Value must be a boolean",
+ })
+ .transform((value) => value === "true")
+ .default("false")
+ .optional(),
issueDate: z.string().date(),
stakeholderId: z.string(),
});
@@ -44,7 +59,7 @@ export const SafeForm: React.FC = ({ type }) => {
proRata: false,
type: SafeTypeEnum.POST_MONEY,
status: SafeStatusEnum.ACTIVE,
- issueDate: new Date().toISOString,
+ issueDate: new Date().toISOString(),
},
});
@@ -58,44 +73,204 @@ export const SafeForm: React.FC = ({ type }) => {
onSubmit={form.handleSubmit(handleSubmit)}
className="flex flex-col gap-y-4"
>
- {
- form.setValue("type", value as SafeTypeEnum);
- }}
- />
+
+
+ Investor (Stakeholder)
+
+ Please select or create the investor account you want to create
+ the SAFE agreement for.
+
+
-
(
- Investment amount
-
+
)}
/>
+
+
+
{
+ form.setValue("type", value as SafeTypeEnum);
+ }}
+ />
+
+
+ {
+ const { onChange, ...rest } = field;
+
+ return (
+
+ Investment amount
+
+
+ {
+ const { floatValue } = values;
+ onChange(floatValue);
+ }}
+ />
+
+
+
+ );
+ }}
+ />
{
+ const { onChange, ...rest } = field;
+
+ return (
+
+
+ Valuation cap (optional)
+
+
+ {
+ const { floatValue } = values;
+ onChange(floatValue);
+ }}
+ />
+
+
+
+ );
+ }}
+ />
+
+ (
-
- Valuation cap (optional)
-
+ Investment date
-
+
)}
/>
+
+
+ {
+ const { onChange, ...rest } = field;
+
+ return (
+
+
+ Discount (optional)
+
+
+ {
+ const { floatValue } = values;
+ onChange(floatValue);
+ }}
+ />
+
+
+
+ );
+ }}
+ />
+
+ {
+ const { onChange, ...rest } = field;
+
+ return (
+
+ Pro-rata rights letter
+
+
+ {
+ const { floatValue } = values;
+ onChange(floatValue);
+ }}
+ />
+
+
+
+ );
+ }}
+ />
+
+ {
+ const { onChange, ...rest } = field;
+
+ return (
+
+ Most favored nation
+
+ {
+ const { floatValue } = values;
+ onChange(floatValue);
+ }}
+ />
+
+
+
+ );
+ }}
+ />
+
);
diff --git a/src/components/safe/form/pre-post-selector.tsx b/src/components/safe/form/pre-post-selector.tsx
index 15aa18483..4aae893ba 100644
--- a/src/components/safe/form/pre-post-selector.tsx
+++ b/src/components/safe/form/pre-post-selector.tsx
@@ -8,12 +8,12 @@ import { useEffect, useState } from "react";
const options = [
{
id: "POST_MONEY",
- title: "Post-money",
+ title: "Post money valuation",
description: "Value of the company after the investment is made.",
},
{
id: "PRE_MONEY",
- title: "Pre-money",
+ title: "Pre money valuation",
description: "Value of the company before the investment is made.",
},
];
From fffc572be6c7f74487937002368398fadebd4d6e Mon Sep 17 00:00:00 2001
From: Puru D
Date: Tue, 9 Jul 2024 01:47:47 -0500
Subject: [PATCH 07/60] feat: create a re-usable stakeholder-selector
---
src/components/safe/form/index.tsx | 9 +--
src/components/safe/safe-actions.tsx | 7 ++-
.../stakeholder/stakeholder-selector.tsx | 60 +++++++++++++++++++
src/components/ui/combobox.tsx | 4 +-
4 files changed, 72 insertions(+), 8 deletions(-)
create mode 100644 src/components/stakeholder/stakeholder-selector.tsx
diff --git a/src/components/safe/form/index.tsx b/src/components/safe/form/index.tsx
index 27f731911..50230ddbe 100644
--- a/src/components/safe/form/index.tsx
+++ b/src/components/safe/form/index.tsx
@@ -7,6 +7,7 @@ import {
FormMessage,
} from "@/components/ui/form";
+import { StakeholderSelector } from "@/components/stakeholder/stakeholder-selector";
import { Input } from "@/components/ui/input";
import { SafeStatusEnum, SafeTypeEnum } from "@/prisma/enums";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -77,12 +78,11 @@ export const SafeForm: React.FC = ({ type }) => {
Investor (Stakeholder)
- Please select or create the investor account you want to create
- the SAFE agreement for.
+ Please select or create the investor account.
- (
@@ -93,7 +93,8 @@ export const SafeForm: React.FC = ({ type }) => {
)}
- />
+ /> */}
+
{
return (
@@ -56,4 +57,4 @@ export function SafeActions() {
);
-}
+};
diff --git a/src/components/stakeholder/stakeholder-selector.tsx b/src/components/stakeholder/stakeholder-selector.tsx
new file mode 100644
index 000000000..a8957e905
--- /dev/null
+++ b/src/components/stakeholder/stakeholder-selector.tsx
@@ -0,0 +1,60 @@
+"use client";
+
+import { LinearCombobox } from "@/components/ui/combobox";
+import { api } from "@/trpc/react";
+import {
+ RiLoader5Line as LoadingIcon,
+ RiAddCircleLine,
+} from "@remixicon/react";
+import { useEffect, useState } from "react";
+
+export function StakeholderSelector() {
+ const [options, setOptions] = useState<{ value: string; label: string }[]>(
+ [],
+ );
+ const { data: stakeholders, isLoading } =
+ api.stakeholder.getStakeholders.useQuery();
+
+ useEffect(() => {
+ if (stakeholders) {
+ setOptions(
+ stakeholders.map((sh) => ({
+ value: sh.id,
+ label: sh.institutionName
+ ? `${sh.institutionName} - ${sh.name}`
+ : sh.name,
+ })),
+ );
+ }
+ }, [stakeholders]);
+
+ return (
+ {
+ console.log(option, option.value);
+ }}
+ >
+
+
+ );
+}
diff --git a/src/components/ui/combobox.tsx b/src/components/ui/combobox.tsx
index 2c2b1e386..554e73ee8 100644
--- a/src/components/ui/combobox.tsx
+++ b/src/components/ui/combobox.tsx
@@ -26,6 +26,7 @@ export type ComboBoxOption = {
};
export const LinearCombobox = ({
+ placeholder,
options,
onValueChange,
defaultOption,
@@ -35,6 +36,7 @@ export const LinearCombobox = ({
onValueChange?: (option: ComboBoxOption) => void;
defaultOption?: ComboBoxOption;
children?: React.ReactNode;
+ placeholder?: string;
}) => {
const [openPopover, setOpenPopover] = useState(false);
const [selectedOption, setSelectedOption] = useState(
@@ -71,7 +73,7 @@ export const LinearCombobox = ({
) : defaultOption ? (
<>{defaultOption.label}>
) : (
- <>Select an Option>
+ <>{placeholder || "Select an option"}>
)}
From 34baf5f7b8322cd93ccc98b6eb5bcac505987843 Mon Sep 17 00:00:00 2001
From: Puru D
Date: Tue, 9 Jul 2024 03:22:41 -0500
Subject: [PATCH 08/60] feat: mfn and pro-rata selector
---
src/components/modals/safe.tsx | 2 +-
src/components/safe/form/index.tsx | 124 ++++++++----------
.../stakeholder/stakeholder-selector.tsx | 26 +++-
3 files changed, 79 insertions(+), 73 deletions(-)
diff --git a/src/components/modals/safe.tsx b/src/components/modals/safe.tsx
index 54e285b0c..c7ce4b3af 100644
--- a/src/components/modals/safe.tsx
+++ b/src/components/modals/safe.tsx
@@ -11,7 +11,7 @@ export const SafeModal: React.FC = ({ type }) => {
return (
value === "true" || value === "false", {
- message: "Value must be a boolean",
- })
- .transform((value) => value === "true")
- .default("false")
- .optional(),
- proRata: z
- .string()
- .refine((value) => value === "true" || value === "false", {
- message: "Value must be a boolean",
- })
- .transform((value) => value === "true")
- .default("false")
- .optional(),
+ mfn: z.boolean().default(false),
+ proRata: z.boolean().default(false),
issueDate: z.string().date(),
stakeholderId: z.string(),
});
@@ -55,7 +43,6 @@ export const SafeForm: React.FC = ({ type }) => {
resolver: zodResolver(SafeFormSchema),
defaultValues: {
capital: 0,
- discountRate: 0,
mfn: false,
proRata: false,
type: SafeTypeEnum.POST_MONEY,
@@ -64,6 +51,8 @@ export const SafeForm: React.FC = ({ type }) => {
},
});
+ const isSubmitting = form.formState.isSubmitting;
+
const handleSubmit = (data: SafeFormType) => {
console.log(data);
};
@@ -82,19 +71,11 @@ export const SafeForm: React.FC = ({ type }) => {
- {/* (
-
-
-
-
-
-
- )}
- /> */}
-
+ {
+ form.setValue("stakeholderId", value);
+ }}
+ />
= ({ type }) => {
{
- const { onChange, ...rest } = field;
-
+ render={() => {
return (
- Pro-rata rights letter
+
+ Pro-rata rights (optional)
+
-
- {
- const { floatValue } = values;
- onChange(floatValue);
- }}
- />
-
-
+ {
+ form.setValue("proRata", option.value === "true");
+ }}
+ />
);
}}
@@ -246,32 +224,42 @@ export const SafeForm: React.FC = ({ type }) => {
{
- const { onChange, ...rest } = field;
-
+ render={() => {
return (
- Most favored nation
-
- {
- const { floatValue } = values;
- onChange(floatValue);
- }}
- />
-
-
+
+ Most favored nation{" "}
+ (optional)
+
+ {
+ form.setValue("mfn", option.value === "true");
+ }}
+ />
);
}}
/>
+
+
+
+
);
diff --git a/src/components/stakeholder/stakeholder-selector.tsx b/src/components/stakeholder/stakeholder-selector.tsx
index a8957e905..ee58f74e1 100644
--- a/src/components/stakeholder/stakeholder-selector.tsx
+++ b/src/components/stakeholder/stakeholder-selector.tsx
@@ -1,5 +1,7 @@
"use client";
+import Tldr from "@/components/common/tldr";
+import { pushModal } from "@/components/modals";
import { LinearCombobox } from "@/components/ui/combobox";
import { api } from "@/trpc/react";
import {
@@ -8,7 +10,11 @@ import {
} from "@remixicon/react";
import { useEffect, useState } from "react";
-export function StakeholderSelector() {
+type StakeholderSelectorType = {
+ onSelect: (stakeholder: string) => void;
+};
+
+export function StakeholderSelector({ onSelect }: StakeholderSelectorType) {
const [options, setOptions] = useState<{ value: string; label: string }[]>(
[],
);
@@ -33,7 +39,7 @@ export function StakeholderSelector() {
placeholder="Select a stakeholder"
options={options}
onValueChange={(option) => {
- console.log(option, option.value);
+ onSelect(option.value);
}}
>