Skip to content

Commit c00ecec

Browse files
committed
feat(security-questionnaire): enhance knowledge base UI and document management features
1 parent ed0474a commit c00ecec

38 files changed

+223
-188
lines changed

apps/app/src/actions/safe-action.ts

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -107,60 +107,51 @@ export const authActionClient = actionClientWithMeta
107107
});
108108
})
109109
.use(async ({ next, metadata, ctx }) => {
110-
const session = await auth.api.getSession({
111-
headers: await headers(),
112-
});
113-
114-
if (!session) {
110+
// Use user and session from previous middleware instead of re-fetching
111+
// This ensures consistency and avoids potential security issues from stale data
112+
if (!ctx.user || !ctx.session) {
115113
throw new Error('Unauthorized');
116114
}
117115

118116
if (metadata.track) {
119-
track(session.user.id, metadata.track.event, {
117+
track(ctx.user.id, metadata.track.event, {
120118
channel: metadata.track.channel,
121-
email: session.user.email,
122-
name: session.user.name,
123-
organizationId: session.session.activeOrganizationId,
119+
email: ctx.user.email,
120+
name: ctx.user.name,
121+
organizationId: ctx.session.activeOrganizationId,
124122
});
125123
}
126124

127-
return next({
128-
ctx: {
129-
...ctx,
130-
user: session.user,
131-
session: session.session,
132-
},
133-
});
125+
return next({ ctx });
134126
})
135127
.use(async ({ next, metadata, clientInput, ctx }) => {
136128
const headersList = await headers();
137-
const session = await auth.api.getSession({
138-
headers: headersList,
139-
});
140-
141-
const member = await auth.api.getActiveMember({
142-
headers: headersList,
143-
});
144-
145-
if (!session) {
129+
130+
// Use user and session from previous middleware for consistency
131+
// Only fetch activeMember as it may require fresh data
132+
if (!ctx.user || !ctx.session) {
146133
throw new Error('Unauthorized');
147134
}
148135

149-
if (!session.session.activeOrganizationId) {
136+
if (!ctx.session.activeOrganizationId) {
150137
throw new Error('Organization not found');
151138
}
152139

140+
const member = await auth.api.getActiveMember({
141+
headers: headersList,
142+
});
143+
153144
if (!member) {
154145
throw new Error('Member not found');
155146
}
156147

157148
const { fileData: _, ...inputForAuditLog } = (clientInput || {}) as any;
158149

159150
const data = {
160-
userId: session.user.id,
161-
email: session.user.email,
162-
name: session.user.name,
163-
organizationId: session.session.activeOrganizationId,
151+
userId: ctx.user.id,
152+
email: ctx.user.email,
153+
name: ctx.user.name,
154+
organizationId: ctx.session.activeOrganizationId,
164155
action: metadata.name,
165156
input: inputForAuditLog,
166157
ipAddress: headersList.get('x-forwarded-for') || null,
@@ -206,9 +197,9 @@ export const authActionClient = actionClientWithMeta
206197
data: {
207198
data: JSON.stringify(data),
208199
memberId: member.id,
209-
userId: session.user.id,
200+
userId: ctx.user.id,
210201
description: metadata.track?.description || null,
211-
organizationId: session.session.activeOrganizationId,
202+
organizationId: ctx.session.activeOrganizationId,
212203
entityId,
213204
entityType,
214205
},

apps/app/src/app/(app)/[orgId]/knowledge-base/components/KnowledgeBaseHeader.tsx

Lines changed: 0 additions & 59 deletions
This file was deleted.
Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import PageCore from '@/components/pages/PageCore.tsx';
1+
import PageWithBreadcrumb from '@/components/pages/PageWithBreadcrumb';
22
import { auth } from '@/utils/auth';
33
import { headers } from 'next/headers';
44
import { notFound } from 'next/navigation';
@@ -7,7 +7,6 @@ import { ContextSection } from './context/components';
77
import { ManualAnswersSection } from './manual-answers/components';
88
import { PublishedPoliciesSection } from './published-policies/components';
99
import { KnowledgeBaseHeader } from './components/KnowledgeBaseHeader';
10-
import { KnowledgeBaseBreadcrumb } from './components/KnowledgeBaseBreadcrumb';
1110
import {
1211
getContextEntries,
1312
getKnowledgeBaseDocuments,
@@ -35,28 +34,30 @@ export default async function KnowledgeBasePage() {
3534
]);
3635

3736
return (
38-
<div className="mx-auto w-full max-w-[1200px] px-6 py-8">
39-
<PageCore>
40-
<KnowledgeBaseBreadcrumb />
41-
<KnowledgeBaseHeader organizationId={organizationId} />
42-
43-
<div className="flex flex-col gap-6">
44-
{/* Published Policies and Context Sections - Side by Side */}
45-
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
46-
<PublishedPoliciesSection policies={policies} />
47-
<ContextSection contextEntries={contextEntries} />
48-
</div>
49-
50-
{/* Manual Answers Section */}
51-
<ManualAnswersSection manualAnswers={manualAnswers} />
52-
53-
{/* Additional Documents Section */}
54-
<AdditionalDocumentsSection
55-
organizationId={organizationId}
56-
documents={documents}
57-
/>
37+
<PageWithBreadcrumb
38+
breadcrumbs={[
39+
{ label: 'Overview', current: true },
40+
]}
41+
className="px-6"
42+
>
43+
<KnowledgeBaseHeader organizationId={organizationId} />
44+
45+
<div className="flex flex-col gap-6">
46+
{/* Published Policies and Context Sections - Side by Side */}
47+
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
48+
<PublishedPoliciesSection policies={policies} />
49+
<ContextSection contextEntries={contextEntries} />
5850
</div>
59-
</PageCore>
60-
</div>
51+
52+
{/* Manual Answers Section */}
53+
<ManualAnswersSection manualAnswers={manualAnswers} />
54+
55+
{/* Additional Documents Section */}
56+
<AdditionalDocumentsSection
57+
organizationId={organizationId}
58+
documents={documents}
59+
/>
60+
</div>
61+
</PageWithBreadcrumb>
6162
);
6263
}

apps/app/src/app/(app)/[orgId]/security-questionnaire/[questionnaireId]/components/QuestionnaireDetailClient.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,15 @@ export function QuestionnaireDetailClient({
7171
answer: r.answer,
7272
sources: r.sources,
7373
failedToGenerate: (r as any).failedToGenerate ?? false, // Preserve failedToGenerate from result
74+
status: (r as any).status ?? 'untouched', // Preserve status field for UI behavior
7475
_originalIndex: (r as any).originalIndex ?? index, // Preserve originalIndex for reference, fallback to map index
7576
}))}
7677
filteredResults={filteredResults?.map((r, index) => ({
7778
question: r.question,
7879
answer: r.answer,
7980
sources: r.sources,
8081
failedToGenerate: (r as any).failedToGenerate ?? false, // Preserve failedToGenerate from result
82+
status: (r as any).status ?? 'untouched', // Preserve status field for UI behavior
8183
_originalIndex: (r as any).originalIndex ?? index, // Preserve originalIndex for reference, fallback to map index
8284
}))}
8385
searchQuery={searchQuery}

apps/app/src/app/(app)/[orgId]/security-questionnaire/[questionnaireId]/page.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1+
import PageWithBreadcrumb from '@/components/pages/PageWithBreadcrumb';
12
import { auth } from '@/utils/auth';
23
import { headers } from 'next/headers';
34
import { notFound } from 'next/navigation';
4-
import PageCore from '@/components/pages/PageCore.tsx';
55
import { QuestionnaireResults } from '../components/QuestionnaireResults';
66
import { useQuestionnaireDetail } from '../hooks/useQuestionnaireDetail';
77
import { getQuestionnaireById } from './data/queries';
88
import { QuestionnaireDetailClient } from './components/QuestionnaireDetailClient';
9-
import { QuestionnaireBreadcrumb } from './components/QuestionnaireBreadcrumb';
109

1110
export default async function QuestionnaireDetailPage({
1211
params,
@@ -36,15 +35,19 @@ export default async function QuestionnaireDetailPage({
3635
}
3736

3837
return (
39-
<PageCore className="mt-10">
40-
<QuestionnaireBreadcrumb filename={questionnaire.filename} organizationId={organizationId} />
38+
<PageWithBreadcrumb
39+
breadcrumbs={[
40+
{ label: 'Overview', href: `/${organizationId}/security-questionnaire` },
41+
{ label: questionnaire.filename, current: true },
42+
]}
43+
>
4144
<QuestionnaireDetailClient
4245
questionnaireId={questionnaireId}
4346
organizationId={organizationId}
4447
initialQuestions={questionnaire.questions}
4548
filename={questionnaire.filename}
4649
/>
47-
</PageCore>
50+
</PageWithBreadcrumb>
4851
);
4952
}
5053

apps/app/src/app/(app)/[orgId]/security-questionnaire/components/KnowledgeBaseDocumentLink.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { LinkIcon, Loader2 } from 'lucide-react';
44
import { useState } from 'react';
5-
import { getKnowledgeBaseDocumentViewUrlAction } from '../../knowledge-base/additional-documents/actions/get-document-view-url';
5+
import { getKnowledgeBaseDocumentViewUrlAction } from '../knowledge-base/additional-documents/actions/get-document-view-url';
66

77
interface KnowledgeBaseDocumentLinkProps {
88
documentId: string;
@@ -37,14 +37,14 @@ export function KnowledgeBaseDocumentLink({
3737
window.open(signedUrl, '_blank', 'noopener,noreferrer');
3838
} else {
3939
// File cannot be viewed in browser - navigate to knowledge base page
40-
const knowledgeBaseUrl = `/${orgId}/knowledge-base`;
40+
const knowledgeBaseUrl = `/${orgId}/security-questionnaire/knowledge-base`;
4141
window.open(knowledgeBaseUrl, '_blank', 'noopener,noreferrer');
4242
}
4343
}
4444
} catch (error) {
4545
console.error('Error opening knowledge base document:', error);
4646
// Fallback: navigate to knowledge base page
47-
const knowledgeBaseUrl = `/${orgId}/knowledge-base`;
47+
const knowledgeBaseUrl = `/${orgId}/security-questionnaire/knowledge-base`;
4848
window.open(knowledgeBaseUrl, '_blank', 'noopener,noreferrer');
4949
} finally {
5050
setIsLoading(false);

apps/app/src/app/(app)/[orgId]/security-questionnaire/components/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export interface QuestionAnswer {
1010
score: number;
1111
}>;
1212
failedToGenerate?: boolean; // Track if auto-generation was attempted but failed
13+
status?: 'untouched' | 'generated' | 'manual'; // Track answer source: untouched, AI-generated, or manually edited
1314
}
1415

apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireParse.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import type { parseQuestionnaireTask } from '@/jobs/tasks/vendors/parse-questionnaire';
44
import { useRealtimeRun } from '@trigger.dev/react-hooks';
55
import { useAction } from 'next-safe-action/hooks';
6+
import { useRouter } from 'next/navigation';
67
import { useEffect } from 'react';
78
import { toast } from 'sonner';
89
import { createRunReadToken, createTriggerToken } from '../actions/create-trigger-token';
@@ -27,6 +28,7 @@ interface UseQuestionnaireParseProps {
2728
>;
2829
setHasClickedAutoAnswer: (clicked: boolean) => void;
2930
setQuestionnaireId: (id: string | null) => void;
31+
orgId: string;
3032
}
3133

3234
export function useQuestionnaireParse({
@@ -44,7 +46,9 @@ export function useQuestionnaireParse({
4446
setQuestionStatuses,
4547
setHasClickedAutoAnswer,
4648
setQuestionnaireId,
49+
orgId,
4750
}: UseQuestionnaireParseProps) {
51+
const router = useRouter();
4852
// Get trigger token for auto-answer (can trigger and read)
4953
useEffect(() => {
5054
async function getAutoAnswerToken() {
@@ -101,6 +105,10 @@ export function useQuestionnaireParse({
101105
setHasClickedAutoAnswer(false);
102106
if (questionnaireId) {
103107
setQuestionnaireId(questionnaireId);
108+
// Redirect to questionnaire detail page after successful parse
109+
setTimeout(() => {
110+
router.push(`/${orgId}/security-questionnaire/${questionnaireId}`);
111+
}, 500); // Small delay to show success toast
104112
}
105113
toast.success(
106114
`Successfully parsed ${questionsAndAnswers.length} question-answer pairs`,

apps/app/src/app/(app)/[orgId]/security-questionnaire/hooks/useQuestionnaireParser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export function useQuestionnaireParser() {
2525
setQuestionStatuses: state.setQuestionStatuses,
2626
setHasClickedAutoAnswer: state.setHasClickedAutoAnswer,
2727
setQuestionnaireId: state.setQuestionnaireId,
28+
orgId: state.orgId,
2829
});
2930

3031
const autoAnswer = useQuestionnaireAutoAnswer({

apps/app/src/app/(app)/[orgId]/knowledge-base/additional-documents/actions/delete-document.ts renamed to apps/app/src/app/(app)/[orgId]/security-questionnaire/knowledge-base/additional-documents/actions/delete-document.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export const deleteKnowledgeBaseDocumentAction = authActionClient
9999
},
100100
});
101101

102-
revalidatePath(`/${activeOrganizationId}/knowledge-base`);
102+
revalidatePath(`/${activeOrganizationId}/security-questionnaire/knowledge-base`);
103103

104104
return {
105105
success: true,

0 commit comments

Comments
 (0)