fix: merge conflicts

This commit is contained in:
Vishnu Kumar
2024-07-19 19:58:39 +05:30
15 changed files with 282 additions and 28 deletions
@@ -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");
@@ -0,0 +1,7 @@
-- AlterTable
ALTER TABLE "Option" ADD COLUMN "cliffYears" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "vestingYears" INTEGER NOT NULL DEFAULT 0;
-- AlterTable
ALTER TABLE "Share" ADD COLUMN "cliffYears" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "vestingYears" INTEGER NOT NULL DEFAULT 0;
+38 -1
View File
@@ -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
@@ -668,8 +700,11 @@ model Share {
debtCancelled Float? // Amount of debt cancelled
otherContributions Float? // Other contributions
cliffYears Int @default(0) // 0 means immediate vesting, 1 means vesting starts after 1 year
vestingYears Int @default(0) // 0 means immediate vesting, 1 means vesting over 1 year
vestingSchedule VestingScheduleEnum
companyLegends ShareLegendsEnum[]
companyLegends ShareLegendsEnum[]
issueDate DateTime
rule144Date DateTime?
@@ -718,6 +753,8 @@ model Option {
type OptionTypeEnum
status OptionStatusEnum @default(DRAFT)
cliffYears Int @default(0) // 0 means immediate vesting, 1 means vesting starts after 1 year
vestingYears Int @default(0) // 0 means immediate vesting, 1 means vesting over 1 year
vestingSchedule VestingScheduleEnum
issueDate DateTime
@@ -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 (
<Button
onClick={() => {
pushModal("BankAccountModal", {
title: "Add a bank account",
subtitle: "Add a bank account to receive funds",
});
}}
>
<RiAddLine className="inline-block h-5 w-5" />
Add a bank account
</Button>
);
};
export default CtaButton;
@@ -0,0 +1,7 @@
import type { BankAccount } from "@prisma/client";
const BankAccountsTable = () => {
return <>Table</>;
};
export default BankAccountsTable;
@@ -0,0 +1,60 @@
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 <UnAuthorizedState />;
}
return (
<Fragment>
{data.bankAccounts.length === 0 ? (
<EmptyState
bordered={false}
icon={<RiBankFill />}
title="Bank accounts"
subtitle="Add a bank account to receive funds"
>
<CtaButton />
</EmptyState>
) : (
<div className="flex flex-col gap-y-3">
<div className="flex items-center justify-between gap-y-3 ">
<div className="gap-y-3">
<h3 className="font-medium">Bank accounts</h3>
<p className="text-sm text-muted-foreground">
Add and manage bank account to receive funds
</p>
</div>
<div>
<CtaButton />
</div>
</div>
{/* <BankAccountsTable accounts={data.bankAccounts} /> */}
<BankAccountsTable />
</div>
)}
</Fragment>
);
};
export default ApiSettingsPage;
@@ -13,7 +13,7 @@ const BillingPage = async () => {
]);
return (
<PageLayout title="Billing" description="manage your billing">
<PageLayout title="Billing" description="Manage payments and billing">
<PlanDetails products={products} subscription={subscription} />
</PageLayout>
);
+1 -1
View File
@@ -26,7 +26,7 @@ const EmptyState = ({
bordered = true,
subtitle,
children,
error,
error = false,
}: EmptyStateProps) => {
return (
<div role="alert" className="overflow-hidden">
@@ -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 (
<Modal size="xl" title={title} subtitle={subtitle}>
Please clone this file and implement the modal.
</Modal>
);
};
+5 -1
View File
@@ -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";
@@ -35,9 +36,12 @@ export const { pushModal, popModal, ModalProvider } = createPushModal({
EquityPlanModal,
IssueShareModal,
IssueStockOptionModal,
AddEsignDocumentModal,
BankAccountModal,
// Safe modals
NewSafeModal,
ExistingSafeModal,
AddEsignDocumentModal,
InvestorModal,
},
});
+27 -20
View File
@@ -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 (
<ul className="text-sm">
{sideNavData.map((item) => {
{companyNav.map((item) => {
const href = `/${companyPublicId}${item.href}`;
const isActive = currentPath === href;
if (item.name === "Billing" && !isBillingEnabled) {
return null;
}
return (
<li key={item.name} className="rounded py-1">
<NavLink
+5 -4
View File
@@ -1,13 +1,14 @@
import { RiProhibitedLine } from "@remixicon/react";
import { RiDoorLockFill } from "@remixicon/react";
import EmptyState, { type EmptyStateProps } from "../common/empty-state";
export function UnAuthorizedState(props: Partial<EmptyStateProps>) {
return (
<EmptyState
bordered={false}
icon={<RiProhibitedLine />}
title="Un Authorized"
subtitle="you don't have access to view this content."
error={true}
icon={<RiDoorLockFill />}
title="Unauthorized"
subtitle="You are not authorized to access this content"
{...props}
/>
);
+1
View File
@@ -7,5 +7,6 @@ export const SUBJECTS = [
"documents",
"company",
"api-keys",
"bank-accounts",
] as const;
export type TSubjects = (typeof SUBJECTS)[number];
+2
View File
@@ -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
+59
View File
@@ -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
}),
});