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 ( +
+ + { + form.setValue("type", value as SafeTypeEnum); + }} + /> + +
+ ( + + Investment amount + + + + + + )} + /> + + ( + + + Valuation cap (optional) + + + + + + + )} + /> +
+ + + ); +}; 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 ( +
+ { + setSelected(value); + onChange(value); + }} + className="mb-2 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4" + > + {options.map((option) => ( + + + + + {option.title} + + + {option.description} + + + + + ))} + + + + {/* TODO: Write an article about the difference between pre-money and post-money */} + + Click here + {" "} + to learn more about the difference between pre-money and post-money + investments. + +
+ ); +}; 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); }} > + + {/* TODO: Write a helkp article on SAFE */} + + + +
Investor (Stakeholder)

@@ -260,10 +286,6 @@ export const SafeForm: React.FC = ({ type }) => { />

- {/* */} -
From 8b2bf82bab497d12919c33ce386ee3c8051b84c2 Mon Sep 17 00:00:00 2001 From: Puru D Date: Sat, 13 Jul 2024 05:11:49 -0500 Subject: [PATCH 15/60] chore: complete SAFE template, TODO - sender details --- src/components/safe/document.tsx | 71 +++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/src/components/safe/document.tsx b/src/components/safe/document.tsx index 0b5951b57..50e658087 100644 --- a/src/components/safe/document.tsx +++ b/src/components/safe/document.tsx @@ -112,13 +112,25 @@ const formatDate = (date: string) => { }; type SafeProps = { - investor: string; investment: number; valuation?: number; date: string; company: { name: string; state: string; + address?: string; + }; + + sender?: { + name: string; + email: string; + title: string; + }; + + investor: { + name: string; + email: string; + address?: string; }; }; @@ -177,9 +189,10 @@ const SafeDocument = ({ THIS CERTIFIES THAT in exchange for the payment by{" "} - {investor} (the “Investor”) - of {formatUsd(investment)}{" "} - (the “Purchase Amount”) on or about{" "} + {investor.name} (the + “Investor”) of{" "} + {formatUsd(investment)} (the + “Purchase Amount”) on or about{" "} {formatDate(date)},{" "} {company.name}, a{" "} {company.state} corporation @@ -712,7 +725,7 @@ const SafeDocument = ({ - + 4. Investor Representations @@ -871,6 +884,54 @@ const SafeDocument = ({ IN WITNESS WHEREOF, the undersigned have caused this Safe to be duly executed and delivered. + + + {company.name} + + By (signature): __________________________________ + + + + Name: ________________________________________ + + + + Title: _________________________________________ + + + Address: ______________________________________ + + + ______________________________________________ + + + Email: ________________________________________ + + + + + INVESTOR: + + By (signature): __________________________________ + + + + Name: ________________________________________ + + + + Title: _________________________________________ + + + Address: ______________________________________ + + + ______________________________________________ + + + Email: ________________________________________ + + ); From c57c4a94e52098f91eda46082cd11c87b85a3866 Mon Sep 17 00:00:00 2001 From: Puru D Date: Wed, 17 Jul 2024 01:18:23 -0500 Subject: [PATCH 16/60] WIP --- src/components/safe/document.tsx | 153 ++---------------- src/components/safe/templates/index.tsx | 132 +++++++++++++++ .../safe/templates/post-money/cap.tsx | 9 ++ .../safe/templates/post-money/discount.tsx | 9 ++ .../safe/templates/post-money/mfn.tsx | 9 ++ .../templates/post-money/pro-rata-letter.tsx | 9 ++ .../templates/pre-money/cap-and-discount.tsx | 5 + .../safe/templates/pre-money/cap.tsx | 5 + .../safe/templates/pre-money/discount.tsx | 5 + .../safe/templates/pre-money/mfn.tsx | 5 + src/lib/format.ts | 15 ++ 11 files changed, 213 insertions(+), 143 deletions(-) create mode 100644 src/components/safe/templates/index.tsx create mode 100644 src/components/safe/templates/post-money/cap.tsx create mode 100644 src/components/safe/templates/post-money/discount.tsx create mode 100644 src/components/safe/templates/post-money/mfn.tsx create mode 100644 src/components/safe/templates/post-money/pro-rata-letter.tsx create mode 100644 src/components/safe/templates/pre-money/cap-and-discount.tsx create mode 100644 src/components/safe/templates/pre-money/cap.tsx create mode 100644 src/components/safe/templates/pre-money/discount.tsx create mode 100644 src/components/safe/templates/pre-money/mfn.tsx create mode 100644 src/lib/format.ts diff --git a/src/components/safe/document.tsx b/src/components/safe/document.tsx index 50e658087..bb9ffdd49 100644 --- a/src/components/safe/document.tsx +++ b/src/components/safe/document.tsx @@ -1,138 +1,6 @@ -import { - Document, - Font, - Link, - Page, - StyleSheet, - Text, - View, -} from "@react-pdf/renderer"; - -const style = StyleSheet.create({ - body: { - paddingTop: 20, - paddingBottom: 20, - paddingHorizontal: 20, - paddingVertical: 20, - fontSize: 10, - color: "#030712", - fontFamily: "Times-Roman", - }, - - textSm: { - fontSize: 8, - }, - - title: { - fontSize: 12, - }, - - upcase: { - textTransform: "uppercase", - }, - - center: { - textAlign: "center", - }, - - right: { - textAlign: "right", - }, - - italic: { - fontStyle: "italic", - fontFamily: "Times-Italic", - }, - - bold: { - fontWeight: "bold", - fontFamily: "Times-Bold", - }, - - underline: { - textDecoration: "underline", - }, - - hightlight: { - color: "blue", - }, - - pt5: { - paddingTop: 5, - }, - - pt10: { - paddingTop: 10, - }, - - pt20: { - paddingTop: 20, - }, - - pb20: { - paddingBottom: 20, - }, -}); - -type Props = { - text?: string; - size?: "sm" | "md" | "lg"; - children?: React.ReactNode; -}; - -const Indent = ({ children, size = "sm" }: Props) => { - switch (size) { - case "md": - return {children}; - case "lg": - return {children}; - default: - return {children}; - } -}; - -const Strong = ({ children }: Props) => { - return {children}; -}; - -const formatUsd = (amount: number) => { - return new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - currencyDisplay: "narrowSymbol", - }).format(amount); -}; - -const formatDate = (date: string) => { - return new Intl.DateTimeFormat("en-US", { - year: "numeric", - month: "long", - day: "numeric", - }).format(new Date(date)); -}; - -type SafeProps = { - investment: number; - valuation?: number; - date: string; - company: { - name: string; - state: string; - address?: string; - }; - - sender?: { - name: string; - email: string; - title: string; - }; - - investor: { - name: string; - email: string; - address?: string; - }; -}; +import { Indent, type SafeProps, style } from "@/components/safe/templates"; +import { formatDate, formatUsd } from "@/lib/format"; +import { Document, Link, Page, Text, View } from "@react-pdf/renderer"; const SafeDocument = ({ investor, @@ -159,12 +27,11 @@ const SafeDocument = ({ THIS INSTRUMENT AND ANY SECURITIES ISSUABLE PURSUANT HERETO HAVE NOT BEEN REGISTERED UNDER THE SECURITIES ACT OF 1933, AS AMENDED (THE “ - SECURITIES ACT - ”), OR UNDER THE SECURITIES LAWS OF CERTAIN STATES. THESE SECURITIES - MAY NOT BE OFFERED, SOLD OR OTHERWISE TRANSFERRED, PLEDGED OR - HYPOTHECATED EXCEPT AS PERMITTED IN THIS SAFE AND UNDER THE ACT AND - APPLICABLE STATE SECURITIES LAWS PURSUANT TO AN EFFECTIVE REGISTRATION - STATEMENT OR AN EXEMPTION THEREFROM. + SECURITIES ACT ”), OR UNDER THE SECURITIES LAWS OF CERTAIN STATES. + THESE SECURITIES MAY NOT BE OFFERED, SOLD OR OTHERWISE TRANSFERRED, + PLEDGED OR HYPOTHECATED EXCEPT AS PERMITTED IN THIS SAFE AND UNDER THE + ACT AND APPLICABLE STATE SECURITIES LAWS PURSUANT TO AN EFFECTIVE + REGISTRATION STATEMENT OR AN EXEMPTION THEREFROM. SAFE - (Simple Agreement for Future Equity) + (Simple Agreement for Future Equity) @@ -216,7 +83,7 @@ const SafeDocument = ({ {valuation && ( - The “Post-Money Valuation Cap” is{" "} + The “Post-Money Valuation Cap” is{" "} {formatUsd(valuation)}. See Section 2 for certain additional defined terms. diff --git a/src/components/safe/templates/index.tsx b/src/components/safe/templates/index.tsx new file mode 100644 index 000000000..b591241e9 --- /dev/null +++ b/src/components/safe/templates/index.tsx @@ -0,0 +1,132 @@ +import { StyleSheet, Text } from "@react-pdf/renderer"; + +// https://www.ycombinator.com/documents +import PostMoneyCap from "./post-money/cap"; +import PostMoneyDiscount from "./post-money/discount"; +import PostMoneyMfn from "./post-money/mfn"; +import ProRataLetter from "./post-money/pro-rata-letter"; + +// https://web.archive.org/web/20180628214544/https://www.ycombinator.com/documents +import PreMoneyCap from "./pre-money/cap"; +import PreMoneyCapDiscount from "./pre-money/cap-and-discount"; +import PreMoneyDiscount from "./pre-money/discount"; +import PreMoneyMfn from "./pre-money/mfn"; + +export const style = StyleSheet.create({ + body: { + paddingTop: 20, + paddingBottom: 20, + paddingHorizontal: 20, + paddingVertical: 20, + fontSize: 10, + color: "#030712", + fontFamily: "Times-Roman", + }, + + textSm: { + fontSize: 8, + }, + + title: { + fontSize: 12, + }, + + upcase: { + textTransform: "uppercase", + }, + + center: { + textAlign: "center", + }, + + right: { + textAlign: "right", + }, + + italic: { + fontStyle: "italic", + fontFamily: "Times-Italic", + }, + + bold: { + fontWeight: "bold", + fontFamily: "Times-Bold", + }, + + underline: { + textDecoration: "underline", + }, + + hightlight: { + color: "blue", + }, + + pt5: { + paddingTop: 5, + }, + + pt10: { + paddingTop: 10, + }, + + pt20: { + paddingTop: 20, + }, + + pb20: { + paddingBottom: 20, + }, +}); + +export type SafeProps = { + investment: number; + valuation?: number; + date: string; + company: { + name: string; + state: string; + address?: string; + }; + + sender?: { + name: string; + email: string; + title: string; + }; + + investor: { + name: string; + email: string; + address?: string; + }; +}; + +export const Indent = ({ + children, + size = "sm", +}: { + children: React.ReactNode; + size?: "sm" | "md" | "lg"; +}) => { + switch (size) { + case "lg": + return {children}; + case "md": + return {children}; + default: + return {children}; + } +}; + +export { + // Post-money + PostMoneyMfn, + ProRataLetter, + PostMoneyDiscount, + PostMoneyCap, + // Pre-money + PreMoneyMfn, + PreMoneyDiscount, + PreMoneyCapDiscount, + PreMoneyCap, +}; diff --git a/src/components/safe/templates/post-money/cap.tsx b/src/components/safe/templates/post-money/cap.tsx new file mode 100644 index 000000000..25daa9b02 --- /dev/null +++ b/src/components/safe/templates/post-money/cap.tsx @@ -0,0 +1,9 @@ +const PostMoneyCap = () => { + return ( +
+

Post Money Valuation Cap

+
+ ); +}; + +export default PostMoneyCap; diff --git a/src/components/safe/templates/post-money/discount.tsx b/src/components/safe/templates/post-money/discount.tsx new file mode 100644 index 000000000..018d6a33b --- /dev/null +++ b/src/components/safe/templates/post-money/discount.tsx @@ -0,0 +1,9 @@ +const PostMoneyDiscount = () => { + return ( +
+

Post Money Discount

+
+ ); +}; + +export default PostMoneyDiscount; diff --git a/src/components/safe/templates/post-money/mfn.tsx b/src/components/safe/templates/post-money/mfn.tsx new file mode 100644 index 000000000..382f4e9dc --- /dev/null +++ b/src/components/safe/templates/post-money/mfn.tsx @@ -0,0 +1,9 @@ +const PostMoneyMfn = () => { + return ( +
+

Post Money MFN

+
+ ); +}; + +export default PostMoneyMfn; diff --git a/src/components/safe/templates/post-money/pro-rata-letter.tsx b/src/components/safe/templates/post-money/pro-rata-letter.tsx new file mode 100644 index 000000000..8a563a621 --- /dev/null +++ b/src/components/safe/templates/post-money/pro-rata-letter.tsx @@ -0,0 +1,9 @@ +const ProRataLetter = () => { + return ( +
+

Pro Rata Letter

+
+ ); +}; + +export default ProRataLetter; diff --git a/src/components/safe/templates/pre-money/cap-and-discount.tsx b/src/components/safe/templates/pre-money/cap-and-discount.tsx new file mode 100644 index 000000000..0434f47b6 --- /dev/null +++ b/src/components/safe/templates/pre-money/cap-and-discount.tsx @@ -0,0 +1,5 @@ +const PreMoneyCapDiscount = () => { + return <>Pre money Cap & Discount; +}; + +export default PreMoneyCapDiscount; diff --git a/src/components/safe/templates/pre-money/cap.tsx b/src/components/safe/templates/pre-money/cap.tsx new file mode 100644 index 000000000..ca72bf303 --- /dev/null +++ b/src/components/safe/templates/pre-money/cap.tsx @@ -0,0 +1,5 @@ +const PreMoneyCap = () => { + return <>Pre money Cap; +}; + +export default PreMoneyCap; diff --git a/src/components/safe/templates/pre-money/discount.tsx b/src/components/safe/templates/pre-money/discount.tsx new file mode 100644 index 000000000..0210b97cd --- /dev/null +++ b/src/components/safe/templates/pre-money/discount.tsx @@ -0,0 +1,5 @@ +const PreMoneyDiscount = () => { + return <>Pre money Discount; +}; + +export default PreMoneyDiscount; diff --git a/src/components/safe/templates/pre-money/mfn.tsx b/src/components/safe/templates/pre-money/mfn.tsx new file mode 100644 index 000000000..396a09afa --- /dev/null +++ b/src/components/safe/templates/pre-money/mfn.tsx @@ -0,0 +1,5 @@ +const PreMoneyMfn = () => { + return <>Pre money MFN; +}; + +export default PreMoneyMfn; diff --git a/src/lib/format.ts b/src/lib/format.ts new file mode 100644 index 000000000..32777dc83 --- /dev/null +++ b/src/lib/format.ts @@ -0,0 +1,15 @@ +export const formatUsd = (amount: number) => { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + currencyDisplay: "narrowSymbol", + }).format(amount); +}; + +export const formatDate = (date: string) => { + return new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }).format(new Date(date)); +}; From 51e8deea598a4becde84ea070a7e7377d5135896 Mon Sep 17 00:00:00 2001 From: Puru D Date: Thu, 18 Jul 2024 02:17:41 -0500 Subject: [PATCH 17/60] feat: create SAFE preview slideover --- .../migration.sql | 14 +++++ .../fundraise/safes/[id]/preview/page.tsx | 53 ---------------- .../[publicId]/fundraise/safes/new/page.tsx | 49 +++++++++++++++ src/components/common/slide-over.tsx | 17 +++++- src/components/modals/safe.tsx | 34 ++++++++++- src/components/safe/document.tsx | 2 + src/components/safe/form/index.tsx | 61 ++++++++++--------- src/components/safe/templates/index.tsx | 2 + src/components/safe/templates/preview.tsx | 58 ++++++++++++++++++ src/server/api/index.ts | 2 +- src/server/api/routes/company/safes/index.ts | 2 +- 11 files changed, 207 insertions(+), 87 deletions(-) create mode 100644 prisma/migrations/20240717202810_missing_migration/migration.sql delete mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/[id]/preview/page.tsx create mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/new/page.tsx create mode 100644 src/components/safe/templates/preview.tsx diff --git a/prisma/migrations/20240717202810_missing_migration/migration.sql b/prisma/migrations/20240717202810_missing_migration/migration.sql new file mode 100644 index 000000000..9633623dc --- /dev/null +++ b/prisma/migrations/20240717202810_missing_migration/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - The values [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] 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 Post Money - Valuation Cap, no Discount', 'YC Post Money - Discount, no Valuation Cap', 'YC Post Money - MFN, no Valuation Cap, no Discount', 'YC Post Money - Valuation Cap, Pro Rata, no Discount', 'YC Post Money - Discount, Pro Rata, no Valuation Cap', 'YC Post Money - MFN, Pro Rata, no Valuation Cap & Discount', 'YC Pre Money - Valuation Cap, no Discount', 'YC Pre Money - Valuation Cap & Discount', 'YC Pre Money - Discount, no Valuation Cap', 'YC Pre Money - MFN, no Valuation Cap', '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/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/[id]/preview/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/[id]/preview/page.tsx deleted file mode 100644 index e5a0cf9f0..000000000 --- a/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/[id]/preview/page.tsx +++ /dev/null @@ -1,53 +0,0 @@ -"use client"; - -import SafeDocument from "@/components/safe/document"; -import { Card } from "@/components/ui/card"; -import { PdfViewer } from "@/components/ui/pdf-viewer"; -import { BlobProvider, usePDF } from "@react-pdf/renderer"; - -const PreviewSafePage = () => { - // const [instance, updateInstance] = usePDF({ document: }); - - // if (instance.loading) return
Loading ...
; - - // if (instance.error) return
Something went wrong: {instance.error}
; - return ( - <> - {/*
- -
*/} - - } - > - {({ blob, url, loading, error }) => { - if (loading) return
Loading ...
; - if (error) return
Something went wrong: {error.toString()}
; - return ( - <> - - -
-
- - Download - - - ); - }} -
- - ); -}; - -export default PreviewSafePage; diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/new/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/new/page.tsx new file mode 100644 index 000000000..f7a569839 --- /dev/null +++ b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/new/page.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { SafePreview } from "@/components/safe/templates"; +import { Card } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Fragment, useState } from "react"; + +const NewSafePage = () => { + const [safeProps, setSafeProps] = useState({ + investor: { name: "_____", email: "" }, + investment: 0, + valuation: 0, + date: new Date().toISOString(), + company: { name: "_____", state: "__" }, + }); + + return ( + +

Create a new SAFE

+
+ +

New SAFE

+ + + setSafeProps({ + ...safeProps, + investor: { ...safeProps.investor, name: e.target.value }, + }) + } + /> +
+ +
+ +
+
+
+ ); +}; + +export default NewSafePage; diff --git a/src/components/common/slide-over.tsx b/src/components/common/slide-over.tsx index cab7493d6..441b4b668 100644 --- a/src/components/common/slide-over.tsx +++ b/src/components/common/slide-over.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Sheet, SheetContent, @@ -15,6 +17,17 @@ type SlideOverProps = { children: React.ReactNode; }; +const sizes = { + sm: "max-w-sm sm:max-w-[400px]", + md: "max-w-md sm:max-w-[500px]", + lg: "max-w-lg sm:max-w-[600px]", + xl: "max-w-xl sm:max-w-[700px]", + "2xl": "max-w-2xl sm:max-w-[800px]", + "3xl": "max-w-3xl sm:max-w-[900px]", + "4xl": "max-w-4xl sm:max-w-[1000px]", + "5xl": "max-w-5xl sm:max-w-[1100px]", +}; + const SlideOver = ({ title, subtitle, @@ -22,10 +35,12 @@ const SlideOver = ({ size = "md", children, }: SlideOverProps) => { + const sizeClass = sizes[size]; + return ( {trigger} - + {title} {subtitle} diff --git a/src/components/modals/safe.tsx b/src/components/modals/safe.tsx index 8d538aa16..9720bfe8c 100644 --- a/src/components/modals/safe.tsx +++ b/src/components/modals/safe.tsx @@ -1,5 +1,8 @@ +import Message from "@/components/common/message"; import Modal from "@/components/common/modal"; import { SafeForm } from "@/components/safe/form"; +import { Button } from "@/components/ui/button"; +import Link from "next/link"; import { useState } from "react"; type SafeModalProps = { @@ -18,9 +21,34 @@ export const SafeModal: React.FC = ({ type }) => { : "Import an existing SAFE agreement" } subtitle={ - type === "create" - ? "Create, sign and send a new SAFE agreement to your investors." - : "Record an existing SAFE agreement to keep track of it in your captable." + type === "create" ? ( + + + + {/* TODO: Write a helkp article on SAFE */} + + + ) : ( + "Record an existing SAFE agreement to keep track of it in your captable." + ) } dialogProps={{ open, diff --git a/src/components/safe/document.tsx b/src/components/safe/document.tsx index bb9ffdd49..a5c207fd0 100644 --- a/src/components/safe/document.tsx +++ b/src/components/safe/document.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Indent, type SafeProps, style } from "@/components/safe/templates"; import { formatDate, formatUsd } from "@/lib/format"; import { Document, Link, Page, Text, View } from "@react-pdf/renderer"; diff --git a/src/components/safe/form/index.tsx b/src/components/safe/form/index.tsx index 3b8ea3458..99a0deff3 100644 --- a/src/components/safe/form/index.tsx +++ b/src/components/safe/form/index.tsx @@ -7,19 +7,22 @@ import { FormMessage, } from "@/components/ui/form"; -import Message from "@/components/common/message"; +import SlideOver from "@/components/common/slide-over"; import { StakeholderSelector } from "@/components/stakeholder/stakeholder-selector"; import { Button } from "@/components/ui/button"; import { LinearCombobox } from "@/components/ui/combobox"; import { Input } from "@/components/ui/input"; import { SafeStatusEnum, SafeTypeEnum } from "@/prisma/enums"; import { zodResolver } from "@hookform/resolvers/zod"; -import Link from "next/link"; import { useForm } from "react-hook-form"; import { NumericFormat } from "react-number-format"; import { z } from "zod"; import { PrePostSelector } from "./pre-post-selector"; +// Safe document preview +import SafeDocument from "@/components/safe/document"; +import { PDFViewer } from "@react-pdf/renderer"; + type SafeFormProps = { type: "create" | "import"; }; @@ -64,31 +67,6 @@ export const SafeForm: React.FC = ({ type }) => { onSubmit={form.handleSubmit(handleSubmit)} className="flex flex-col gap-y-4" > - - - - {/* TODO: Write a helkp article on SAFE */} - - -
Investor (Stakeholder) @@ -286,7 +264,34 @@ export const SafeForm: React.FC = ({ type }) => { />
-
+
+ + Preview + + } + > + + + + + + + ); +} diff --git a/src/trpc/routers/stakeholder-router/procedures/get-stakeholders.ts b/src/trpc/routers/stakeholder-router/procedures/get-stakeholders.ts index 3540fbdd1..98457e75e 100644 --- a/src/trpc/routers/stakeholder-router/procedures/get-stakeholders.ts +++ b/src/trpc/routers/stakeholder-router/procedures/get-stakeholders.ts @@ -1,13 +1,29 @@ import { withAccessControl } from "@/trpc/api/trpc"; +import { StakeholderRelationshipEnum } from "@prisma/client"; +import { z } from "zod"; export const getStakeholdersProcedure = withAccessControl .meta({ policies: { stakeholder: { allow: ["read"] } } }) - .query(async ({ ctx }) => { + .input( + z + .object({ + investor: z.boolean().optional(), + }) + .optional(), + ) + .query(async ({ ctx, input }) => { const { db, membership } = ctx; + const investor = input?.investor; + + console.log("------------------------------------------------>"); + console.log("isInvestor", investor); const data = await db.stakeholder.findMany({ where: { companyId: membership.companyId, + ...(investor && { + currentRelationship: StakeholderRelationshipEnum.INVESTOR, + }), }, include: { company: { From 3553ee36f95ac4d3cd7f55ab2d727d8b3c3d28f3 Mon Sep 17 00:00:00 2001 From: Puru D Date: Thu, 18 Jul 2024 03:35:12 -0500 Subject: [PATCH 20/60] feat: add a feature to reload investor list if it does not auto-populate --- src/components/common/modal.tsx | 4 +- src/components/common/push-modal.tsx | 3 +- .../modals/investor/add-investor-modal.tsx | 10 +- .../stakeholder/investor-selector.tsx | 96 +++++++++++-------- 4 files changed, 71 insertions(+), 42 deletions(-) diff --git a/src/components/common/modal.tsx b/src/components/common/modal.tsx index b4c39e781..906e20f2b 100644 --- a/src/components/common/modal.tsx +++ b/src/components/common/modal.tsx @@ -24,10 +24,12 @@ const sizes = { screen: "max-w-[96vw]", }; +export type ModalSizeType = keyof typeof sizes; + export type ModalProps = { title: string | React.ReactNode; subtitle?: string | React.ReactNode; - size?: keyof typeof sizes; + size?: ModalSizeType; trigger?: React.ReactNode; children: React.ReactNode; dialogProps?: DialogProps; diff --git a/src/components/common/push-modal.tsx b/src/components/common/push-modal.tsx index ffdc7ce9a..e25f3ee0d 100644 --- a/src/components/common/push-modal.tsx +++ b/src/components/common/push-modal.tsx @@ -8,12 +8,13 @@ import { } from "@/components/ui/dialog"; import { CaptableLogo } from "@/components/common/logo"; +import type { ModalSizeType } from "@/components/common/modal"; import { cn } from "@/lib/utils"; export type ModalProps = { title: string | React.ReactNode; subtitle?: string | React.ReactNode; - size?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl"; + size?: ModalSizeType; children: React.ReactNode; scrollable?: boolean; }; diff --git a/src/components/modals/investor/add-investor-modal.tsx b/src/components/modals/investor/add-investor-modal.tsx index 9b0bdc4de..03914c74c 100644 --- a/src/components/modals/investor/add-investor-modal.tsx +++ b/src/components/modals/investor/add-investor-modal.tsx @@ -1,16 +1,22 @@ "use client"; +import type { ModalSizeType } from "@/components/common/modal"; import Modal from "@/components/common/push-modal"; import { InvestorForm } from "./add-investor-form"; type InvestorType = { title: string | React.ReactNode; subtitle: string | React.ReactNode; + size?: ModalSizeType; }; -export const InvestorModal = ({ title, subtitle }: InvestorType) => { +export const InvestorModal = ({ + title, + subtitle, + size = "md", +}: InvestorType) => { return ( - + ); diff --git a/src/components/stakeholder/investor-selector.tsx b/src/components/stakeholder/investor-selector.tsx index ce73be9c5..40b1cccea 100644 --- a/src/components/stakeholder/investor-selector.tsx +++ b/src/components/stakeholder/investor-selector.tsx @@ -1,12 +1,12 @@ "use client"; -import Message from "@/components/common/message"; import { pushModal } from "@/components/modals"; import { LinearCombobox } from "@/components/ui/combobox"; import { api } from "@/trpc/react"; import { RiLoader5Line as LoadingIcon, - RiAddCircleLine, + RiAddLine, + RiRefreshLine, } from "@remixicon/react"; import { useEffect, useState } from "react"; @@ -18,10 +18,14 @@ export function InvestorSelector({ onSelect }: StakeholderSelectorType) { const [options, setOptions] = useState<{ value: string; label: string }[]>( [], ); - const { data: stakeholders, isLoading } = - api.stakeholder.getStakeholders.useQuery({ - investor: true, - }); + + const { + data: stakeholders, + isLoading, + refetch, + } = api.stakeholder.getStakeholders.useQuery({ + investor: true, + }); useEffect(() => { if (stakeholders) { @@ -38,43 +42,59 @@ export function InvestorSelector({ onSelect }: StakeholderSelectorType) { return ( { onSelect(option.value); }} > - +
+ {!isLoading && ( + + )} + + +
); } From e8b666327be9205f18eaa1b2cb53738bbd3dac22 Mon Sep 17 00:00:00 2001 From: Puru D Date: Thu, 18 Jul 2024 23:25:43 -0500 Subject: [PATCH 21/60] feat: model for bank account --- .../migration.sql | 30 +++++++++++++++++ prisma/schema.prisma | 32 +++++++++++++++++++ src/components/modals/bank-account-modal.tsx | 16 ++++++++++ src/components/modals/index.ts | 2 ++ 4 files changed, 80 insertions(+) create mode 100644 prisma/migrations/20240718223224_add_bank_account/migration.sql create mode 100644 src/components/modals/bank-account-modal.tsx diff --git a/prisma/migrations/20240718223224_add_bank_account/migration.sql b/prisma/migrations/20240718223224_add_bank_account/migration.sql new file mode 100644 index 000000000..5129d6064 --- /dev/null +++ b/prisma/migrations/20240718223224_add_bank_account/migration.sql @@ -0,0 +1,30 @@ +-- CreateEnum +CREATE TYPE "BankAccountTypeEnum" AS ENUM ('CHECKING', 'SAVINGS'); + +-- CreateTable +CREATE TABLE "BankAccount" ( + "id" TEXT NOT NULL, + "beneficiaryName" TEXT NOT NULL, + "beneficiaryAddress" TEXT NOT NULL, + "bankName" TEXT NOT NULL, + "bankAddress" TEXT NOT NULL, + "accountNumber" TEXT NOT NULL, + "routingNumber" TEXT NOT NULL, + "accountType" "BankAccountTypeEnum" NOT NULL DEFAULT 'CHECKING', + "swiftCode" TEXT, + "primary" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "companyId" TEXT NOT NULL, + + CONSTRAINT "BankAccount_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "BankAccount_companyId_idx" ON "BankAccount"("companyId"); + +-- CreateIndex +CREATE UNIQUE INDEX "BankAccount_companyId_accountNumber_key" ON "BankAccount"("companyId", "accountNumber"); + +-- CreateIndex +CREATE UNIQUE INDEX "BankAccount_companyId_primary_key" ON "BankAccount"("companyId", "primary"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index bd0d259a6..22d1c6c90 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -157,10 +157,42 @@ model Company { billingCustomers BillingCustomer[] customRoles CustomRole[] apiKeys ApiKey[] + BankAccount BankAccount[] @@unique([publicId]) } +enum BankAccountTypeEnum { + CHECKING + SAVINGS +} + +model BankAccount { + id String @id @default(cuid()) + beneficiaryName String + beneficiaryAddress String + bankName String + bankAddress String + accountNumber String // iban or account number + routingNumber String // ABA or routing number + accountType BankAccountTypeEnum @default(CHECKING) + + // International bank information + swiftCode String? // BIC or SWIFT code + + primary Boolean @default(false) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + companyId String + company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) + + @@unique([companyId, accountNumber]) + @@unique([companyId, primary], name: "unique_primary_account") + @@index([companyId]) +} + enum MemberStatusEnum { ACTIVE INACTIVE diff --git a/src/components/modals/bank-account-modal.tsx b/src/components/modals/bank-account-modal.tsx new file mode 100644 index 000000000..687e63614 --- /dev/null +++ b/src/components/modals/bank-account-modal.tsx @@ -0,0 +1,16 @@ +"use client"; + +import Modal from "@/components/common/push-modal"; + +type ShareClassType = { + title: string | React.ReactNode; + subtitle: string | React.ReactNode; +}; + +export const BankAccountModal = ({ title, subtitle }: ShareClassType) => { + return ( + + Please clone this file and implement the modal. + + ); +}; diff --git a/src/components/modals/index.ts b/src/components/modals/index.ts index 26599e12e..07ee97b6f 100644 --- a/src/components/modals/index.ts +++ b/src/components/modals/index.ts @@ -1,6 +1,7 @@ "use client"; import { createPushModal } from "pushmodal"; +import { BankAccountModal } from "./bank-account-modal"; import { DocumentUploadModal } from "./document-upload-modal"; import { EquityPlanModal } from "./equity-pan/equity-plan-modal"; import { ExistingSafeModal } from "./existing-safe-modal"; @@ -37,6 +38,7 @@ export const { pushModal, popModal, ModalProvider } = createPushModal({ IssueShareModal, IssueStockOptionModal, AddEsignDocumentModal, + BankAccountModal, // Safe modals SafeModal, From 78e8c265a0e585560aa789553a18209cdd9038c2 Mon Sep 17 00:00:00 2001 From: Puru D Date: Fri, 19 Jul 2024 00:55:38 -0500 Subject: [PATCH 22/60] feat: add error state to EmptyState component --- src/components/common/empty-state.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/common/empty-state.tsx b/src/components/common/empty-state.tsx index e6fe465c3..7d7f28b50 100644 --- a/src/components/common/empty-state.tsx +++ b/src/components/common/empty-state.tsx @@ -17,6 +17,7 @@ export type EmptyStateProps = { subtitle: string | React.ReactNode; icon?: React.ReactElement>; children?: React.ReactNode; + error?: boolean; }; const EmptyState = ({ @@ -25,6 +26,7 @@ const EmptyState = ({ bordered = true, subtitle, children, + error = false, }: EmptyStateProps) => { return (
@@ -36,8 +38,15 @@ const EmptyState = ({ )} >
-
- {icon} +
+ + {icon} +
{title &&

{title}

} From 1d9feb27426df06acf8fb2c4c9811062e02541e3 Mon Sep 17 00:00:00 2001 From: Puru D Date: Fri, 19 Jul 2024 00:57:33 -0500 Subject: [PATCH 23/60] feat: getting started with bank account settings --- .../bank-accounts/components/cta-button.tsx | 23 ++++++++ .../bank-accounts/components/table.tsx | 5 ++ .../settings/bank-accounts/page.tsx | 59 +++++++++++++++++++ .../[publicId]/settings/billing/page.tsx | 2 +- src/components/settings/settings-sidebar.tsx | 47 ++++++++------- src/components/ui/un-authorized-state.tsx | 9 +-- src/lib/rbac/subjects.ts | 1 + src/trpc/api/root.ts | 2 + src/trpc/routers/bank-accounts/router.ts | 59 +++++++++++++++++++ 9 files changed, 182 insertions(+), 25 deletions(-) create mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/cta-button.tsx create mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx create mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx create mode 100644 src/trpc/routers/bank-accounts/router.ts diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/cta-button.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/cta-button.tsx new file mode 100644 index 000000000..5ec8bb910 --- /dev/null +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/cta-button.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { pushModal } from "@/components/modals"; +import { Button } from "@/components/ui/button"; +import { RiAddLine } from "@remixicon/react"; + +const CtaButton = () => { + return ( + + ); +}; + +export default CtaButton; diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx new file mode 100644 index 000000000..76cfecc2f --- /dev/null +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/components/table.tsx @@ -0,0 +1,5 @@ +const BankAccountsTable = ({ accounts }) => { + return <>Table; +}; + +export default BankAccountsTable; diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx new file mode 100644 index 000000000..3090a581e --- /dev/null +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx @@ -0,0 +1,59 @@ +import EmptyState from "@/components/common/empty-state"; +import { UnAuthorizedState } from "@/components/ui/un-authorized-state"; +import { serverAccessControl } from "@/lib/rbac/access-control"; +import { api } from "@/trpc/server"; +import { RiBankFill } from "@remixicon/react"; +import type { Metadata } from "next"; +import { Fragment } from "react"; +import CtaButton from "./components/cta-button"; +import BankAccountsTable from "./components/table"; + +export const metadata: Metadata = { + title: "Bank accounts", +}; +const ApiSettingsPage = async () => { + const { allow } = await serverAccessControl(); + + const data = await allow(api.bankAccounts.getAll.query(), [ + "bank-accounts", + "read", + ]); + + if (!data) { + return ; + } + + return ( + + {data.bankAccounts.length === 0 ? ( + } + title="Bank accounts" + subtitle="Add a bank account to receive funds" + > + + + ) : ( +
+
+
+

Bank accounts

+

+ Add and manage bank account to receive funds +

+
+ +
+ +
+
+ + +
+ )} +
+ ); +}; + +export default ApiSettingsPage; diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/billing/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/billing/page.tsx index 95e9a052f..84937e1a7 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/billing/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/billing/page.tsx @@ -13,7 +13,7 @@ const BillingPage = async () => { ]); return ( - + ); diff --git a/src/components/settings/settings-sidebar.tsx b/src/components/settings/settings-sidebar.tsx index f2fc1541d..fbe0a04ef 100644 --- a/src/components/settings/settings-sidebar.tsx +++ b/src/components/settings/settings-sidebar.tsx @@ -6,6 +6,8 @@ import { RiAccountCircleLine, RiBankCardFill, RiBankCardLine, + RiBankFill, + RiBankLine, RiBuildingFill, RiBuildingLine, RiGroup2Fill, @@ -41,6 +43,26 @@ const companyNav = [ icon: RiShieldUserLine, activeIcon: RiShieldUserFill, }, + + { + name: "Billing", + href: "/settings/billing", + icon: RiBankCardLine, + activeIcon: RiBankCardFill, + }, + + { + name: "API Keys", + href: "/settings/api", + icon: RiTerminalBoxLine, + activeIcon: RiTerminalBoxFill, + }, + { + name: "Bank Accounts", + href: "/settings/bank-accounts", + icon: RiBankLine, + activeIcon: RiBankFill, + }, ]; const accountNav = [ @@ -57,12 +79,6 @@ const accountNav = [ activeIcon: RiLock2Fill, }, - { - name: "API Keys", - href: "/settings/api", - icon: RiTerminalBoxLine, - activeIcon: RiTerminalBoxFill, - }, { name: "Notifications", href: "/settings/notifications", @@ -80,25 +96,16 @@ export function SettingsSidebar({ isBillingEnabled }: SettingsSidebarProps) { const { data } = useSession(); const companyPublicId = data?.user.companyPublicId; - const sideNavData = [ - ...companyNav, - ...(isBillingEnabled - ? [ - { - name: "Billing", - href: "/settings/billing", - icon: RiBankCardLine, - activeIcon: RiBankCardFill, - }, - ] - : []), - ]; return (
    - {sideNavData.map((item) => { + {companyNav.map((item) => { const href = `/${companyPublicId}${item.href}`; const isActive = currentPath === href; + if (item.name === "Billing" && !isBillingEnabled) { + return null; + } + return (
  • ) { return ( } - title="Un Authorized" - subtitle="you don't have access to view this content." + error={true} + icon={} + title="Unauthorized" + subtitle="You are not authorized to access this content" {...props} /> ); diff --git a/src/lib/rbac/subjects.ts b/src/lib/rbac/subjects.ts index c07fb5d32..0fe9db07c 100644 --- a/src/lib/rbac/subjects.ts +++ b/src/lib/rbac/subjects.ts @@ -7,5 +7,6 @@ export const SUBJECTS = [ "documents", "company", "api-keys", + "bank-accounts", ] as const; export type TSubjects = (typeof SUBJECTS)[number]; diff --git a/src/trpc/api/root.ts b/src/trpc/api/root.ts index 314c30bf9..998682a07 100644 --- a/src/trpc/api/root.ts +++ b/src/trpc/api/root.ts @@ -2,6 +2,7 @@ import { createTRPCRouter } from "@/trpc/api/trpc"; import { apiKeyRouter } from "../routers/api-key/router"; import { auditRouter } from "../routers/audit-router/router"; import { authRouter } from "../routers/auth/router"; +import { bankAccountsRouter } from "../routers/bank-accounts/router"; import { billingRouter } from "../routers/billing-router/router"; import { bucketRouter } from "../routers/bucket-router/router"; import { commonRouter } from "../routers/common/router"; @@ -52,6 +53,7 @@ export const appRouter = createTRPCRouter({ billing: billingRouter, rbac: rbacRouter, apiKey: apiKeyRouter, + bankAccounts: bankAccountsRouter, }); // export type definition of API diff --git a/src/trpc/routers/bank-accounts/router.ts b/src/trpc/routers/bank-accounts/router.ts new file mode 100644 index 000000000..b0eecf80b --- /dev/null +++ b/src/trpc/routers/bank-accounts/router.ts @@ -0,0 +1,59 @@ +import { generatePublicId } from "@/common/id"; +import { createApiToken, createSecureHash } from "@/lib/crypto"; +import { createTRPCRouter, withAccessControl } from "@/trpc/api/trpc"; +import { TRPCError } from "@trpc/server"; +import z from "zod"; + +export const bankAccountsRouter = createTRPCRouter({ + getAll: withAccessControl + .meta({ policies: { "bank-accounts": { allow: ["read"] } } }) + .query(async ({ ctx }) => { + const { + db, + membership: { companyId }, + } = ctx; + + const bankAccounts = await db.bankAccount.findMany({ + where: { + companyId, + }, + + orderBy: { + createdAt: "desc", + }, + + select: { + id: true, + bankName: true, + accountNumber: true, + primary: true, + createdAt: true, + }, + }); + + return { + bankAccounts, + }; + }), + + create: withAccessControl + .meta({ policies: { "bank-accounts": { allow: ["create"] } } }) + .mutation(async ({ ctx }) => { + // const { + // db, + // membership: { companyId, memberId }, + // } = ctx; + // TODO // Implement create mutation + }), + + delete: withAccessControl + .input(z.object({ id: z.string() })) + .meta({ policies: { "bank-accounts": { allow: ["delete"] } } }) + .mutation(async ({ ctx, input }) => { + // const { + // db, + // membership: { memberId, companyId }, + // } = ctx; + // TODO // Implement delete mutation + }), +}); From 9b6852b287d634901a2c268283c11fb32771fc21 Mon Sep 17 00:00:00 2001 From: Puru D Date: Fri, 19 Jul 2024 18:00:28 -0500 Subject: [PATCH 24/60] feat: move existing selectors to selector folder, and add bank-account-selector --- .../settings/bank-accounts/page.tsx | 6 +- src/components/safe/form/index.tsx | 4 +- src/components/safe/safe-actions.tsx | 2 +- .../selector/bank-account-selector.tsx | 100 ++++++++++++++++++ .../investor-selector.tsx | 0 .../stakeholder-selector.tsx | 0 src/trpc/routers/bank-accounts/router.ts | 4 +- 7 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 src/components/selector/bank-account-selector.tsx rename src/components/{stakeholder => selector}/investor-selector.tsx (100%) rename src/components/{stakeholder => selector}/stakeholder-selector.tsx (100%) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx index 357d32436..a06db7098 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx @@ -14,18 +14,18 @@ export const metadata: Metadata = { const ApiSettingsPage = async () => { const { allow } = await serverAccessControl(); - const data = await allow(api.bankAccounts.getAll.query(), [ + const bankAccounts = await allow(api.bankAccounts.getAll.query(), [ "bank-accounts", "read", ]); - if (!data) { + if (!bankAccounts) { return ; } return ( - {data.bankAccounts.length === 0 ? ( + {bankAccounts.length === 0 ? ( } diff --git a/src/components/safe/form/index.tsx b/src/components/safe/form/index.tsx index 2f3d5db58..c8357c467 100644 --- a/src/components/safe/form/index.tsx +++ b/src/components/safe/form/index.tsx @@ -8,7 +8,7 @@ import { } from "@/components/ui/form"; import SlideOver from "@/components/common/slide-over"; -import { InvestorSelector } from "@/components/stakeholder/investor-selector"; +import { InvestorSelector } from "@/components/selector/investor-selector"; import { Button } from "@/components/ui/button"; import { LinearCombobox } from "@/components/ui/combobox"; import { Input } from "@/components/ui/input"; @@ -39,6 +39,8 @@ const SafeFormSchema = z.object({ proRata: z.boolean().optional().default(false), issueDate: z.string().date(), stakeholderId: z.string(), + memberId: z.string(), + bankAccountId: z.string(), }); type SafeFormType = z.infer; diff --git a/src/components/safe/safe-actions.tsx b/src/components/safe/safe-actions.tsx index edcb7f0bc..ffb21f81d 100644 --- a/src/components/safe/safe-actions.tsx +++ b/src/components/safe/safe-actions.tsx @@ -10,7 +10,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -export const SafeActions = async () => { +export const SafeActions = () => { return ( diff --git a/src/components/selector/bank-account-selector.tsx b/src/components/selector/bank-account-selector.tsx new file mode 100644 index 000000000..947569245 --- /dev/null +++ b/src/components/selector/bank-account-selector.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { pushModal } from "@/components/modals"; +import { LinearCombobox } from "@/components/ui/combobox"; +import { api } from "@/trpc/react"; +import { + RiLoader5Line as LoadingIcon, + RiAddLine, + RiRefreshLine, +} from "@remixicon/react"; +import { useEffect, useState } from "react"; + +type StakeholderSelectorType = { + onSelect: (stakeholder: string) => void; +}; + +export function BankAccountSelector({ onSelect }: StakeholderSelectorType) { + const [options, setOptions] = useState<{ value: string; label: string }[]>( + [], + ); + + const { + data: bankAccounts, + isLoading, + refetch, + } = api.bankAccounts.getAll.useQuery(); + + useEffect(() => { + if (bankAccounts) { + setOptions( + bankAccounts.map((account) => ({ + value: account.id, + label: `${account.bankName} - ${account.accountNumber}`, + })), + ); + } + }, [bankAccounts]); + + return ( + { + onSelect(option.value); + }} + > +
    + {!isLoading && ( + + )} + + +
    +
    + ); +} diff --git a/src/components/stakeholder/investor-selector.tsx b/src/components/selector/investor-selector.tsx similarity index 100% rename from src/components/stakeholder/investor-selector.tsx rename to src/components/selector/investor-selector.tsx diff --git a/src/components/stakeholder/stakeholder-selector.tsx b/src/components/selector/stakeholder-selector.tsx similarity index 100% rename from src/components/stakeholder/stakeholder-selector.tsx rename to src/components/selector/stakeholder-selector.tsx diff --git a/src/trpc/routers/bank-accounts/router.ts b/src/trpc/routers/bank-accounts/router.ts index b0eecf80b..3309ac984 100644 --- a/src/trpc/routers/bank-accounts/router.ts +++ b/src/trpc/routers/bank-accounts/router.ts @@ -31,9 +31,7 @@ export const bankAccountsRouter = createTRPCRouter({ }, }); - return { - bankAccounts, - }; + return bankAccounts; }), create: withAccessControl From 25259ba428d6b19fd0d1f61d11e096abfe349485 Mon Sep 17 00:00:00 2001 From: Puru D Date: Fri, 19 Jul 2024 18:02:11 -0500 Subject: [PATCH 25/60] fix: return bankAccountId onSelect --- src/components/selector/bank-account-selector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/selector/bank-account-selector.tsx b/src/components/selector/bank-account-selector.tsx index 947569245..e2ae20468 100644 --- a/src/components/selector/bank-account-selector.tsx +++ b/src/components/selector/bank-account-selector.tsx @@ -11,7 +11,7 @@ import { import { useEffect, useState } from "react"; type StakeholderSelectorType = { - onSelect: (stakeholder: string) => void; + onSelect: (bankAccountId: string) => void; }; export function BankAccountSelector({ onSelect }: StakeholderSelectorType) { From 7e3e3677385a27203c78ff85a2fa9b61ba215095 Mon Sep 17 00:00:00 2001 From: Puru D Date: Fri, 19 Jul 2024 18:03:13 -0500 Subject: [PATCH 26/60] chore: update BankAccountSelectorType --- src/components/selector/bank-account-selector.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/selector/bank-account-selector.tsx b/src/components/selector/bank-account-selector.tsx index e2ae20468..ab850b9ac 100644 --- a/src/components/selector/bank-account-selector.tsx +++ b/src/components/selector/bank-account-selector.tsx @@ -10,11 +10,11 @@ import { } from "@remixicon/react"; import { useEffect, useState } from "react"; -type StakeholderSelectorType = { +type BankAccountSelectorType = { onSelect: (bankAccountId: string) => void; }; -export function BankAccountSelector({ onSelect }: StakeholderSelectorType) { +export function BankAccountSelector({ onSelect }: BankAccountSelectorType) { const [options, setOptions] = useState<{ value: string; label: string }[]>( [], ); From 35c46e795949f56f1237a2e35322aab0da020aff Mon Sep 17 00:00:00 2001 From: Puru D Date: Fri, 19 Jul 2024 18:17:06 -0500 Subject: [PATCH 27/60] feat: complete team member selector --- src/components/selector/member-selector.tsx | 115 ++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/components/selector/member-selector.tsx diff --git a/src/components/selector/member-selector.tsx b/src/components/selector/member-selector.tsx new file mode 100644 index 000000000..4f3842517 --- /dev/null +++ b/src/components/selector/member-selector.tsx @@ -0,0 +1,115 @@ +"use client"; + +import { pushModal } from "@/components/modals"; +import { LinearCombobox } from "@/components/ui/combobox"; +import { api } from "@/trpc/react"; +import { useEffect, useState } from "react"; + +import { + RiLoader5Line as LoadingIcon, + RiAddLine, + RiRefreshLine, +} from "@remixicon/react"; + +type MemberSelectorType = { + onSelect: (memberId: string) => void; +}; + +export function MemberSelector({ onSelect }: MemberSelectorType) { + const [options, setOptions] = useState<{ value: string; label: string }[]>( + [], + ); + + const { + data: members, + isLoading, + refetch, + } = api.member.getMembers.useQuery(); + + const { data: roles, isLoading: loadingRoles } = + api.rbac.listRoles.useQuery(); + + useEffect(() => { + if (members) { + setOptions( + members.data.map((member) => ({ + value: member.id, + label: (member.user.name || member.user.email) as string, + })), + ); + } + }, [members]); + + return ( + { + onSelect(option.value); + }} + > +
    + {!isLoading && ( + + )} + + +
    +
    + ); +} From dfa00f106dc32a9bde2919db345aac094878cbe7 Mon Sep 17 00:00:00 2001 From: Puru D Date: Sat, 20 Jul 2024 00:06:14 -0500 Subject: [PATCH 28/60] chore: remove console.log --- .../routers/stakeholder-router/procedures/get-stakeholders.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/trpc/routers/stakeholder-router/procedures/get-stakeholders.ts b/src/trpc/routers/stakeholder-router/procedures/get-stakeholders.ts index 98457e75e..d4886215a 100644 --- a/src/trpc/routers/stakeholder-router/procedures/get-stakeholders.ts +++ b/src/trpc/routers/stakeholder-router/procedures/get-stakeholders.ts @@ -15,9 +15,6 @@ export const getStakeholdersProcedure = withAccessControl const { db, membership } = ctx; const investor = input?.investor; - console.log("------------------------------------------------>"); - console.log("isInvestor", investor); - const data = await db.stakeholder.findMany({ where: { companyId: membership.companyId, From 1f19b3b5b9a0f3a01f521721507a8aec7063dc73 Mon Sep 17 00:00:00 2001 From: Puru D Date: Wed, 24 Jul 2024 02:31:05 -0500 Subject: [PATCH 29/60] feat: add company rep and bank account selector to safe modal --- src/components/safe/form/index.tsx | 60 ++++++++++++++++----- src/components/selector/member-selector.tsx | 2 +- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/components/safe/form/index.tsx b/src/components/safe/form/index.tsx index c8357c467..4e93a0260 100644 --- a/src/components/safe/form/index.tsx +++ b/src/components/safe/form/index.tsx @@ -8,7 +8,9 @@ import { } from "@/components/ui/form"; import SlideOver from "@/components/common/slide-over"; +import { BankAccountSelector } from "@/components/selector/bank-account-selector"; import { InvestorSelector } from "@/components/selector/investor-selector"; +import { MemberSelector } from "@/components/selector/member-selector"; import { Button } from "@/components/ui/button"; import { LinearCombobox } from "@/components/ui/combobox"; import { Input } from "@/components/ui/input"; @@ -69,13 +71,31 @@ export const SafeForm: React.FC = ({ type }) => { onSubmit={form.handleSubmit(handleSubmit)} className="flex flex-col gap-y-4" > -
    - - Investor (Stakeholder) -

    - Please select or create the investor account. -

    -
    + { + form.setValue("type", value as SafeTypeEnum); + }} + /> + +
    + { + return ( + + Company representative + { + form.setValue("memberId", value); + }} + /> + + + ); + }} + /> = ({ type }) => { render={() => { return ( + Investor { form.setValue("stakeholderId", value); @@ -93,14 +114,25 @@ export const SafeForm: React.FC = ({ type }) => { ); }} /> -
    - { - form.setValue("type", value as SafeTypeEnum); - }} - /> + { + return ( + + Bank account + { + form.setValue("bankAccountId", value); + }} + /> + + + ); + }} + /> +
    { onSelect(option.value); From a390c00add028efbe0875b7e336f2dbe86771c92 Mon Sep 17 00:00:00 2001 From: Puru D Date: Wed, 24 Jul 2024 02:39:30 -0500 Subject: [PATCH 30/60] chore: add help text and reorder inputs --- src/components/safe/form/index.tsx | 37 ++++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/components/safe/form/index.tsx b/src/components/safe/form/index.tsx index 4e93a0260..cf79ea38d 100644 --- a/src/components/safe/form/index.tsx +++ b/src/components/safe/form/index.tsx @@ -1,6 +1,7 @@ import { Form, FormControl, + FormDescription, FormField, FormItem, FormLabel, @@ -85,12 +86,15 @@ export const SafeForm: React.FC = ({ type }) => { render={() => { return ( - Company representative + Team member { form.setValue("memberId", value); }} /> + + Responsible for signing the SAFE agreement + ); @@ -127,6 +131,9 @@ export const SafeForm: React.FC = ({ type }) => { form.setValue("bankAccountId", value); }} /> + + Bank account to receive the investment + ); @@ -165,6 +172,20 @@ export const SafeForm: React.FC = ({ type }) => { }} /> + ( + + Investment date + + + + + + )} + /> + = ({ type }) => { ); }} /> - - ( - - Investment date - - - - - - )} - />
    From 703cdefbcb555b6d9627aaf509219125723ca7d7 Mon Sep 17 00:00:00 2001 From: Puru D Date: Wed, 24 Jul 2024 02:58:16 -0500 Subject: [PATCH 31/60] chore: minor cleanups --- src/app/verify-member/[token]/page.tsx | 2 +- src/components/ui/combobox.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/verify-member/[token]/page.tsx b/src/app/verify-member/[token]/page.tsx index 1d464cad6..c68b3c307 100644 --- a/src/app/verify-member/[token]/page.tsx +++ b/src/app/verify-member/[token]/page.tsx @@ -1,7 +1,7 @@ import { VerifyMemberForm } from "@/components/member/verify-member-form"; import { authOptions } from "@/server/auth"; import { checkVerificationToken } from "@/server/member"; -import { type Metadata } from "next"; +import type { Metadata } from "next"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; diff --git a/src/components/ui/combobox.tsx b/src/components/ui/combobox.tsx index 554e73ee8..05b7483ad 100644 --- a/src/components/ui/combobox.tsx +++ b/src/components/ui/combobox.tsx @@ -132,7 +132,7 @@ export const LinearCombobox = ({
    {selectedOption?.value === option.value && ( - + )}
    From f3a4b4470b9f45a208e52fb9f1403ca0b239f5bf Mon Sep 17 00:00:00 2001 From: Puru D Date: Wed, 24 Jul 2024 03:21:57 -0500 Subject: [PATCH 32/60] feat: add tooltip with fields descriptions --- src/components/common/help-tooltip.tsx | 31 +++++++++++++++++++ src/components/modals/safe.tsx | 2 +- src/components/safe/form/index.tsx | 27 ++++++++++------ .../safe/form/pre-post-selector.tsx | 15 +-------- 4 files changed, 51 insertions(+), 24 deletions(-) create mode 100644 src/components/common/help-tooltip.tsx diff --git a/src/components/common/help-tooltip.tsx b/src/components/common/help-tooltip.tsx new file mode 100644 index 000000000..1736e4b8b --- /dev/null +++ b/src/components/common/help-tooltip.tsx @@ -0,0 +1,31 @@ +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +import { cn } from "@/lib/utils"; +import { RiQuestionLine as HelpIcon } from "@remixicon/react"; + +type HelpTooltipProps = { + className?: string; + text: string; +}; + +const HelpTooltip = ({ text, className }: HelpTooltipProps) => { + return ( + + + + + + {text} + + + ); +}; + +export default HelpTooltip; diff --git a/src/components/modals/safe.tsx b/src/components/modals/safe.tsx index 9720bfe8c..b8426724f 100644 --- a/src/components/modals/safe.tsx +++ b/src/components/modals/safe.tsx @@ -39,7 +39,7 @@ export const SafeModal: React.FC = ({ type }) => { Upload custom SAFE agreement - {/* TODO: Write a helkp article on SAFE */} + {/* TODO: Write a help article on SAFE */} ) : ( - "Record an existing SAFE agreement to keep track of it in your captable." + + + + {/* TODO: Write a help article on SAFE */} + + ) } dialogProps={{ diff --git a/src/server/api/routes/company/safes/create.ts b/src/server/api/routes/company/safes/create.ts index 7fd2f0133..55c631013 100644 --- a/src/server/api/routes/company/safes/create.ts +++ b/src/server/api/routes/company/safes/create.ts @@ -29,6 +29,9 @@ const route = createRoute({ method: "post", path: "/v1/companies/:id/safes", request: { params: RequestSchema, json: RequestJsonSchema }, + summary: "Create SAFE", + description: "Create a SAFE agreement.", + tags: ["SAFEs"], responses: { 200: { content: { diff --git a/src/server/api/routes/company/safes/deleteOne.ts b/src/server/api/routes/company/safes/deleteOne.ts index 49c15bd54..437bf7600 100644 --- a/src/server/api/routes/company/safes/deleteOne.ts +++ b/src/server/api/routes/company/safes/deleteOne.ts @@ -38,6 +38,9 @@ const route = createRoute({ method: "delete", path: "/v1/companies/:id/safes/:safeId", request: { params: RequestSchema }, + summary: "Delete SAFE", + description: "Delete a SAFE agreement.", + tags: ["SAFEs"], responses: { 200: { content: { diff --git a/src/server/api/routes/company/safes/getMany.ts b/src/server/api/routes/company/safes/getMany.ts index dbb640e33..25da41679 100644 --- a/src/server/api/routes/company/safes/getMany.ts +++ b/src/server/api/routes/company/safes/getMany.ts @@ -25,6 +25,9 @@ const route = createRoute({ method: "get", path: "/v1/companies/:id/safes", request: { params: RequestSchema }, + summary: "List SAFEs", + description: "List a SAFE agreements.", + tags: ["SAFEs"], responses: { 200: { content: { diff --git a/src/server/api/routes/company/safes/getOne.ts b/src/server/api/routes/company/safes/getOne.ts index c4ac3860e..cdfcf71ed 100644 --- a/src/server/api/routes/company/safes/getOne.ts +++ b/src/server/api/routes/company/safes/getOne.ts @@ -38,6 +38,9 @@ const route = createRoute({ method: "get", path: "/v1/companies/:id/safes/:safeId", request: { params: RequestSchema }, + summary: "Get SAFE", + description: "Get a SAFE agreement.", + tags: ["SAFEs"], responses: { 200: { content: { diff --git a/src/server/api/routes/company/safes/update.ts b/src/server/api/routes/company/safes/update.ts index 59b96a2a4..f75e1a70d 100644 --- a/src/server/api/routes/company/safes/update.ts +++ b/src/server/api/routes/company/safes/update.ts @@ -38,6 +38,9 @@ const route = createRoute({ method: "put", path: "/v1/companies/:id/safes/:safeId", request: { params: RequestSchema }, + summary: "Update SAFE", + description: "Update a SAFE agreement.", + tags: ["SAFEs"], responses: { 200: { content: { From a335eb03d9bde83c3f84c2bfdd3d28445ca03d2d Mon Sep 17 00:00:00 2001 From: Puru D Date: Thu, 8 Aug 2024 01:05:47 -0500 Subject: [PATCH 34/60] feat: getting started with safe APIs --- src/server/api/index.ts | 2 +- src/server/api/routes/company/safes/create.ts | 65 --------- .../api/routes/company/safes/deleteOne.ts | 80 ---------- .../api/routes/company/safes/getMany.ts | 61 -------- src/server/api/routes/company/safes/getOne.ts | 74 ---------- src/server/api/routes/company/safes/index.ts | 14 -- src/server/api/routes/company/safes/update.ts | 74 ---------- src/server/api/routes/safe/create.ts | 112 ++++++++++++++ src/server/api/routes/safe/delete.ts | 108 ++++++++++++++ src/server/api/routes/safe/getMany.ts | 75 ++++++++++ src/server/api/routes/safe/getOne.ts | 76 ++++++++++ src/server/api/routes/safe/index.ts | 14 ++ src/server/api/routes/safe/update.ts | 138 ++++++++++++++++++ src/server/api/schema/safe.ts | 76 ++++++++++ src/server/api/schema/safes.ts | 10 -- 15 files changed, 600 insertions(+), 379 deletions(-) delete mode 100644 src/server/api/routes/company/safes/create.ts delete mode 100644 src/server/api/routes/company/safes/deleteOne.ts delete mode 100644 src/server/api/routes/company/safes/getMany.ts delete mode 100644 src/server/api/routes/company/safes/getOne.ts delete mode 100644 src/server/api/routes/company/safes/index.ts delete mode 100644 src/server/api/routes/company/safes/update.ts create mode 100644 src/server/api/routes/safe/create.ts create mode 100644 src/server/api/routes/safe/delete.ts create mode 100644 src/server/api/routes/safe/getMany.ts create mode 100644 src/server/api/routes/safe/getOne.ts create mode 100644 src/server/api/routes/safe/index.ts create mode 100644 src/server/api/routes/safe/update.ts create mode 100644 src/server/api/schema/safe.ts delete mode 100644 src/server/api/schema/safes.ts diff --git a/src/server/api/index.ts b/src/server/api/index.ts index c89b138f8..6502a6620 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -1,7 +1,7 @@ import { PublicAPI } from "./hono"; import { middlewareServices } from "./middlewares/services"; import { registerCompanyRoutes } from "./routes/company"; -import { registerSafeRoutes } from "./routes/company/safes"; +import { registerSafeRoutes } from "./routes/safe"; import { registerShareRoutes } from "./routes/share"; import { registerStakeholderRoutes } from "./routes/stakeholder"; diff --git a/src/server/api/routes/company/safes/create.ts b/src/server/api/routes/company/safes/create.ts deleted file mode 100644 index 55c631013..000000000 --- a/src/server/api/routes/company/safes/create.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { withCompanyAuth } from "@/server/api/auth"; -import { ApiError, ErrorResponses } from "@/server/api/error"; -import type { PublicAPI } from "@/server/api/hono"; -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"; - -export const RequestSchema = z.object({ - id: z - .string() - .cuid() - .openapi({ - description: "Company ID", - param: { - name: "id", - in: "path", - }, - - example: "cly405ci60000i7ngbiel3m5l", - }), -}); - -export const RequestJsonSchema = z.object({ - name: z.string().min(1), -}); - -const route = createRoute({ - method: "post", - path: "/v1/companies/:id/safes", - request: { params: RequestSchema, json: RequestJsonSchema }, - summary: "Create SAFE", - description: "Create a SAFE agreement.", - tags: ["SAFEs"], - responses: { - 200: { - content: { - "application/json": { - schema: ApiSafesSchema, - }, - }, - description: "Create a SAFE", - }, - - ...ErrorResponses, - }, -}); - -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 SAFE - return c.json(company, 200); - }); -}; - -export default create; diff --git a/src/server/api/routes/company/safes/deleteOne.ts b/src/server/api/routes/company/safes/deleteOne.ts deleted file mode 100644 index 437bf7600..000000000 --- a/src/server/api/routes/company/safes/deleteOne.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { withCompanyAuth } from "@/server/api/auth"; -import { ApiError, ErrorResponses } 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", - }), - - safeId: z - .string() - .cuid() - .openapi({ - description: "Security ID", - param: { - name: "safeId", - in: "path", - }, - - example: "cly43q7220000i7nggrlj2a8g", - }), -}); - -const route = createRoute({ - method: "delete", - path: "/v1/companies/:id/safes/:safeId", - request: { params: RequestSchema }, - summary: "Delete SAFE", - description: "Delete a SAFE agreement.", - tags: ["SAFEs"], - responses: { - 200: { - content: { - "application/json": { - schema: ApiDeleteResponseSchema, - }, - }, - description: "Delete a SAFE by ID", - }, - - ...ErrorResponses, - }, -}); - -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 SAFE by ID - return c.json( - { - success: true, - message: "Resource successfully deleted", - }, - 200, - ); - }); -}; - -export default deleteOne; diff --git a/src/server/api/routes/company/safes/getMany.ts b/src/server/api/routes/company/safes/getMany.ts deleted file mode 100644 index 25da41679..000000000 --- a/src/server/api/routes/company/safes/getMany.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { withCompanyAuth } from "@/server/api/auth"; -import { ApiError, ErrorResponses } from "@/server/api/error"; -import type { PublicAPI } from "@/server/api/hono"; -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"; - -export const RequestSchema = z.object({ - id: z - .string() - .cuid() - .openapi({ - description: "SAFE ID", - param: { - name: "id", - in: "path", - }, - - example: "cly405ci60000i7ngbiel3m5l", - }), -}); - -const route = createRoute({ - method: "get", - path: "/v1/companies/:id/safes", - request: { params: RequestSchema }, - summary: "List SAFEs", - description: "List a SAFE agreements.", - tags: ["SAFEs"], - responses: { - 200: { - content: { - "application/json": { - schema: ApiSafesSchema, - }, - }, - description: "Get a SAFE by ID", - }, - - ...ErrorResponses, - }, -}); - -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 safes - return c.json(company, 200); - }); -}; - -export default getMany; diff --git a/src/server/api/routes/company/safes/getOne.ts b/src/server/api/routes/company/safes/getOne.ts deleted file mode 100644 index cdfcf71ed..000000000 --- a/src/server/api/routes/company/safes/getOne.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { withCompanyAuth } from "@/server/api/auth"; -import { ApiError, ErrorResponses } from "@/server/api/error"; -import type { PublicAPI } from "@/server/api/hono"; -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"; - -export const RequestSchema = z.object({ - id: z - .string() - .cuid() - .openapi({ - description: "Company ID", - param: { - name: "id", - in: "path", - }, - - example: "cly405ci60000i7ngbiel3m5l", - }), - - safeId: z - .string() - .cuid() - .openapi({ - description: "SAFE ID", - param: { - name: "safeId", - in: "path", - }, - - example: "cly43q7220000i7nggrlj2a8g", - }), -}); - -const route = createRoute({ - method: "get", - path: "/v1/companies/:id/safes/:safeId", - request: { params: RequestSchema }, - summary: "Get SAFE", - description: "Get a SAFE agreement.", - tags: ["SAFEs"], - responses: { - 200: { - content: { - "application/json": { - schema: ApiSafesSchema, - }, - }, - description: "Get a SAFE by ID", - }, - - ...ErrorResponses, - }, -}); - -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 SAFE by ID - return c.json(company, 200); - }); -}; - -export default getOne; diff --git a/src/server/api/routes/company/safes/index.ts b/src/server/api/routes/company/safes/index.ts deleted file mode 100644 index 560f384ec..000000000 --- a/src/server/api/routes/company/safes/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -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"; - -export const registerSafeRoutes = (api: PublicAPI) => { - update(api); - create(api); - getOne(api); - getMany(api); - deleteOne(api); -}; diff --git a/src/server/api/routes/company/safes/update.ts b/src/server/api/routes/company/safes/update.ts deleted file mode 100644 index f75e1a70d..000000000 --- a/src/server/api/routes/company/safes/update.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { withCompanyAuth } from "@/server/api/auth"; -import { ApiError, ErrorResponses } from "@/server/api/error"; -import type { PublicAPI } from "@/server/api/hono"; -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"; - -export const RequestSchema = z.object({ - id: z - .string() - .cuid() - .openapi({ - description: "Company ID", - param: { - name: "id", - in: "path", - }, - - example: "cly405ci60000i7ngbiel3m5l", - }), - - safeId: z - .string() - .cuid() - .openapi({ - description: "SAFE ID", - param: { - name: "safeId", - in: "path", - }, - - example: "cly43q7220000i7nggrlj2a8g", - }), -}); - -const route = createRoute({ - method: "put", - path: "/v1/companies/:id/safes/:safeId", - request: { params: RequestSchema }, - summary: "Update SAFE", - description: "Update a SAFE agreement.", - tags: ["SAFEs"], - responses: { - 200: { - content: { - "application/json": { - schema: ApiSafesSchema, - }, - }, - description: "Create a SAFE", - }, - - ...ErrorResponses, - }, -}); - -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 SAFE - return c.json(company, 200); - }); -}; - -export default update; diff --git a/src/server/api/routes/safe/create.ts b/src/server/api/routes/safe/create.ts new file mode 100644 index 000000000..8c01bed0a --- /dev/null +++ b/src/server/api/routes/safe/create.ts @@ -0,0 +1,112 @@ +import { + CreateStakeholderSchema, + StakeholderSchema, +} from "@/server/api/schema/stakeholder"; +import { + authMiddleware, + withAuthApiV1, +} from "@/server/api/utils/endpoint-creator"; +import { z } from "@hono/zod-openapi"; + +const ResponseSchema = z.object({ + message: z.string(), + data: z.array(StakeholderSchema.pick({ id: true, name: true })), +}); + +const ParamsSchema = z.object({ + companyId: z.string().openapi({ + param: { + name: "companyId", + in: "path", + }, + description: "Company ID", + type: "string", + example: "clxwbok580000i7nge8nm1ry0", + }), +}); + +export const create = withAuthApiV1 + .createRoute({ + method: "post", + path: "/v1/{companyId}/stakeholders", + summary: "Create stakeholders", + description: "Add one or more stakeholder accounts to a company.", + tags: ["Stakeholder"], + middleware: [authMiddleware()], + request: { + params: ParamsSchema, + body: { + content: { + "application/json": { + schema: CreateStakeholderSchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: + "Confirmation of stakeholder created with relevant details.", + }, + }, + }) + .handler(async (c) => { + const { db, audit, client } = c.get("services"); + const { membership } = c.get("session"); + const { requestIp, userAgent } = client as { + requestIp: string; + userAgent: string; + }; + + const body = c.req.valid("json"); + + const stakeholders = await db.$transaction(async (tx) => { + const inputDataWithCompanyId = body.map((stakeholder) => ({ + ...stakeholder, + companyId: membership.companyId, + })); + + const addedStakeholders = await tx.stakeholder.createManyAndReturn({ + data: inputDataWithCompanyId, + select: { + id: true, + name: true, + }, + }); + + const auditPromises = addedStakeholders.map((stakeholder) => + audit.create( + { + action: "stakeholder.added", + companyId: membership.companyId, + actor: { type: "user", id: membership.userId }, + context: { + requestIp, + userAgent, + }, + target: [{ type: "stakeholder", id: stakeholder.id }], + summary: `${membership.user.name} added the stakholder in the company : ${stakeholder.name}`, + }, + tx, + ), + ); + await Promise.all(auditPromises); + + return addedStakeholders; + }); + + const data: z.infer["data"] = stakeholders; + + return c.json( + { + data, + message: "Stakeholders successfully created.", + }, + 200, + ); + }); diff --git a/src/server/api/routes/safe/delete.ts b/src/server/api/routes/safe/delete.ts new file mode 100644 index 000000000..8b88ed8ec --- /dev/null +++ b/src/server/api/routes/safe/delete.ts @@ -0,0 +1,108 @@ +import { ApiError } from "@/server/api/error"; +import { z } from "@hono/zod-openapi"; + +import { + authMiddleware, + withAuthApiV1, +} from "@/server/api/utils/endpoint-creator"; + +const ParamsSchema = z.object({ + id: z.string().openapi({ + param: { + name: "id", + in: "path", + }, + description: "Safe ID", + type: "string", + example: "clyabgufg004u5tbtnz0r4cax", + }), + companyId: z.string().openapi({ + param: { + name: "companyId", + in: "path", + }, + description: "Company ID", + type: "string", + example: "clxwbok580000i7nge8nm1ry0", + }), +}); + +const ResponseSchema = z.object({ + message: z.string(), +}); + +export const _delete = withAuthApiV1 + .createRoute({ + summary: "Delete a safe", + description: "Remove a safe from a company by ID.", + tags: ["Safe"], + method: "delete", + path: "/v1/{companyId}/safes/{id}", + middleware: [authMiddleware()], + request: { params: ParamsSchema }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Confirmation that the safe has been removed.", + }, + }, + }) + .handler(async (c) => { + const { db, audit, client } = c.get("services"); + const { membership } = c.get("session"); + const { requestIp, userAgent } = client; + const { id } = c.req.valid("param"); + + await db.$transaction(async (tx) => { + const stakeholder = await tx.stakeholder.findUnique({ + where: { + id, + companyId: membership.companyId, + }, + select: { + id: true, + companyId: true, + name: true, + }, + }); + + if (!stakeholder) { + throw new ApiError({ + code: "NOT_FOUND", + message: "No stakeholder with the provided Id", + }); + } + + await tx.stakeholder.delete({ + where: { + id: stakeholder.id, + }, + }); + + await audit.create( + { + action: "stakeholder.deleted", + companyId: membership.companyId, + actor: { type: "user", id: membership.userId }, + context: { + requestIp, + userAgent, + }, + target: [{ type: "stakeholder", id }], + summary: `${membership.user.name} deleted the stakeholder ${stakeholder.name} - ${stakeholder.id}`, + }, + tx, + ); + }); + + return c.json( + { + message: "Stakeholder deleted successfully", + }, + 200, + ); + }); diff --git a/src/server/api/routes/safe/getMany.ts b/src/server/api/routes/safe/getMany.ts new file mode 100644 index 000000000..a541b6020 --- /dev/null +++ b/src/server/api/routes/safe/getMany.ts @@ -0,0 +1,75 @@ +import { + PaginationQuerySchema, + PaginationResponseSchema, +} from "@/server/api/schema/pagination"; +import { StakeholderSchema } from "@/server/api/schema/stakeholder"; +import { + authMiddleware, + withAuthApiV1, +} from "@/server/api/utils/endpoint-creator"; +import { z } from "@hono/zod-openapi"; + +const ResponseSchema = z.object({ + data: z.array(StakeholderSchema), + meta: PaginationResponseSchema, +}); + +const ParamsSchema = z.object({ + companyId: z.string().openapi({ + param: { + name: "companyId", + in: "path", + }, + description: "Company ID", + type: "string", + example: "clxwbok580000i7nge8nm1ry0", + }), +}); + +export const getMany = withAuthApiV1 + .createRoute({ + summary: "List stakeholders", + description: + "Retrieve a paginated list of all stakeholders in the company.", + tags: ["Stakeholder"], + method: "get", + path: "/v1/{companyId}/stakeholders", + middleware: [authMiddleware()], + request: { + query: PaginationQuerySchema, + params: ParamsSchema, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "A list of stakeholders in a company with their details.", + }, + }, + }) + .handler(async (c) => { + const { membership } = c.get("session"); + const { db } = c.get("services"); + const query = c.req.valid("query"); + + const [data, meta] = await db.stakeholder + .paginate({ where: { companyId: membership.companyId } }) + .withCursor({ + limit: query.limit, + after: query.cursor, + }); + + const response: z.infer = { + data: data.map((stakeholder) => ({ + ...stakeholder, + createdAt: stakeholder.createdAt.toISOString(), + updatedAt: stakeholder.updatedAt.toISOString(), + })), + meta, + }; + + return c.json(response, 200); + }); diff --git a/src/server/api/routes/safe/getOne.ts b/src/server/api/routes/safe/getOne.ts new file mode 100644 index 000000000..ce62eed1a --- /dev/null +++ b/src/server/api/routes/safe/getOne.ts @@ -0,0 +1,76 @@ +import { ApiError } from "@/server/api/error"; +import { ApiSafeSchema, type ApiSafeType } from "@/server/api/schema/safe"; +import { + authMiddleware, + withAuthApiV1, +} from "@/server/api/utils/endpoint-creator"; +import { z } from "@hono/zod-openapi"; + +const ParamsSchema = z.object({ + id: z.string().openapi({ + param: { + name: "id", + in: "path", + }, + description: "Safe ID", + type: "string", + example: "clzkure8d0000f1ng0hrv9fg9", + }), + companyId: z.string().openapi({ + param: { + name: "companyId", + in: "path", + }, + description: "Company ID", + type: "string", + example: "clxwbok580000i7nge8nm1ry0", + }), +}); + +const ResponseSchema = z.object({ + data: ApiSafeSchema, +}); + +export const getOne = withAuthApiV1 + .createRoute({ + summary: "Get a safe", + description: "Get a safe by ID", + tags: ["Safe"], + method: "get", + path: "/v1/{companyId}/safes/{id}", + middleware: [authMiddleware()], + request: { + params: ParamsSchema, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Safe details", + }, + }, + }) + .handler(async (c) => { + const { db } = c.get("services"); + const { membership } = c.get("session"); + const { id } = c.req.valid("param"); + + const safe = (await db.safe.findUnique({ + where: { + id, + companyId: membership.companyId, + }, + })) as ApiSafeType | null; + + if (!safe) { + throw new ApiError({ + code: "NOT_FOUND", + message: "No safe with the provided Id", + }); + } + + return c.json({ data: safe }, 200); + }); diff --git a/src/server/api/routes/safe/index.ts b/src/server/api/routes/safe/index.ts new file mode 100644 index 000000000..d54fd6044 --- /dev/null +++ b/src/server/api/routes/safe/index.ts @@ -0,0 +1,14 @@ +import type { PublicAPI } from "@/server/api/hono"; +import { create } from "./create"; +import { _delete } from "./delete"; +import { getMany } from "./getMany"; +import { getOne } from "./getOne"; +import { update } from "./update"; + +export const registerSafeRoutes = (api: PublicAPI) => { + api.openapi(getOne.route, getOne.handler); + api.openapi(update.route, update.handler); + api.openapi(_delete.route, _delete.handler); + api.openapi(create.route, create.handler); + api.openapi(getMany.route, getMany.handler); +}; diff --git a/src/server/api/routes/safe/update.ts b/src/server/api/routes/safe/update.ts new file mode 100644 index 000000000..b3a74614c --- /dev/null +++ b/src/server/api/routes/safe/update.ts @@ -0,0 +1,138 @@ +import { ApiError } from "@/server/api/error"; +import { + StakeholderSchema, + type TStakeholderSchema, + type TUpdateStakeholderSchema, + UpdateStakeholderSchema, +} from "@/server/api/schema/stakeholder"; +import { + authMiddleware, + withAuthApiV1, +} from "@/server/api/utils/endpoint-creator"; +import { z } from "@hono/zod-openapi"; + +const ParamsSchema = z.object({ + id: z.string().openapi({ + param: { + name: "id", + in: "path", + }, + description: "Safe ID", + type: "string", + example: "clyabgufg004u5tbtnz0r4cax", + }), + companyId: z.string().openapi({ + param: { + name: "companyId", + in: "path", + }, + description: "Company ID", + type: "string", + example: "clxwbok580000i7nge8nm1ry0", + }), +}); + +const ResponseSchema = z.object({ + message: z.string(), + data: StakeholderSchema, +}); + +export const update = withAuthApiV1 + .createRoute({ + summary: "Update a stakeholder", + description: "Modify the details of a stakeholder by their ID.", + tags: ["Stakeholder"], + method: "patch", + path: "/v1/{companyId}/stakeholders/{id}", + middleware: [authMiddleware()], + request: { + params: ParamsSchema, + body: { + content: { + "application/json": { + schema: UpdateStakeholderSchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Confirmation of updated stakeholder details.", + }, + }, + }) + .handler(async (c) => { + const { id } = c.req.valid("param"); + const { db, audit, client } = c.get("services"); + const { membership } = c.get("session"); + const { requestIp, userAgent } = client as { + requestIp: string; + userAgent: string; + }; + + const body = await c.req.json(); + + const updatedStakeHolder = await db.$transaction(async (tx) => { + const stakeholder = await tx.stakeholder.findUnique({ + where: { + id, + companyId: membership.companyId, + }, + select: { + id: true, + companyId: true, + name: true, + }, + }); + + if (!stakeholder) { + throw new ApiError({ + code: "NOT_FOUND", + message: "No stakeholder with the provided Id", + }); + } + + const updatedStakeHolder = await tx.stakeholder.update({ + where: { + id: stakeholder.id, + }, + data: body, + }); + + await audit.create( + { + action: "stakeholder.updated", + companyId: membership.companyId, + actor: { type: "user", id: membership.userId }, + context: { + requestIp, + userAgent, + }, + target: [{ type: "stakeholder", id: stakeholder.id }], + summary: `${membership.user.name} updated the stakeholder details in the company : ${updatedStakeHolder.name}`, + }, + tx, + ); + + return updatedStakeHolder; + }); + + const data: TStakeholderSchema = { + ...updatedStakeHolder, + createdAt: updatedStakeHolder.createdAt.toISOString(), + updatedAt: updatedStakeHolder.updatedAt.toISOString(), + }; + + return c.json( + { + message: "Stakeholder updated successfully", + data, + }, + 200, + ); + }); diff --git a/src/server/api/schema/safe.ts b/src/server/api/schema/safe.ts new file mode 100644 index 000000000..63f971655 --- /dev/null +++ b/src/server/api/schema/safe.ts @@ -0,0 +1,76 @@ +import { z } from "@hono/zod-openapi"; +import { SafeStatusEnum, SafeTypeEnum } from "@prisma/client"; + +const types = z.nativeEnum(SafeTypeEnum); +const statuses = z.nativeEnum(SafeStatusEnum); + +export const ApiSafeSchema = z.object({ + id: z.string().cuid().openapi({ + description: "SAFE ID", + example: "cly402ncj0000i7ng9l0v04qr", + }), + externalId: z.string().optional().openapi({ + description: "External ID of the safe", + example: "1234567890", + }), + type: types.openapi({ + description: "Type of the safe", + example: "POST_MONEY", + }), + status: statuses.openapi({ + description: "Status of the safe", + example: "ACTIVE", + }), + capital: z.number().openapi({ + description: "SAFE investment capital", + example: 10000, + }), + valuationCap: z.number().optional().openapi({ + description: "Valuation cap of the safe", + example: 100000000, + }), + discountRate: z.number().optional().openapi({ + description: "Discount rate in percentage", + example: 5, + }), + mfn: z.boolean().optional().openapi({ + description: "Most favoured nation", + example: true, + }), + proRata: z.boolean().optional().openapi({ + description: "Pro rata rights", + example: true, + }), + stakeholderId: z.string().cuid().openapi({ + description: "Stakeholder / Investor ID", + example: "clzkv7w5c0000f1ngf3yq2s45", + }), + documents: z + .array(z.string().url()) + .optional() + .openapi({ + description: "Secure links to SAFE documents", + example: [ + "https://docs.captable.inc/document1.pdf", + "https://docs.captable.inc/document2.pdf", + ], + }), + companyId: z.string().cuid().openapi({ + description: "Company ID", + example: "clzkvdnhj0000f1ngf3fxakwu", + }), + issueDate: z.date().optional().openapi({ + description: "Date of issue", + example: "2021-01-01", + }), + boardApprovalDate: z.date().optional().openapi({ + description: "Date of board approval", + example: "2021-01-01", + }), + createdAt: z.date().openapi({ + description: "Created at timestamp", + example: "2021-01-01", + }), +}); + +export type ApiSafeType = z.infer; diff --git a/src/server/api/schema/safes.ts b/src/server/api/schema/safes.ts deleted file mode 100644 index bd337376e..000000000 --- a/src/server/api/schema/safes.ts +++ /dev/null @@ -1,10 +0,0 @@ -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; From 1341e0751fe8b1c1a4583de97eacac22f0ed2fe7 Mon Sep 17 00:00:00 2001 From: Puru D Date: Thu, 8 Aug 2024 02:59:36 -0500 Subject: [PATCH 35/60] feat: complete getOne and getMany SAFE api --- src/server/api/middlewares/bearer-token.ts | 2 +- src/server/api/routes/safe/getMany.ts | 58 +++++++++------------- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/server/api/middlewares/bearer-token.ts b/src/server/api/middlewares/bearer-token.ts index 625576ba8..7e367b995 100644 --- a/src/server/api/middlewares/bearer-token.ts +++ b/src/server/api/middlewares/bearer-token.ts @@ -35,7 +35,7 @@ export const accessTokenAuthMiddleware = ( async function authenticateWithAccessToken( bearerToken: string, c: Context, - withoutMembershipCheck: undefined | boolean, + withoutMembershipCheck: undefined | boolean = false, ) { const [clientId, clientSecret] = bearerToken.split(":") as [string, string]; diff --git a/src/server/api/routes/safe/getMany.ts b/src/server/api/routes/safe/getMany.ts index a541b6020..94766717d 100644 --- a/src/server/api/routes/safe/getMany.ts +++ b/src/server/api/routes/safe/getMany.ts @@ -1,19 +1,11 @@ -import { - PaginationQuerySchema, - PaginationResponseSchema, -} from "@/server/api/schema/pagination"; -import { StakeholderSchema } from "@/server/api/schema/stakeholder"; +import { ApiError } from "@/server/api/error"; +import { ApiSafeSchema, type ApiSafeType } from "@/server/api/schema/safe"; import { authMiddleware, withAuthApiV1, } from "@/server/api/utils/endpoint-creator"; import { z } from "@hono/zod-openapi"; -const ResponseSchema = z.object({ - data: z.array(StakeholderSchema), - meta: PaginationResponseSchema, -}); - const ParamsSchema = z.object({ companyId: z.string().openapi({ param: { @@ -26,17 +18,19 @@ const ParamsSchema = z.object({ }), }); +const ResponseSchema = z.object({ + data: ApiSafeSchema.array(), +}); + export const getMany = withAuthApiV1 .createRoute({ - summary: "List stakeholders", - description: - "Retrieve a paginated list of all stakeholders in the company.", - tags: ["Stakeholder"], + summary: "List safes", + description: "List all safes in the company", + tags: ["Safe"], method: "get", - path: "/v1/{companyId}/stakeholders", + path: "/v1/{companyId}/safes", middleware: [authMiddleware()], request: { - query: PaginationQuerySchema, params: ParamsSchema, }, responses: { @@ -46,30 +40,26 @@ export const getMany = withAuthApiV1 schema: ResponseSchema, }, }, - description: "A list of stakeholders in a company with their details.", + description: "Safe details", }, }, }) .handler(async (c) => { - const { membership } = c.get("session"); const { db } = c.get("services"); - const query = c.req.valid("query"); + const { companyId } = c.req.valid("param"); - const [data, meta] = await db.stakeholder - .paginate({ where: { companyId: membership.companyId } }) - .withCursor({ - limit: query.limit, - after: query.cursor, - }); + const safes = (await db.safe.findMany({ + where: { + companyId, + }, + })) as ApiSafeType[]; - const response: z.infer = { - data: data.map((stakeholder) => ({ - ...stakeholder, - createdAt: stakeholder.createdAt.toISOString(), - updatedAt: stakeholder.updatedAt.toISOString(), - })), - meta, - }; + if (!safes) { + throw new ApiError({ + code: "NOT_FOUND", + message: "No safe with the provided Id", + }); + } - return c.json(response, 200); + return c.json({ data: safes }, 200); }); From 49250572ba58f041086178dd49c00ee948a15682 Mon Sep 17 00:00:00 2001 From: Puru D Date: Thu, 8 Aug 2024 03:18:25 -0500 Subject: [PATCH 36/60] chore: minor cleanups --- prisma/schema.prisma | 7 +- src/components/common/modal.tsx | 1 + src/server/api/routes/company/getMany.ts | 6 +- src/server/api/routes/safe/create.ts | 8 +- src/server/api/routes/safe/delete.ts | 6 +- src/server/api/routes/safe/getMany.ts | 6 +- src/server/api/routes/safe/getOne.ts | 8 +- src/server/api/routes/safe/index.ts | 6 +- src/server/api/routes/safe/update.ts | 2 +- src/server/api/schema/safe.ts | 140 ++++++++++++----------- 10 files changed, 102 insertions(+), 88 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 26e648bd0..6ced16d16 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -189,6 +189,7 @@ model BankAccount { companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) + safes Safe[] @@unique([companyId, accountNumber]) @@unique([companyId, primary], name: "unique_primary_account") @@ -829,7 +830,7 @@ enum SafeTemplateEnum { model Safe { id String @id @default(cuid()) - publicId String // eg. SAFE-01 + publicId String? // eg. SAFE-01 type SafeTypeEnum @default(POST_MONEY) status SafeStatusEnum @default(DRAFT) capital Float // Amount of money invested @@ -850,6 +851,9 @@ model Safe { companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) + bankAccountId String + bankAccount BankAccount @relation(fields: [bankAccountId], references: [id], onDelete: Cascade) + issueDate DateTime boardApprovalDate DateTime? createdAt DateTime @default(now()) @@ -858,6 +862,7 @@ model Safe { @@unique([publicId, companyId]) @@index([companyId]) @@index([stakeholderId]) + @@index([bankAccountId]) } enum ConvertibleStatusEnum { diff --git a/src/components/common/modal.tsx b/src/components/common/modal.tsx index 906e20f2b..c52d7c872 100644 --- a/src/components/common/modal.tsx +++ b/src/components/common/modal.tsx @@ -22,6 +22,7 @@ const sizes = { "3xl": "max-w-3xl", "4xl": "max-w-4xl", screen: "max-w-[96vw]", + "5xl": "max-w-5xl", }; export type ModalSizeType = keyof typeof sizes; diff --git a/src/server/api/routes/company/getMany.ts b/src/server/api/routes/company/getMany.ts index d025b41e9..bcd668dd7 100644 --- a/src/server/api/routes/company/getMany.ts +++ b/src/server/api/routes/company/getMany.ts @@ -14,7 +14,9 @@ export const getMany = withAuthApiV1 200: { content: { "application/json": { - schema: z.array(CompanySchema), + schema: z.object({ + data: z.array(CompanySchema), + }), }, }, description: "A list of companies with their details.", @@ -36,5 +38,5 @@ export const getMany = withAuthApiV1 }, }); - return c.json(companies, 200); + return c.json({ data: companies }, 200); }); diff --git a/src/server/api/routes/safe/create.ts b/src/server/api/routes/safe/create.ts index 8c01bed0a..42cb1b0d5 100644 --- a/src/server/api/routes/safe/create.ts +++ b/src/server/api/routes/safe/create.ts @@ -28,10 +28,10 @@ const ParamsSchema = z.object({ export const create = withAuthApiV1 .createRoute({ method: "post", - path: "/v1/{companyId}/stakeholders", - summary: "Create stakeholders", - description: "Add one or more stakeholder accounts to a company.", - tags: ["Stakeholder"], + path: "/v1/{companyId}/safes", + summary: "Create SAFEs", + description: "Add one or more SAFEs to a company.", + tags: ["SAFEs"], middleware: [authMiddleware()], request: { params: ParamsSchema, diff --git a/src/server/api/routes/safe/delete.ts b/src/server/api/routes/safe/delete.ts index 8b88ed8ec..6cb50abf8 100644 --- a/src/server/api/routes/safe/delete.ts +++ b/src/server/api/routes/safe/delete.ts @@ -33,9 +33,9 @@ const ResponseSchema = z.object({ export const _delete = withAuthApiV1 .createRoute({ - summary: "Delete a safe", - description: "Remove a safe from a company by ID.", - tags: ["Safe"], + summary: "Delete a SAFE", + description: "Remove a SAFE from a company by ID.", + tags: ["SAFEs"], method: "delete", path: "/v1/{companyId}/safes/{id}", middleware: [authMiddleware()], diff --git a/src/server/api/routes/safe/getMany.ts b/src/server/api/routes/safe/getMany.ts index 94766717d..53facb455 100644 --- a/src/server/api/routes/safe/getMany.ts +++ b/src/server/api/routes/safe/getMany.ts @@ -24,9 +24,9 @@ const ResponseSchema = z.object({ export const getMany = withAuthApiV1 .createRoute({ - summary: "List safes", - description: "List all safes in the company", - tags: ["Safe"], + summary: "List SAFEs", + description: "List all SAFEs in the company", + tags: ["SAFEs"], method: "get", path: "/v1/{companyId}/safes", middleware: [authMiddleware()], diff --git a/src/server/api/routes/safe/getOne.ts b/src/server/api/routes/safe/getOne.ts index ce62eed1a..6cdfb8a04 100644 --- a/src/server/api/routes/safe/getOne.ts +++ b/src/server/api/routes/safe/getOne.ts @@ -33,9 +33,9 @@ const ResponseSchema = z.object({ export const getOne = withAuthApiV1 .createRoute({ - summary: "Get a safe", - description: "Get a safe by ID", - tags: ["Safe"], + summary: "Get a SAFE", + description: "Get a SAFE by ID", + tags: ["SAFEs"], method: "get", path: "/v1/{companyId}/safes/{id}", middleware: [authMiddleware()], @@ -68,7 +68,7 @@ export const getOne = withAuthApiV1 if (!safe) { throw new ApiError({ code: "NOT_FOUND", - message: "No safe with the provided Id", + message: `SAFE with id ${id} could not be found`, }); } diff --git a/src/server/api/routes/safe/index.ts b/src/server/api/routes/safe/index.ts index d54fd6044..bbbb48d9c 100644 --- a/src/server/api/routes/safe/index.ts +++ b/src/server/api/routes/safe/index.ts @@ -7,8 +7,8 @@ import { update } from "./update"; export const registerSafeRoutes = (api: PublicAPI) => { api.openapi(getOne.route, getOne.handler); - api.openapi(update.route, update.handler); - api.openapi(_delete.route, _delete.handler); - api.openapi(create.route, create.handler); + // api.openapi(update.route, update.handler); + // api.openapi(_delete.route, _delete.handler); + // api.openapi(create.route, create.handler); api.openapi(getMany.route, getMany.handler); }; diff --git a/src/server/api/routes/safe/update.ts b/src/server/api/routes/safe/update.ts index b3a74614c..5438f2a10 100644 --- a/src/server/api/routes/safe/update.ts +++ b/src/server/api/routes/safe/update.ts @@ -41,7 +41,7 @@ export const update = withAuthApiV1 .createRoute({ summary: "Update a stakeholder", description: "Modify the details of a stakeholder by their ID.", - tags: ["Stakeholder"], + tags: ["SAFEs"], method: "patch", path: "/v1/{companyId}/stakeholders/{id}", middleware: [authMiddleware()], diff --git a/src/server/api/schema/safe.ts b/src/server/api/schema/safe.ts index 63f971655..12cd7128f 100644 --- a/src/server/api/schema/safe.ts +++ b/src/server/api/schema/safe.ts @@ -4,73 +4,79 @@ import { SafeStatusEnum, SafeTypeEnum } from "@prisma/client"; const types = z.nativeEnum(SafeTypeEnum); const statuses = z.nativeEnum(SafeStatusEnum); -export const ApiSafeSchema = z.object({ - id: z.string().cuid().openapi({ - description: "SAFE ID", - example: "cly402ncj0000i7ng9l0v04qr", - }), - externalId: z.string().optional().openapi({ - description: "External ID of the safe", - example: "1234567890", - }), - type: types.openapi({ - description: "Type of the safe", - example: "POST_MONEY", - }), - status: statuses.openapi({ - description: "Status of the safe", - example: "ACTIVE", - }), - capital: z.number().openapi({ - description: "SAFE investment capital", - example: 10000, - }), - valuationCap: z.number().optional().openapi({ - description: "Valuation cap of the safe", - example: 100000000, - }), - discountRate: z.number().optional().openapi({ - description: "Discount rate in percentage", - example: 5, - }), - mfn: z.boolean().optional().openapi({ - description: "Most favoured nation", - example: true, - }), - proRata: z.boolean().optional().openapi({ - description: "Pro rata rights", - example: true, - }), - stakeholderId: z.string().cuid().openapi({ - description: "Stakeholder / Investor ID", - example: "clzkv7w5c0000f1ngf3yq2s45", - }), - documents: z - .array(z.string().url()) - .optional() - .openapi({ - description: "Secure links to SAFE documents", - example: [ - "https://docs.captable.inc/document1.pdf", - "https://docs.captable.inc/document2.pdf", - ], +export const ApiSafeSchema = z + .object({ + id: z.string().cuid().openapi({ + description: "SAFE ID", + example: "cly402ncj0000i7ng9l0v04qr", }), - companyId: z.string().cuid().openapi({ - description: "Company ID", - example: "clzkvdnhj0000f1ngf3fxakwu", - }), - issueDate: z.date().optional().openapi({ - description: "Date of issue", - example: "2021-01-01", - }), - boardApprovalDate: z.date().optional().openapi({ - description: "Date of board approval", - example: "2021-01-01", - }), - createdAt: z.date().openapi({ - description: "Created at timestamp", - example: "2021-01-01", - }), -}); + publicId: z.string().optional().openapi({ + description: "External ID of the safe", + example: "1234567890", + }), + type: types.openapi({ + description: "Type of the safe", + example: "POST_MONEY", + }), + status: statuses.openapi({ + description: "Status of the safe", + example: "ACTIVE", + }), + capital: z.number().openapi({ + description: "SAFE investment capital", + example: 10000, + }), + valuationCap: z.number().optional().openapi({ + description: "Valuation cap of the safe", + example: 100000000, + }), + discountRate: z.number().optional().openapi({ + description: "Discount rate in percentage", + example: 5, + }), + mfn: z.boolean().optional().openapi({ + description: "Most favoured nation", + example: true, + }), + proRata: z.boolean().optional().openapi({ + description: "Pro rata rights", + example: true, + }), + stakeholderId: z.string().cuid().openapi({ + description: "Stakeholder / Investor ID", + example: "clzkv7w5c0000f1ngf3yq2s45", + }), + documents: z + .array(z.string().url()) + .optional() + .openapi({ + description: "Secure links to SAFE documents", + example: [ + "https://docs.captable.inc/document1.pdf", + "https://docs.captable.inc/document2.pdf", + ], + }), + companyId: z.string().cuid().openapi({ + description: "Company ID", + example: "clzkvdnhj0000f1ngf3fxakwu", + }), + bankAccountId: z.string().cuid().optional().openapi({ + description: "Bank account ID to receive funds", + example: "clzkv7w5c0000f1ngf3yq2s45", + }), + issueDate: z.date().optional().openapi({ + description: "Date of issue", + example: "2021-01-01", + }), + boardApprovalDate: z.date().optional().openapi({ + description: "Date of board approval", + example: "2021-01-01", + }), + createdAt: z.date().openapi({ + description: "Created at timestamp", + example: "2021-01-01", + }), + }) + .openapi("SAFE"); export type ApiSafeType = z.infer; From 9bf060daa2832b637395b91c4e6dba845f9684d1 Mon Sep 17 00:00:00 2001 From: Puru D Date: Tue, 13 Aug 2024 23:30:57 -0500 Subject: [PATCH 37/60] WIP --- package.json | 2 +- pnpm-lock.yaml | 145 ++-- .../migration.sql | 12 + .../fundraise/safes/preview/page.tsx | 36 + src/components/safe/document.tsx | 809 ----------------- src/components/safe/form/index.tsx | 12 +- src/components/safe/templates/index.tsx | 10 + .../safe/templates/post-money/cap.tsx | 818 ++++++++++++++++- .../safe/templates/post-money/discount.tsx | 821 +++++++++++++++++- src/lib/format.ts | 7 + 10 files changed, 1789 insertions(+), 883 deletions(-) create mode 100644 prisma/migrations/20240905205102_missing_migration/migration.sql create mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/preview/page.tsx delete mode 100644 src/components/safe/document.tsx diff --git a/package.json b/package.json index e75f9d3fc..fe917a23f 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "lodash-es": "^4.17.21", "mime": "^4.0.3", "nanoid": "^5.0.4", - "next": "^14.2.4", + "next": "^14.2.5", "next-auth": "^4.24.7", "next-nprogress-bar": "^2.3.11", "nodemailer": "^6.9.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3183bacd..b2c3195c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,7 +34,7 @@ importers: version: 3.9.0(react-hook-form@7.52.1(react@18.3.1)) '@next-auth/prisma-adapter': specifier: ^1.0.7 - version: 1.0.7(@prisma/client@5.14.0(prisma@5.14.0))(next-auth@4.24.7(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1)) + version: 1.0.7(@prisma/client@5.14.0(prisma@5.14.0))(next-auth@4.24.7(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1)) '@prisma/client': specifier: ^5.13.0 version: 5.14.0(prisma@5.14.0) @@ -106,7 +106,7 @@ importers: version: 0.4.18(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(postcss@8.4.40)(react-dom@18.2.0(react@18.3.1))(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) '@sentry/nextjs': specifier: ^8.19.0 - version: 8.19.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.92.0) + version: 8.19.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.92.0) '@simplewebauthn/browser': specifier: ^10.0.0 version: 10.0.0 @@ -136,7 +136,7 @@ importers: version: 10.45.2(@trpc/server@10.45.2) '@trpc/next': specifier: ^10.45.2 - version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@10.45.2)(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@10.45.2)(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@trpc/react-query': specifier: ^10.43.6 version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) @@ -192,11 +192,11 @@ importers: specifier: ^5.0.4 version: 5.0.7 next: - specifier: ^14.2.4 - version: 14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + specifier: ^14.2.5 + version: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) next-auth: specifier: ^4.24.7 - version: 4.24.7(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + version: 4.24.7(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) next-nprogress-bar: specifier: ^2.3.11 version: 2.3.12 @@ -1563,8 +1563,8 @@ packages: '@next/env@14.1.4': resolution: {integrity: sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ==} - '@next/env@14.2.4': - resolution: {integrity: sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==} + '@next/env@14.2.5': + resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} '@next/swc-darwin-arm64@14.1.4': resolution: {integrity: sha512-ubmUkbmW65nIAOmoxT1IROZdmmJMmdYvXIe8211send9ZYJu+SqxSnJM4TrPj9wmL6g9Atvj0S/2cFmMSS99jg==} @@ -1572,8 +1572,8 @@ packages: cpu: [arm64] os: [darwin] - '@next/swc-darwin-arm64@14.2.4': - resolution: {integrity: sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==} + '@next/swc-darwin-arm64@14.2.5': + resolution: {integrity: sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1584,8 +1584,8 @@ packages: cpu: [x64] os: [darwin] - '@next/swc-darwin-x64@14.2.4': - resolution: {integrity: sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==} + '@next/swc-darwin-x64@14.2.5': + resolution: {integrity: sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1596,8 +1596,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-gnu@14.2.4': - resolution: {integrity: sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==} + '@next/swc-linux-arm64-gnu@14.2.5': + resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1608,8 +1608,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.4': - resolution: {integrity: sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==} + '@next/swc-linux-arm64-musl@14.2.5': + resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1620,8 +1620,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.4': - resolution: {integrity: sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==} + '@next/swc-linux-x64-gnu@14.2.5': + resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1632,8 +1632,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.4': - resolution: {integrity: sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==} + '@next/swc-linux-x64-musl@14.2.5': + resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1644,8 +1644,8 @@ packages: cpu: [arm64] os: [win32] - '@next/swc-win32-arm64-msvc@14.2.4': - resolution: {integrity: sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==} + '@next/swc-win32-arm64-msvc@14.2.5': + resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1656,8 +1656,8 @@ packages: cpu: [ia32] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.4': - resolution: {integrity: sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==} + '@next/swc-win32-ia32-msvc@14.2.5': + resolution: {integrity: sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -1668,8 +1668,8 @@ packages: cpu: [x64] os: [win32] - '@next/swc-win32-x64-msvc@14.2.4': - resolution: {integrity: sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==} + '@next/swc-win32-x64-msvc@14.2.5': + resolution: {integrity: sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -5017,6 +5017,15 @@ packages: supports-color: optional: true + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} @@ -5750,6 +5759,10 @@ packages: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -6616,8 +6629,8 @@ packages: sass: optional: true - next@14.2.4: - resolution: {integrity: sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==} + next@14.2.5: + resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -9720,7 +9733,7 @@ snapshots: '@eslint/config-array@0.17.1': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.4 + debug: 4.3.6 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -9728,10 +9741,10 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.3.6 espree: 10.1.0 globals: 14.0.0 - ignore: 5.3.1 + ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -10077,10 +10090,10 @@ snapshots: - supports-color optional: true - '@next-auth/prisma-adapter@1.0.7(@prisma/client@5.14.0(prisma@5.14.0))(next-auth@4.24.7(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))': + '@next-auth/prisma-adapter@1.0.7(@prisma/client@5.14.0(prisma@5.14.0))(next-auth@4.24.7(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))': dependencies: '@prisma/client': 5.14.0(prisma@5.14.0) - next-auth: 4.24.7(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + next-auth: 4.24.7(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@next/bundle-analyzer@14.2.3': dependencies: @@ -10091,60 +10104,60 @@ snapshots: '@next/env@14.1.4': {} - '@next/env@14.2.4': {} + '@next/env@14.2.5': {} '@next/swc-darwin-arm64@14.1.4': optional: true - '@next/swc-darwin-arm64@14.2.4': + '@next/swc-darwin-arm64@14.2.5': optional: true '@next/swc-darwin-x64@14.1.4': optional: true - '@next/swc-darwin-x64@14.2.4': + '@next/swc-darwin-x64@14.2.5': optional: true '@next/swc-linux-arm64-gnu@14.1.4': optional: true - '@next/swc-linux-arm64-gnu@14.2.4': + '@next/swc-linux-arm64-gnu@14.2.5': optional: true '@next/swc-linux-arm64-musl@14.1.4': optional: true - '@next/swc-linux-arm64-musl@14.2.4': + '@next/swc-linux-arm64-musl@14.2.5': optional: true '@next/swc-linux-x64-gnu@14.1.4': optional: true - '@next/swc-linux-x64-gnu@14.2.4': + '@next/swc-linux-x64-gnu@14.2.5': optional: true '@next/swc-linux-x64-musl@14.1.4': optional: true - '@next/swc-linux-x64-musl@14.2.4': + '@next/swc-linux-x64-musl@14.2.5': optional: true '@next/swc-win32-arm64-msvc@14.1.4': optional: true - '@next/swc-win32-arm64-msvc@14.2.4': + '@next/swc-win32-arm64-msvc@14.2.5': optional: true '@next/swc-win32-ia32-msvc@14.1.4': optional: true - '@next/swc-win32-ia32-msvc@14.2.4': + '@next/swc-win32-ia32-msvc@14.2.5': optional: true '@next/swc-win32-x64-msvc@14.1.4': optional: true - '@next/swc-win32-x64-msvc@14.2.4': + '@next/swc-win32-x64-msvc@14.2.5': optional: true '@nodelib/fs.scandir@2.1.5': @@ -12079,7 +12092,7 @@ snapshots: '@scalar/nextjs-api-reference@0.4.18(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(postcss@8.4.40)(react-dom@18.2.0(react@18.3.1))(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: '@scalar/api-reference': 1.24.20(postcss@8.4.40)(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - next: 14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + next: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) react: 18.3.1 transitivePeerDependencies: - '@babel/core' @@ -12303,7 +12316,7 @@ snapshots: '@sentry/types': 8.19.0 '@sentry/utils': 8.19.0 - '@sentry/nextjs@8.19.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.92.0)': + '@sentry/nextjs@8.19.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.92.0)': dependencies: '@opentelemetry/instrumentation-http': 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.25.1 @@ -12317,7 +12330,7 @@ snapshots: '@sentry/vercel-edge': 8.19.0 '@sentry/webpack-plugin': 2.20.1(webpack@5.92.0) chalk: 3.0.0 - next: 14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + next: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) resolve: 1.22.8 rollup: 3.29.4 stacktrace-parser: 0.1.10 @@ -13149,13 +13162,13 @@ snapshots: dependencies: '@trpc/server': 10.45.2 - '@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@10.45.2)(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': + '@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@10.45.2)(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@trpc/client': 10.45.2(@trpc/server@10.45.2) '@trpc/react-query': 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@trpc/server': 10.45.2 - next: 14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + next: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(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) @@ -14633,6 +14646,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.6: + dependencies: + ms: 2.1.2 + decimal.js-light@2.5.1: {} decode-named-character-reference@1.0.2: @@ -14930,7 +14947,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.6 escape-string-regexp: 4.0.0 eslint-scope: 8.0.2 eslint-visitor-keys: 4.0.0 @@ -14941,7 +14958,7 @@ snapshots: file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - ignore: 5.3.1 + ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -15586,6 +15603,8 @@ snapshots: ignore@5.3.1: {} + ignore@5.3.2: {} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -16723,13 +16742,13 @@ snapshots: neo-async@2.6.2: {} - next-auth@4.24.7(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1): + next-auth@4.24.7(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.24.5 '@panva/hkdf': 1.1.1 cookie: 0.5.0 jose: 4.15.5 - next: 14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + next: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) oauth: 0.9.15 openid-client: 5.6.5 preact: 10.22.0 @@ -16770,9 +16789,9 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1): + next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 14.2.4 + '@next/env': 14.2.5 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001620 @@ -16782,15 +16801,15 @@ snapshots: react-dom: 18.2.0(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.24.5)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.4 - '@next/swc-darwin-x64': 14.2.4 - '@next/swc-linux-arm64-gnu': 14.2.4 - '@next/swc-linux-arm64-musl': 14.2.4 - '@next/swc-linux-x64-gnu': 14.2.4 - '@next/swc-linux-x64-musl': 14.2.4 - '@next/swc-win32-arm64-msvc': 14.2.4 - '@next/swc-win32-ia32-msvc': 14.2.4 - '@next/swc-win32-x64-msvc': 14.2.4 + '@next/swc-darwin-arm64': 14.2.5 + '@next/swc-darwin-x64': 14.2.5 + '@next/swc-linux-arm64-gnu': 14.2.5 + '@next/swc-linux-arm64-musl': 14.2.5 + '@next/swc-linux-x64-gnu': 14.2.5 + '@next/swc-linux-x64-musl': 14.2.5 + '@next/swc-win32-arm64-msvc': 14.2.5 + '@next/swc-win32-ia32-msvc': 14.2.5 + '@next/swc-win32-x64-msvc': 14.2.5 '@opentelemetry/api': 1.9.0 transitivePeerDependencies: - '@babel/core' diff --git a/prisma/migrations/20240905205102_missing_migration/migration.sql b/prisma/migrations/20240905205102_missing_migration/migration.sql new file mode 100644 index 000000000..948343da5 --- /dev/null +++ b/prisma/migrations/20240905205102_missing_migration/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - Added the required column `bankAccountId` to the `Safe` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Safe" ADD COLUMN "bankAccountId" TEXT NOT NULL, +ALTER COLUMN "publicId" DROP NOT NULL; + +-- CreateIndex +CREATE INDEX "Safe_bankAccountId_idx" ON "Safe"("bankAccountId"); diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/preview/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/preview/page.tsx new file mode 100644 index 000000000..5c254c619 --- /dev/null +++ b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/preview/page.tsx @@ -0,0 +1,36 @@ +"use client"; +import { PostMoneyCap, PostMoneyDiscount } from "@/components/safe/templates"; +import { PDFViewer } from "@react-pdf/renderer"; + +const SafePreview = () => { + return ( + + + + ); +}; + +export default SafePreview; diff --git a/src/components/safe/document.tsx b/src/components/safe/document.tsx deleted file mode 100644 index a5c207fd0..000000000 --- a/src/components/safe/document.tsx +++ /dev/null @@ -1,809 +0,0 @@ -"use client"; - -import { Indent, type SafeProps, style } from "@/components/safe/templates"; -import { formatDate, formatUsd } from "@/lib/format"; -import { Document, Link, Page, Text, View } from "@react-pdf/renderer"; - -const SafeDocument = ({ - investor, - investment, - valuation, - date, - company, -}: SafeProps) => { - return ( - - - Version 1.2 - - POST-MONEY VALUATION CAP - - - THIS INSTRUMENT AND ANY SECURITIES ISSUABLE PURSUANT HERETO HAVE NOT - BEEN REGISTERED UNDER THE SECURITIES ACT OF 1933, AS AMENDED (THE “ - SECURITIES ACT ”), OR UNDER THE SECURITIES LAWS OF CERTAIN STATES. - THESE SECURITIES MAY NOT BE OFFERED, SOLD OR OTHERWISE TRANSFERRED, - PLEDGED OR HYPOTHECATED EXCEPT AS PERMITTED IN THIS SAFE AND UNDER THE - ACT AND APPLICABLE STATE SECURITIES LAWS PURSUANT TO AN EFFECTIVE - REGISTRATION STATEMENT OR AN EXEMPTION THEREFROM. - - - - Captable, Inc. - - - SAFE - - - (Simple Agreement for Future Equity) - - - - - THIS CERTIFIES THAT in exchange for the payment by{" "} - {investor.name} (the - “Investor”) of{" "} - {formatUsd(investment)} (the - “Purchase Amount”) on or about{" "} - {formatDate(date)},{" "} - {company.name}, a{" "} - {company.state} corporation - (the “Company”), issues to the Investor the right to certain shares - of the Company’s Capital Stock, subject to the terms described - below. - - - - - - This Safe is one of the forms available at{" "} - - http://ycombinator.com/documents - {" "} - and the Company and the Investor agree that neither one has modified - the form, except to fill in blanks and bracketed terms. - - - - {valuation && ( - - - The “Post-Money Valuation Cap” is{" "} - {formatUsd(valuation)}. See - Section 2 for certain additional defined terms. - - - )} - - - 1. Events - - - - - (a){" "} - - Equity Financing - - . If there is an Equity Financing before the termination of this - Safe, on the initial closing of such Equity Financing, this Safe - will automatically convert into the greater of: (1) the number of - shares of Standard Preferred Stock equal to the Purchase Amount - divided by the lowest price per share of the Standard Preferred - Stock; or (2) the number of shares of Safe Preferred Stock equal to - the Purchase Amount divided by the Safe Price. - - - - - - In connection with the automatic conversion of this Safe into shares - of Standard Preferred Stock or Safe Preferred Stock, the Investor - will execute and deliver to the Company all of the transaction - documents related to the Equity Financing; provided, that such - documents (i) are the same documents to be entered into with the - purchasers of Standard Preferred Stock, with appropriate variations - for the Safe Preferred Stock if applicable, and (ii) have customary - exceptions to any drag-along applicable to the Investor, including - (without limitation) limited representations, warranties, liability - and indemnification obligations for the Investor. - - - - - - (b){" "} - Liquidity Event - . If there is a Liquidity Event before the termination of this Safe, - the Investor will automatically be entitled (subject to the - liquidation priority set forth in Section 1(d) below) to receive a - portion of Proceeds, due and payable to the Investor immediately - prior to, or concurrent with, the consummation of such Liquidity - Event, equal to the greater of (i) the Purchase Amount (the - “Cash-Out Amount”) or (ii) the amount payable on the number of - shares of Common Stock equal to the Purchase Amount divided by the - Liquidity Price (the “Conversion Amount”). If any of the Company’s - securityholders are given a choice as to the form and amount of - Proceeds to be received in a Liquidity Event, the Investor will be - given the same choice, provided that the Investor may not choose to - receive a form of consideration that the Investor would be - ineligible to receive as a result of the Investor’s failure to - satisfy any requirement or limitation generally applicable to the - Company’s securityholders, or under any applicable laws. - - - - - - Notwithstanding the foregoing, in connection with a Change of - Control intended to qualify as a tax-free reorganization, the - Company may reduce the cash portion of Proceeds payable to the - Investor by the amount determined by its board of directors in good - faith for such Change of Control to qualify as a tax-free - reorganization for U.S. federal income tax purposes, provided that - such reduction (A) does not reduce the total Proceeds payable to - such Investor and (B) is applied in the same manner and on a pro - rata basis to all securityholders who have equal priority to the - Investor under Section 1(d). - - - - - - (c){" "} - - Dissolution Event - - . If there is a Dissolution Event before the termination of this - Safe, the Investor will automatically be entitled (subject to the - liquidation priority set forth in Section 1(d) below) to receive a - portion of Proceeds equal to the Cash-Out Amount, due and payable to - the Investor immediately prior to the consummation of the - Dissolution Event. - - - - - - (d){" "} - - Liquidation Priority - - . In a Liquidity Event or Dissolution Event, this Safe is intended - to operate like standard non-participating Preferred Stock. The - Investor’s right to receive its Cash-Out Amount is: - - - - - - (i) {" "} Junior to payment of outstanding indebtedness and - creditor claims, including contractual claims for payment and - convertible promissory notes (to the extent such convertible - promissory notes are not actually or notionally converted into - Capital Stock); - - - - - - (ii) {" "} On par with payments for other Safes and/or Preferred - Stock, and if the applicable Proceeds are insufficient to permit - full payments to the Investor and such other Safes and/or Preferred - Stock, the applicable Proceeds will be distributed pro rata to the - Investor and such other Safes and/or Preferred Stock in proportion - to the full payments that would otherwise be due; and - - - - - - (iii) {" "} Senior to payments for Common Stock. - - - - - - The Investor’s right to receive its Conversion Amount is (A) on par - with payments for Common Stock and other Safes and/or Preferred - Stock who are also receiving Conversion Amounts or Proceeds on a - similar as-converted to Common Stock basis, and (B) junior to - payments described in clauses (i) and (ii) above (in the latter - case, to the extent such payments are Cash-Out Amounts or similar - liquidation preferences). - - - - - - (e) Termination - . This Safe will automatically terminate (without relieving the - Company of any obligations arising from a prior breach of or - non-compliance with this Safe) immediately following the earliest to - occur of: (i) the issuance of Capital Stock to the Investor pursuant - to the automatic conversion of this Safe under Section 1(a); or (ii) - the payment, or setting aside for payment, of amounts due the - Investor pursuant to Section 1(b) or Section 1(c). - - - - - 2. Definitions - - - - - “Capital Stock” means the capital stock of the Company, including, - without limitation, the “Common Stock” and the “Preferred Stock.” - - - - - - “Change of Control” means (i) a transaction or series of related - transactions in which any “person” or “group” (within the meaning of - Section 13(d) and 14(d) of the Securities Exchange Act of 1934, as - amended), becomes the “beneficial owner” (as defined in Rule 13d-3 - under the Securities Exchange Act of 1934, as amended), directly or - indirectly, of more than 50% of the outstanding voting securities of - the Company having the right to vote for the election of members of - the Company’s board of directors, (ii) any reorganization, merger or - consolidation of the Company, other than a transaction or series of - related transactions in which the holders of the voting securities - of the Company outstanding immediately prior to such transaction or - series of related transactions retain, immediately after such - transaction or series of related transactions, at least a majority - of the total voting power represented by the outstanding voting - securities of the Company or such other surviving or resulting - entity or (iii) a sale, lease or other disposition of all or - substantially all of the assets of the Company. - - - - - - “Company Capitalization” is calculated as of immediately prior to - the Equity Financing and (without double-counting, in each case - calculated on an as-converted to Common Stock basis): - - - - {/* List */} - - - - - Includes all shares of Capital Stock issued and outstanding; - - - - - Includes all Converting Securities; - - - - - Includes all (i) issued and outstanding Options and (ii) Promised - Options; and - - - - - - Includes the Unissued Option Pool, except that any increase to the - Unissued Option Pool in connection with the Equity Financing will - only be included to the extent that the number of Promised Options - exceeds the Unissued Option Pool prior to such increase. - - - - - - - “Converting Securities” includes this Safe and other convertible - securities issued by the Company, including but not limited to: (i) - other Safes; (ii) convertible promissory notes and other convertible - debt instruments; and (iii) convertible securities that have the - right to convert into shares of Capital Stock. - - - - - - “Direct Listing” means the Company’s initial listing of its Common - Stock (other than shares of Common Stock not eligible for resale - under Rule 144 under the Securities Act) on a national securities - exchange by means of an effective registration statement on Form S-1 - filed by the Company with the SEC that registers shares of existing - capital stock of the Company for resale, as approved by the - Company’s board of directors. For the avoidance of doubt, a Direct - Listing will not be deemed to be an underwritten offering and will - not involve any underwriting services. - - - - - - “Dissolution Event” means (i) a voluntary termination of operations, - (ii) a general assignment for the benefit of the Company’s creditors - or (iii) any other liquidation, dissolution or winding up of the - Company (excluding a Liquidity Event), whether voluntary or - involuntary. - - - - - - “Dividend Amount” means, with respect to any date on which the - Company pays a dividend on its outstanding Common Stock, the amount - of such dividend that is paid per share of Common Stock multiplied - by (x) the Purchase Amount divided by (y) the Liquidity Price - (treating the dividend date as a Liquidity Event solely for purposes - of calculating such Liquidity Price). - - - - - - “Equity Financing” means a bona fide transaction or series of - transactions with the principal purpose of raising capital, pursuant - to which the Company issues and sells Preferred Stock at a fixed - valuation, including but not limited to, a pre-money or post-money - valuation. - - - - - - “Initial Public Offering” means the closing of the Company’s first - firm commitment underwritten initial public offering of Common Stock - pursuant to a registration statement filed under the Securities Act. - - - - - - “Liquidity Capitalization” is calculated as of immediately prior to - the Liquidity Event, and (without double- counting, in each case - calculated on an as-converted to Common Stock basis):{" "} - - - - - - - - Includes all shares of Capital Stock issued and outstanding; - - - - - - Includes all (i) issued and outstanding Options and (ii) to the - extent receiving Proceeds, Promised Options; - - - - - - Includes all Converting Securities, other than any Safes and other - convertible securities (including without limitation shares of - Preferred Stock) where the holders of such securities are - receiving Cash-Out Amounts or similar liquidation preference - payments in lieu of Conversion Amounts or similar “as-converted” - payments; and - - - - - Excludes the Unissued Option Pool. - - - - - - “Liquidity Event” means a Change of Control, a Direct Listing or an - Initial Public Offering. - - - - - - “Liquidity Price” means the price per share equal to the Post-Money - Valuation Cap divided by the Liquidity Capitalization. - - - - - - “Options” includes options, restricted stock awards or purchases, - RSUs, SARs, warrants or similar securities, vested or unvested. - - - - - - “Proceeds” means cash and other assets (including without limitation - stock consideration) that are proceeds from the Liquidity Event or - the Dissolution Event, as applicable, and legally available for - distribution.{" "} - - - - - - “Promised Options” means promised but ungranted Options that are the - greater of those (i) promised pursuant to agreements or - understandings made prior to the execution of, or in connection - with, the term sheet or letter of intent for the Equity Financing or - Liquidity Event, as applicable (or the initial closing of the Equity - Financing or consummation of the Liquidity Event, if there is no - term sheet or letter of intent), (ii) in the case of an Equity - Financing, treated as outstanding Options in the calculation of the - Standard Preferred Stock’s price per share, or (iii) in the case of - a Liquidity Event, treated as outstanding Options in the calculation - of the distribution of the Proceeds. - - - - - - “Safe” means an instrument containing a future right to shares of - Capital Stock, similar in form and content to this instrument, - purchased by investors for the purpose of funding the Company’s - business operations. References to “this Safe” mean this specific - instrument.{" "} - - - - - - “Safe Preferred Stock” means the shares of the series of Preferred - Stock issued to the Investor in an Equity Financing, having the - identical rights, privileges, preferences, seniority, liquidation - multiple and restrictions as the shares of Standard Preferred Stock, - except that any price-based preferences (such as the per share - liquidation amount, initial conversion price and per share dividend - amount) will be based on the Safe Price.{" "} - - - - - - “Safe Price” means the price per share equal to the Post-Money - Valuation Cap divided by the Company Capitalization. - - - - - - “Standard Preferred Stock” means the shares of the series of - Preferred Stock issued to the investors investing new money in the - Company in connection with the initial closing of the Equity - Financing. - - - - - - “Unissued Option Pool” means all shares of Capital Stock that are - reserved, available for future grant and not subject to any - outstanding Options or Promised Options (but in the case of a - Liquidity Event, only to the extent Proceeds are payable on such - Promised Options) under any equity incentive or similar Company - plan. - - - - - 3. Company Representations - - - - - (a) The Company is a corporation duly organized, validly existing - and in good standing under the laws of its state of incorporation, - and has the power and authority to own, lease and operate its - properties and carry on its business as now conducted. - - - - - - (b) The execution, delivery and performance by the Company of this - Safe is within the power of the Company and has been duly authorized - by all necessary actions on the part of the Company (subject to - section 3(d)). This Safe constitutes a legal, valid and binding - obligation of the Company, enforceable against the Company in - accordance with its terms, except as limited by bankruptcy, - insolvency or other laws of general application relating to or - affecting the enforcement of creditors’ rights generally and general - principles of equity. To its knowledge, the Company is not in - violation of (i) its current certificate of incorporation or bylaws, - (ii) any material statute, rule or regulation applicable to the - Company or (iii) any material debt or contract to which the Company - is a party or by which it is bound, where, in each case, such - violation or default, individually, or together with all such - violations or defaults, could reasonably be expected to have a - material adverse effect on the Company. - - - - - - (c) The performance and consummation of the transactions - contemplated by this Safe do not and will not: (i) violate any - material judgment, statute, rule or regulation applicable to the - Company; (ii) result in the acceleration of any material debt or - contract to which the Company is a party or by which it is bound; or - (iii) result in the creation or imposition of any lien on any - property, asset or revenue of the Company or the suspension, - forfeiture, or nonrenewal of any material permit, license or - authorization applicable to the Company, its business or operations. - - - - - - (d) No consents or approvals are required in connection with the - performance of this Safe, other than: (i) the Company’s corporate - approvals; (ii) any qualifications or filings under applicable - securities laws; and (iii) necessary corporate approvals for the - authorization of Capital Stock issuable pursuant to Section 1. - - - - - - (e) To its knowledge, the Company owns or possesses (or can obtain - on commercially reasonable terms) sufficient legal rights to all - patents, trademarks, service marks, trade names, copyrights, trade - secrets, licenses, information, processes and other intellectual - property rights necessary for its business as now conducted and as - currently proposed to be conducted, without any conflict with, or - infringement of the rights of, others. - - - - - 4. Investor Representations - - - - - (a) The Investor has full legal capacity, power and authority to - execute and deliver this Safe and to perform its obligations - hereunder. This Safe constitutes a valid and binding obligation of - the Investor, enforceable in accordance with its terms, except as - limited by bankruptcy, insolvency or other laws of general - application relating to or affecting the enforcement of creditors’ - rights generally and general principles of equity. - - - - - - (b) The Investor is an accredited investor as such term is defined - in Rule 501 of Regulation D under the Securities Act, and - acknowledges and agrees that if not an accredited investor at the - time of an Equity Financing, the Company may void this Safe and - return the Purchase Amount. The Investor has been advised that this - Safe and the underlying securities have not been registered under - the Securities Act, or any state securities laws and, therefore, - cannot be resold unless they are registered under the Securities Act - and applicable state securities laws or unless an exemption from - such registration requirements is available. The Investor is - purchasing this Safe and the securities to be acquired by the - Investor hereunder for its own account for investment, not as a - nominee or agent, and not with a view to, or for resale in - connection with, the distribution thereof, and the Investor has no - present intention of selling, granting any participation in, or - otherwise distributing the same. The Investor has such knowledge and - experience in financial and business matters that the Investor is - capable of evaluating the merits and risks of such investment, is - able to incur a complete loss of such investment without impairing - the Investor’s financial condition and is able to bear the economic - risk of such investment for an indefinite period of time.{" "} - - - - - 5. Miscellaneous - - - - - (a) Any provision of this Safe may be amended, waived or modified by - written consent of the Company and either (i) the Investor or (ii) - the majority-in-interest of all then-outstanding Safes with the same - “Post-Money Valuation Cap” and “Discount Rate” as this Safe (and - Safes lacking one or both of such terms will be considered to be the - same with respect to such term(s)), provided that with respect to - clause (ii): (A) the Purchase Amount may not be amended, waived or - modified in this manner, (B) the consent of the Investor and each - holder of such Safes must be solicited (even if not obtained), and - (C) such amendment, waiver or modification treats all such holders - in the same manner. “Majority-in-interest” refers to the holders of - the applicable group of Safes whose Safes have a total Purchase - Amount greater than 50% of the total Purchase Amount of all of such - applicable group of Safes. - - - - - - (b) Any notice required or permitted by this Safe will be deemed - sufficient when delivered personally or by overnight courier or sent - by email to the relevant address listed on the signature page, or 48 - hours after being deposited in the U.S. mail as certified or - registered mail with postage prepaid, addressed to the party to be - notified at such party’s address listed on the signature page, as - subsequently modified by written notice. - - - - - - (c) The Investor is not entitled, as a holder of this Safe, to vote - or be deemed a holder of Capital Stock for any purpose other than - tax purposes, nor will anything in this Safe be construed to confer - on the Investor, as such, any rights of a Company stockholder or - rights to vote for the election of directors or on any matter - submitted to Company stockholders, or to give or withhold consent to - any corporate action or to receive notice of meetings, until shares - have been issued on the terms described in Section 1. However, if - the Company pays a dividend on outstanding shares of Common Stock - (that is not payable in shares of Common Stock) while this Safe is - outstanding, the Company will pay the Dividend Amount to the - Investor at the same time. - - - - - - (d) Neither this Safe nor the rights in this Safe are transferable - or assignable, by operation of law or otherwise, by either party - without the prior written consent of the other; provided, however, - that this Safe and/or its rights may be assigned without the - Company’s consent by the Investor (i) to the Investor’s estate, - heirs, executors, administrators, guardians and/or successors in the - event of Investor’s death or disability, or (ii) to any other entity - who directly or indirectly, controls, is controlled by or is under - common control with the Investor, including, without limitation, any - general partner, managing member, officer or director of the - Investor, or any venture capital fund now or hereafter existing - which is controlled by one or more general partners or managing - members of, or shares the same management company with, the - Investor. - - - - - - (e) In the event any one or more of the provisions of this Safe is - for any reason held to be invalid, illegal or unenforceable, in - whole or in part or in any respect, or in the event that any one or - more of the provisions of this Safe operate or would prospectively - operate to invalidate this Safe, then and in any such event, such - provision(s) only will be deemed null and void and will not affect - any other provision of this Safe and the remaining provisions of - this Safe will remain operative and in full force and effect and - will not be affected, prejudiced, or disturbed thereby. - - - - - - (f) All rights and obligations hereunder will be governed by the - laws of the State of [Governing Law Jurisdiction], without regard to - the conflicts of law provisions of such jurisdiction. - - - - - - (g) The parties acknowledge and agree that for United States federal - and state income tax purposes this Safe is, and at all times has - been, intended to be characterized as stock, and more particularly - as common stock for purposes of Sections 304, 305, 306, 354, 368, - 1036 and 1202 of the Internal Revenue Code of 1986, as amended. - Accordingly, the parties agree to treat this Safe consistent with - the foregoing intent for all United States federal and state income - tax purposes (including, without limitation, on their respective tax - returns or other informational statements). - - - - - (Signature page follows) - - - - - - IN WITNESS WHEREOF, the undersigned have caused this Safe to be duly - executed and delivered. - - - - {company.name} - - By (signature): __________________________________ - - - - Name: ________________________________________ - - - - Title: _________________________________________ - - - Address: ______________________________________ - - - ______________________________________________ - - - Email: ________________________________________ - - - - - INVESTOR: - - By (signature): __________________________________ - - - - Name: ________________________________________ - - - - Title: _________________________________________ - - - Address: ______________________________________ - - - ______________________________________________ - - - Email: ________________________________________ - - - - - ); -}; - -export default SafeDocument; diff --git a/src/components/safe/form/index.tsx b/src/components/safe/form/index.tsx index 626afafb2..4afa4d0b7 100644 --- a/src/components/safe/form/index.tsx +++ b/src/components/safe/form/index.tsx @@ -24,7 +24,7 @@ import { z } from "zod"; import { PrePostSelector } from "./pre-post-selector"; // Safe document preview -import SafeDocument from "@/components/safe/document"; +import { PostMoneyCap, PostMoneyDiscount } from "@/components/safe/templates"; import { PDFViewer } from "@react-pdf/renderer"; type SafeFormProps = { @@ -326,7 +326,15 @@ export const SafeForm: React.FC = ({ type }) => { } > - { +"use client"; + +import { Indent, type SafeProps, style } from "@/components/safe/templates"; +import { formatDate, formatUsd } from "@/lib/format"; +import { Document, Link, Page, Text, View } from "@react-pdf/renderer"; + +const PostMoneyCap = ({ + investor, + investment, + valuation, + date, + company, + options, +}: SafeProps) => { + const { title, author, subject, keywords, creator, producer } = options; + return ( -
    -

    Post Money Valuation Cap

    -
    + + + Version 1.2 + + POST-MONEY VALUATION CAP + + + THIS INSTRUMENT AND ANY SECURITIES ISSUABLE PURSUANT HERETO HAVE NOT + BEEN REGISTERED UNDER THE SECURITIES ACT OF 1933, AS AMENDED (THE “ + SECURITIES ACT ”), OR UNDER THE SECURITIES LAWS OF CERTAIN STATES. + THESE SECURITIES MAY NOT BE OFFERED, SOLD OR OTHERWISE TRANSFERRED, + PLEDGED OR HYPOTHECATED EXCEPT AS PERMITTED IN THIS SAFE AND UNDER THE + ACT AND APPLICABLE STATE SECURITIES LAWS PURSUANT TO AN EFFECTIVE + REGISTRATION STATEMENT OR AN EXEMPTION THEREFROM. + + + + Captable, Inc. + + + SAFE + + + (Simple Agreement for Future Equity) + + + + + THIS CERTIFIES THAT in exchange for the payment by{" "} + {investor.name} (the + “Investor”) of{" "} + {formatUsd(investment)} (the + “Purchase Amount”) on or about{" "} + {formatDate(date)},{" "} + {company.name}, a{" "} + {company.state} corporation + (the “Company”), issues to the Investor the right to certain shares + of the Company’s Capital Stock, subject to the terms described + below. + + + + + + This Safe is one of the forms available at{" "} + + http://ycombinator.com/documents + {" "} + and the Company and the Investor agree that neither one has modified + the form, except to fill in blanks and bracketed terms. + + + + {valuation && ( + + + The “Post-Money Valuation Cap” is{" "} + {formatUsd(valuation)}. See + Section 2 for certain additional defined terms. + + + )} + + + 1. Events + + + + + (a){" "} + + Equity Financing + + . If there is an Equity Financing before the termination of this + Safe, on the initial closing of such Equity Financing, this Safe + will automatically convert into the greater of: (1) the number of + shares of Standard Preferred Stock equal to the Purchase Amount + divided by the lowest price per share of the Standard Preferred + Stock; or (2) the number of shares of Safe Preferred Stock equal to + the Purchase Amount divided by the Safe Price. + + + + + + In connection with the automatic conversion of this Safe into shares + of Standard Preferred Stock or Safe Preferred Stock, the Investor + will execute and deliver to the Company all of the transaction + documents related to the Equity Financing; provided, that such + documents (i) are the same documents to be entered into with the + purchasers of Standard Preferred Stock, with appropriate variations + for the Safe Preferred Stock if applicable, and (ii) have customary + exceptions to any drag-along applicable to the Investor, including + (without limitation) limited representations, warranties, liability + and indemnification obligations for the Investor. + + + + + + (b){" "} + Liquidity Event + . If there is a Liquidity Event before the termination of this Safe, + the Investor will automatically be entitled (subject to the + liquidation priority set forth in Section 1(d) below) to receive a + portion of Proceeds, due and payable to the Investor immediately + prior to, or concurrent with, the consummation of such Liquidity + Event, equal to the greater of (i) the Purchase Amount (the + “Cash-Out Amount”) or (ii) the amount payable on the number of + shares of Common Stock equal to the Purchase Amount divided by the + Liquidity Price (the “Conversion Amount”). If any of the Company’s + securityholders are given a choice as to the form and amount of + Proceeds to be received in a Liquidity Event, the Investor will be + given the same choice, provided that the Investor may not choose to + receive a form of consideration that the Investor would be + ineligible to receive as a result of the Investor’s failure to + satisfy any requirement or limitation generally applicable to the + Company’s securityholders, or under any applicable laws. + + + + + + Notwithstanding the foregoing, in connection with a Change of + Control intended to qualify as a tax-free reorganization, the + Company may reduce the cash portion of Proceeds payable to the + Investor by the amount determined by its board of directors in good + faith for such Change of Control to qualify as a tax-free + reorganization for U.S. federal income tax purposes, provided that + such reduction (A) does not reduce the total Proceeds payable to + such Investor and (B) is applied in the same manner and on a pro + rata basis to all securityholders who have equal priority to the + Investor under Section 1(d). + + + + + + (c){" "} + + Dissolution Event + + . If there is a Dissolution Event before the termination of this + Safe, the Investor will automatically be entitled (subject to the + liquidation priority set forth in Section 1(d) below) to receive a + portion of Proceeds equal to the Cash-Out Amount, due and payable to + the Investor immediately prior to the consummation of the + Dissolution Event. + + + + + + (d){" "} + + Liquidation Priority + + . In a Liquidity Event or Dissolution Event, this Safe is intended + to operate like standard non-participating Preferred Stock. The + Investor’s right to receive its Cash-Out Amount is: + + + + + + (i) {" "} Junior to payment of outstanding indebtedness and + creditor claims, including contractual claims for payment and + convertible promissory notes (to the extent such convertible + promissory notes are not actually or notionally converted into + Capital Stock); + + + + + + (ii) {" "} On par with payments for other Safes and/or Preferred + Stock, and if the applicable Proceeds are insufficient to permit + full payments to the Investor and such other Safes and/or Preferred + Stock, the applicable Proceeds will be distributed pro rata to the + Investor and such other Safes and/or Preferred Stock in proportion + to the full payments that would otherwise be due; and + + + + + + (iii) {" "} Senior to payments for Common Stock. + + + + + + The Investor’s right to receive its Conversion Amount is (A) on par + with payments for Common Stock and other Safes and/or Preferred + Stock who are also receiving Conversion Amounts or Proceeds on a + similar as-converted to Common Stock basis, and (B) junior to + payments described in clauses (i) and (ii) above (in the latter + case, to the extent such payments are Cash-Out Amounts or similar + liquidation preferences). + + + + + + (e) Termination + . This Safe will automatically terminate (without relieving the + Company of any obligations arising from a prior breach of or + non-compliance with this Safe) immediately following the earliest to + occur of: (i) the issuance of Capital Stock to the Investor pursuant + to the automatic conversion of this Safe under Section 1(a); or (ii) + the payment, or setting aside for payment, of amounts due the + Investor pursuant to Section 1(b) or Section 1(c). + + + + + 2. Definitions + + + + + “Capital Stock” means the capital stock of the Company, including, + without limitation, the “Common Stock” and the “Preferred Stock.” + + + + + + “Change of Control” means (i) a transaction or series of related + transactions in which any “person” or “group” (within the meaning of + Section 13(d) and 14(d) of the Securities Exchange Act of 1934, as + amended), becomes the “beneficial owner” (as defined in Rule 13d-3 + under the Securities Exchange Act of 1934, as amended), directly or + indirectly, of more than 50% of the outstanding voting securities of + the Company having the right to vote for the election of members of + the Company’s board of directors, (ii) any reorganization, merger or + consolidation of the Company, other than a transaction or series of + related transactions in which the holders of the voting securities + of the Company outstanding immediately prior to such transaction or + series of related transactions retain, immediately after such + transaction or series of related transactions, at least a majority + of the total voting power represented by the outstanding voting + securities of the Company or such other surviving or resulting + entity or (iii) a sale, lease or other disposition of all or + substantially all of the assets of the Company. + + + + + + “Company Capitalization” is calculated as of immediately prior to + the Equity Financing and (without double-counting, in each case + calculated on an as-converted to Common Stock basis): + + + + {/* List */} + + + + + Includes all shares of Capital Stock issued and outstanding; + + + + + Includes all Converting Securities; + + + + + Includes all (i) issued and outstanding Options and (ii) Promised + Options; and + + + + + + Includes the Unissued Option Pool, except that any increase to the + Unissued Option Pool in connection with the Equity Financing will + only be included to the extent that the number of Promised Options + exceeds the Unissued Option Pool prior to such increase. + + + + + + + “Converting Securities” includes this Safe and other convertible + securities issued by the Company, including but not limited to: (i) + other Safes; (ii) convertible promissory notes and other convertible + debt instruments; and (iii) convertible securities that have the + right to convert into shares of Capital Stock. + + + + + + “Direct Listing” means the Company’s initial listing of its Common + Stock (other than shares of Common Stock not eligible for resale + under Rule 144 under the Securities Act) on a national securities + exchange by means of an effective registration statement on Form S-1 + filed by the Company with the SEC that registers shares of existing + capital stock of the Company for resale, as approved by the + Company’s board of directors. For the avoidance of doubt, a Direct + Listing will not be deemed to be an underwritten offering and will + not involve any underwriting services. + + + + + + “Dissolution Event” means (i) a voluntary termination of operations, + (ii) a general assignment for the benefit of the Company’s creditors + or (iii) any other liquidation, dissolution or winding up of the + Company (excluding a Liquidity Event), whether voluntary or + involuntary. + + + + + + “Dividend Amount” means, with respect to any date on which the + Company pays a dividend on its outstanding Common Stock, the amount + of such dividend that is paid per share of Common Stock multiplied + by (x) the Purchase Amount divided by (y) the Liquidity Price + (treating the dividend date as a Liquidity Event solely for purposes + of calculating such Liquidity Price). + + + + + + “Equity Financing” means a bona fide transaction or series of + transactions with the principal purpose of raising capital, pursuant + to which the Company issues and sells Preferred Stock at a fixed + valuation, including but not limited to, a pre-money or post-money + valuation. + + + + + + “Initial Public Offering” means the closing of the Company’s first + firm commitment underwritten initial public offering of Common Stock + pursuant to a registration statement filed under the Securities Act. + + + + + + “Liquidity Capitalization” is calculated as of immediately prior to + the Liquidity Event, and (without double- counting, in each case + calculated on an as-converted to Common Stock basis):{" "} + + + + + + + + Includes all shares of Capital Stock issued and outstanding; + + + + + + Includes all (i) issued and outstanding Options and (ii) to the + extent receiving Proceeds, Promised Options; + + + + + + Includes all Converting Securities, other than any Safes and other + convertible securities (including without limitation shares of + Preferred Stock) where the holders of such securities are + receiving Cash-Out Amounts or similar liquidation preference + payments in lieu of Conversion Amounts or similar “as-converted” + payments; and + + + + + Excludes the Unissued Option Pool. + + + + + + “Liquidity Event” means a Change of Control, a Direct Listing or an + Initial Public Offering. + + + + + + “Liquidity Price” means the price per share equal to the Post-Money + Valuation Cap divided by the Liquidity Capitalization. + + + + + + “Options” includes options, restricted stock awards or purchases, + RSUs, SARs, warrants or similar securities, vested or unvested. + + + + + + “Proceeds” means cash and other assets (including without limitation + stock consideration) that are proceeds from the Liquidity Event or + the Dissolution Event, as applicable, and legally available for + distribution.{" "} + + + + + + “Promised Options” means promised but ungranted Options that are the + greater of those (i) promised pursuant to agreements or + understandings made prior to the execution of, or in connection + with, the term sheet or letter of intent for the Equity Financing or + Liquidity Event, as applicable (or the initial closing of the Equity + Financing or consummation of the Liquidity Event, if there is no + term sheet or letter of intent), (ii) in the case of an Equity + Financing, treated as outstanding Options in the calculation of the + Standard Preferred Stock’s price per share, or (iii) in the case of + a Liquidity Event, treated as outstanding Options in the calculation + of the distribution of the Proceeds. + + + + + + “Safe” means an instrument containing a future right to shares of + Capital Stock, similar in form and content to this instrument, + purchased by investors for the purpose of funding the Company’s + business operations. References to “this Safe” mean this specific + instrument.{" "} + + + + + + “Safe Preferred Stock” means the shares of the series of Preferred + Stock issued to the Investor in an Equity Financing, having the + identical rights, privileges, preferences, seniority, liquidation + multiple and restrictions as the shares of Standard Preferred Stock, + except that any price-based preferences (such as the per share + liquidation amount, initial conversion price and per share dividend + amount) will be based on the Safe Price.{" "} + + + + + + “Safe Price” means the price per share equal to the Post-Money + Valuation Cap divided by the Company Capitalization. + + + + + + “Standard Preferred Stock” means the shares of the series of + Preferred Stock issued to the investors investing new money in the + Company in connection with the initial closing of the Equity + Financing. + + + + + + “Unissued Option Pool” means all shares of Capital Stock that are + reserved, available for future grant and not subject to any + outstanding Options or Promised Options (but in the case of a + Liquidity Event, only to the extent Proceeds are payable on such + Promised Options) under any equity incentive or similar Company + plan. + + + + + 3. Company Representations + + + + + (a) The Company is a corporation duly organized, validly existing + and in good standing under the laws of its state of incorporation, + and has the power and authority to own, lease and operate its + properties and carry on its business as now conducted. + + + + + + (b) The execution, delivery and performance by the Company of this + Safe is within the power of the Company and has been duly authorized + by all necessary actions on the part of the Company (subject to + section 3(d)). This Safe constitutes a legal, valid and binding + obligation of the Company, enforceable against the Company in + accordance with its terms, except as limited by bankruptcy, + insolvency or other laws of general application relating to or + affecting the enforcement of creditors’ rights generally and general + principles of equity. To its knowledge, the Company is not in + violation of (i) its current certificate of incorporation or bylaws, + (ii) any material statute, rule or regulation applicable to the + Company or (iii) any material debt or contract to which the Company + is a party or by which it is bound, where, in each case, such + violation or default, individually, or together with all such + violations or defaults, could reasonably be expected to have a + material adverse effect on the Company. + + + + + + (c) The performance and consummation of the transactions + contemplated by this Safe do not and will not: (i) violate any + material judgment, statute, rule or regulation applicable to the + Company; (ii) result in the acceleration of any material debt or + contract to which the Company is a party or by which it is bound; or + (iii) result in the creation or imposition of any lien on any + property, asset or revenue of the Company or the suspension, + forfeiture, or nonrenewal of any material permit, license or + authorization applicable to the Company, its business or operations. + + + + + + (d) No consents or approvals are required in connection with the + performance of this Safe, other than: (i) the Company’s corporate + approvals; (ii) any qualifications or filings under applicable + securities laws; and (iii) necessary corporate approvals for the + authorization of Capital Stock issuable pursuant to Section 1. + + + + + + (e) To its knowledge, the Company owns or possesses (or can obtain + on commercially reasonable terms) sufficient legal rights to all + patents, trademarks, service marks, trade names, copyrights, trade + secrets, licenses, information, processes and other intellectual + property rights necessary for its business as now conducted and as + currently proposed to be conducted, without any conflict with, or + infringement of the rights of, others. + + + + + 4. Investor Representations + + + + + (a) The Investor has full legal capacity, power and authority to + execute and deliver this Safe and to perform its obligations + hereunder. This Safe constitutes a valid and binding obligation of + the Investor, enforceable in accordance with its terms, except as + limited by bankruptcy, insolvency or other laws of general + application relating to or affecting the enforcement of creditors’ + rights generally and general principles of equity. + + + + + + (b) The Investor is an accredited investor as such term is defined + in Rule 501 of Regulation D under the Securities Act, and + acknowledges and agrees that if not an accredited investor at the + time of an Equity Financing, the Company may void this Safe and + return the Purchase Amount. The Investor has been advised that this + Safe and the underlying securities have not been registered under + the Securities Act, or any state securities laws and, therefore, + cannot be resold unless they are registered under the Securities Act + and applicable state securities laws or unless an exemption from + such registration requirements is available. The Investor is + purchasing this Safe and the securities to be acquired by the + Investor hereunder for its own account for investment, not as a + nominee or agent, and not with a view to, or for resale in + connection with, the distribution thereof, and the Investor has no + present intention of selling, granting any participation in, or + otherwise distributing the same. The Investor has such knowledge and + experience in financial and business matters that the Investor is + capable of evaluating the merits and risks of such investment, is + able to incur a complete loss of such investment without impairing + the Investor’s financial condition and is able to bear the economic + risk of such investment for an indefinite period of time.{" "} + + + + + 5. Miscellaneous + + + + + (a) Any provision of this Safe may be amended, waived or modified by + written consent of the Company and either (i) the Investor or (ii) + the majority-in-interest of all then-outstanding Safes with the same + “Post-Money Valuation Cap” and “Discount Rate” as this Safe (and + Safes lacking one or both of such terms will be considered to be the + same with respect to such term(s)), provided that with respect to + clause (ii): (A) the Purchase Amount may not be amended, waived or + modified in this manner, (B) the consent of the Investor and each + holder of such Safes must be solicited (even if not obtained), and + (C) such amendment, waiver or modification treats all such holders + in the same manner. “Majority-in-interest” refers to the holders of + the applicable group of Safes whose Safes have a total Purchase + Amount greater than 50% of the total Purchase Amount of all of such + applicable group of Safes. + + + + + + (b) Any notice required or permitted by this Safe will be deemed + sufficient when delivered personally or by overnight courier or sent + by email to the relevant address listed on the signature page, or 48 + hours after being deposited in the U.S. mail as certified or + registered mail with postage prepaid, addressed to the party to be + notified at such party’s address listed on the signature page, as + subsequently modified by written notice. + + + + + + (c) The Investor is not entitled, as a holder of this Safe, to vote + or be deemed a holder of Capital Stock for any purpose other than + tax purposes, nor will anything in this Safe be construed to confer + on the Investor, as such, any rights of a Company stockholder or + rights to vote for the election of directors or on any matter + submitted to Company stockholders, or to give or withhold consent to + any corporate action or to receive notice of meetings, until shares + have been issued on the terms described in Section 1. However, if + the Company pays a dividend on outstanding shares of Common Stock + (that is not payable in shares of Common Stock) while this Safe is + outstanding, the Company will pay the Dividend Amount to the + Investor at the same time. + + + + + + (d) Neither this Safe nor the rights in this Safe are transferable + or assignable, by operation of law or otherwise, by either party + without the prior written consent of the other; provided, however, + that this Safe and/or its rights may be assigned without the + Company’s consent by the Investor (i) to the Investor’s estate, + heirs, executors, administrators, guardians and/or successors in the + event of Investor’s death or disability, or (ii) to any other entity + who directly or indirectly, controls, is controlled by or is under + common control with the Investor, including, without limitation, any + general partner, managing member, officer or director of the + Investor, or any venture capital fund now or hereafter existing + which is controlled by one or more general partners or managing + members of, or shares the same management company with, the + Investor. + + + + + + (e) In the event any one or more of the provisions of this Safe is + for any reason held to be invalid, illegal or unenforceable, in + whole or in part or in any respect, or in the event that any one or + more of the provisions of this Safe operate or would prospectively + operate to invalidate this Safe, then and in any such event, such + provision(s) only will be deemed null and void and will not affect + any other provision of this Safe and the remaining provisions of + this Safe will remain operative and in full force and effect and + will not be affected, prejudiced, or disturbed thereby. + + + + + + (f) All rights and obligations hereunder will be governed by the + laws of the State of [Governing Law Jurisdiction], without regard to + the conflicts of law provisions of such jurisdiction. + + + + + + (g) The parties acknowledge and agree that for United States federal + and state income tax purposes this Safe is, and at all times has + been, intended to be characterized as stock, and more particularly + as common stock for purposes of Sections 304, 305, 306, 354, 368, + 1036 and 1202 of the Internal Revenue Code of 1986, as amended. + Accordingly, the parties agree to treat this Safe consistent with + the foregoing intent for all United States federal and state income + tax purposes (including, without limitation, on their respective tax + returns or other informational statements). + + + + + (Signature page follows) + + + + + + IN WITNESS WHEREOF, the undersigned have caused this Safe to be duly + executed and delivered. + + + + {company.name} + + By (signature): __________________________________ + + + + Name: ________________________________________ + + + + Title: _________________________________________ + + + Address: ______________________________________ + + + ______________________________________________ + + + Email: ________________________________________ + + + + + INVESTOR: + + By (signature): __________________________________ + + + + Name: ________________________________________ + + + + Title: _________________________________________ + + + Address: ______________________________________ + + + ______________________________________________ + + + Email: ________________________________________ + + + + ); }; diff --git a/src/components/safe/templates/post-money/discount.tsx b/src/components/safe/templates/post-money/discount.tsx index 018d6a33b..f895761a3 100644 --- a/src/components/safe/templates/post-money/discount.tsx +++ b/src/components/safe/templates/post-money/discount.tsx @@ -1,8 +1,821 @@ -const PostMoneyDiscount = () => { +"use client"; + +import { Indent, type SafeProps, style } from "@/components/safe/templates"; +import { formatDate, formatPercentage, formatUsd } from "@/lib/format"; +import { Document, Link, Page, Text, View } from "@react-pdf/renderer"; + +const PostMoneyDiscount = ({ + investor, + investment, + discountRate, + date, + company, + options, +}: SafeProps) => { + const { title, author, subject, keywords, creator, producer } = options; + return ( -
    -

    Post Money Discount

    -
    + + + Version 1.2 + + DISCOUNT ONLY + + + THIS INSTRUMENT AND ANY SECURITIES ISSUABLE PURSUANT HERETO HAVE NOT + BEEN REGISTERED UNDER THE SECURITIES ACT OF 1933, AS AMENDED (THE “ + SECURITIES ACT ”), OR UNDER THE SECURITIES LAWS OF CERTAIN STATES. + THESE SECURITIES MAY NOT BE OFFERED, SOLD OR OTHERWISE TRANSFERRED, + PLEDGED OR HYPOTHECATED EXCEPT AS PERMITTED IN THIS SAFE AND UNDER THE + ACT AND APPLICABLE STATE SECURITIES LAWS PURSUANT TO AN EFFECTIVE + REGISTRATION STATEMENT OR AN EXEMPTION THEREFROM. + + + + Captable, Inc. + + + SAFE + + + (Simple Agreement for Future Equity) + + + + + THIS CERTIFIES{" "} + THAT in exchange for the payment by{" "} + {investor.name} (the + “Investor”) of{" "} + {formatUsd(investment)} (the + “Purchase Amount”) on or about{" "} + {formatDate(date)},{" "} + {company.name}, a{" "} + {company.state} corporation + (the “Company”), issues to the Investor the right to certain shares + of the Company’s Capital Stock, subject to the terms described + below. + + + + + + This Safe is one of the forms available at{" "} + + http://ycombinator.com/documents + {" "} + and the Company and the Investor agree that neither one has modified + the form, except to fill in blanks and bracketed terms. + + + + {discountRate && ( + + + The “Discount rate” is{" "} + + {formatPercentage(discountRate)} + + . See Section 2 for certain additional defined terms. + + + )} + + + 1. Events + + + + + (a){" "} + + Equity Financing + + . If there is an Equity Financing before the termination of this + Safe, on the initial closing of such Equity Financing, this Safe + will automatically convert into the greater of: (1) the number of + shares of Standard Preferred Stock equal to the Purchase Amount + divided by the lowest price per share of the Standard Preferred + Stock; or (2) the number of shares of Safe Preferred Stock equal to + the Purchase Amount divided by the Safe Price. + + + + + + In connection with the automatic conversion of this Safe into shares + of Standard Preferred Stock or Safe Preferred Stock, the Investor + will execute and deliver to the Company all of the transaction + documents related to the Equity Financing; provided, that such + documents (i) are the same documents to be entered into with the + purchasers of Standard Preferred Stock, with appropriate variations + for the Safe Preferred Stock if applicable, and (ii) have customary + exceptions to any drag-along applicable to the Investor, including + (without limitation) limited representations, warranties, liability + and indemnification obligations for the Investor. + + + + + + (b){" "} + Liquidity Event + . If there is a Liquidity Event before the termination of this Safe, + the Investor will automatically be entitled (subject to the + liquidation priority set forth in Section 1(d) below) to receive a + portion of Proceeds, due and payable to the Investor immediately + prior to, or concurrent with, the consummation of such Liquidity + Event, equal to the greater of (i) the Purchase Amount (the + “Cash-Out Amount”) or (ii) the amount payable on the number of + shares of Common Stock equal to the Purchase Amount divided by the + Liquidity Price (the “Conversion Amount”). If any of the Company’s + securityholders are given a choice as to the form and amount of + Proceeds to be received in a Liquidity Event, the Investor will be + given the same choice, provided that the Investor may not choose to + receive a form of consideration that the Investor would be + ineligible to receive as a result of the Investor’s failure to + satisfy any requirement or limitation generally applicable to the + Company’s securityholders, or under any applicable laws. + + + + + + Notwithstanding the foregoing, in connection with a Change of + Control intended to qualify as a tax-free reorganization, the + Company may reduce the cash portion of Proceeds payable to the + Investor by the amount determined by its board of directors in good + faith for such Change of Control to qualify as a tax-free + reorganization for U.S. federal income tax purposes, provided that + such reduction (A) does not reduce the total Proceeds payable to + such Investor and (B) is applied in the same manner and on a pro + rata basis to all securityholders who have equal priority to the + Investor under Section 1(d). + + + + + + (c){" "} + + Dissolution Event + + . If there is a Dissolution Event before the termination of this + Safe, the Investor will automatically be entitled (subject to the + liquidation priority set forth in Section 1(d) below) to receive a + portion of Proceeds equal to the Cash-Out Amount, due and payable to + the Investor immediately prior to the consummation of the + Dissolution Event. + + + + + + (d){" "} + + Liquidation Priority + + . In a Liquidity Event or Dissolution Event, this Safe is intended + to operate like standard non-participating Preferred Stock. The + Investor’s right to receive its Cash-Out Amount is: + + + + + + (i) {" "} Junior to payment of outstanding indebtedness and + creditor claims, including contractual claims for payment and + convertible promissory notes (to the extent such convertible + promissory notes are not actually or notionally converted into + Capital Stock); + + + + + + (ii) {" "} On par with payments for other Safes and/or Preferred + Stock, and if the applicable Proceeds are insufficient to permit + full payments to the Investor and such other Safes and/or Preferred + Stock, the applicable Proceeds will be distributed pro rata to the + Investor and such other Safes and/or Preferred Stock in proportion + to the full payments that would otherwise be due; and + + + + + + (iii) {" "} Senior to payments for Common Stock. + + + + + + The Investor’s right to receive its Conversion Amount is (A) on par + with payments for Common Stock and other Safes and/or Preferred + Stock who are also receiving Conversion Amounts or Proceeds on a + similar as-converted to Common Stock basis, and (B) junior to + payments described in clauses (i) and (ii) above (in the latter + case, to the extent such payments are Cash-Out Amounts or similar + liquidation preferences). + + + + + + (e) Termination + . This Safe will automatically terminate (without relieving the + Company of any obligations arising from a prior breach of or + non-compliance with this Safe) immediately following the earliest to + occur of: (i) the issuance of Capital Stock to the Investor pursuant + to the automatic conversion of this Safe under Section 1(a); or (ii) + the payment, or setting aside for payment, of amounts due the + Investor pursuant to Section 1(b) or Section 1(c). + + + + + 2. Definitions + + + + + “Capital Stock” means the capital stock of the Company, including, + without limitation, the “Common Stock” and the “Preferred Stock.” + + + + + + “Change of Control” means (i) a transaction or series of related + transactions in which any “person” or “group” (within the meaning of + Section 13(d) and 14(d) of the Securities Exchange Act of 1934, as + amended), becomes the “beneficial owner” (as defined in Rule 13d-3 + under the Securities Exchange Act of 1934, as amended), directly or + indirectly, of more than 50% of the outstanding voting securities of + the Company having the right to vote for the election of members of + the Company’s board of directors, (ii) any reorganization, merger or + consolidation of the Company, other than a transaction or series of + related transactions in which the holders of the voting securities + of the Company outstanding immediately prior to such transaction or + series of related transactions retain, immediately after such + transaction or series of related transactions, at least a majority + of the total voting power represented by the outstanding voting + securities of the Company or such other surviving or resulting + entity or (iii) a sale, lease or other disposition of all or + substantially all of the assets of the Company. + + + + + + “Company Capitalization” is calculated as of immediately prior to + the Equity Financing and (without double-counting, in each case + calculated on an as-converted to Common Stock basis): + + + + {/* List */} + + + + + Includes all shares of Capital Stock issued and outstanding; + + + + + Includes all Converting Securities; + + + + + Includes all (i) issued and outstanding Options and (ii) Promised + Options; and + + + + + + Includes the Unissued Option Pool, except that any increase to the + Unissued Option Pool in connection with the Equity Financing will + only be included to the extent that the number of Promised Options + exceeds the Unissued Option Pool prior to such increase. + + + + + + + “Converting Securities” includes this Safe and other convertible + securities issued by the Company, including but not limited to: (i) + other Safes; (ii) convertible promissory notes and other convertible + debt instruments; and (iii) convertible securities that have the + right to convert into shares of Capital Stock. + + + + + + “Direct Listing” means the Company’s initial listing of its Common + Stock (other than shares of Common Stock not eligible for resale + under Rule 144 under the Securities Act) on a national securities + exchange by means of an effective registration statement on Form S-1 + filed by the Company with the SEC that registers shares of existing + capital stock of the Company for resale, as approved by the + Company’s board of directors. For the avoidance of doubt, a Direct + Listing will not be deemed to be an underwritten offering and will + not involve any underwriting services. + + + + + + “Dissolution Event” means (i) a voluntary termination of operations, + (ii) a general assignment for the benefit of the Company’s creditors + or (iii) any other liquidation, dissolution or winding up of the + Company (excluding a Liquidity Event), whether voluntary or + involuntary. + + + + + + “Dividend Amount” means, with respect to any date on which the + Company pays a dividend on its outstanding Common Stock, the amount + of such dividend that is paid per share of Common Stock multiplied + by (x) the Purchase Amount divided by (y) the Liquidity Price + (treating the dividend date as a Liquidity Event solely for purposes + of calculating such Liquidity Price). + + + + + + “Equity Financing” means a bona fide transaction or series of + transactions with the principal purpose of raising capital, pursuant + to which the Company issues and sells Preferred Stock at a fixed + valuation, including but not limited to, a pre-money or post-money + valuation. + + + + + + “Initial Public Offering” means the closing of the Company’s first + firm commitment underwritten initial public offering of Common Stock + pursuant to a registration statement filed under the Securities Act. + + + + + + “Liquidity Capitalization” is calculated as of immediately prior to + the Liquidity Event, and (without double- counting, in each case + calculated on an as-converted to Common Stock basis):{" "} + + + + + + + + Includes all shares of Capital Stock issued and outstanding; + + + + + + Includes all (i) issued and outstanding Options and (ii) to the + extent receiving Proceeds, Promised Options; + + + + + + Includes all Converting Securities, other than any Safes and other + convertible securities (including without limitation shares of + Preferred Stock) where the holders of such securities are + receiving Cash-Out Amounts or similar liquidation preference + payments in lieu of Conversion Amounts or similar “as-converted” + payments; and + + + + + Excludes the Unissued Option Pool. + + + + + + “Liquidity Event” means a Change of Control, a Direct Listing or an + Initial Public Offering. + + + + + + “Liquidity Price” means the price per share equal to the Post-Money + Valuation Cap divided by the Liquidity Capitalization. + + + + + + “Options” includes options, restricted stock awards or purchases, + RSUs, SARs, warrants or similar securities, vested or unvested. + + + + + + “Proceeds” means cash and other assets (including without limitation + stock consideration) that are proceeds from the Liquidity Event or + the Dissolution Event, as applicable, and legally available for + distribution.{" "} + + + + + + “Promised Options” means promised but ungranted Options that are the + greater of those (i) promised pursuant to agreements or + understandings made prior to the execution of, or in connection + with, the term sheet or letter of intent for the Equity Financing or + Liquidity Event, as applicable (or the initial closing of the Equity + Financing or consummation of the Liquidity Event, if there is no + term sheet or letter of intent), (ii) in the case of an Equity + Financing, treated as outstanding Options in the calculation of the + Standard Preferred Stock’s price per share, or (iii) in the case of + a Liquidity Event, treated as outstanding Options in the calculation + of the distribution of the Proceeds. + + + + + + “Safe” means an instrument containing a future right to shares of + Capital Stock, similar in form and content to this instrument, + purchased by investors for the purpose of funding the Company’s + business operations. References to “this Safe” mean this specific + instrument.{" "} + + + + + + “Safe Preferred Stock” means the shares of the series of Preferred + Stock issued to the Investor in an Equity Financing, having the + identical rights, privileges, preferences, seniority, liquidation + multiple and restrictions as the shares of Standard Preferred Stock, + except that any price-based preferences (such as the per share + liquidation amount, initial conversion price and per share dividend + amount) will be based on the Safe Price.{" "} + + + + + + “Safe Price” means the price per share equal to the Post-Money + Valuation Cap divided by the Company Capitalization. + + + + + + “Standard Preferred Stock” means the shares of the series of + Preferred Stock issued to the investors investing new money in the + Company in connection with the initial closing of the Equity + Financing. + + + + + + “Unissued Option Pool” means all shares of Capital Stock that are + reserved, available for future grant and not subject to any + outstanding Options or Promised Options (but in the case of a + Liquidity Event, only to the extent Proceeds are payable on such + Promised Options) under any equity incentive or similar Company + plan. + + + + + 3. Company Representations + + + + + (a) The Company is a corporation duly organized, validly existing + and in good standing under the laws of its state of incorporation, + and has the power and authority to own, lease and operate its + properties and carry on its business as now conducted. + + + + + + (b) The execution, delivery and performance by the Company of this + Safe is within the power of the Company and has been duly authorized + by all necessary actions on the part of the Company (subject to + section 3(d)). This Safe constitutes a legal, valid and binding + obligation of the Company, enforceable against the Company in + accordance with its terms, except as limited by bankruptcy, + insolvency or other laws of general application relating to or + affecting the enforcement of creditors’ rights generally and general + principles of equity. To its knowledge, the Company is not in + violation of (i) its current certificate of incorporation or bylaws, + (ii) any material statute, rule or regulation applicable to the + Company or (iii) any material debt or contract to which the Company + is a party or by which it is bound, where, in each case, such + violation or default, individually, or together with all such + violations or defaults, could reasonably be expected to have a + material adverse effect on the Company. + + + + + + (c) The performance and consummation of the transactions + contemplated by this Safe do not and will not: (i) violate any + material judgment, statute, rule or regulation applicable to the + Company; (ii) result in the acceleration of any material debt or + contract to which the Company is a party or by which it is bound; or + (iii) result in the creation or imposition of any lien on any + property, asset or revenue of the Company or the suspension, + forfeiture, or nonrenewal of any material permit, license or + authorization applicable to the Company, its business or operations. + + + + + + (d) No consents or approvals are required in connection with the + performance of this Safe, other than: (i) the Company’s corporate + approvals; (ii) any qualifications or filings under applicable + securities laws; and (iii) necessary corporate approvals for the + authorization of Capital Stock issuable pursuant to Section 1. + + + + + + (e) To its knowledge, the Company owns or possesses (or can obtain + on commercially reasonable terms) sufficient legal rights to all + patents, trademarks, service marks, trade names, copyrights, trade + secrets, licenses, information, processes and other intellectual + property rights necessary for its business as now conducted and as + currently proposed to be conducted, without any conflict with, or + infringement of the rights of, others. + + + + + 4. Investor Representations + + + + + (a) The Investor has full legal capacity, power and authority to + execute and deliver this Safe and to perform its obligations + hereunder. This Safe constitutes a valid and binding obligation of + the Investor, enforceable in accordance with its terms, except as + limited by bankruptcy, insolvency or other laws of general + application relating to or affecting the enforcement of creditors’ + rights generally and general principles of equity. + + + + + + (b) The Investor is an accredited investor as such term is defined + in Rule 501 of Regulation D under the Securities Act, and + acknowledges and agrees that if not an accredited investor at the + time of an Equity Financing, the Company may void this Safe and + return the Purchase Amount. The Investor has been advised that this + Safe and the underlying securities have not been registered under + the Securities Act, or any state securities laws and, therefore, + cannot be resold unless they are registered under the Securities Act + and applicable state securities laws or unless an exemption from + such registration requirements is available. The Investor is + purchasing this Safe and the securities to be acquired by the + Investor hereunder for its own account for investment, not as a + nominee or agent, and not with a view to, or for resale in + connection with, the distribution thereof, and the Investor has no + present intention of selling, granting any participation in, or + otherwise distributing the same. The Investor has such knowledge and + experience in financial and business matters that the Investor is + capable of evaluating the merits and risks of such investment, is + able to incur a complete loss of such investment without impairing + the Investor’s financial condition and is able to bear the economic + risk of such investment for an indefinite period of time.{" "} + + + + + 5. Miscellaneous + + + + + (a) Any provision of this Safe may be amended, waived or modified by + written consent of the Company and either (i) the Investor or (ii) + the majority-in-interest of all then-outstanding Safes with the same + “Post-Money Valuation Cap” and “Discount Rate” as this Safe (and + Safes lacking one or both of such terms will be considered to be the + same with respect to such term(s)), provided that with respect to + clause (ii): (A) the Purchase Amount may not be amended, waived or + modified in this manner, (B) the consent of the Investor and each + holder of such Safes must be solicited (even if not obtained), and + (C) such amendment, waiver or modification treats all such holders + in the same manner. “Majority-in-interest” refers to the holders of + the applicable group of Safes whose Safes have a total Purchase + Amount greater than 50% of the total Purchase Amount of all of such + applicable group of Safes. + + + + + + (b) Any notice required or permitted by this Safe will be deemed + sufficient when delivered personally or by overnight courier or sent + by email to the relevant address listed on the signature page, or 48 + hours after being deposited in the U.S. mail as certified or + registered mail with postage prepaid, addressed to the party to be + notified at such party’s address listed on the signature page, as + subsequently modified by written notice. + + + + + + (c) The Investor is not entitled, as a holder of this Safe, to vote + or be deemed a holder of Capital Stock for any purpose other than + tax purposes, nor will anything in this Safe be construed to confer + on the Investor, as such, any rights of a Company stockholder or + rights to vote for the election of directors or on any matter + submitted to Company stockholders, or to give or withhold consent to + any corporate action or to receive notice of meetings, until shares + have been issued on the terms described in Section 1. However, if + the Company pays a dividend on outstanding shares of Common Stock + (that is not payable in shares of Common Stock) while this Safe is + outstanding, the Company will pay the Dividend Amount to the + Investor at the same time. + + + + + + (d) Neither this Safe nor the rights in this Safe are transferable + or assignable, by operation of law or otherwise, by either party + without the prior written consent of the other; provided, however, + that this Safe and/or its rights may be assigned without the + Company’s consent by the Investor (i) to the Investor’s estate, + heirs, executors, administrators, guardians and/or successors in the + event of Investor’s death or disability, or (ii) to any other entity + who directly or indirectly, controls, is controlled by or is under + common control with the Investor, including, without limitation, any + general partner, managing member, officer or director of the + Investor, or any venture capital fund now or hereafter existing + which is controlled by one or more general partners or managing + members of, or shares the same management company with, the + Investor. + + + + + + (e) In the event any one or more of the provisions of this Safe is + for any reason held to be invalid, illegal or unenforceable, in + whole or in part or in any respect, or in the event that any one or + more of the provisions of this Safe operate or would prospectively + operate to invalidate this Safe, then and in any such event, such + provision(s) only will be deemed null and void and will not affect + any other provision of this Safe and the remaining provisions of + this Safe will remain operative and in full force and effect and + will not be affected, prejudiced, or disturbed thereby. + + + + + + (f) All rights and obligations hereunder will be governed by the + laws of the State of [Governing Law Jurisdiction], without regard to + the conflicts of law provisions of such jurisdiction. + + + + + + (g) The parties acknowledge and agree that for United States federal + and state income tax purposes this Safe is, and at all times has + been, intended to be characterized as stock, and more particularly + as common stock for purposes of Sections 304, 305, 306, 354, 368, + 1036 and 1202 of the Internal Revenue Code of 1986, as amended. + Accordingly, the parties agree to treat this Safe consistent with + the foregoing intent for all United States federal and state income + tax purposes (including, without limitation, on their respective tax + returns or other informational statements). + + + + + (Signature page follows) + + + + + + IN WITNESS WHEREOF, the undersigned have caused this Safe to be duly + executed and delivered. + + + + {company.name} + + By (signature): __________________________________ + + + + Name: ________________________________________ + + + + Title: _________________________________________ + + + Address: ______________________________________ + + + ______________________________________________ + + + Email: ________________________________________ + + + + + INVESTOR: + + By (signature): __________________________________ + + + + Name: ________________________________________ + + + + Title: _________________________________________ + + + Address: ______________________________________ + + + ______________________________________________ + + + Email: ________________________________________ + + + + ); }; diff --git a/src/lib/format.ts b/src/lib/format.ts index 32777dc83..47b8c9e76 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -13,3 +13,10 @@ export const formatDate = (date: string) => { day: "numeric", }).format(new Date(date)); }; + +export const formatPercentage = (percentage: number) => { + return new Intl.NumberFormat("en-US", { + style: "percent", + minimumFractionDigits: 2, + }).format(percentage / 100); +}; From fff59f3866de9a80116aa99d459220e4974b6896 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Thu, 19 Sep 2024 20:48:01 +0530 Subject: [PATCH 38/60] fix: lock file --- pnpm-lock.yaml | 748 +++++++++++++++++++++---------------------------- 1 file changed, 321 insertions(+), 427 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2af2d2456..8cbd7d02a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ importers: version: 0.12.4(@tiptap/pm@2.4.0)(@types/react@18.3.3)(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) + version: 2.1.8(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@hono/swagger-ui': specifier: ^0.4.0 version: 0.4.0(hono@4.5.8) @@ -34,7 +34,7 @@ importers: version: 3.9.0(react-hook-form@7.52.1(react@18.3.1)) '@next-auth/prisma-adapter': specifier: ^1.0.7 - version: 1.0.7(@prisma/client@5.14.0(prisma@5.14.0))(next-auth@4.24.7(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1)) + version: 1.0.7(@prisma/client@5.14.0(prisma@5.14.0))(next-auth@4.24.7(next@14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1)) '@prisma/client': specifier: ^5.13.0 version: 5.14.0(prisma@5.14.0) @@ -103,10 +103,10 @@ importers: version: 4.2.0(react@18.3.1) '@scalar/nextjs-api-reference': specifier: ^0.4.18 - version: 0.4.18(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(postcss@8.4.40)(react-dom@18.2.0(react@18.3.1))(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + version: 0.4.18(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(postcss@8.4.40)(react-dom@18.2.0(react@18.3.1))(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) '@sentry/nextjs': specifier: ^8.19.0 - version: 8.19.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.92.0) + version: 8.19.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(next@14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.92.0) '@simplewebauthn/browser': specifier: ^10.0.0 version: 10.0.0 @@ -121,7 +121,7 @@ importers: version: 4.1.0 '@t3-oss/env-nextjs': specifier: ^0.10.1 - version: 0.10.1(typescript@5.4.5)(zod@3.23.8) + version: 0.10.1(typescript@5.5.4)(zod@3.23.8) '@tanstack/react-query': specifier: ^4.36.1 version: 4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1) @@ -136,7 +136,7 @@ importers: version: 10.45.2(@trpc/server@10.45.2) '@trpc/next': specifier: ^10.45.2 - version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@10.45.2)(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@10.45.2)(next@14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@trpc/react-query': specifier: ^10.43.6 version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) @@ -193,10 +193,10 @@ importers: version: 5.0.7 next: specifier: ^14.2.5 - version: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + version: 14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) next-auth: specifier: ^4.24.7 - version: 4.24.7(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + version: 4.24.7(next@14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) next-nprogress-bar: specifier: ^2.3.13 version: 2.3.13 @@ -211,7 +211,7 @@ importers: version: 1.17.1 pg-boss: specifier: ^10.1.1 - version: 10.1.1 + version: 10.1.3 pino: specifier: ^9.3.1 version: 9.3.1 @@ -223,7 +223,7 @@ importers: version: 0.7.4(@prisma/client@5.14.0(prisma@5.14.0)) prisma-json-types-generator: specifier: ^3.0.4 - version: 3.0.4(prisma@5.14.0)(typescript@5.4.5) + version: 3.0.4(prisma@5.14.0)(typescript@5.5.4) pushmodal: specifier: ^1.0.4 version: 1.0.4(@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1) @@ -338,7 +338,7 @@ importers: version: 9.2.22 knip: specifier: ^5.17.2 - version: 5.17.4(@types/node@20.12.12)(typescript@5.4.5) + version: 5.17.4(@types/node@20.12.12)(typescript@5.5.4) lint-staged: specifier: ^15.2.2 version: 15.2.2 @@ -356,7 +356,7 @@ importers: version: 4.10.4 typescript: specifier: ^5.4.5 - version: 5.4.5 + version: 5.5.4 vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.12.12)(terser@5.31.1) @@ -1217,8 +1217,8 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/react-dom@2.1.1': - resolution: {integrity: sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==} + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -1229,17 +1229,26 @@ 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==} + '@floating-ui/react@0.26.15': + resolution: {integrity: sha512-WKmfLkxTwCm09Dxq4LpjL3EPbZVSp5wvnap1jmculsfnzg2Ag/pCkP+OPyjE5dFMXqX97hsLIqJehboZ5XAHXw==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' + '@floating-ui/react@0.26.24': + resolution: {integrity: sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==} + 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/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} '@floating-ui/vue@1.0.7': resolution: {integrity: sha512-tm9aMT9IrMzoZfzPpsoZHP7j7ULZ0p9AzCJV6i2H8sAlKe36tAnwuQLHdm7vE0SnRkHJJXuMB/gNz4gFdHLNrg==} @@ -1256,8 +1265,8 @@ packages: react: ^16 || ^17 || ^18 react-dom: ^16 || ^17 || ^18 - '@headlessui/react@2.1.2': - resolution: {integrity: sha512-Kb3hgk9gRNRcTZktBrKdHhF3xFhYkca1Rk6e1/im2ENf83dgN54orMW0uSKTXFnUpZOUFZ+wcY05LlipwgZIFQ==} + '@headlessui/react@2.1.8': + resolution: {integrity: sha512-uajqVkAcVG/wHwG9Fh5PFMcFpf2VxM4vNRNKxRjuK009kePVur8LkuuygHfIE+2uZ7z7GnlTtYsyUe6glPpTLg==} engines: {node: '>=10'} peerDependencies: react: ^18 @@ -1520,11 +1529,17 @@ packages: '@next/bundle-analyzer@14.2.3': resolution: {integrity: sha512-Z88hbbngMs7njZKI8kTJIlpdLKYfMSLwnsqYe54AP4aLmgL70/Ynx/J201DQ+q2Lr6FxFw1uCeLGImDrHOl2ZA==} + '@next/env@14.2.12': + resolution: {integrity: sha512-3fP29GIetdwVIfIRyLKM7KrvJaqepv+6pVodEbx0P5CaMLYBtx+7eEg8JYO5L9sveJO87z9eCReceZLi0hxO1Q==} + '@next/env@14.2.3': resolution: {integrity: sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==} - '@next/env@14.2.5': - resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} + '@next/swc-darwin-arm64@14.2.12': + resolution: {integrity: sha512-crHJ9UoinXeFbHYNok6VZqjKnd8rTd7K3Z2zpyzF1ch7vVNKmhjv/V7EHxep3ILoN8JB9AdRn/EtVVyG9AkCXw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] '@next/swc-darwin-arm64@14.2.3': resolution: {integrity: sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==} @@ -1532,10 +1547,10 @@ packages: cpu: [arm64] os: [darwin] - '@next/swc-darwin-arm64@14.2.5': - resolution: {integrity: sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==} + '@next/swc-darwin-x64@14.2.12': + resolution: {integrity: sha512-JbEaGbWq18BuNBO+lCtKfxl563Uw9oy2TodnN2ioX00u7V1uzrsSUcg3Ep9ce+P0Z9es+JmsvL2/rLphz+Frcw==} engines: {node: '>= 10'} - cpu: [arm64] + cpu: [x64] os: [darwin] '@next/swc-darwin-x64@14.2.3': @@ -1544,11 +1559,11 @@ packages: cpu: [x64] os: [darwin] - '@next/swc-darwin-x64@14.2.5': - resolution: {integrity: sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==} + '@next/swc-linux-arm64-gnu@14.2.12': + resolution: {integrity: sha512-qBy7OiXOqZrdp88QEl2H4fWalMGnSCrr1agT/AVDndlyw2YJQA89f3ttR/AkEIP9EkBXXeGl6cC72/EZT5r6rw==} engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] + cpu: [arm64] + os: [linux] '@next/swc-linux-arm64-gnu@14.2.3': resolution: {integrity: sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==} @@ -1556,8 +1571,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-gnu@14.2.5': - resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} + '@next/swc-linux-arm64-musl@14.2.12': + resolution: {integrity: sha512-EfD9L7o9biaQxjwP1uWXnk3vYZi64NVcKUN83hpVkKocB7ogJfyH2r7o1pPnMtir6gHZiGCeHKagJ0yrNSLNHw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1568,10 +1583,10 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.5': - resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} + '@next/swc-linux-x64-gnu@14.2.12': + resolution: {integrity: sha512-iQ+n2pxklJew9IpE47hE/VgjmljlHqtcD5UhZVeHICTPbLyrgPehaKf2wLRNjYH75udroBNCgrSSVSVpAbNoYw==} engines: {node: '>= 10'} - cpu: [arm64] + cpu: [x64] os: [linux] '@next/swc-linux-x64-gnu@14.2.3': @@ -1580,8 +1595,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.5': - resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} + '@next/swc-linux-x64-musl@14.2.12': + resolution: {integrity: sha512-rFkUkNwcQ0ODn7cxvcVdpHlcOpYxMeyMfkJuzaT74xjAa5v4fxP4xDk5OoYmPi8QNLDs3UgZPMSBmpBuv9zKWA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1592,11 +1607,11 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.5': - resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} + '@next/swc-win32-arm64-msvc@14.2.12': + resolution: {integrity: sha512-PQFYUvwtHs/u0K85SG4sAdDXYIPXpETf9mcEjWc0R4JmjgMKSDwIU/qfZdavtP6MPNiMjuKGXHCtyhR/M5zo8g==} engines: {node: '>= 10'} - cpu: [x64] - os: [linux] + cpu: [arm64] + os: [win32] '@next/swc-win32-arm64-msvc@14.2.3': resolution: {integrity: sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==} @@ -1604,10 +1619,10 @@ packages: cpu: [arm64] os: [win32] - '@next/swc-win32-arm64-msvc@14.2.5': - resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==} + '@next/swc-win32-ia32-msvc@14.2.12': + resolution: {integrity: sha512-FAj2hMlcbeCV546eU2tEv41dcJb4NeqFlSXU/xL/0ehXywHnNpaYajOUvn3P8wru5WyQe6cTZ8fvckj/2XN4Vw==} engines: {node: '>= 10'} - cpu: [arm64] + cpu: [ia32] os: [win32] '@next/swc-win32-ia32-msvc@14.2.3': @@ -1616,10 +1631,10 @@ packages: cpu: [ia32] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.5': - resolution: {integrity: sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==} + '@next/swc-win32-x64-msvc@14.2.12': + resolution: {integrity: sha512-yu8QvV53sBzoIVRHsxCHqeuS8jYq6Lrmdh0briivuh+Brsp6xjg80MAozUsBTAV9KNmY08KlX0KYTWz1lbPzEg==} engines: {node: '>= 10'} - cpu: [ia32] + cpu: [x64] os: [win32] '@next/swc-win32-x64-msvc@14.2.3': @@ -1628,12 +1643,6 @@ packages: cpu: [x64] os: [win32] - '@next/swc-win32-x64-msvc@14.2.5': - resolution: {integrity: sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2676,34 +2685,29 @@ packages: '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} -<<<<<<< HEAD - '@react-aria/focus@3.17.1': - resolution: {integrity: sha512-FLTySoSNqX++u0nWZJPPN5etXY0WBxaIe/YuL/GTEeuqUIuC/2bJSaw5hlsM6T2yjy6Y/VAxBcKSdAFUlU6njQ==} + '@react-aria/focus@3.18.2': + resolution: {integrity: sha512-Jc/IY+StjA3uqN73o6txKQ527RFU7gnG5crEl5Xy3V+gbYp2O5L3ezAo/E0Ipi2cyMbG6T5Iit1IDs7hcGu8aw==} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@react-aria/interactions@3.21.3': - resolution: {integrity: sha512-BWIuf4qCs5FreDJ9AguawLVS0lV9UU+sK4CCnbCNNmYqOWY+1+gRXCsnOM32K+oMESBxilAjdHW5n1hsMqYMpA==} + '@react-aria/interactions@3.22.2': + resolution: {integrity: sha512-xE/77fRVSlqHp2sfkrMeNLrqf2amF/RyuAS6T5oDJemRSgYM3UoxTbWjucPhfnoW7r32pFPHHgz4lbdX8xqD/g==} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@react-aria/ssr@3.9.4': - resolution: {integrity: sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==} + '@react-aria/ssr@3.9.5': + resolution: {integrity: sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ==} engines: {node: '>= 12'} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@react-aria/utils@3.24.1': - resolution: {integrity: sha512-O3s9qhPMd6n42x9sKeJ3lhu5V1Tlnzhu6Yk8QOvDuXf7UGuUjXf9mzfHJt1dYzID4l9Fwm8toczBzPM9t0jc8Q==} + '@react-aria/utils@3.25.2': + resolution: {integrity: sha512-GdIvG8GBJJZygB4L2QJP1Gabyn2mjFsha73I2wSe+o4DYeGWoJiMZRM06PyTIxLH4S7Sn7eVDtsSBfkc2VY/NA==} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@react-email/body@0.0.9': - resolution: {integrity: sha512-bSGF6j+MbfQKYnnN+Kf57lGp/J+ci+435OMIv/BKAtfmNzHL+ptRrsINJELiO8QzwnZmQjTGKSMAMMJiQS+xwQ==} -======= '@react-email/body@0.0.10': resolution: {integrity: sha512-dMJyL9aU25ieatdPtVjCyQ/WHZYHwNc+Hy/XpF8Cc18gu21cUynVEeYQzFSeigDRMeBQ3PGAyjVDPIob7YlGwA==} ->>>>>>> main peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -2865,15 +2869,15 @@ 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==} + '@react-stately/utils@3.10.3': + resolution: {integrity: sha512-moClv7MlVSHpbYtQIkm0Cx+on8Pgt1XqtPx6fy9rQFb2DNc9u1G3AUVnqA17buOkH1vLxAtX4MedlxMWyRCYYA==} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - '@react-types/shared@3.23.1': - resolution: {integrity: sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==} + '@react-types/shared@3.24.1': + resolution: {integrity: sha512-AUQeGYEm/zDTN6zLzdXolDxz3Jk5dDL7f506F07U8tBwxNNI3WRdhU84G0/AaFikOZzDXhOZDr3MhQMzyE7Ydw==} peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 '@remirror/core-constants@2.0.2': resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} @@ -3515,36 +3519,31 @@ packages: react: '>=16.8' react-dom: '>=16.8' - '@tanstack/react-virtual@3.5.1': - resolution: {integrity: sha512-jIsuhfgy8GqA67PdWqg73ZB2LFE+HD9hjWL1L6ifEIZVyZVAKpYmgUG4WsKQ005aEyImJmbuimPiEvc57IY0Aw==} + '@tanstack/react-virtual@3.10.8': + resolution: {integrity: sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 -<<<<<<< HEAD - '@tanstack/react-virtual@3.8.1': - resolution: {integrity: sha512-dP5a7giEM4BQWLJ7K07ToZv8rF51mzbrBMkf0scg1QNYuFx3utnPUBPUHdzaowZhIez1K2XS78amuzD+YGRA5Q==} + '@tanstack/react-virtual@3.5.1': + resolution: {integrity: sha512-jIsuhfgy8GqA67PdWqg73ZB2LFE+HD9hjWL1L6ifEIZVyZVAKpYmgUG4WsKQ005aEyImJmbuimPiEvc57IY0Aw==} 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==} -======= '@tanstack/table-core@8.20.1': resolution: {integrity: sha512-5Ly5TIRHnWH7vSDell9B/OVyV380qqIJVg7H7R7jU4fPEmOD4smqAX7VRflpYI09srWR8aj5OLD2Ccs1pI5mTg==} ->>>>>>> main engines: {node: '>=12'} + '@tanstack/virtual-core@3.10.8': + resolution: {integrity: sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==} + '@tanstack/virtual-core@3.5.1': resolution: {integrity: sha512-046+AUSiDru/V9pajE1du8WayvBKeCvJ2NmKPy/mR8/SbKKrqmSbj7LJBfXE+nSq4f5TBXvnCzu0kcYebI9WdQ==} '@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: @@ -4827,15 +4826,6 @@ packages: supports-color: optional: true - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} @@ -5248,6 +5238,11 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@10.3.15: + resolution: {integrity: sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==} + engines: {node: '>=16 || 14 >=14.18'} + hasBin: true + glob@10.3.4: resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==} engines: {node: '>=16 || 14 >=14.17'} @@ -5487,17 +5482,6 @@ packages: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} -<<<<<<< HEAD - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - -======= ->>>>>>> main import-in-the-middle@1.7.1: resolution: {integrity: sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg==} @@ -6249,6 +6233,10 @@ packages: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} + minipass@7.1.1: + resolution: {integrity: sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==} + engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -6323,8 +6311,8 @@ packages: next-nprogress-bar@2.3.13: resolution: {integrity: sha512-eJQDvPSUwZ2yRyNKqAH6xTFI4RtIFvs+qtY8essTtfooyaxLGmkanEO7vL83U166bullMtVpixD6FQk5SQkhMw==} - next@14.2.3: - resolution: {integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==} + next@14.2.12: + resolution: {integrity: sha512-cDOtUSIeoOvt1skKNihdExWMTybx3exnvbFbb9ecZDIxlvIbREQzt9A5Km3Zn3PfU+IFjyYGsHS+lN9VInAGKA==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -6341,8 +6329,8 @@ packages: sass: optional: true - next@14.2.5: - resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} + next@14.2.3: + resolution: {integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -6573,34 +6561,37 @@ packages: peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} - pg-boss@10.1.1: - resolution: {integrity: sha512-2t7gz5nEUYFabj8czWWFRUSyPDQ5t+K/EF5l9Q5lHn2iwyPPKgIfwK+8LKgRfyHRUePTDQhogsGcwOlNczfZ5Q==} + pg-boss@10.1.3: + resolution: {integrity: sha512-Jb/dxMv3qUezWks8XIn4XGHkapPLpUBRBiUs5nTeaRYYy0vjOlGBPu6V35TnlkfVNpebHYQy4hseAIeRR42rdQ==} engines: {node: '>=20'} pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} - pg-connection-string@2.6.4: - resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} + pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-pool@3.6.2: - resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==} + pg-pool@3.7.0: + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} peerDependencies: pg: '>=8.0' pg-protocol@1.6.1: resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} + pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.11.5: - resolution: {integrity: sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==} + pg@8.13.0: + resolution: {integrity: sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==} engines: {node: '>= 8.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -7651,8 +7642,8 @@ packages: resolution: {integrity: sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==} engines: {node: '>=16'} - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true @@ -9017,7 +9008,7 @@ snapshots: '@blocknote/react@0.12.4(@tiptap/pm@2.4.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@blocknote/core': 0.12.4 - '@floating-ui/react': 0.26.19(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + '@floating-ui/react': 0.26.15(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@mantine/core': 7.9.2(@mantine/hooks@7.9.2(react@18.3.1))(@types/react@18.3.3)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@mantine/hooks': 7.9.2(react@18.3.1) '@mantine/utils': 6.0.21(react@18.3.1) @@ -9360,57 +9351,21 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true -<<<<<<< HEAD - '@eslint-community/eslint-utils@4.4.0(eslint@9.8.0)': - dependencies: - eslint: 9.8.0 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.11.0': {} - - '@eslint/config-array@0.17.1': - dependencies: - '@eslint/object-schema': 2.1.4 - debug: 4.3.6 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@eslint/eslintrc@3.1.0': - dependencies: - ajv: 6.12.6 - debug: 4.3.6 - espree: 10.1.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.8.0': {} - - '@eslint/object-schema@2.1.4': {} - -======= ->>>>>>> main '@faker-js/faker@8.4.1': {} '@floating-ui/core@1.6.2': dependencies: - '@floating-ui/utils': 0.2.4 + '@floating-ui/utils': 0.2.3 '@floating-ui/dom@1.6.3': dependencies: '@floating-ui/core': 1.6.2 - '@floating-ui/utils': 0.2.3 + '@floating-ui/utils': 0.2.2 '@floating-ui/dom@1.6.5': dependencies: '@floating-ui/core': 1.6.2 - '@floating-ui/utils': 0.2.4 + '@floating-ui/utils': 0.2.3 '@floating-ui/react-dom@1.3.0(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': dependencies: @@ -9424,7 +9379,7 @@ 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)': + '@floating-ui/react-dom@2.1.2(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/dom': 1.6.5 react: 18.3.1 @@ -9438,23 +9393,33 @@ 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)': + '@floating-ui/react@0.26.15(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.0.9(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + '@floating-ui/utils': 0.2.2 + react: 18.3.1 + react-dom: 18.2.0(react@18.3.1) + tabbable: 6.2.0 + + '@floating-ui/react@0.26.24(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 + '@floating-ui/react-dom': 2.1.2(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + '@floating-ui/utils': 0.2.8 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/utils@0.2.8': {} - '@floating-ui/vue@1.0.7(vue@3.4.30(typescript@5.4.5))': + '@floating-ui/vue@1.0.7(vue@3.4.30(typescript@5.5.4))': dependencies: '@floating-ui/dom': 1.6.5 '@floating-ui/utils': 0.2.3 - vue-demi: 0.14.8(vue@3.4.30(typescript@5.4.5)) + vue-demi: 0.14.8(vue@3.4.30(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -9474,12 +9439,12 @@ 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)': + '@headlessui/react@2.1.8(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) + '@floating-ui/react': 0.26.24(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + '@react-aria/focus': 3.18.2(react@18.3.1) + '@react-aria/interactions': 3.22.2(react@18.3.1) + '@tanstack/react-virtual': 3.10.8(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) @@ -9487,10 +9452,10 @@ snapshots: dependencies: tailwindcss: 3.4.3 - '@headlessui/vue@1.7.22(vue@3.4.30(typescript@5.4.5))': + '@headlessui/vue@1.7.22(vue@3.4.30(typescript@5.5.4))': dependencies: - '@tanstack/vue-virtual': 3.7.0(vue@3.4.30(typescript@5.4.5)) - vue: 3.4.30(typescript@5.4.5) + '@tanstack/vue-virtual': 3.7.0(vue@3.4.30(typescript@5.5.4)) + vue: 3.4.30(typescript@5.5.4) '@hexagon/base64@1.1.28': {} @@ -9690,7 +9655,7 @@ snapshots: '@mantine/core@7.9.2(@mantine/hooks@7.9.2(react@18.3.1))(@types/react@18.3.3)(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) + '@floating-ui/react': 0.26.15(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@mantine/hooks': 7.9.2(react@18.3.1) clsx: 2.1.0 react: 18.3.1 @@ -9726,10 +9691,10 @@ snapshots: - supports-color optional: true - '@next-auth/prisma-adapter@1.0.7(@prisma/client@5.14.0(prisma@5.14.0))(next-auth@4.24.7(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))': + '@next-auth/prisma-adapter@1.0.7(@prisma/client@5.14.0(prisma@5.14.0))(next-auth@4.24.7(next@14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))': dependencies: '@prisma/client': 5.14.0(prisma@5.14.0) - next-auth: 4.24.7(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + next-auth: 4.24.7(next@14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@next/bundle-analyzer@14.2.3': dependencies: @@ -9738,64 +9703,64 @@ snapshots: - bufferutil - utf-8-validate + '@next/env@14.2.12': {} + '@next/env@14.2.3': {} - '@next/env@14.2.5': {} + '@next/swc-darwin-arm64@14.2.12': + optional: true '@next/swc-darwin-arm64@14.2.3': optional: true - '@next/swc-darwin-arm64@14.2.5': + '@next/swc-darwin-x64@14.2.12': optional: true '@next/swc-darwin-x64@14.2.3': optional: true - '@next/swc-darwin-x64@14.2.5': + '@next/swc-linux-arm64-gnu@14.2.12': optional: true '@next/swc-linux-arm64-gnu@14.2.3': optional: true - '@next/swc-linux-arm64-gnu@14.2.5': + '@next/swc-linux-arm64-musl@14.2.12': optional: true '@next/swc-linux-arm64-musl@14.2.3': optional: true - '@next/swc-linux-arm64-musl@14.2.5': + '@next/swc-linux-x64-gnu@14.2.12': optional: true '@next/swc-linux-x64-gnu@14.2.3': optional: true - '@next/swc-linux-x64-gnu@14.2.5': + '@next/swc-linux-x64-musl@14.2.12': optional: true '@next/swc-linux-x64-musl@14.2.3': optional: true - '@next/swc-linux-x64-musl@14.2.5': + '@next/swc-win32-arm64-msvc@14.2.12': optional: true '@next/swc-win32-arm64-msvc@14.2.3': optional: true - '@next/swc-win32-arm64-msvc@14.2.5': + '@next/swc-win32-ia32-msvc@14.2.12': optional: true '@next/swc-win32-ia32-msvc@14.2.3': optional: true - '@next/swc-win32-ia32-msvc@14.2.5': + '@next/swc-win32-x64-msvc@14.2.12': optional: true '@next/swc-win32-x64-msvc@14.2.3': optional: true - '@next/swc-win32-x64-msvc@14.2.5': - optional: true - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -10926,42 +10891,38 @@ snapshots: '@radix-ui/rect@1.1.0': {} -<<<<<<< HEAD - '@react-aria/focus@3.17.1(react@18.3.1)': + '@react-aria/focus@3.18.2(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) + '@react-aria/interactions': 3.22.2(react@18.3.1) + '@react-aria/utils': 3.25.2(react@18.3.1) + '@react-types/shared': 3.24.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)': + '@react-aria/interactions@3.22.2(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) + '@react-aria/ssr': 3.9.5(react@18.3.1) + '@react-aria/utils': 3.25.2(react@18.3.1) + '@react-types/shared': 3.24.1(react@18.3.1) '@swc/helpers': 0.5.11 react: 18.3.1 - '@react-aria/ssr@3.9.4(react@18.3.1)': + '@react-aria/ssr@3.9.5(react@18.3.1)': dependencies: '@swc/helpers': 0.5.11 react: 18.3.1 - '@react-aria/utils@3.24.1(react@18.3.1)': + '@react-aria/utils@3.25.2(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) + '@react-aria/ssr': 3.9.5(react@18.3.1) + '@react-stately/utils': 3.10.3(react@18.3.1) + '@react-types/shared': 3.24.1(react@18.3.1) '@swc/helpers': 0.5.11 clsx: 2.1.1 react: 18.3.1 - '@react-email/body@0.0.9(react@18.3.1)': -======= '@react-email/body@0.0.10(react@18.3.1)': ->>>>>>> main dependencies: react: 18.3.1 @@ -11180,12 +11141,12 @@ snapshots: '@react-pdf/types@2.5.0': {} - '@react-stately/utils@3.10.1(react@18.3.1)': + '@react-stately/utils@3.10.3(react@18.3.1)': dependencies: '@swc/helpers': 0.5.11 react: 18.3.1 - '@react-types/shared@3.23.1(react@18.3.1)': + '@react-types/shared@3.24.1(react@18.3.1)': dependencies: react: 18.3.1 @@ -11268,14 +11229,14 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.18.0': optional: true - '@scalar/api-client-modal@0.0.16(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/api-client-modal@0.0.16(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: - '@scalar/client-app': 0.1.14(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - '@scalar/components': 0.12.4(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/client-app': 0.1.14(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/components': 0.12.4(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) '@scalar/oas-utils': 0.2.5 '@scalar/object-utils': 1.1.2 - vue: 3.4.30(typescript@5.4.5) - vue-router: 4.4.0(vue@3.4.30(typescript@5.4.5)) + vue: 3.4.30(typescript@5.5.4) + vue-router: 4.4.0(vue@3.4.30(typescript@5.5.4)) transitivePeerDependencies: - '@jest/globals' - '@types/bun' @@ -11288,24 +11249,24 @@ snapshots: - typescript - vitest - '@scalar/api-client@1.3.19(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/api-client@1.3.19(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: - '@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/components': 0.12.4(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.5.4)) + '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.5.4)) + '@scalar/components': 0.12.4(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) '@scalar/oas-utils': 0.2.5 '@scalar/openapi-parser': 0.7.1 - '@scalar/themes': 0.9.8(typescript@5.4.5) - '@scalar/use-codemirror': 0.11.4(typescript@5.4.5) - '@scalar/use-toasts': 0.7.4(typescript@5.4.5) - '@scalar/use-tooltip': 1.0.1(typescript@5.4.5) - '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.4.5)) + '@scalar/themes': 0.9.8(typescript@5.5.4) + '@scalar/use-codemirror': 0.11.4(typescript@5.5.4) + '@scalar/use-toasts': 0.7.4(typescript@5.5.4) + '@scalar/use-tooltip': 1.0.1(typescript@5.5.4) + '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.5.4)) axios: 1.7.2 httpsnippet-lite: 3.0.5 nanoid: 5.0.7 pretty-bytes: 6.1.1 pretty-ms: 8.0.0 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - '@jest/globals' - '@types/bun' @@ -11317,21 +11278,21 @@ snapshots: - typescript - vitest - '@scalar/api-reference@1.24.20(postcss@8.4.40)(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/api-reference@1.24.20(postcss@8.4.40)(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: - '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.4.5)) - '@scalar/api-client': 1.3.19(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - '@scalar/api-client-modal': 0.0.16(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - '@scalar/components': 0.12.4(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.5.4)) + '@scalar/api-client': 1.3.19(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/api-client-modal': 0.0.16(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/components': 0.12.4(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) '@scalar/oas-utils': 0.2.5 '@scalar/openapi-parser': 0.7.1 '@scalar/snippetz': 0.1.6 - '@scalar/themes': 0.9.8(typescript@5.4.5) - '@scalar/use-toasts': 0.7.4(typescript@5.4.5) - '@scalar/use-tooltip': 1.0.1(typescript@5.4.5) + '@scalar/themes': 0.9.8(typescript@5.5.4) + '@scalar/use-toasts': 0.7.4(typescript@5.5.4) + '@scalar/use-tooltip': 1.0.1(typescript@5.5.4) '@unhead/schema': 1.9.14 - '@unhead/vue': 1.9.14(vue@3.4.30(typescript@5.4.5)) - '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.4.5)) + '@unhead/vue': 1.9.14(vue@3.4.30(typescript@5.5.4)) + '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.5.4)) axios: 1.7.2 fuse.js: 7.0.0 github-slugger: 2.0.0 @@ -11339,7 +11300,7 @@ snapshots: postcss-nested: 6.0.1(postcss@8.4.40) unhead: 1.9.14 unified: 11.0.4 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - '@jest/globals' - '@types/bun' @@ -11353,26 +11314,26 @@ snapshots: - typescript - vitest - '@scalar/client-app@0.1.14(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/client-app@0.1.14(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: '@headlessui/tailwindcss': 0.2.1(tailwindcss@3.4.3) - '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.4.5)) - '@scalar/components': 0.12.4(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - '@scalar/draggable': 0.1.3(typescript@5.4.5) + '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.5.4)) + '@scalar/components': 0.12.4(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/draggable': 0.1.3(typescript@5.5.4) '@scalar/oas-utils': 0.2.5 '@scalar/object-utils': 1.1.2 '@scalar/openapi-parser': 0.7.1 - '@scalar/use-toasts': 0.7.4(typescript@5.4.5) - '@scalar/use-tooltip': 1.0.1(typescript@5.4.5) - '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.4.5)) + '@scalar/use-toasts': 0.7.4(typescript@5.5.4) + '@scalar/use-tooltip': 1.0.1(typescript@5.5.4) + '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.5.4)) axios: 1.7.2 - cva: 1.0.0-beta.1(typescript@5.4.5) + cva: 1.0.0-beta.1(typescript@5.5.4) js-cookie: 3.0.5 nanoid: 5.0.7 pretty-bytes: 6.1.1 pretty-ms: 8.0.0 - vue: 3.4.30(typescript@5.4.5) - vue-router: 4.4.0(vue@3.4.30(typescript@5.4.5)) + vue: 3.4.30(typescript@5.5.4) + vue-router: 4.4.0(vue@3.4.30(typescript@5.5.4)) zod: 3.23.8 transitivePeerDependencies: - '@jest/globals' @@ -11408,20 +11369,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@scalar/components@0.12.4(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/components@0.12.4(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: - '@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)) + '@floating-ui/utils': 0.2.2 + '@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.5.4)) + '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.5.4)) '@scalar/code-highlight': 0.0.5 '@scalar/oas-utils': 0.2.5 '@storybook/test': 8.1.11(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.4.5)) - cva: 1.0.0-beta.1(typescript@5.4.5) + '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.5.4)) + cva: 1.0.0-beta.1(typescript@5.5.4) nanoid: 5.0.7 - radix-vue: 1.8.5(vue@3.4.30(typescript@5.4.5)) + radix-vue: 1.8.5(vue@3.4.30(typescript@5.5.4)) tailwind-merge: 2.4.0 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - '@jest/globals' - '@types/bun' @@ -11433,16 +11394,16 @@ snapshots: - typescript - vitest - '@scalar/draggable@0.1.3(typescript@5.4.5)': + '@scalar/draggable@0.1.3(typescript@5.5.4)': dependencies: - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - typescript - '@scalar/nextjs-api-reference@0.4.18(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(postcss@8.4.40)(react-dom@18.2.0(react@18.3.1))(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/nextjs-api-reference@0.4.18(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(postcss@8.4.40)(react-dom@18.2.0(react@18.3.1))(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: - '@scalar/api-reference': 1.24.20(postcss@8.4.40)(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - next: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + '@scalar/api-reference': 1.24.20(postcss@8.4.40)(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + next: 14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) react: 18.3.1 transitivePeerDependencies: - '@babel/core' @@ -11518,13 +11479,13 @@ snapshots: '@scalar/snippetz-plugin-node-ofetch': 0.1.1 '@scalar/snippetz-plugin-node-undici': 0.1.6 - '@scalar/themes@0.9.8(typescript@5.4.5)': + '@scalar/themes@0.9.8(typescript@5.5.4)': dependencies: - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - typescript - '@scalar/use-codemirror@0.11.4(typescript@5.4.5)': + '@scalar/use-codemirror@0.11.4(typescript@5.5.4)': dependencies: '@codemirror/autocomplete': 6.16.3(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.2)(@lezer/common@1.2.1) '@codemirror/commands': 6.6.0 @@ -11541,25 +11502,25 @@ snapshots: '@replit/codemirror-css-color-picker': 6.1.1(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.2) '@uiw/codemirror-themes': 4.22.2(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.2) codemirror: 6.0.1(@lezer/common@1.2.1) - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) optionalDependencies: y-codemirror.next: 0.3.5(@codemirror/state@6.4.1)(@codemirror/view@6.28.2)(yjs@13.6.15) yjs: 13.6.15 transitivePeerDependencies: - typescript - '@scalar/use-toasts@0.7.4(typescript@5.4.5)': + '@scalar/use-toasts@0.7.4(typescript@5.5.4)': dependencies: nanoid: 5.0.7 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) vue-sonner: 1.1.3 transitivePeerDependencies: - typescript - '@scalar/use-tooltip@1.0.1(typescript@5.4.5)': + '@scalar/use-tooltip@1.0.1(typescript@5.5.4)': dependencies: - radix-vue: 1.8.5(vue@3.4.30(typescript@5.4.5)) - vue: 3.4.30(typescript@5.4.5) + radix-vue: 1.8.5(vue@3.4.30(typescript@5.5.4)) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - '@vue/composition-api' - typescript @@ -11666,7 +11627,7 @@ snapshots: '@sentry/types': 8.19.0 '@sentry/utils': 8.19.0 - '@sentry/nextjs@8.19.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.92.0)': + '@sentry/nextjs@8.19.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(next@14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.92.0)': dependencies: '@opentelemetry/instrumentation-http': 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.25.1 @@ -11680,7 +11641,7 @@ snapshots: '@sentry/vercel-edge': 8.19.0 '@sentry/webpack-plugin': 2.20.1(webpack@5.92.0) chalk: 3.0.0 - next: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + next: 14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) resolve: 1.22.8 rollup: 3.29.4 stacktrace-parser: 0.1.10 @@ -12221,18 +12182,18 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.6.2 - '@t3-oss/env-core@0.10.1(typescript@5.4.5)(zod@3.23.8)': + '@t3-oss/env-core@0.10.1(typescript@5.5.4)(zod@3.23.8)': dependencies: zod: 3.23.8 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.4 - '@t3-oss/env-nextjs@0.10.1(typescript@5.4.5)(zod@3.23.8)': + '@t3-oss/env-nextjs@0.10.1(typescript@5.5.4)(zod@3.23.8)': dependencies: - '@t3-oss/env-core': 0.10.1(typescript@5.4.5)(zod@3.23.8) + '@t3-oss/env-core': 0.10.1(typescript@5.5.4)(zod@3.23.8) zod: 3.23.8 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.4 '@tailwindcss/typography@0.5.13(tailwindcss@3.4.3)': dependencies: @@ -12258,34 +12219,30 @@ snapshots: react: 18.3.1 react-dom: 18.2.0(react@18.3.1) - '@tanstack/react-virtual@3.5.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': + '@tanstack/react-virtual@3.10.8(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/virtual-core': 3.5.1 + '@tanstack/virtual-core': 3.10.8 react: 18.3.1 react-dom: 18.2.0(react@18.3.1) -<<<<<<< HEAD - '@tanstack/react-virtual@3.8.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': + '@tanstack/react-virtual@3.5.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/virtual-core': 3.8.1 + '@tanstack/virtual-core': 3.5.1 react: 18.3.1 react-dom: 18.2.0(react@18.3.1) - '@tanstack/table-core@8.19.2': {} -======= '@tanstack/table-core@8.20.1': {} ->>>>>>> main + + '@tanstack/virtual-core@3.10.8': {} '@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))': + '@tanstack/vue-virtual@3.7.0(vue@3.4.30(typescript@5.5.4))': dependencies: '@tanstack/virtual-core': 3.7.0 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) '@testing-library/dom@10.1.0': dependencies: @@ -12461,13 +12418,13 @@ snapshots: dependencies: '@trpc/server': 10.45.2 - '@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@10.45.2)(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': + '@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@10.45.2)(next@14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@trpc/client': 10.45.2(@trpc/server@10.45.2) '@trpc/react-query': 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@trpc/server': 10.45.2 - next: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + next: 14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(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) @@ -12689,13 +12646,13 @@ snapshots: dependencies: '@unhead/schema': 1.9.14 - '@unhead/vue@1.9.14(vue@3.4.30(typescript@5.4.5))': + '@unhead/vue@1.9.14(vue@3.4.30(typescript@5.5.4))': dependencies: '@unhead/schema': 1.9.14 '@unhead/shared': 1.9.14 hookable: 5.5.3 unhead: 1.9.14 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) '@vitest/expect@1.6.0': dependencies: @@ -12774,29 +12731,29 @@ snapshots: '@vue/shared': 3.4.30 csstype: 3.1.3 - '@vue/server-renderer@3.4.30(vue@3.4.30(typescript@5.4.5))': + '@vue/server-renderer@3.4.30(vue@3.4.30(typescript@5.5.4))': dependencies: '@vue/compiler-ssr': 3.4.30 '@vue/shared': 3.4.30 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) '@vue/shared@3.4.30': {} - '@vueuse/core@10.11.0(vue@3.4.30(typescript@5.4.5))': + '@vueuse/core@10.11.0(vue@3.4.30(typescript@5.5.4))': dependencies: '@types/web-bluetooth': 0.0.20 '@vueuse/metadata': 10.11.0 - '@vueuse/shared': 10.11.0(vue@3.4.30(typescript@5.4.5)) - vue-demi: 0.14.8(vue@3.4.30(typescript@5.4.5)) + '@vueuse/shared': 10.11.0(vue@3.4.30(typescript@5.5.4)) + vue-demi: 0.14.8(vue@3.4.30(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue '@vueuse/metadata@10.11.0': {} - '@vueuse/shared@10.11.0(vue@3.4.30(typescript@5.4.5))': + '@vueuse/shared@10.11.0(vue@3.4.30(typescript@5.5.4))': dependencies: - vue-demi: 0.14.8(vue@3.4.30(typescript@5.4.5)) + vue-demi: 0.14.8(vue@3.4.30(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -13840,11 +13797,11 @@ snapshots: csstype@3.1.3: {} - cva@1.0.0-beta.1(typescript@5.4.5): + cva@1.0.0-beta.1(typescript@5.5.4): dependencies: clsx: 2.0.0 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.4 d3-array@3.2.4: dependencies: @@ -13898,10 +13855,6 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.3.6: - dependencies: - ms: 2.1.2 - decimal.js-light@2.5.1: {} decode-named-character-reference@1.0.2: @@ -14145,67 +14098,6 @@ snapshots: esrecurse: 4.3.0 estraverse: 4.3.0 -<<<<<<< HEAD - eslint-scope@8.0.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.0.0: {} - - eslint@9.8.0: - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) - '@eslint-community/regexpp': 4.11.0 - '@eslint/config-array': 0.17.1 - '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.8.0 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.3.0 - '@nodelib/fs.walk': 1.2.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.6 - escape-string-regexp: 4.0.0 - eslint-scope: 8.0.2 - eslint-visitor-keys: 4.0.0 - espree: 10.1.0 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - - espree@10.1.0: - dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) - eslint-visitor-keys: 4.0.0 - - esquery@1.6.0: - dependencies: - estraverse: 5.3.0 - -======= ->>>>>>> main esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -14426,6 +14318,14 @@ snapshots: glob-to-regexp@0.4.1: {} + glob@10.3.15: + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.4 + minipass: 7.1.1 + path-scurry: 1.11.1 + glob@10.3.4: dependencies: foreground-child: 3.1.1 @@ -14804,16 +14704,6 @@ snapshots: ignore@5.3.1: {} -<<<<<<< HEAD - ignore@5.3.2: {} - - import-fresh@3.3.0: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - -======= ->>>>>>> main import-in-the-middle@1.7.1: dependencies: acorn: 8.11.3 @@ -15034,7 +14924,7 @@ snapshots: klona@2.0.6: {} - knip@5.17.4(@types/node@20.12.12)(typescript@5.4.5): + knip@5.17.4(@types/node@20.12.12)(typescript@5.5.4): dependencies: '@ericcornelissen/bash-parser': 0.5.2 '@nodelib/fs.walk': 2.0.0 @@ -15053,7 +14943,7 @@ snapshots: smol-toml: 1.1.4 strip-json-comments: 5.0.1 summary: 2.1.0 - typescript: 5.4.5 + typescript: 5.5.4 zod: 3.23.8 zod-validation-error: 3.3.0(zod@3.23.8) @@ -15884,6 +15774,8 @@ snapshots: minipass@5.0.0: optional: true + minipass@7.1.1: {} + minipass@7.1.2: {} minizlib@2.1.2: @@ -15931,13 +15823,13 @@ snapshots: neo-async@2.6.2: {} - next-auth@4.24.7(next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1): + next-auth@4.24.7(next@14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(nodemailer@6.9.14)(react-dom@18.2.0(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.24.5 '@panva/hkdf': 1.1.1 cookie: 0.5.0 jose: 4.15.5 - next: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + next: 14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) oauth: 0.9.15 openid-client: 5.6.5 preact: 10.22.0 @@ -15952,9 +15844,9 @@ snapshots: dependencies: nprogress: 0.2.0 - next@14.2.3(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1): + next@14.2.12(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 14.2.3 + '@next/env': 14.2.12 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001620 @@ -15964,23 +15856,23 @@ snapshots: react-dom: 18.2.0(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.24.5)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.3 - '@next/swc-darwin-x64': 14.2.3 - '@next/swc-linux-arm64-gnu': 14.2.3 - '@next/swc-linux-arm64-musl': 14.2.3 - '@next/swc-linux-x64-gnu': 14.2.3 - '@next/swc-linux-x64-musl': 14.2.3 - '@next/swc-win32-arm64-msvc': 14.2.3 - '@next/swc-win32-ia32-msvc': 14.2.3 - '@next/swc-win32-x64-msvc': 14.2.3 + '@next/swc-darwin-arm64': 14.2.12 + '@next/swc-darwin-x64': 14.2.12 + '@next/swc-linux-arm64-gnu': 14.2.12 + '@next/swc-linux-arm64-musl': 14.2.12 + '@next/swc-linux-x64-gnu': 14.2.12 + '@next/swc-linux-x64-musl': 14.2.12 + '@next/swc-win32-arm64-msvc': 14.2.12 + '@next/swc-win32-ia32-msvc': 14.2.12 + '@next/swc-win32-x64-msvc': 14.2.12 '@opentelemetry/api': 1.9.0 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - next@14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1): + next@14.2.3(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 14.2.5 + '@next/env': 14.2.3 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001620 @@ -15990,15 +15882,15 @@ snapshots: react-dom: 18.2.0(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.24.5)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.5 - '@next/swc-darwin-x64': 14.2.5 - '@next/swc-linux-arm64-gnu': 14.2.5 - '@next/swc-linux-arm64-musl': 14.2.5 - '@next/swc-linux-x64-gnu': 14.2.5 - '@next/swc-linux-x64-musl': 14.2.5 - '@next/swc-win32-arm64-msvc': 14.2.5 - '@next/swc-win32-ia32-msvc': 14.2.5 - '@next/swc-win32-x64-msvc': 14.2.5 + '@next/swc-darwin-arm64': 14.2.3 + '@next/swc-darwin-x64': 14.2.3 + '@next/swc-linux-arm64-gnu': 14.2.3 + '@next/swc-linux-arm64-musl': 14.2.3 + '@next/swc-linux-x64-gnu': 14.2.3 + '@next/swc-linux-x64-musl': 14.2.3 + '@next/swc-win32-arm64-msvc': 14.2.3 + '@next/swc-win32-ia32-msvc': 14.2.3 + '@next/swc-win32-x64-msvc': 14.2.3 '@opentelemetry/api': 1.9.0 transitivePeerDependencies: - '@babel/core' @@ -16194,10 +16086,10 @@ snapshots: peberminta@0.9.0: {} - pg-boss@10.1.1: + pg-boss@10.1.3: dependencies: cron-parser: 4.9.0 - pg: 8.11.5 + pg: 8.13.0 serialize-error: 8.1.0 transitivePeerDependencies: - pg-native @@ -16205,16 +16097,18 @@ snapshots: pg-cloudflare@1.1.1: optional: true - pg-connection-string@2.6.4: {} + pg-connection-string@2.7.0: {} pg-int8@1.0.1: {} - pg-pool@3.6.2(pg@8.11.5): + pg-pool@3.7.0(pg@8.13.0): dependencies: - pg: 8.11.5 + pg: 8.13.0 pg-protocol@1.6.1: {} + pg-protocol@1.7.0: {} + pg-types@2.2.0: dependencies: pg-int8: 1.0.1 @@ -16223,11 +16117,11 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.11.5: + pg@8.13.0: dependencies: - pg-connection-string: 2.6.4 - pg-pool: 3.6.2(pg@8.11.5) - pg-protocol: 1.6.1 + pg-connection-string: 2.7.0 + pg-pool: 3.7.0(pg@8.13.0) + pg-protocol: 1.7.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: @@ -16390,12 +16284,12 @@ snapshots: dependencies: '@prisma/client': 5.14.0(prisma@5.14.0) - prisma-json-types-generator@3.0.4(prisma@5.14.0)(typescript@5.4.5): + prisma-json-types-generator@3.0.4(prisma@5.14.0)(typescript@5.5.4): dependencies: '@prisma/generator-helper': 5.9.1 prisma: 5.14.0 tslib: 2.6.2 - typescript: 5.4.5 + typescript: 5.5.4 prisma@5.14.0: dependencies: @@ -16559,20 +16453,20 @@ snapshots: quick-format-unescaped@4.0.4: {} - radix-vue@1.8.5(vue@3.4.30(typescript@5.4.5)): + radix-vue@1.8.5(vue@3.4.30(typescript@5.5.4)): dependencies: '@floating-ui/dom': 1.6.5 - '@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.4.5)) + '@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.5.4)) '@internationalized/date': 3.5.4 '@internationalized/number': 3.5.3 - '@tanstack/vue-virtual': 3.7.0(vue@3.4.30(typescript@5.4.5)) - '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.4.5)) - '@vueuse/shared': 10.11.0(vue@3.4.30(typescript@5.4.5)) + '@tanstack/vue-virtual': 3.7.0(vue@3.4.30(typescript@5.5.4)) + '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.5.4)) + '@vueuse/shared': 10.11.0(vue@3.4.30(typescript@5.5.4)) aria-hidden: 1.2.4 defu: 6.1.4 fast-deep-equal: 3.1.3 nanoid: 5.0.7 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - '@vue/composition-api' @@ -17314,7 +17208,7 @@ snapshots: dependencies: '@jridgewell/gen-mapping': 0.3.5 commander: 4.1.1 - glob: 10.4.5 + glob: 10.3.15 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.6 @@ -17496,7 +17390,7 @@ snapshots: type-fest@4.18.2: {} - typescript@5.4.5: {} + typescript@5.5.4: {} ua-parser-js@1.0.37: {} @@ -17810,26 +17704,26 @@ snapshots: vlq@0.2.3: {} - vue-demi@0.14.8(vue@3.4.30(typescript@5.4.5)): + vue-demi@0.14.8(vue@3.4.30(typescript@5.5.4)): dependencies: - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) - vue-router@4.4.0(vue@3.4.30(typescript@5.4.5)): + vue-router@4.4.0(vue@3.4.30(typescript@5.5.4)): dependencies: '@vue/devtools-api': 6.6.3 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) vue-sonner@1.1.3: {} - vue@3.4.30(typescript@5.4.5): + vue@3.4.30(typescript@5.5.4): dependencies: '@vue/compiler-dom': 3.4.30 '@vue/compiler-sfc': 3.4.30 '@vue/runtime-dom': 3.4.30 - '@vue/server-renderer': 3.4.30(vue@3.4.30(typescript@5.4.5)) + '@vue/server-renderer': 3.4.30(vue@3.4.30(typescript@5.5.4)) '@vue/shared': 3.4.30 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.4 w3c-keyname@2.2.8: {} From 79b77e5e3135dd4f0512441e7659b3fc48cad46e Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Fri, 20 Sep 2024 08:51:42 +0530 Subject: [PATCH 39/60] feat: update schema --- prisma/schema.prisma | 66 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b107fea8e..fafbe2ac7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -233,6 +233,7 @@ model Member { updates Update[] dataRooms DataRoomRecipient[] UpdateRecipient UpdateRecipient[] + SafeSignerMember SafeSignerMember[] @@unique([companyId, userId]) @@index([companyId]) @@ -296,13 +297,13 @@ model Stakeholder { investments Investment[] shares Share[] options Option[] - safes Safe[] convertibleNotes ConvertibleNote[] updates UpdateRecipient[] dataRooms DataRoomRecipient[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + SafeSignerStakeholder SafeSignerStakeholder[] @@index([companyId]) } @@ -844,12 +845,15 @@ model Safe { documents Document[] - stakeholderId String - stakeholder Stakeholder @relation(fields: [stakeholderId], references: [id], onDelete: Cascade) - companyId String company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) + signer SafeSignerMember @relation(fields: [safeSignerMemberId], references: [id]) + safeSignerMemberId String + + signerStakeholder SafeSignerStakeholder @relation(fields: [safeSignerStakeholderId], references: [id]) + safeSignerStakeholderId String + bankAccountId String bankAccount BankAccount @relation(fields: [bankAccountId], references: [id], onDelete: Cascade) @@ -860,8 +864,31 @@ model Safe { @@unique([publicId, companyId]) @@index([companyId]) - @@index([stakeholderId]) @@index([bankAccountId]) + @@index([safeSignerMemberId]) + @@index([safeSignerStakeholderId]) +} + +model SafeSignerMember { + id String @id @default(cuid()) + + member Member @relation(fields: [memberId], references: [id]) + memberId String + Safe Safe[] + fields DocumentCustomField[] + + @@index([memberId]) +} + +model SafeSignerStakeholder { + id String @id @default(cuid()) + + stakeholder Stakeholder @relation(fields: [stakeholderId], references: [id]) + stakeholderId String + Safe Safe[] + fields DocumentCustomField[] + + @@index([stakeholderId]) } enum ConvertibleStatusEnum { @@ -1152,3 +1179,28 @@ model AccessToken { @@index([userId]) @@index([typeEnum, clientId]) } + +model DocumentCustomField { + id String @id @default(cuid()) + name String + type FieldTypes @default(TEXT) + defaultValue String @default("") + readOnly Boolean @default(false) + required Boolean @default(false) + prefilledValue String? + + /// [TemplateFieldMeta] + meta Json @default("{}") + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + safeSignerStakeholder SafeSignerStakeholder? @relation(fields: [safeSignerStakeholderId], references: [id]) + safeSignerStakeholderId String? + + safeSignerMember SafeSignerMember? @relation(fields: [safeSignerMemberId], references: [id]) + safeSignerMemberId String? + + @@index([safeSignerStakeholderId]) + @@index([safeSignerMemberId]) +} From cb030b29b4f2148974789d5cedd5834d8a8aa068 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Tue, 24 Sep 2024 21:27:30 +0530 Subject: [PATCH 40/60] feat: add endpoint creator --- src/server/api/api-client.ts | 115 +++++++++++++++++++++++ src/server/api/utils/endpoint-creator.ts | 3 +- 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/server/api/api-client.ts diff --git a/src/server/api/api-client.ts b/src/server/api/api-client.ts new file mode 100644 index 000000000..1543f7df6 --- /dev/null +++ b/src/server/api/api-client.ts @@ -0,0 +1,115 @@ +import { env } from "@/env"; +import type { + RouteConfigToTypedResponse, + createRoute, + z, +} from "@hono/zod-openapi"; + +// biome-ignore lint/suspicious/noExplicitAny: +type ExtractParams = T extends { params: z.ZodType } + ? z.infer + : never; +// biome-ignore lint/suspicious/noExplicitAny: +type ExtractQuery = T extends { query: z.ZodType } + ? z.infer + : never; + +type ExtractRequestBody = T extends { + body: { + content: { + "application/json": { + // biome-ignore lint/suspicious/noExplicitAny: + schema: z.ZodType; + }; + }; + }; +} + ? z.infer + : never; + +// biome-ignore lint/complexity/noBannedTypes: +type IfNever = [T] extends [never] ? {} : Obj; + +export type APIClientParams = IfNever< + ExtractParams, + { urlParams: ExtractParams } +> & + IfNever< + ExtractQuery, + { searchParams: ExtractQuery } + > & + IfNever< + ExtractRequestBody, + { json: ExtractRequestBody } + > & { + headers?: Headers; + }; + +type RouteConfig = Parameters[0]; + +export async function createClient( + method: U["method"], + url: U["path"], + params: APIClientParams, +) { + const path = buildPath(url, params); + const headers = buildHeaders(params.headers); + const requestOptions = buildRequestOptions(method, headers, params); + + const response = await fetch(path, requestOptions); + + return response.json() as RouteConfigToTypedResponse["_data"]; +} + +function interpolatePath( + path: string, + params: Record, +): string { + return path.replace(/{([^}]+)}/g, (_, key) => + encodeURIComponent(String(params[key])), + ); +} + +function buildHeaders(customHeaders?: HeadersInit): Headers { + const headers = new Headers(customHeaders); + headers.set("Content-Type", "application/json"); + return headers; +} + +function buildPath( + url: string, + params: APIClientParams, +): string { + console.log({ env: env.NEXT_PUBLIC_BASE_URL }); + + let path = interpolatePath( + `${"http://localhost:3000"}/api${url}`, + "urlParams" in params ? params.urlParams : {}, + ); + + if ("searchParams" in params) { + const queryString = new URLSearchParams(params.searchParams).toString(); + path += `?${queryString}`; + } + + return path; +} + +function buildRequestOptions( + method: string, + headers: Headers, + params: APIClientParams, +): RequestInit { + const requestOptions: RequestInit = { + method: method.toUpperCase(), + credentials: "include", + headers, + cache: "no-store", + }; + + if ("json" in params) { + requestOptions.body = JSON.stringify(params.json); + } + + return requestOptions; +} diff --git a/src/server/api/utils/endpoint-creator.ts b/src/server/api/utils/endpoint-creator.ts index d22d9f158..9f2092b58 100644 --- a/src/server/api/utils/endpoint-creator.ts +++ b/src/server/api/utils/endpoint-creator.ts @@ -25,6 +25,7 @@ const AuthHeaderSchema = z.object({ authorization: z .string() .regex(/^Bearer [a-zA-Z0-9_]+/) + .optional() .openapi({ description: "Bearer token to authorize the request", example: "Bearer api_x0X0x0X0x0X0x0X0x0X0x0X", @@ -92,7 +93,7 @@ const createApi = ( }; export const authMiddleware = (option?: accessTokenAuthMiddlewareOptions) => - some(sessionCookieAuthMiddleware(), accessTokenAuthMiddleware(option)); + some(accessTokenAuthMiddleware(option), sessionCookieAuthMiddleware()); export const ApiV1 = createApi("v1"); From 799a30bdb901de5a38ca3cb0257c2f22334a8778 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Tue, 24 Sep 2024 21:28:34 +0530 Subject: [PATCH 41/60] fix: script strategy --- src/components/public-env-script.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/public-env-script.tsx b/src/components/public-env-script.tsx index c1baa9cab..d38c24984 100644 --- a/src/components/public-env-script.tsx +++ b/src/components/public-env-script.tsx @@ -6,12 +6,13 @@ import Script from "next/script"; export function PublicEnvScript() { noStore(); const publicEnvs = JSON.stringify(getPublicEnv()); + return (