Skip to content

Commit

Permalink
feat: safe
Browse files Browse the repository at this point in the history
  • Loading branch information
G3root committed Oct 2, 2024
1 parent b640655 commit 6d20478
Show file tree
Hide file tree
Showing 20 changed files with 814 additions and 123 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"lodash-es": "^4.17.21",
"mime": "^4.0.3",
"nanoid": "^5.0.4",
"next": "^14.2.5",
"next": "^14.2.14",
"next-auth": "^4.24.7",
"next-nprogress-bar": "^2.3.13",
"nodemailer": "^6.9.14",
Expand Down
116 changes: 58 additions & 58 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

19 changes: 9 additions & 10 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -848,11 +848,11 @@ model Safe {
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
signer SafeSignerMember @relation(fields: [signerMemberId], references: [id])
signerMemberId String
signerMember SafeSignerMember @relation(fields: [signerMemberId], references: [id])
signerMemberId String @unique
signerStakeholder SafeSignerStakeholder @relation(fields: [signerStakeholderId], references: [id])
signerStakeholderId String
signerStakeholderId String @unique
bankAccountId String
bankAccount BankAccount @relation(fields: [bankAccountId], references: [id], onDelete: Cascade)
Expand All @@ -870,11 +870,11 @@ model Safe {
}

model SafeSignerMember {
id String @id @default(cuid())
id String @id @default(cuid())
status SafeSigningStatus @default(PENDING)
member Member @relation(fields: [memberId], references: [id])
memberId String
Safe Safe[]
safe Safe?
fields DocumentCustomField[]
SafeSigningToken SafeSigningToken[]
Expand All @@ -891,17 +891,16 @@ model SafeSignerStakeholder {
status SafeSigningStatus @default(PENDING)
stakeholder Stakeholder @relation(fields: [stakeholderId], references: [id])
stakeholderId String
Safe Safe[]
safe Safe?
fields DocumentCustomField[]
SafeSigningToken SafeSigningToken[]
@@index([stakeholderId])
}

model SafeSigningToken {
id String @id @default(cuid())
status SafeSigningStatus @default(PENDING)
token String @unique
id String @id @default(cuid())
token String @unique
signerMember SafeSignerMember? @relation(fields: [signerMemberId], references: [id])
signerMemberId String?
Expand Down
34 changes: 34 additions & 0 deletions src/app/(documents)/safe/[token]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { SafeSigningForm } from "@/components/safe/safe-signing-form";
import { SafeTemplateRenderer } from "@/components/safe/safe-template-render";
import { SigningFieldRenderer } from "@/components/signing-field/signing-field-renderer";
import { Button } from "@/components/ui/button";
import { SafeSigningFieldProvider } from "@/providers/safe-signing-field-provider";
import { api } from "@/trpc/server";

export default async function SafeSignPage({
params,
}: { params: { token: string } }) {
const fields = await api.safe.getSigningFields.query({ token: params.token });

return (
<SafeSigningFieldProvider safeId={fields.safeId} token={params.token}>
<div className="flex min-h-screen bg-gray-50">
<div className="flex h-full flex-grow flex-col">
<div className="mx-auto min-h-full w-full px-5 py-10 lg:px-8 2xl:max-w-screen-xl">
<div className="grid grid-cols-12">
<div className="col-span-12 ">
<SafeTemplateRenderer />
</div>
</div>
</div>
</div>
<div className="sticky top-0 flex min-h-full w-80 flex-col lg:border-l">
<SafeSigningForm>
<SigningFieldRenderer fields={fields.fields} />
<Button type="submit">Complete signing</Button>
</SafeSigningForm>
</div>
</div>
</SafeSigningFieldProvider>
);
}
47 changes: 33 additions & 14 deletions src/components/safe/form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"use client";

import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
Expand All @@ -24,16 +25,17 @@ import { z } from "zod";
import { PrePostSelector } from "./pre-post-selector";

// Safe document preview
import { PostMoneyCap, PostMoneyDiscount } from "@/components/safe/templates";
import { PostMoneyDiscount } from "@/components/safe/templates";
import { createSafe } from "@/server/api/client-handlers/safe";
import { PDFViewer } from "@react-pdf/renderer";
import { useMutation } from "@tanstack/react-query";
import { useSession } from "next-auth/react";

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(),
Expand All @@ -42,14 +44,19 @@ const SafeFormSchema = z.object({
mfn: z.boolean().optional().default(false),
proRata: z.boolean().optional().default(false),
issueDate: z.string().date(),
stakeholderId: z.string(),
memberId: z.string(),
signerStakeholderId: z.string(),
signerMemberId: z.string(),
bankAccountId: z.string(),
});

type SafeFormType = z.infer<typeof SafeFormSchema>;

const placeholder = "________________________________________";

export const SafeForm: React.FC<SafeFormProps> = ({ type }) => {
const { data: session } = useSession();
const { mutate } = useMutation(createSafe);

const form = useForm<SafeFormType>({
resolver: zodResolver(SafeFormSchema),
defaultValues: {
Expand All @@ -64,7 +71,10 @@ export const SafeForm: React.FC<SafeFormProps> = ({ type }) => {
const isSubmitting = form.formState.isSubmitting;

const handleSubmit = (data: SafeFormType) => {
console.log(data);
mutate({
json: { ...data },
urlParams: { companyId: session?.user.companyId ?? "" },
});
};

return (
Expand All @@ -83,7 +93,7 @@ export const SafeForm: React.FC<SafeFormProps> = ({ type }) => {
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-5 mt-5">
<FormField
control={form.control}
name="stakeholderId"
name="signerMemberId"
render={() => {
return (
<FormItem>
Expand All @@ -96,7 +106,7 @@ export const SafeForm: React.FC<SafeFormProps> = ({ type }) => {
</FormLabel>
<MemberSelector
onSelect={(value: string) => {
form.setValue("memberId", value);
form.setValue("signerMemberId", value);
}}
/>

Expand All @@ -108,14 +118,14 @@ export const SafeForm: React.FC<SafeFormProps> = ({ type }) => {

<FormField
control={form.control}
name="stakeholderId"
name="signerStakeholderId"
render={() => {
return (
<FormItem>
<FormLabel>Investor</FormLabel>
<InvestorSelector
onSelect={(value: string) => {
form.setValue("stakeholderId", value);
form.setValue("signerStakeholderId", value);
}}
/>
<FormMessage className="text-xs font-light" />
Expand All @@ -126,7 +136,7 @@ export const SafeForm: React.FC<SafeFormProps> = ({ type }) => {

<FormField
control={form.control}
name="stakeholderId"
name="bankAccountId"
render={() => {
return (
<FormItem>
Expand Down Expand Up @@ -336,8 +346,17 @@ export const SafeForm: React.FC<SafeFormProps> = ({ type }) => {
keywords: "YC, SAFE, Post Money, Discount",
}}
investor={{
name: "Puru Dahal",
email: "",
name: placeholder,
email: placeholder,
address: placeholder,
signature: placeholder,
title: placeholder,
}}
sender={{
email: placeholder,
name: placeholder,
title: placeholder,
signature: placeholder,
}}
investment={100000}
valuation={1000000}
Expand Down
40 changes: 40 additions & 0 deletions src/components/safe/safe-signing-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use client";

import type { SafeSigningFieldForm } from "@/providers/safe-signing-field-provider";
import { api } from "@/trpc/react";
import { useRouter } from "next/navigation";
import type { ReactNode } from "react";
import { useFormContext } from "react-hook-form";
import { toast } from "sonner";

interface SafeSigningFormProps {
children: ReactNode;
}

export function SafeSigningForm({ children }: SafeSigningFormProps) {
const { handleSubmit } = useFormContext<SafeSigningFieldForm>();
const router = useRouter();
const { mutate } = api.safe.sign.useMutation({
onSuccess() {
toast.success("🎉 Great job, you are done signing this document.");
router.push("/");
},
});

const onSubmit = (values: SafeSigningFieldForm) => {
mutate({
data: values.fieldValues,
safeId: values.safeId,
token: values.token,
});
};

return (
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-y-4 px-2 py-10"
>
{children}
</form>
);
}
35 changes: 35 additions & 0 deletions src/components/safe/safe-template-render.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import { PDFViewer } from "@react-pdf/renderer";
import { PostMoneyDiscount } from "./templates";

export function SafeTemplateRenderer() {
return (
<PDFViewer
className="w-full h-screen border-none rounded-md"
showToolbar={false}
>
<PostMoneyDiscount
options={{
author: "Y Combinator",
creator: "Captable, Inc.",
producer: "Captable, Inc.",
title: "YC SAFE - Post Money Discount",
subject: "YC SAFE - Post Money Discount",
keywords: "YC, SAFE, Post Money, Discount",
}}
investor={{
name: "Puru Dahal",
email: "",
}}
investment={100000}
discountRate={10}
date={new Date().toISOString()}
company={{
name: "Company Inc.",
state: "CA",
}}
/>
</PDFViewer>
);
}
5 changes: 4 additions & 1 deletion src/components/safe/templates/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,15 @@ export type SafeProps = {
name: string;
email: string;
title: string;
signature?: string;
};

investor: {
name: string;
email: string;
address?: string;
address?: string | null;
title?: string;
signature?: string;
};

options: {
Expand Down
Loading

0 comments on commit 6d20478

Please sign in to comment.