Skip to content
Merged

save #49

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client'

import { useRef, useState, useCallback } from 'react'
import type { Question } from '../questions'

export function useQuestionNavigation(questions: Question[]) {
const [currentStep, setCurrentStep] = useState(1)
const questionRefs = useRef<Record<string, React.RefObject<HTMLDivElement | null>>>({})

// Initialize refs for each question
questions.forEach((q) => {
if (!questionRefs.current[q.id]) {
questionRefs.current[q.id] = { current: null }
}
})

const scrollToQuestion = useCallback((index: number) => {
const questionId = questions[index]?.id
if (questionId && questionRefs.current[questionId]?.current) {
questionRefs.current[questionId].current?.scrollIntoView({
behavior: 'smooth',
block: 'start'
})
setCurrentStep(index + 1)
}
}, [questions])

const goToPrevious = useCallback(() => {
scrollToQuestion(Math.max(0, currentStep - 2))
}, [currentStep, scrollToQuestion])

const goToNext = useCallback(() => {
scrollToQuestion(Math.min(questions.length - 1, currentStep))
}, [currentStep, questions.length, scrollToQuestion])

return {
currentStep,
questionRefs,
scrollToQuestion,
goToPrevious,
goToNext,
totalSteps: questions.length
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SVG Icons as components

export const BackArrowIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 0.5L1.5 8L8 15.5" stroke="#465169" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M1.5 8H15.5" stroke="#465169" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)

export const NavArrowLeftIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.5 8.5L1.5 8.5" stroke="#465169" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5 3.5L1.5 8L5 12.5" stroke="#465169" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)

export const NavArrowRightIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 8.5L14.5 8.5" stroke="#465169" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11 3.5L14.5 8L11 12.5" stroke="#465169" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)

export const CloseIcon = () => (
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 0.5L0.5 9.5" stroke="#465169" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M0.5 0.5L9.5 9.5" stroke="#465169" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)

export const ChevronIcon = () => (
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.5 0.5L5 5L9.5 0.5" stroke="#465169" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)

export const PrivadoAgentIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="8" fill="url(#paint0_linear)" />
<path opacity="0.75" d="M-1 9.55V13.45L7 18V15.0035V14.1L2.33333 11.5L7 8.9V7.509V5L-1 9.55Z" fill="white" />
<path opacity="0.9" d="M9 -1V1.91967V2.8L13.6667 5.33333L9 7.86667V9.22199V18L13 16.1V9.45L17 7.23333V3.43333L9 -1Z" fill="white" />
<defs>
<linearGradient id="paint0_linear" x1="8" y1="16" x2="8" y2="0" gradientUnits="userSpaceOnUse">
<stop stopColor="#B380FF" />
<stop offset="1" stopColor="#FFBBBB" />
</linearGradient>
</defs>
</svg>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use client'

import { VeltCommentTool, VeltCommentBubble } from '@veltdev/react'
import { DropdownInput, PrivadoAgentBadge } from './ui-components'
import type { Question } from './questions'

interface QuestionSectionProps {
question: Question
sectionRef: React.RefObject<HTMLDivElement | null>
value: string
onChange: (value: string) => void
activeCommentToolId: string | null
}

export const QuestionSection = ({ question, sectionRef, value, onChange, activeCommentToolId }: QuestionSectionProps) => {
const targetElementId = `question-${question.id}`

return (
<div
ref={sectionRef}
id={targetElementId}
className="flex flex-col gap-[24px] items-start px-[48px] py-[32px] w-full relative group"
style={{
backgroundColor: 'rgb(255, 255, 255)',
borderTop: '1px solid rgb(237, 240, 248)'
}}
>
{/* Question Title with Comment Tools */}
<div className="flex justify-between items-start w-full">
<div className="flex flex-col gap-[4px] items-start flex-1">
<p
className="text-[18px] leading-[24px] w-full"
style={{ fontFamily: "'TT Interphases Pro Variable', Inter, system-ui, sans-serif" }}
>
<span style={{ color: '#5c6c8a' }}>{question.number}.</span>{' '}
<span style={{ color: '#172026', fontWeight: 500 }}>{question.title}</span>
</p>
<p
className="text-[13px] leading-[20px] w-full"
style={{
fontFamily: "'TT Interphases Pro Variable', Inter, system-ui, sans-serif",
color: '#5c6c8a'
}}
>
{question.description}
</p>
</div>

{/* [Velt] Comment tools for each question row */}
{/* activeCommentToolId is used to show the active comment tool / bubble */}
<div className={`flex items-center gap-1 ml-4 ${activeCommentToolId === question.id ? 'velt-comment-tool-wrapper-active' : ''}`}>
<VeltCommentBubble
// targetElementId={targetElementId}
openDialog={false}
context={{ questionId: question.id, questionNumber: question.number, questionTitle: question.title}}
/>
<VeltCommentTool
contextInPageModeComposer={true}
// targetElementId={targetElementId}
context={{ questionId: question.id, questionNumber: question.number, questionTitle: question.title}}
/>
</div>
</div>

{/* Dropdown Input */}
<DropdownInput
value={value || question.defaultValue}
options={question.options}
onChange={onChange}
/>

{/* Privado Agent Badge */}
<PrivadoAgentBadge />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Question data structure
export interface Question {
id: string
number: string
title: string
description: string
defaultValue: string
options: string[]
}

export const questions: Question[] = [
{
id: 'q1',
number: '3.1',
title: 'Which application or product line does this repository belong to?',
description: 'This grouping will create reports at an application/product level.',
defaultValue: 'Healthcare-backend',
options: ['Healthcare-backend', 'Finance-api', 'User-service', 'Analytics-engine'],
},
{
id: 'q2',
number: '3.2',
title: 'What type of personal data does this processing involve?',
description: 'Select the categories of personal data being processed.',
defaultValue: 'Healthcare-backend',
options: ['Healthcare-backend', 'Contact-info', 'Financial-data', 'Location-data'],
},
{
id: 'q3',
number: '3.3',
title: 'What is the legal basis for processing this data?',
description: 'Select the applicable legal basis under GDPR Article 6.',
defaultValue: 'Healthcare-backend',
options: ['Healthcare-backend', 'Consent', 'Contract', 'Legal-obligation'],
},
{
id: 'q4',
number: '3.4',
title: 'How long will the data be retained?',
description: 'Specify the retention period for this data processing activity.',
defaultValue: 'Healthcare-backend',
options: ['Healthcare-backend', '1-year', '3-years', '7-years'],
},
{
id: 'q5',
number: '3.5',
title: 'Are there any data transfers outside the EEA?',
description: 'Indicate if data is transferred to third countries.',
defaultValue: 'Healthcare-backend',
options: ['Healthcare-backend', 'Yes-with-SCCs', 'Yes-with-adequacy', 'No-transfers'],
},
{
id: 'q6',
number: '3.6',
title: 'What security measures are in place?',
description: 'Describe the technical and organizational security measures.',
defaultValue: 'Healthcare-backend',
options: ['Healthcare-backend', 'Encryption', 'Access-controls', 'Audit-logging'],
},
{
id: 'q7',
number: '3.7',
title: 'Is a Data Protection Impact Assessment required?',
description: 'Determine if a DPIA is needed based on risk assessment.',
defaultValue: 'Healthcare-backend',
options: ['Healthcare-backend', 'Yes-required', 'No-not-required', 'Under-review'],
},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
'use client'

import { NavArrowLeftIcon, NavArrowRightIcon } from './icons'

interface ProgressIndicatorProps {
currentStep: number
totalSteps: number
}

const ProgressIndicator = ({ currentStep, totalSteps }: ProgressIndicatorProps) => {
const progress = Math.round((currentStep / totalSteps) * 100)

return (
<div className="flex items-center gap-[8px]">
<span
className="text-[13px] leading-[18px] whitespace-nowrap"
style={{
fontFamily: "'TT Interphases Pro Variable', Inter, system-ui, sans-serif",
color: '#172026'
}}
>
Step {currentStep} of {totalSteps}
</span>

{/* Progress Bar */}
<div
className="w-[108px] h-[8px] rounded-[16px] overflow-hidden"
style={{ backgroundColor: 'rgb(221, 227, 238)' }}
>
<div
className="h-full rounded-l-[4px] rounded-r-[1px] transition-all duration-300"
style={{
backgroundColor: 'rgb(29, 202, 115)',
width: `${(progress / 100) * 108}px`
}}
/>
</div>

<span
className="text-[12px] leading-[16px] whitespace-nowrap"
style={{
fontFamily: "'TT Interphases Pro Variable', Inter, system-ui, sans-serif",
color: '#465169'
}}
>
{progress}%
</span>
</div>
)
}

interface NavigationButtonsProps {
currentStep: number
totalSteps: number
onPrevious: () => void
onNext: () => void
}

const NavigationButtons = ({ currentStep, totalSteps, onPrevious, onNext }: NavigationButtonsProps) => {
return (
<div className="flex items-center gap-[12px]">
{/* Arrow buttons */}
<div
className="flex items-center justify-center rounded-[6px] overflow-hidden"
style={{
backgroundColor: 'rgb(255, 255, 255)',
boxShadow: 'var(--pia-shadow-btn-tertiary, 0px 0px 0px 1px rgba(12, 55, 136, 0.14), 0px 1px 2px rgba(92, 108, 138, 0.24))'
}}
>
<button
className="flex items-center justify-center p-[6px] rounded-[8px] overflow-hidden hover:bg-gray-50 transition-colors disabled:opacity-50"
onClick={onPrevious}
disabled={currentStep <= 1}
aria-label="Previous question"
>
<NavArrowLeftIcon />
</button>
<div
className="w-px self-stretch"
style={{ backgroundColor: 'rgba(12, 55, 136, 0.14)' }}
/>
<button
className="flex items-center justify-center p-[6px] rounded-[8px] overflow-hidden hover:bg-gray-50 transition-colors disabled:opacity-50"
onClick={onNext}
disabled={currentStep >= totalSteps}
aria-label="Next question"
>
<NavArrowRightIcon />
</button>
</div>

{/* Submit Button */}
<button
className="flex items-center justify-center gap-[2px] px-[6px] py-[5px] rounded-[8px] overflow-hidden"
style={{
backgroundColor: '#754cff',
boxShadow: '0px 0px 0px 1px #5a34d9, 0px 1px 2px rgba(23, 32, 38, 0.24), inset 0px 1px 0px 0px rgba(255, 255, 255, 0.3)'
}}
>
<div className="flex items-center justify-center px-[4px]">
<span
className="text-[14px] leading-[18px] font-medium whitespace-nowrap"
style={{
fontFamily: "'TT Interphases Pro Variable', Inter, system-ui, sans-serif",
color: '#ffffff'
}}
>
Submit Assessment
</span>
</div>
</button>
</div>
)
}

interface SecondHeaderBarProps {
currentStep: number
totalSteps: number
onPrevious: () => void
onNext: () => void
}

export const SecondHeaderBar = ({ currentStep, totalSteps, onPrevious, onNext }: SecondHeaderBarProps) => {
return (
<div
className="flex items-center justify-between pl-[24px] pr-[20px] py-[12px] flex-shrink-0"
style={{
backgroundColor: 'var(--pia-canvas-bg, rgba(255, 255, 255, 0.6))',
backdropFilter: 'blur(20px)',
borderBottom: '1px solid var(--pia-border-medium, rgb(221, 227, 238))',
}}
>
<div className="flex items-center">
<p
className="text-[16px] leading-[24px] whitespace-nowrap"
style={{ fontFamily: "'TT Interphases Pro Variable', Inter, system-ui, sans-serif" }}
>
<span style={{ color: '#172026', fontWeight: 500 }}>Section 3 </span>
<span style={{ color: '#465169' }}>– </span>
<span style={{ color: '#465169', fontWeight: 400 }}>Fundamental principles</span>
</p>
</div>

<div className="flex items-center gap-[24px]">
<ProgressIndicator currentStep={currentStep} totalSteps={totalSteps} />
<NavigationButtons
currentStep={currentStep}
totalSteps={totalSteps}
onPrevious={onPrevious}
onNext={onNext}
/>
</div>
</div>
)
}
Loading
Loading