Skip to content

Commit c4c9193

Browse files
authored
Merge pull request #549 from trycompai/lewis/comp-trust
[dev] [carhartlewis] lewis/comp-trust
2 parents fdff32b + 42480c8 commit c4c9193

40 files changed

Lines changed: 934 additions & 1 deletion

apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/layout.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export default async function Layout({
3232
path: `/${orgId}/settings`,
3333
label: t("settings.general.title"),
3434
},
35+
{
36+
path: `/${orgId}/settings/trust-portal`,
37+
label: "Trust Portal",
38+
},
3539
{
3640
path: `/${orgId}/settings/api-keys`,
3741
label: t("settings.api_keys.title"),
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// update-organization-name-action.ts
2+
3+
"use server";
4+
5+
import { db } from "@comp/db";
6+
import { revalidatePath, revalidateTag } from "next/cache";
7+
import { authActionClient } from "@/actions/safe-action";
8+
import { z } from "zod";
9+
10+
const trustPortalSwitchSchema = z.object({
11+
enabled: z.boolean(),
12+
});
13+
14+
export const trustPortalSwitchAction = authActionClient
15+
.schema(trustPortalSwitchSchema)
16+
.metadata({
17+
name: "trust-portal-switch",
18+
track: {
19+
event: "trust-portal-switch",
20+
channel: "server",
21+
},
22+
})
23+
.action(async ({ parsedInput, ctx }) => {
24+
const { enabled } = parsedInput;
25+
const { activeOrganizationId } = ctx.session;
26+
27+
if (!activeOrganizationId) {
28+
throw new Error("No active organization");
29+
}
30+
31+
try {
32+
await db.$transaction(async () => {
33+
await db.trust.upsert({
34+
where: { organizationId: activeOrganizationId },
35+
update: { status: enabled ? "published" : "draft" },
36+
create: {
37+
organizationId: activeOrganizationId,
38+
status: enabled ? "published" : "draft",
39+
},
40+
});
41+
});
42+
43+
revalidatePath("/settings/trust-portal");
44+
revalidateTag(`organization_${activeOrganizationId}`);
45+
46+
return {
47+
success: true,
48+
};
49+
} catch (error) {
50+
console.error(error);
51+
throw new Error("Failed to update organization name");
52+
}
53+
});
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"use client";
2+
3+
import { useI18n } from "@/locales/client";
4+
import {
5+
Card,
6+
CardContent,
7+
CardDescription,
8+
CardFooter,
9+
CardHeader,
10+
CardTitle,
11+
} from "@comp/ui/card";
12+
import {
13+
Form,
14+
FormControl,
15+
FormDescription,
16+
FormField,
17+
FormItem,
18+
FormLabel,
19+
FormMessage,
20+
} from "@comp/ui/form";
21+
import { Switch } from "@comp/ui/switch";
22+
import { zodResolver } from "@hookform/resolvers/zod";
23+
import { useAction } from "next-safe-action/hooks";
24+
import { useForm } from "react-hook-form";
25+
import { toast } from "sonner";
26+
import { z } from "zod";
27+
import { trustPortalSwitchAction } from "../actions/trust-portal-switch";
28+
29+
const trustPortalSwitchSchema = z.object({
30+
enabled: z.boolean(),
31+
});
32+
33+
export function TrustPortalSwitch({
34+
enabled,
35+
slug,
36+
}: {
37+
enabled: boolean;
38+
slug: string;
39+
}) {
40+
const t = useI18n();
41+
42+
const trustPortalSwitch = useAction(trustPortalSwitchAction, {
43+
onSuccess: () => {
44+
toast.success("Trust portal status updated");
45+
},
46+
onError: () => {
47+
toast.error("Failed to update trust portal status");
48+
},
49+
});
50+
51+
const form = useForm<z.infer<typeof trustPortalSwitchSchema>>({
52+
resolver: zodResolver(trustPortalSwitchSchema),
53+
defaultValues: {
54+
enabled: enabled,
55+
},
56+
});
57+
58+
const onSubmit = async (data: z.infer<typeof trustPortalSwitchSchema>) => {
59+
await trustPortalSwitch.execute(data);
60+
};
61+
62+
const onChange = (checked: boolean) => {
63+
form.setValue("enabled", checked);
64+
trustPortalSwitch.execute({ enabled: checked });
65+
};
66+
67+
return (
68+
<Form {...form}>
69+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
70+
<Card>
71+
<CardHeader>
72+
<CardTitle>Trust Portal Configuration</CardTitle>
73+
<CardDescription className="space-y-4 flex flex-row justify-between">
74+
<div className="max-w-[600px]">
75+
Enable the trust portal for your organization.
76+
</div>
77+
</CardDescription>
78+
</CardHeader>
79+
<CardContent>
80+
<FormField
81+
control={form.control}
82+
name="enabled"
83+
render={({ field }) => (
84+
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
85+
<div className="space-y-0.5">
86+
<FormLabel>Publish Trust Portal</FormLabel>
87+
<FormDescription>
88+
Enable the trust portal for your organization.
89+
</FormDescription>
90+
</div>
91+
<FormControl>
92+
<Switch
93+
checked={field.value}
94+
onCheckedChange={onChange}
95+
disabled={trustPortalSwitch.status === "executing"}
96+
/>
97+
</FormControl>
98+
</FormItem>
99+
)}
100+
/>
101+
</CardContent>
102+
<CardFooter className="flex justify-between">
103+
<div className="text-xs text-muted-foreground">
104+
Your trust portal will be live & accessible at https://trust.trycomp.ai/{slug}
105+
</div>
106+
</CardFooter>
107+
</Card>
108+
</form>
109+
</Form>
110+
);
111+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { LogoSpinner } from "@/components/logo-spinner";
2+
import { getI18n } from "@/locales/server";
3+
import { Button } from "@comp/ui/button";
4+
import {
5+
Card,
6+
CardContent,
7+
CardDescription,
8+
CardFooter,
9+
CardHeader,
10+
CardTitle,
11+
} from "@comp/ui/card";
12+
import { Plus } from "lucide-react";
13+
14+
export default async function Loading() {
15+
const t = await getI18n();
16+
17+
return (
18+
<div className="mx-auto max-w-7xl">
19+
<Card>
20+
<CardHeader className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
21+
<div>
22+
<CardTitle>
23+
{t("settings.api_keys.list_title")}
24+
</CardTitle>
25+
<CardDescription>
26+
{t("settings.api_keys.list_description")}
27+
</CardDescription>
28+
</div>
29+
<Button
30+
className="flex items-center gap-1 self-start sm:self-auto"
31+
disabled
32+
aria-label={t("settings.api_keys.create")}
33+
>
34+
<Plus className="h-4 w-4" />
35+
{t("settings.api_keys.create")}
36+
</Button>
37+
</CardHeader>
38+
<CardContent>
39+
<LogoSpinner />
40+
</CardContent>
41+
<CardFooter className="text-sm text-muted-foreground">
42+
{t("settings.api_keys.security_note")}
43+
</CardFooter>
44+
</Card>
45+
</div>
46+
);
47+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { auth } from "@/utils/auth";
2+
import { headers } from "next/headers";
3+
import { cache } from "react";
4+
5+
import { ApiKeysTable } from "@/components/tables/api-keys";
6+
import { getI18n } from "@/locales/server";
7+
import { db } from "@comp/db";
8+
import type { Metadata } from "next";
9+
import { setStaticParamsLocale } from "next-international/server";
10+
import { TrustPortalSwitch } from "./components/TrustPortalSwitch";
11+
12+
export default async function TrustPortalSettings({
13+
params,
14+
}: {
15+
params: Promise<{ locale: string }>;
16+
}) {
17+
const { locale } = await params;
18+
setStaticParamsLocale(locale);
19+
const t = await getI18n();
20+
21+
const trustPortal = await getTrustPortal();
22+
23+
return (
24+
<div className="mx-auto max-w-7xl">
25+
<TrustPortalSwitch enabled={trustPortal?.enabled ?? false} slug={trustPortal?.slug ?? ""} />
26+
</div>
27+
);
28+
}
29+
30+
export async function generateMetadata({
31+
params,
32+
}: {
33+
params: Promise<{ locale: string }>;
34+
}): Promise<Metadata> {
35+
const { locale } = await params;
36+
setStaticParamsLocale(locale);
37+
const t = await getI18n();
38+
39+
return {
40+
title: "Trust Portal",
41+
};
42+
}
43+
44+
const getTrustPortal = cache(async () => {
45+
const session = await auth.api.getSession({
46+
headers: await headers(),
47+
});
48+
49+
if (!session?.session.activeOrganizationId) {
50+
return null;
51+
}
52+
53+
const slug = await db.organization.findUnique({
54+
where: {
55+
id: session.session.activeOrganizationId,
56+
},
57+
select: {
58+
slug: true,
59+
},
60+
});
61+
62+
const trustPortal = await db.trust.findUnique({
63+
where: {
64+
organizationId: session.session.activeOrganizationId,
65+
status: "published",
66+
},
67+
});
68+
69+
if (!trustPortal) {
70+
return {
71+
enabled: false,
72+
slug: slug?.slug,
73+
};
74+
}
75+
76+
return {
77+
enabled: true,
78+
slug: slug?.slug,
79+
};
80+
});

apps/framework-editor/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@comp/framework-editor",
33
"private": true,
44
"scripts": {
5-
"dev": "next dev",
5+
"dev": "next dev --port 3004",
66
"build": "next build",
77
"start": "next start",
88
"lint": "next lint",

apps/trust/.gitignore

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts

apps/trust/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
# or
14+
bun dev
15+
```
16+
17+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18+
19+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20+
21+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22+
23+
## Learn More
24+
25+
To learn more about Next.js, take a look at the following resources:
26+
27+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29+
30+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31+
32+
## Deploy on Vercel
33+
34+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35+
36+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

0 commit comments

Comments
 (0)