Skip to content

Commit c714ffb

Browse files
committed
feat: enhance InstructionsWizard and WizardCompletionSummary with auto-fill functionality and improved UI; add tests for question defaults
1 parent 0c9531e commit c714ffb

File tree

7 files changed

+368
-112
lines changed

7 files changed

+368
-112
lines changed

components/instructions-wizard.tsx

Lines changed: 187 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22

3-
import { useMemo, useState } from "react"
3+
import { useCallback, useMemo, useState } from "react"
44
import { Button } from "@/components/ui/button"
55
import { Undo2 } from "lucide-react"
66

@@ -101,6 +101,9 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
101101
const [pendingConfirmation, setPendingConfirmation] = useState<WizardConfirmationIntent | null>(null)
102102
const [generatedFile, setGeneratedFile] = useState<GeneratedFileResult | null>(null)
103103
const [isGenerating, setIsGenerating] = useState(false)
104+
const [isFrameworkFastTrackPromptVisible, setIsFrameworkFastTrackPromptVisible] = useState(false)
105+
const [autoFilledQuestionMap, setAutoFilledQuestionMap] = useState<Record<string, boolean>>({})
106+
const [autoFillNotice, setAutoFillNotice] = useState<string | null>(null)
104107

105108
const selectedFile = useMemo(() => {
106109
if (selectedFileId) {
@@ -123,6 +126,11 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
123126
[dynamicSteps]
124127
)
125128

129+
const nonFrameworkSteps = useMemo(
130+
() => wizardSteps.filter((step) => step.id !== FRAMEWORK_STEP_ID),
131+
[wizardSteps]
132+
)
133+
126134
const currentStep = wizardSteps[currentStepIndex] ?? null
127135
const currentQuestion = currentStep?.questions[currentQuestionIndex] ?? null
128136

@@ -131,9 +139,21 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
131139
[wizardSteps]
132140
)
133141

142+
const remainingQuestionCount = useMemo(
143+
() => nonFrameworkSteps.reduce((count, step) => count + step.questions.length, 0),
144+
[nonFrameworkSteps]
145+
)
146+
134147
const completionSummary = useMemo(
135-
() => buildCompletionSummary(selectedFile ?? null, selectedFileFormatLabel, wizardSteps, responses),
136-
[selectedFile, selectedFileFormatLabel, wizardSteps, responses]
148+
() =>
149+
buildCompletionSummary(
150+
selectedFile ?? null,
151+
selectedFileFormatLabel,
152+
wizardSteps,
153+
responses,
154+
autoFilledQuestionMap
155+
),
156+
[selectedFile, selectedFileFormatLabel, wizardSteps, responses, autoFilledQuestionMap]
137157
)
138158

139159
const currentAnswerValue = currentQuestion ? responses[currentQuestion.id] : undefined
@@ -167,6 +187,37 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
167187
? `Use default (${defaultAnswer.label})`
168188
: "Use default"
169189

190+
const showFrameworkPivot = !isComplete && isFrameworkFastTrackPromptVisible
191+
const showQuestionControls = !isComplete && !isFrameworkFastTrackPromptVisible
192+
193+
const markQuestionsAutoFilled = useCallback((questionIds: string[]) => {
194+
if (questionIds.length === 0) {
195+
return
196+
}
197+
198+
setAutoFilledQuestionMap((prev) => {
199+
const next = { ...prev }
200+
questionIds.forEach((id) => {
201+
if (id !== FRAMEWORK_QUESTION_ID) {
202+
next[id] = true
203+
}
204+
})
205+
return next
206+
})
207+
}, [])
208+
209+
const clearAutoFilledFlag = useCallback((questionId: string) => {
210+
setAutoFilledQuestionMap((prev) => {
211+
if (!prev[questionId]) {
212+
return prev
213+
}
214+
215+
const next = { ...prev }
216+
delete next[questionId]
217+
return next
218+
})
219+
}, [])
220+
170221
if (!currentStep || !currentQuestion) {
171222
return null
172223
}
@@ -198,6 +249,10 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
198249
}
199250

