Skip to content

Commit a3db984

Browse files
[dev] [Marfuen] mariano/copy (#1759)
* feat(security-questionnaire): add loading component and sidebar for questionnaire * feat(security-questionnaire): simplify layout and enhance QuestionnaireParser component * chore(security-questionnaire): update CTA text and tooltip for policy publishing --------- Co-authored-by: Mariano Fuentes <marfuen98@gmail.com>
1 parent 849966e commit a3db984

File tree

9 files changed

+368
-381
lines changed

9 files changed

+368
-381
lines changed

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

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

33
import { QuestionnaireResults } from './QuestionnaireResults';
44
import { QuestionnaireUpload } from './QuestionnaireUpload';
5+
import { QuestionnaireSidebar } from './QuestionnaireSidebar';
56
import { useQuestionnaireParser } from '../hooks/useQuestionnaireParser';
67

78
export function QuestionnaireParser() {
@@ -41,22 +42,47 @@ export function QuestionnaireParser() {
4142
handleToggleSource,
4243
} = useQuestionnaireParser();
4344

44-
if (!results || results.length === 0) {
45+
const hasResults = results && results.length > 0;
46+
47+
if (!hasResults) {
4548
return (
46-
<QuestionnaireUpload
47-
selectedFile={selectedFile}
48-
onFileSelect={handleFileSelect}
49-
onFileRemove={() => setSelectedFile(null)}
50-
onParse={handleParse}
51-
isLoading={isLoading}
52-
parseStatus={parseStatus}
53-
orgId={orgId}
54-
/>
49+
<div className="flex flex-col gap-6">
50+
<div className="flex flex-col gap-2 lg:gap-3">
51+
<h1 className="text-xl lg:text-2xl font-semibold text-foreground">
52+
Security Questionnaire
53+
</h1>
54+
<p className="text-xs lg:text-sm text-muted-foreground leading-relaxed max-w-3xl">
55+
Automatically analyze and answer questionnaires using AI. Upload questionnaires from
56+
vendors, and our system will extract questions and generate answers based on your
57+
organization's policies and documentation.
58+
</p>
59+
</div>
60+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 lg:gap-12">
61+
<div className="lg:col-span-2">
62+
<QuestionnaireUpload
63+
selectedFile={selectedFile}
64+
onFileSelect={handleFileSelect}
65+
onFileRemove={() => setSelectedFile(null)}
66+
onParse={handleParse}
67+
isLoading={isLoading}
68+
parseStatus={parseStatus}
69+
orgId={orgId}
70+
/>
71+
</div>
72+
<div className="lg:col-span-1">
73+
<QuestionnaireSidebar />
74+
</div>
75+
</div>
76+
</div>
5577
);
5678
}
5779

5880
return (
59-
<QuestionnaireResults
81+
<div className="flex flex-col gap-6">
82+
<h1 className="text-xl lg:text-2xl font-semibold text-foreground">
83+
Vendor Questionnaire
84+
</h1>
85+
<QuestionnaireResults
6086
orgId={orgId}
6187
results={results}
6288
filteredResults={filteredResults}
@@ -85,6 +111,7 @@ export function QuestionnaireParser() {
85111
totalCount={totalCount}
86112
answeredCount={answeredCount}
87113
progressPercentage={progressPercentage}
88-
/>
114+
/>
115+
</div>
89116
);
90117
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function QuestionnaireResults({
6969
progressPercentage,
7070
}: QuestionnaireResultsProps) {
7171
return (
72-
<div className="flex flex-col w-full gap-6">
72+
<div className="flex flex-col gap-4 min-w-0">
7373
<QuestionnaireResultsHeader
7474
showExitDialog={showExitDialog}
7575
onShowExitDialogChange={onShowExitDialogChange}
@@ -89,11 +89,11 @@ export function QuestionnaireResults({
8989
onExport={onExport}
9090
/>
9191

92-
<div className="flex flex-col flex-1 min-h-0">
92+
<div className="flex flex-col flex-1 min-h-0 min-w-0">
9393
{results && results.length > 0 ? (
9494
<>
95-
<ScrollArea className="flex-1">
96-
<div className="pr-4">
95+
<ScrollArea className="flex-1 min-w-0">
96+
<div className="min-w-0">
9797
{filteredResults && filteredResults.length > 0 ? (
9898
<>
9999
<div className="hidden lg:block">

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

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

33
import { Button } from '@comp/ui/button';
44
import { Textarea } from '@comp/ui/textarea';
5-
import {
6-
BookOpen,
7-
ChevronDown,
8-
ChevronUp,
9-
Link as LinkIcon,
10-
Loader2,
11-
Sparkles,
12-
} from 'lucide-react';
5+
import { BookOpen, ChevronDown, ChevronUp, Link as LinkIcon, Loader2 } from 'lucide-react';
136
import Link from 'next/link';
147
import type { QuestionAnswer } from './types';
158

@@ -112,11 +105,13 @@ export function QuestionnaireResultsCards({
112105
e.stopPropagation();
113106
onAnswerSingleQuestion(originalIndex);
114107
}}
115-
disabled={answeringQuestionIndex === originalIndex || (isAutoAnswering && hasClickedAutoAnswer)}
108+
disabled={
109+
answeringQuestionIndex === originalIndex ||
110+
(isAutoAnswering && hasClickedAutoAnswer)
111+
}
116112
className="w-full justify-center"
117113
>
118-
<Sparkles className="mr-1.5 h-3.5 w-3.5" />
119-
Auto-Answer
114+
Answer
120115
</Button>
121116
)}
122117
{qa.failedToGenerate && (
@@ -198,4 +193,3 @@ export function QuestionnaireResultsCards({
198193
</div>
199194
);
200195
}
201-

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

Lines changed: 72 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,13 @@ import {
1919
} from '@comp/ui/dropdown-menu';
2020
import { Input } from '@comp/ui/input';
2121
import {
22-
BookOpen,
2322
ChevronDown,
2423
Download,
2524
File,
2625
FileSpreadsheet,
2726
FileText as FileTextIcon,
2827
Loader2,
2928
Search,
30-
X,
3129
Zap,
3230
} from 'lucide-react';
3331
import type { QuestionAnswer } from './types';
@@ -71,72 +69,73 @@ export function QuestionnaireResultsHeader({
7169
}: QuestionnaireResultsHeaderProps) {
7270
return (
7371
<>
74-
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-4 pb-4 border-b border-border/50">
75-
<div className="flex items-center gap-2 lg:gap-3">
76-
<Button
77-
variant="ghost"
78-
size="icon"
79-
onClick={() => onShowExitDialogChange(true)}
80-
disabled={isLoading}
81-
className="h-8 w-8 text-muted-foreground hover:text-foreground"
82-
title="Exit and start over"
83-
>
84-
<X className="h-4 w-4" />
85-
</Button>
72+
<div className="flex flex-col gap-4">
73+
<AlertDialog open={showExitDialog} onOpenChange={onShowExitDialogChange}>
74+
<AlertDialogContent className="max-w-[calc(100vw-2rem)] sm:max-w-lg">
75+
<AlertDialogHeader>
76+
<AlertDialogTitle>Exit questionnaire session?</AlertDialogTitle>
77+
<AlertDialogDescription>
78+
This will discard all questions and answers. Make sure to export your work before
79+
exiting if you want to keep it.
80+
</AlertDialogDescription>
81+
</AlertDialogHeader>
82+
<AlertDialogFooter>
83+
<AlertDialogCancel>Cancel</AlertDialogCancel>
84+
<AlertDialogAction
85+
onClick={onExit}
86+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
87+
>
88+
Exit and Discard
89+
</AlertDialogAction>
90+
</AlertDialogFooter>
91+
</AlertDialogContent>
92+
</AlertDialog>
8693

87-
<AlertDialog open={showExitDialog} onOpenChange={onShowExitDialogChange}>
88-
<AlertDialogContent className="max-w-[calc(100vw-2rem)] sm:max-w-lg">
89-
<AlertDialogHeader>
90-
<AlertDialogTitle>Exit questionnaire session?</AlertDialogTitle>
91-
<AlertDialogDescription>
92-
This will discard all questions and answers. Make sure to export your work before
93-
exiting if you want to keep it.
94-
</AlertDialogDescription>
95-
</AlertDialogHeader>
96-
<AlertDialogFooter>
97-
<AlertDialogCancel>Cancel</AlertDialogCancel>
98-
<AlertDialogAction
99-
onClick={onExit}
100-
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
101-
>
102-
Exit and Discard
103-
</AlertDialogAction>
104-
</AlertDialogFooter>
105-
</AlertDialogContent>
106-
</AlertDialog>
107-
<div className="h-4 w-px bg-border" />
108-
<BookOpen className="h-4 lg:h-5 w-4 lg:w-5 text-muted-foreground" />
109-
<div className="flex flex-col gap-1 lg:gap-1.5">
110-
<h2 className="text-base lg:text-lg font-semibold text-foreground">
111-
Questions & Answers
112-
</h2>
113-
<div className="flex items-center gap-2 lg:gap-3">
114-
<p className="text-xs text-muted-foreground">
115-
{searchQuery && filteredResults ? `${filteredResults.length} of ` : ''}
116-
{totalCount} questions • {answeredCount} answered
117-
</p>
118-
<div className="h-1 w-16 lg:w-20 bg-muted rounded-full overflow-hidden">
119-
<div
120-
className="h-full bg-primary transition-all duration-500"
121-
style={{ width: `${progressPercentage}%` }}
122-
/>
123-
</div>
94+
<div className="grid grid-cols-3 gap-8">
95+
{/* Total Questions */}
96+
<div className="px-4 py-3.5 border-l-2 border-l-primary/40">
97+
<div className="text-muted-foreground mb-1 text-[10px] font-medium uppercase tracking-widest">
98+
Questions
99+
</div>
100+
<div className="text-foreground text-2xl font-semibold tabular-nums tracking-tight">
101+
{totalCount}
102+
</div>
103+
</div>
104+
105+
{/* Answered */}
106+
<div className="px-4 py-3.5 border-l-2 border-l-emerald-500/40">
107+
<div className="text-muted-foreground mb-1 text-[10px] font-medium uppercase tracking-widest">
108+
Answered
109+
</div>
110+
<div className="text-foreground text-2xl font-semibold tabular-nums tracking-tight">
111+
{answeredCount}
112+
</div>
113+
</div>
114+
115+
{/* Progress */}
116+
<div className="px-4 py-3.5 border-l-2 border-l-blue-500/40">
117+
<div className="text-muted-foreground mb-1 text-[10px] font-medium uppercase tracking-widest">
118+
Progress
119+
</div>
120+
<div className="text-foreground text-2xl font-semibold tabular-nums tracking-tight">
121+
{progressPercentage}%
124122
</div>
125123
</div>
126124
</div>
127125

128-
<div className="flex flex-col lg:flex-row items-stretch lg:items-center gap-2 lg:gap-3 w-full lg:w-auto">
129-
<div className="relative flex-1 lg:w-72">
130-
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
126+
<div className="flex items-center justify-between gap-3">
127+
<div className="relative max-w-md">
131128
<Input
132-
placeholder="Search..."
129+
placeholder="Search questions..."
133130
value={searchQuery}
134131
onChange={(e) => onSearchChange(e.target.value)}
135-
className="pl-9 h-9 text-sm"
132+
className="text-sm w-80"
133+
leftIcon={<Search className="h-4 w-4" />}
136134
/>
137135
</div>
138-
<div className="flex items-center gap-2 w-full lg:w-auto">
139-
<div className="relative flex-1 lg:flex-initial">
136+
137+
<div className="flex items-center gap-3">
138+
<div className="relative">
140139
{!hasClickedAutoAnswer && results.some((qa) => !qa.answer) && (
141140
<>
142141
<style
@@ -161,30 +160,24 @@ export function QuestionnaireResultsHeader({
161160
onAutoAnswer();
162161
}}
163162
disabled={isAutoAnswering || isLoading}
164-
size="sm"
165-
className="relative z-10 h-9 w-full lg:w-auto"
163+
size="default"
166164
>
167165
{isAutoAnswering ? (
168-
<Loader2 className="h-4 w-4 animate-spin" />
166+
<Loader2 className="size-4 animate-spin" />
169167
) : (
170168
<>
171-
<Zap className="mr-2 h-4 w-4" />
172-
Auto-Answer All
169+
<Zap className="size-4" />
170+
Auto-Fill All
173171
</>
174172
)}
175173
</Button>
176174
</div>
177175
<DropdownMenu>
178176
<DropdownMenuTrigger asChild>
179-
<Button
180-
variant="outline"
181-
size="sm"
182-
disabled={isExporting || isLoading}
183-
className="h-9"
184-
>
185-
<Download className="mr-2 h-4 w-4" />
177+
<Button variant="outline" size="default" disabled={isExporting || isLoading}>
178+
<Download className="size-4" />
186179
Export
187-
<ChevronDown className="ml-2 h-4 w-4" />
180+
<ChevronDown className="size-4" />
188181
</Button>
189182
</DropdownMenuTrigger>
190183
<DropdownMenuContent align="end">
@@ -195,11 +188,17 @@ export function QuestionnaireResultsHeader({
195188
<FileSpreadsheet className="mr-2 h-4 w-4" />
196189
Excel
197190
</DropdownMenuItem>
198-
<DropdownMenuItem onClick={() => onExport('csv')} disabled={isExporting || isLoading}>
191+
<DropdownMenuItem
192+
onClick={() => onExport('csv')}
193+
disabled={isExporting || isLoading}
194+
>
199195
<FileTextIcon className="mr-2 h-4 w-4" />
200196
CSV
201197
</DropdownMenuItem>
202-
<DropdownMenuItem onClick={() => onExport('pdf')} disabled={isExporting || isLoading}>
198+
<DropdownMenuItem
199+
onClick={() => onExport('pdf')}
200+
disabled={isExporting || isLoading}
201+
>
203202
<File className="mr-2 h-4 w-4" />
204203
PDF
205204
</DropdownMenuItem>
@@ -211,4 +210,3 @@ export function QuestionnaireResultsHeader({
211210
</>
212211
);
213212
}
214-

0 commit comments

Comments
 (0)