);
}
+
+async function getComplianceOverview(organizationId: string) {
+ const frameworks = await db.organizationFramework.findMany({
+ where: { organizationId },
+ include: {
+ organizationControl: true,
+ framework: true,
+ },
+ });
+
+ return { frameworks };
+}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/layout.tsx
index eb25e20991..0e53af7c23 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/layout.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/layout.tsx
@@ -2,10 +2,8 @@ import { auth } from "@/auth";
import { Title } from "@/components/title";
import { getI18n } from "@/locales/server";
import { db } from "@bubba/db";
-import { SecondaryMenu } from "@bubba/ui/secondary-menu";
import { unstable_cache } from "next/cache";
import { redirect } from "next/navigation";
-import { Suspense } from "react";
export default async function Layout({
children,
@@ -33,13 +31,21 @@ export default async function Layout({
}
return (
-
+
+
+
+ {children}
+
+ );
+
+ /* return (
+
- );
+ ); */
}
const getPolicy = unstable_cache(
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/settings/layout.tsx
index 07ac2611d8..7103268daa 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/layout.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/settings/layout.tsx
@@ -14,7 +14,11 @@ export default async function Layout({
items={[
{ path: "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/settings", label: t("settings.general.title") },
{ path: "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/settings/members", label: t("settings.members.title") },
- { path: "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/settings/billing", label: t("settings.billing.title") },
+ {
+ path: "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/settings/billing",
+ label: t("settings.billing.title"),
+ enabled: false,
+ },
]}
/>
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/members/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/settings/members/page.tsx
new file mode 100644
index 0000000000..f9dbba63c0
--- /dev/null
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/settings/members/page.tsx
@@ -0,0 +1,9 @@
+import { TeamMembers } from "@/components/settings/team/team-members";
+
+export default async function Members() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/app/src/auth/config.ts b/apps/app/src/auth/config.ts
index 5d5ca9ba21..65319f21ae 100644
--- a/apps/app/src/auth/config.ts
+++ b/apps/app/src/auth/config.ts
@@ -62,10 +62,10 @@ export const authConfig: NextAuthConfig = {
await db.user.update({
where: {
id: user.id,
- organizationId: user.organizationId,
},
data: {
lastLogin: new Date(),
+ organizationId: user.organizationId,
},
});
}
diff --git a/apps/app/src/auth/org.ts b/apps/app/src/auth/org.ts
index ca5a15605a..e70f468d7d 100644
--- a/apps/app/src/auth/org.ts
+++ b/apps/app/src/auth/org.ts
@@ -35,9 +35,10 @@ export async function createOrganizationAndConnectUser(input: {
name: initialName,
tier: "free",
website: "",
- users: {
- connect: {
- id: input.userId,
+ members: {
+ create: {
+ userId: input.userId,
+ role: "admin",
},
},
},
@@ -69,5 +70,10 @@ export async function createOrganizationAndConnectUser(input: {
data: { stripeCustomerId },
});
+ await db.user.update({
+ where: { id: input.userId },
+ data: { organizationId: organization.id },
+ });
+
return organization.id;
}
diff --git a/apps/app/src/auth/stripe.ts b/apps/app/src/auth/stripe.ts
index 3740b35c1e..a6efe664d8 100644
--- a/apps/app/src/auth/stripe.ts
+++ b/apps/app/src/auth/stripe.ts
@@ -11,5 +11,5 @@ if (!process.env.STRIPE_WEBHOOK_SECRET) {
export const stripeWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
- apiVersion: "2024-12-18.acacia",
+ apiVersion: "2025-01-27.acacia",
});
diff --git a/apps/app/src/components/settings/team/team-members.tsx b/apps/app/src/components/settings/team/team-members.tsx
new file mode 100644
index 0000000000..d171e5c5f9
--- /dev/null
+++ b/apps/app/src/components/settings/team/team-members.tsx
@@ -0,0 +1,21 @@
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@bubba/ui/tabs";
+import { Suspense } from "react";
+
+export function TeamMembers() {
+ return (
+
+
+
+ Team Members
+
+
+ Pending Invitations
+
+
+
+ Test
+
+ Test 2
+
+ );
+}
diff --git a/apps/app/src/components/tables/members/index.tsx b/apps/app/src/components/tables/members/index.tsx
new file mode 100644
index 0000000000..c1fea0e175
--- /dev/null
+++ b/apps/app/src/components/tables/members/index.tsx
@@ -0,0 +1,11 @@
+import { getOrganizationUsersAction } from "@/actions/organization/get-organization-users-action";
+import { auth } from "@/auth";
+import { db } from "@bubba/db";
+
+export async function MembersTable() {
+ const session = await auth();
+
+ const members = await getOrganizationUsersAction();
+
+ return
MembersTable
;
+}
diff --git a/apps/web/src/app/(home)/page.tsx b/apps/web/src/app/(home)/page.tsx
index a6497b438a..bc3c80df45 100644
--- a/apps/web/src/app/(home)/page.tsx
+++ b/apps/web/src/app/(home)/page.tsx
@@ -42,7 +42,7 @@ export default function Home() {
Join us on Discord
diff --git a/apps/web/src/app/(legal)/privacy/page.tsx b/apps/web/src/app/(home)/privacy/page.tsx
similarity index 77%
rename from apps/web/src/app/(legal)/privacy/page.tsx
rename to apps/web/src/app/(home)/privacy/page.tsx
index 45091f750b..c5611ea129 100644
--- a/apps/web/src/app/(legal)/privacy/page.tsx
+++ b/apps/web/src/app/(home)/privacy/page.tsx
@@ -12,11 +12,11 @@ export default function PrivacyPage() {
-
+
Privacy Policy
-
diff --git a/apps/web/src/app/(legal)/terms/page.tsx b/apps/web/src/app/(home)/terms/page.tsx
similarity index 76%
rename from apps/web/src/app/(legal)/terms/page.tsx
rename to apps/web/src/app/(home)/terms/page.tsx
index eb583e4555..0f8badbf72 100644
--- a/apps/web/src/app/(legal)/terms/page.tsx
+++ b/apps/web/src/app/(home)/terms/page.tsx
@@ -12,11 +12,11 @@ export default function TermsPage() {
-
+
Terms of Service
-
diff --git a/apps/web/src/app/(legal)/layout.tsx b/apps/web/src/app/(legal)/layout.tsx
deleted file mode 100644
index f4a26b8e22..0000000000
--- a/apps/web/src/app/(legal)/layout.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { SiteFooter } from "@/app/components/site-footer";
-import { SiteHeader } from "@/app/components/site-header";
-
-export default function LegalLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- return (
-
-
- {children}
-
-
- );
-}
diff --git a/apps/web/src/app/components/legal/privacy-content.tsx b/apps/web/src/app/components/legal/privacy-content.tsx
index 7943716d5e..30086d0f1f 100644
--- a/apps/web/src/app/components/legal/privacy-content.tsx
+++ b/apps/web/src/app/components/legal/privacy-content.tsx
@@ -1,6 +1,6 @@
export function PrivacyContent() {
return (
-
+
Last updated: February 1, 2025
diff --git a/apps/web/src/app/components/legal/terms-content.tsx b/apps/web/src/app/components/legal/terms-content.tsx
index cd556d40f3..8cd7c226ab 100644
--- a/apps/web/src/app/components/legal/terms-content.tsx
+++ b/apps/web/src/app/components/legal/terms-content.tsx
@@ -1,6 +1,6 @@
export function TermsContent() {
return (
-
+
Last updated: February 1, 2025
@@ -8,10 +8,10 @@ export function TermsContent() {
1. Agreement to Terms
- By accessing or using Bubba AI, Inc.'s services, you agree to be bound
- by these Terms of Service and all applicable laws and regulations. If
- you do not agree with any of these terms, you are prohibited from
- using or accessing our services.
+ By accessing or using Comp AI's services, you agree to be bound by
+ these Terms of Service and all applicable laws and regulations. If you
+ do not agree with any of these terms, you are prohibited from using or
+ accessing our services.
@@ -22,7 +22,7 @@ export function TermsContent() {
non-exclusive, non-transferable license to use our services for your
organization's internal business purposes.
-
2.1 Restrictions
+
2.1 Restrictions
You may not:
Modify or copy our materials without explicit permission
diff --git a/apps/web/src/app/components/waitlist-form.tsx b/apps/web/src/app/components/waitlist-form.tsx
index 3203ce5fe7..02953154d0 100644
--- a/apps/web/src/app/components/waitlist-form.tsx
+++ b/apps/web/src/app/components/waitlist-form.tsx
@@ -56,7 +56,7 @@ export function WaitlistForm() {
Thanks, you're on the list!
Join Community
diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
deleted file mode 100644
index 42b49d7aeb..0000000000
--- a/apps/web/src/app/globals.css
+++ /dev/null
@@ -1,86 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-body {
- font-family: Arial, Helvetica, sans-serif;
-}
-
-@layer base {
- :root {
- --background: 0 0% 100%;
- --foreground: 0 0% 3.9%;
- --card: 0 0% 100%;
- --card-foreground: 0 0% 3.9%;
- --popover: 0 0% 100%;
- --popover-foreground: 0 0% 3.9%;
- --primary: 0 0% 9%;
- --primary-foreground: 0 0% 98%;
- --secondary: 0 0% 96.1%;
- --secondary-foreground: 0 0% 9%;
- --muted: 0 0% 96.1%;
- --muted-foreground: 0 0% 45.1%;
- --accent: 0 0% 96.1%;
- --accent-foreground: 0 0% 9%;
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 0% 98%;
- --border: 0 0% 89.8%;
- --input: 0 0% 89.8%;
- --ring: 0 0% 3.9%;
- --chart-1: 12 76% 61%;
- --chart-2: 173 58% 39%;
- --chart-3: 197 37% 24%;
- --chart-4: 43 74% 66%;
- --chart-5: 27 87% 67%;
- --radius: 0.5rem;
- }
- .dark {
- --background: 0 0% 3.9%;
- --foreground: 0 0% 98%;
- --card: 0 0% 3.9%;
- --card-foreground: 0 0% 98%;
- --popover: 0 0% 3.9%;
- --popover-foreground: 0 0% 98%;
- --primary: 0 0% 98%;
- --primary-foreground: 0 0% 9%;
- --secondary: 0 0% 14.9%;
- --secondary-foreground: 0 0% 98%;
- --muted: 0 0% 14.9%;
- --muted-foreground: 0 0% 63.9%;
- --accent: 0 0% 14.9%;
- --accent-foreground: 0 0% 98%;
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 0 0% 98%;
- --border: 0 0% 14.9%;
- --input: 0 0% 14.9%;
- --ring: 0 0% 83.1%;
- --chart-1: 220 70% 50%;
- --chart-2: 160 60% 45%;
- --chart-3: 30 80% 55%;
- --chart-4: 280 65% 60%;
- --chart-5: 340 75% 55%;
- }
-}
-
-@layer base {
- * {
- @apply border-border;
- }
- body {
- @apply bg-background text-foreground;
- }
-}
-
-@layer components {
- .border-grid {
- @apply border-border;
- }
-
- .container-wrapper {
- @apply w-full;
- }
-
- .container {
- @apply mx-auto max-w-7xl px-4 sm:px-6 lg:px-8;
- }
-}
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index 3a18c35798..8637685851 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -1,24 +1,16 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import { Geist } from "next/font/google";
import { Toaster } from "sonner";
-import "@/styles/globals.css";
import "@bubba/ui/globals.css";
import { ThemeProvider } from "@/app/providers";
import { cn } from "@/lib/utils";
import { GoogleTagManager } from "@next/third-parties/google";
-import { SiteFooter } from "./components/site-footer";
-import { SiteHeader } from "./components/site-header";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
-
export const metadata: Metadata = {
title: "Comp AI",
description: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index 2cdc0b691e..4d2919a493 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -76,25 +76,27 @@ model User {
lastLogin DateTime?
organizationId String?
- organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
-
- accounts Account[]
- sessions Session[]
- Risk Risk[]
- RiskMitigationTask RiskMitigationTask[]
- RiskComment RiskComment[]
- AuditLog AuditLog[]
- TaskAttachment TaskAttachment[]
- TaskComments TaskComments[]
- Artifact Artifact[]
- VendorComment VendorComment[]
- VendorMitigationTask VendorMitigationTask[]
- VendorTaskComments VendorTaskComments[]
- VendorTaskAttachment VendorTaskAttachment[]
- Vendors Vendors[]
- VendorRiskAssessment VendorRiskAssessment[]
- RiskAttachment RiskAttachment[]
-
+ organization Organization? @relation("CurrentOrganization", fields: [organizationId], references: [id], onDelete: SetNull)
+
+ accounts Account[]
+ sessions Session[]
+ Risk Risk[]
+ RiskMitigationTask RiskMitigationTask[]
+ RiskComment RiskComment[]
+ AuditLog AuditLog[]
+ TaskAttachment TaskAttachment[]
+ TaskComments TaskComments[]
+ Artifact Artifact[]
+ VendorComment VendorComment[]
+ VendorMitigationTask VendorMitigationTask[]
+ VendorTaskComments VendorTaskComments[]
+ VendorTaskAttachment VendorTaskAttachment[]
+ Vendors Vendors[]
+ VendorRiskAssessment VendorRiskAssessment[]
+ RiskAttachment RiskAttachment[]
+ organizationMemberships OrganizationMember[]
+
+ @@index([email])
@@index([organizationId])
}
@@ -106,7 +108,8 @@ model Organization {
tier Tier @default(free)
policiesCreated Boolean @default(false)
- users User[]
+ users User[] @relation("CurrentOrganization")
+
Risk Risk[]
AuditLog AuditLog[]
RiskMitigationTask RiskMitigationTask[]
@@ -128,6 +131,7 @@ model Organization {
VendorRiskAssessment VendorRiskAssessment[]
VendorContact VendorContact[]
RiskAttachment RiskAttachment[]
+ members OrganizationMember[]
@@index([stripeCustomerId])
}
@@ -689,3 +693,30 @@ model RiskAttachment {
@@index([riskId])
@@index([organizationId])
}
+
+enum MembershipRole {
+ owner
+ admin
+ member
+ viewer
+}
+
+model OrganizationMember {
+ id String @id @default(cuid())
+ role MembershipRole @default(member)
+ invitedEmail String?
+ accepted Boolean @default(false)
+ department Departments @default(none)
+ joinedAt DateTime @default(now())
+ lastActive DateTime?
+
+ userId String
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ organizationId String
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
+
+ @@unique([userId, organizationId])
+ @@index([organizationId])
+ @@index([userId])
+}
diff --git a/packages/ui/src/components/secondary-menu.tsx b/packages/ui/src/components/secondary-menu.tsx
index d7374b04e7..8717b6ff95 100644
--- a/packages/ui/src/components/secondary-menu.tsx
+++ b/packages/ui/src/components/secondary-menu.tsx
@@ -1,11 +1,22 @@
"use client";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@bubba/ui/tooltip";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "../utils";
interface SecondaryMenuProps {
- items: { path: string; label: string; query?: Record }[];
+ items: {
+ enabled?: boolean;
+ path: string;
+ label: string;
+ query?: Record;
+ }[];
isChild?: boolean;
}
@@ -36,22 +47,46 @@ export function SecondaryMenu({ items, isChild }: SecondaryMenuProps) {
isChild ? "space-x-3" : "space-x-6",
)}
>
- {items.map((item) => (
-
- {item.label}
-
- ))}
+ {items.map((item) => {
+ const isDisabled = item.enabled === false;
+
+ const itemContent = (
+
+ {item.label}
+
+ );
+
+ return (
+
+ {isDisabled ? (
+
+
+ {itemContent}
+
+ Coming soon
+
+
+
+ ) : (
+
+ {itemContent}
+
+ )}
+
+ );
+ })}
);
diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx
index 0c8912a272..5604e4dc47 100644
--- a/packages/ui/src/components/tabs.tsx
+++ b/packages/ui/src/components/tabs.tsx
@@ -14,7 +14,7 @@ const TabsList = React.forwardRef<
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center bg-accent p-1 text-muted-foreground",
- className
+ className,
)}
{...props}
/>
@@ -29,7 +29,7 @@ const TabsTrigger = React.forwardRef<
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground",
- className
+ className,
)}
{...props}
/>
@@ -44,7 +44,7 @@ const TabsContent = React.forwardRef<
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none",
- className
+ className,
)}
{...props}
/>
diff --git a/packages/ui/src/globals.css b/packages/ui/src/globals.css
index da507ee9c3..e290c59c21 100644
--- a/packages/ui/src/globals.css
+++ b/packages/ui/src/globals.css
@@ -80,6 +80,21 @@
--novel-highlight-gray: #3a3a3a;
}
+
+@layer components {
+ .border-grid {
+ @apply border-border;
+ }
+
+ .container-wrapper {
+ @apply w-full;
+ }
+
+ .container {
+ @apply mx-auto max-w-7xl px-4 sm:px-6 lg:px-8;
+ }
+}
+
.ProseMirror ol {
@apply my-4 ml-6;
}
@@ -601,3 +616,14 @@ pre {
scrollbar-width: thin;
scrollbar-color: var(--muted-foreground) transparent;
}
+
+@layer utilities {
+ .scrollbar-hide {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+ }
+
+ .scrollbar-hide::-webkit-scrollbar {
+ display: none; /* Chrome, Safari and Opera */
+ }
+}
\ No newline at end of file