200251
const goToPrevious = () => {
252+
if (isFrameworkFastTrackPromptVisible) {
253+
setIsFrameworkFastTrackPromptVisible(false)
254+
}
255+
201256
const isFirstQuestionInStep = currentQuestionIndex === 0
202257
const isFirstStep = currentStepIndex === 0
203258

@@ -233,6 +288,12 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
233288
answers: question.answers.map(mapAnswerSourceToWizard),
234289
}))
235290

291+
const followUpQuestionCount =
292+
mappedQuestions.length + suffixSteps.reduce((count, step) => count + step.questions.length, 0)
293+
294+
setAutoFilledQuestionMap({})
295+
setAutoFillNotice(null)
296+
236297
setDynamicSteps([
237298
{
238299
id: `framework-${frameworkId}`,
@@ -241,6 +302,8 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
241302
},
242303
])
243304

305+
setIsFrameworkFastTrackPromptVisible(followUpQuestionCount > 0)
306+
244307
setResponses((prev) => {
245308
const next = { ...prev }
246309
mappedQuestions.forEach((question) => {
@@ -255,10 +318,75 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
255318
} catch (error) {
256319
console.error(`Unable to load questions for framework "${frameworkId}"`, error)
257320
setDynamicSteps([])
321+
setIsFrameworkFastTrackPromptVisible(false)
258322
} finally {
259323
}
260324
}
261325

326+
const applyDefaultsAcrossWizard = () => {
327+
setGeneratedFile(null)
328+
setAutoFilledQuestionMap({})
329+
330+
const autoFilledIds: string[] = []
331+
332+
setResponses((prev) => {
333+
const next: Responses = { ...prev }
334+
335+
wizardSteps.forEach((step) => {
336+
step.questions.forEach((question) => {
337+
if (question.id === FRAMEWORK_QUESTION_ID) {
338+
return
339+
}
340+
341+
const defaultAnswers = question.answers.filter((answer) => answer.isDefault && !answer.disabled)
342+
343+
if (defaultAnswers.length === 0) {
344+
return
345+
}
346+
347+
autoFilledIds.push(question.id)
348+
349+
next[question.id] = question.allowMultiple
350+
? defaultAnswers.map((answer) => answer.value)
351+
: defaultAnswers[0]?.value
352+
})
353+
})
354+
355+
return next
356+
})
357+
358+
markQuestionsAutoFilled(autoFilledIds)
359+
360+
if (autoFilledIds.length > 0) {
361+
setAutoFillNotice("We applied the recommended defaults for you. Tweak any section before generating.")
362+
} else {
363+
setAutoFillNotice(null)
364+
}
365+
366+
setIsFrameworkFastTrackPromptVisible(false)
367+
368+
const lastStepIndex = Math.max(wizardSteps.length - 1, 0)
369+
const lastStep = wizardSteps[lastStepIndex]
370+
const lastQuestionIndex = lastStep ? Math.max(lastStep.questions.length - 1, 0) : 0
371+
372+
setCurrentStepIndex(lastStepIndex)
373+
setCurrentQuestionIndex(lastQuestionIndex)
374+
setIsComplete(true)
375+
}
376+
377+
const beginStepByStepFlow = () => {
378+
const firstNonFrameworkIndex = wizardSteps.findIndex((step) => step.id !== FRAMEWORK_STEP_ID)
379+
380+
if (firstNonFrameworkIndex !== -1) {
381+
setCurrentStepIndex(firstNonFrameworkIndex)
382+
setCurrentQuestionIndex(0)
383+
}
384+
385+
setIsFrameworkFastTrackPromptVisible(false)
386+
setIsComplete(false)
387+
setAutoFillNotice(null)
388+
}
389+
262390
const handleAnswerClick = async (answer: WizardAnswer) => {
263391
if (answer.disabled) {
264392
return
@@ -292,6 +420,8 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
292420
[currentQuestion.id]: nextValue,
293421
}))
294422

423+
clearAutoFilledFlag(currentQuestion.id)
424+
295425
const isFrameworkQuestion = currentQuestion.id === FRAMEWORK_QUESTION_ID
296426
const shouldAutoAdvance =
297427
!isFrameworkQuestion &&
@@ -309,6 +439,7 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
309439
await loadFrameworkQuestions(answer.value, answer.label)
310440
} else {
311441
setDynamicSteps([])
442+
setIsFrameworkFastTrackPromptVisible(false)
312443
}
313444
}
314445
}
@@ -329,6 +460,8 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
329460
[currentQuestion.id]: nextValue,
330461
}))
331462

