Skip to content

Commit 883caeb

Browse files
Marfuenclaude
andauthored
feat(portal): allow employees to view signed policies (#2446)
* fix(onboarding): reorder steps so cloud question comes before software Move the infrastructure/cloud hosting question ahead of the software question in the onboarding wizard for a more logical flow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(portal): add signed policies list page * feat(portal): add link to signed policies from dashboard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e488a95 commit 883caeb

2 files changed

Lines changed: 133 additions & 10 deletions

File tree

apps/portal/src/app/(app)/(home)/[orgId]/components/tasks/PoliciesAccordionItem.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,23 @@ export function PoliciesAccordionItem({ policies, member }: PoliciesAccordionIte
105105
);
106106
})}
107107
</div>
108-
<Button
109-
onClick={handleAcceptAllPolicies}
110-
disabled={hasAcceptedPolicies || isAcceptingAll}
111-
>
112-
{isAcceptingAll
113-
? 'Accepting...'
114-
: hasAcceptedPolicies
115-
? 'All Policies Accepted'
116-
: 'Accept All'}
117-
</Button>
108+
<div className="flex items-center gap-2">
109+
<Button
110+
onClick={handleAcceptAllPolicies}
111+
disabled={hasAcceptedPolicies || isAcceptingAll}
112+
>
113+
{isAcceptingAll
114+
? 'Accepting...'
115+
: hasAcceptedPolicies
116+
? 'All Policies Accepted'
117+
: 'Accept All'}
118+
</Button>
119+
{hasAcceptedPolicies && (
120+
<Link href={`/${member.organizationId}/policies`}>
121+
<Button variant="outline">View Signed Policies</Button>
122+
</Link>
123+
)}
124+
</div>
118125
</>
119126
) : (
120127
<p className="text-muted-foreground text-sm">No policies ready to be signed.</p>
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { auth } from '@/app/lib/auth';
2+
import { db } from '@db/server';
3+
import {
4+
Breadcrumb,
5+
Card,
6+
CardContent,
7+
PageHeader,
8+
PageLayout,
9+
Stack,
10+
Text,
11+
} from '@trycompai/design-system';
12+
import { Document } from '@trycompai/design-system/icons';
13+
import { headers } from 'next/headers';
14+
import Link from 'next/link';
15+
import { redirect } from 'next/navigation';
16+
17+
export default async function SignedPoliciesPage({
18+
params,
19+
}: {
20+
params: Promise<{ orgId: string }>;
21+
}) {
22+
const { orgId } = await params;
23+
24+
const session = await auth.api.getSession({
25+
headers: await headers(),
26+
});
27+
28+
if (!session?.user) {
29+
redirect('/auth');
30+
}
31+
32+
const member = await db.member.findFirst({
33+
where: {
34+
userId: session.user.id,
35+
organizationId: orgId,
36+
deactivated: false,
37+
},
38+
});
39+
40+
if (!member) {
41+
redirect('/');
42+
}
43+
44+
const policies = await db.policy.findMany({
45+
where: {
46+
organizationId: orgId,
47+
status: 'published',
48+
isRequiredToSign: true,
49+
isArchived: false,
50+
signedBy: { has: member.id },
51+
},
52+
orderBy: { name: 'asc' },
53+
select: {
54+
id: true,
55+
name: true,
56+
description: true,
57+
updatedAt: true,
58+
},
59+
});
60+
61+
return (
62+
<PageLayout>
63+
<Breadcrumb
64+
items={[
65+
{
66+
label: 'Overview',
67+
href: `/${orgId}`,
68+
props: { render: <Link href={`/${orgId}`} /> },
69+
},
70+
{ label: 'Signed Policies', isCurrent: true },
71+
]}
72+
/>
73+
<Stack gap="md">
74+
<PageHeader title="Signed Policies" />
75+
{policies.length === 0 ? (
76+
<Text variant="muted" size="sm">
77+
No signed policies yet.
78+
</Text>
79+
) : (
80+
<div className="space-y-2">
81+
{policies.map((policy) => (
82+
<Link
83+
key={policy.id}
84+
href={`/${orgId}/policy/${policy.id}`}
85+
className="block"
86+
>
87+
<Card>
88+
<CardContent>
89+
<div className="flex items-center gap-3">
90+
<div className="text-muted-foreground">
91+
<Document size={20} />
92+
</div>
93+
<div className="min-w-0 flex-1">
94+
<Text size="sm" weight="medium">
95+
{policy.name}
96+
</Text>
97+
{policy.description && (
98+
<Text variant="muted" size="sm">
99+
{policy.description}
100+
</Text>
101+
)}
102+
</div>
103+
<Text variant="muted" size="sm">
104+
{new Date(policy.updatedAt).toLocaleDateString()}
105+
</Text>
106+
</div>
107+
</CardContent>
108+
</Card>
109+
</Link>
110+
))}
111+
</div>
112+
)}
113+
</Stack>
114+
</PageLayout>
115+
);
116+
}

0 commit comments

Comments
 (0)