@@ -10,13 +10,22 @@ import { apiRequest } from "@/lib/queryClient";
1010import { Select , SelectContent , SelectItem , SelectTrigger , SelectValue } from "@/components/ui/select" ;
1111import { Table , TableBody , TableCell , TableHead , TableHeader , TableRow } from "@/components/ui/table" ;
1212import { LineChart , Line , ResponsiveContainer } from "recharts" ;
13+ import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogDescription , DialogFooter } from "@/components/ui/dialog" ;
14+ import { Textarea } from "@/components/ui/textarea" ;
1315
1416export default function AuditPage ( ) {
1517 const [ selectedRepoId , setSelectedRepoId ] = useState ( "" ) ;
1618 const [ branch , setBranch ] = useState ( "" ) ;
1719 const [ currentAuditId , setCurrentAuditId ] = useState < string | null > ( null ) ;
1820 const [ logs , setLogs ] = useState < { log : string , progress : number , timestamp : string } [ ] > ( [ ] ) ;
1921 const [ progress , setProgress ] = useState ( 0 ) ;
22+
23+ // Feedback Modal State
24+ const [ showFeedbackModal , setShowFeedbackModal ] = useState ( false ) ;
25+ const [ feedbackRating , setFeedbackRating ] = useState < number | null > ( null ) ;
26+ const [ valueRating , setValueRating ] = useState < number | null > ( null ) ;
27+ const [ feedbackText , setFeedbackText ] = useState ( "" ) ;
28+
2029 const { toast } = useToast ( ) ;
2130 const queryClient = useQueryClient ( ) ;
2231
@@ -160,6 +169,45 @@ export default function AuditPage() {
160169 enabled : ! ! currentAuditId && audit ?. status === "complete" ,
161170 } ) ;
162171
172+ const { data : feedbackData } = useQuery < any > ( {
173+ queryKey : [ "/api/audits" , currentAuditId , "feedback" ] ,
174+ enabled : ! ! currentAuditId && audit ?. status === "complete" ,
175+ } ) ;
176+
177+ const showFeedbackMutation = useMutation ( {
178+ mutationFn : async ( ) => {
179+ const res = await apiRequest ( "POST" , `/api/audits/${ currentAuditId } /feedback/show` ) ;
180+ return res . json ( ) ;
181+ }
182+ } ) ;
183+
184+ const submitFeedbackMutation = useMutation ( {
185+ mutationFn : async ( data : { responses : any , freeText : string } ) => {
186+ const res = await apiRequest ( "POST" , `/api/audits/${ currentAuditId } /feedback/submit` , data ) ;
187+ return res . json ( ) ;
188+ } ,
189+ onSuccess : ( ) => {
190+ toast ( {
191+ title : "Feedback Submitted" ,
192+ description : "Thank you for helping us improve CodeGuard!" ,
193+ } ) ;
194+ setShowFeedbackModal ( false ) ;
195+ queryClient . invalidateQueries ( { queryKey : [ "/api/audits" , currentAuditId , "feedback" ] } ) ;
196+ }
197+ } ) ;
198+
199+ const handleDownloadPdf = ( ) => {
200+ window . open ( `/api/audits/${ currentAuditId } /pdf` , '_blank' ) ;
201+
202+ // Wait a brief moment then show feedback prompt if they haven't responded yet
203+ setTimeout ( ( ) => {
204+ if ( ! feedbackData ?. respondedAt ) {
205+ showFeedbackMutation . mutate ( ) ;
206+ setShowFeedbackModal ( true ) ;
207+ }
208+ } , 1500 ) ;
209+ } ;
210+
163211 const createOrderMutation = useMutation ( {
164212 mutationFn : async ( ) => {
165213 const res = await apiRequest ( "POST" , "/api/orders" , {
@@ -332,7 +380,7 @@ export default function AuditPage() {
332380 < Download className = "mr-2 h-4 w-4" />
333381 JSON
334382 </ Button >
335- < Button variant = "outline" size = "sm" onClick = { ( ) => window . open ( `/api/audits/ ${ currentAuditId } /pdf` , '_blank' ) } >
383+ < Button variant = "outline" size = "sm" onClick = { handleDownloadPdf } >
336384 < FileText className = "mr-2 h-4 w-4" />
337385 PDF
338386 </ Button >
@@ -457,6 +505,80 @@ export default function AuditPage() {
457505 </ CardContent >
458506 </ Card >
459507 ) }
508+
509+ { /* Post-Download Feedback Modal */ }
510+ < Dialog open = { showFeedbackModal } onOpenChange = { setShowFeedbackModal } >
511+ < DialogContent className = "sm:max-w-[425px]" >
512+ < DialogHeader >
513+ < DialogTitle > Quick Feedback</ DialogTitle >
514+ < DialogDescription >
515+ We're constantly improving our ASVS 5.0 audit engine. How did we do on this report?
516+ </ DialogDescription >
517+ </ DialogHeader >
518+ < div className = "grid gap-6 py-4" >
519+ < div className = "space-y-3" >
520+ < Label > How accurate were the security findings?</ Label >
521+ < div className = "flex justify-between items-center" >
522+ < span className = "text-xs text-muted-foreground" > Poor</ span >
523+ { [ 1 , 2 , 3 , 4 , 5 ] . map ( ( val ) => (
524+ < Button
525+ key = { val }
526+ variant = { feedbackRating === val ? "default" : "outline" }
527+ className = "w-10 h-10 rounded-full p-0"
528+ onClick = { ( ) => setFeedbackRating ( val ) }
529+ >
530+ { val }
531+ </ Button >
532+ ) ) }
533+ < span className = "text-xs text-muted-foreground" > Excellent</ span >
534+ </ div >
535+ </ div >
536+
537+ < div className = "space-y-3" >
538+ < Label > Would you pay for this level of automated audit?</ Label >
539+ < div className = "flex justify-between items-center" >
540+ < span className = "text-xs text-muted-foreground" > No</ span >
541+ { [ 1 , 2 , 3 , 4 , 5 ] . map ( ( val ) => (
542+ < Button
543+ key = { `val-${ val } ` }
544+ variant = { valueRating === val ? "default" : "outline" }
545+ className = "w-10 h-10 rounded-full p-0"
546+ onClick = { ( ) => setValueRating ( val ) }
547+ >
548+ { val }
549+ </ Button >
550+ ) ) }
551+ < span className = "text-xs text-muted-foreground" > Definitely</ span >
552+ </ div >
553+ </ div >
554+
555+ < div className = "space-y-2" >
556+ < Label htmlFor = "feedback" > Any additional comments? (Optional)</ Label >
557+ < Textarea
558+ id = "feedback"
559+ placeholder = "What was missing? What was helpful?"
560+ value = { feedbackText }
561+ onChange = { ( e ) => setFeedbackText ( e . target . value ) }
562+ />
563+ </ div >
564+ </ div >
565+ < DialogFooter >
566+ < Button variant = "ghost" onClick = { ( ) => setShowFeedbackModal ( false ) } > Skip</ Button >
567+ < Button
568+ onClick = { ( ) => {
569+ submitFeedbackMutation . mutate ( {
570+ responses : { accuracy : feedbackRating , willingnessToPay : valueRating } ,
571+ freeText : feedbackText
572+ } ) ;
573+ } }
574+ disabled = { ! feedbackRating || submitFeedbackMutation . isPending }
575+ >
576+ { submitFeedbackMutation . isPending && < Loader2 className = "mr-2 h-4 w-4 animate-spin" /> }
577+ Submit Feedback
578+ </ Button >
579+ </ DialogFooter >
580+ </ DialogContent >
581+ </ Dialog >
460582 </ div >
461583 ) ;
462584}
0 commit comments