463+
clearAutoFilledFlag(currentQuestion.id)
464+
332465
const isFrameworkQuestion = currentQuestion.id === FRAMEWORK_QUESTION_ID
333466

334467
if (isFrameworkQuestion) {
@@ -348,6 +481,9 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
348481
setIsComplete(false)
349482
setGeneratedFile(null)
350483
setIsGenerating(false)
484+
setIsFrameworkFastTrackPromptVisible(false)
485+
setAutoFilledQuestionMap({})
486+
setAutoFillNotice(null)
351487
}
352488

353489
const resetWizard = () => {
@@ -456,15 +592,18 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
456592
: undefined
457593
const showChangeFile = Boolean(onClose && selectedFile)
458594

595+
const topButtonLabel = showFrameworkPivot ? "Choose a different network" : "Start Over"
596+
const topButtonHandler = showFrameworkPivot ? () => goToPrevious() : () => requestResetWizard()
597+
459598
const wizardLayout = (
460599
<div className="mx-auto flex w-full max-w-4xl flex-col gap-6">
461600
<Button
462601
variant="destructive"
463602
size="sm"
464-
onClick={requestResetWizard}
603+
onClick={topButtonHandler}
465604
className="fixed left-4 top-4 z-40"
466605
>
467-
Start Over
606+
{topButtonLabel}
468607
</Button>
469608

470609
{selectedFile ? (
@@ -484,14 +623,38 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
484623
</section>
485624
) : null}
486625

487-
{isComplete ? (
488-
<WizardCompletionSummary
489-
summary={completionSummary}
490-
onBack={goToPrevious}
491-
onGenerate={() => void generateInstructionsFile()}
492-
isGenerating={isGenerating}
493-
/>
494-
) : (
626+
{showFrameworkPivot ? (
627+
<section className="rounded-3xl border border-border/80 bg-card/95 p-6 shadow-lg">
628+
<div className="flex flex-col gap-6">
629+
<div className="space-y-4">
630+
<div className="space-y-2">
631+
<h1 className="text-3xl font-semibold text-foreground">Skip the deep dive?</h1>
632+
<p className="text-sm text-muted-foreground">
633+
We can auto-apply the recommended answers for the next {remainingQuestionCount}{" "}
634+
{remainingQuestionCount === 1 ? "question" : "questions"} across these sections. (You can still tweak the defaults.)
635+
</p>
636+
</div>
637+
<div className="flex flex-wrap items-center justify-between gap-3">
638+
<div className="flex flex-wrap gap-2">
639+
<Button variant="secondary" className="px-5" onClick={() => beginStepByStepFlow()}>
640+
Fill it out step-by-step
641+
</Button>
642+
<Button
643+
onClick={() => applyDefaultsAcrossWizard()}
644+
disabled={remainingQuestionCount === 0}
645+
className="px-5"
646+
>
647+
Use recommended defaults
648+
</Button>
649+
</div>
650+
</div>
651+
</div>
652+
653+
</div>
654+
</section>
655+
) : null}
656+
657+
{showQuestionControls ? (
495658
<section className="rounded-3xl border border-border/80 bg-card/95 p-6 shadow-lg">
496659
<div className="flex flex-col gap-4">
497660
<div className="flex flex-wrap items-center gap-2">
@@ -531,7 +694,17 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
531694
</div>
532695
</div>
533696
</section>
534-
)}
697+
) : null}
698+
699+
{isComplete ? (
700+
<WizardCompletionSummary
701+
summary={completionSummary}
702+
onBack={goToPrevious}
703+
onGenerate={() => void generateInstructionsFile()}
704+
isGenerating={isGenerating}
705+
autoFillNotice={autoFillNotice}
706+
/>
707+
) : null}
535708

536709
{pendingConfirmation ? (
537710
<WizardConfirmationDialog

0 commit comments

Comments
 (0)