11"use client"
22
3- import { useCallback , useMemo , useState } from "react"
3+ import { useCallback , useEffect , useMemo , useState } from "react"
44import { Button } from "@/components/ui/button"
55import { Undo2 } from "lucide-react"
66
7- import type { DataQuestionSource , FileOutputConfig , FrameworkConfig , InstructionsWizardProps , Responses , WizardAnswer , WizardConfirmationIntent , WizardQuestion , WizardStep } from "@/types/wizard"
8- import rawFrameworks from "@/data/frameworks.json"
7+ import type { DataQuestionSource , FileOutputConfig , InstructionsWizardProps , Responses , WizardAnswer , WizardConfirmationIntent , WizardQuestion , WizardStep } from "@/types/wizard"
8+ import frameworksData from "@/data/frameworks.json"
99import generalData from "@/data/general.json"
1010import architectureData from "@/data/architecture.json"
1111import performanceData from "@/data/performance.json"
@@ -16,43 +16,40 @@ import FinalOutputView from "./final-output-view"
1616import { WizardAnswerGrid } from "./wizard-answer-grid"
1717import { WizardCompletionSummary } from "./wizard-completion-summary"
1818import { WizardConfirmationDialog } from "./wizard-confirmation-dialog"
19+ import { WizardEditAnswerDialog } from "./wizard-edit-answer-dialog"
1920
2021import { generateInstructions } from "@/lib/instructions-api"
2122import { buildCompletionSummary } from "@/lib/wizard-summary"
2223import { serializeWizardResponses } from "@/lib/wizard-response"
23- import { buildStepFromQuestionSet , getFormatLabel , mapAnswerSourceToWizard } from "@/lib/wizard-utils"
24+ import { buildFileOptionsFromQuestion , buildStepFromQuestionSet , getFormatLabel , mapAnswerSourceToWizard } from "@/lib/wizard-utils"
2425import type { GeneratedFileResult } from "@/types/output"
2526
26- const fileOptions = filesData as FileOutputConfig [ ]
27- const defaultFileOption =
28- fileOptions . find ( ( file ) => file . isDefault ) ??
29- fileOptions . find ( ( file ) => file . enabled !== false ) ??
30- fileOptions [ 0 ] ??
31- null
32-
3327const FRAMEWORK_STEP_ID = "frameworks"
3428const FRAMEWORK_QUESTION_ID = "frameworkSelection"
3529const DEVCONTEXT_ROOT_URL = "https://devcontext.xyz/"
3630
37- const frameworksStep : WizardStep = {
38- id : FRAMEWORK_STEP_ID ,
39- title : "Choose Your Framework" ,
40- questions : [
41- {
42- id : FRAMEWORK_QUESTION_ID ,
43- question : "Which framework are you working with?" ,
44- answers : ( rawFrameworks as FrameworkConfig [ ] ) . map ( ( framework ) => ( {
45- value : framework . id ,
46- label : framework . label ,
47- icon : framework . icon ,
48- disabled : framework . enabled === false ,
49- disabledLabel : framework . enabled === false ? "Soon" : undefined ,
50- docs : framework . docs ,
51- isDefault : framework . isDefault ,
52- } ) ) ,
53- } ,
54- ] ,
55- }
31+ const frameworkQuestionSet = frameworksData as DataQuestionSource [ ]
32+ const frameworksStep = buildStepFromQuestionSet (
33+ FRAMEWORK_STEP_ID ,
34+ "Choose Your Framework" ,
35+ frameworkQuestionSet
36+ )
37+ const frameworkQuestion = frameworksStep . questions . find ( ( question ) => question . id === FRAMEWORK_QUESTION_ID ) ?? null
38+
39+ const fileQuestionSet = filesData as DataQuestionSource [ ]
40+ const fileQuestion = fileQuestionSet [ 0 ] ?? null
41+ const fileOptions = buildFileOptionsFromQuestion ( fileQuestion )
42+ const defaultFileOption =
43+ fileOptions . find ( ( file ) => file . isDefault ) ??
44+ fileOptions [ 0 ] ??
45+ null
46+ const fileSummaryQuestion = fileQuestion
47+ ? {
48+ id : fileQuestion . id ,
49+ question : fileQuestion . question ,
50+ isReadOnlyOnSummary : fileQuestion . isReadOnlyOnSummary ,
51+ }
52+ : null
5653
5754const generalStep = buildStepFromQuestionSet (
5855 "general" ,
@@ -104,6 +101,13 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
104101 const [ isFrameworkFastTrackPromptVisible , setIsFrameworkFastTrackPromptVisible ] = useState ( false )
105102 const [ autoFilledQuestionMap , setAutoFilledQuestionMap ] = useState < Record < string , boolean > > ( { } )
106103 const [ autoFillNotice , setAutoFillNotice ] = useState < string | null > ( null )
104+ const [ activeEditQuestionId , setActiveEditQuestionId ] = useState < string | null > ( null )
105+
106+ useEffect ( ( ) => {
107+ if ( ! isComplete && activeEditQuestionId ) {
108+ setActiveEditQuestionId ( null )
109+ }
110+ } , [ isComplete , activeEditQuestionId ] )
107111
108112 const selectedFile = useMemo ( ( ) => {
109113 if ( selectedFileId ) {
@@ -131,6 +135,21 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
131135 [ wizardSteps ]
132136 )
133137
138+ const wizardQuestionsById = useMemo ( ( ) => {
139+ const lookup : Record < string , WizardQuestion > = { }
140+
141+ wizardSteps . forEach ( ( step ) => {
142+ step . questions . forEach ( ( question ) => {
143+ lookup [ question . id ] = question
144+ } )
145+ } )
146+
147+ return lookup
148+ } , [ wizardSteps ] )
149+
150+ const editingQuestion = activeEditQuestionId ? wizardQuestionsById [ activeEditQuestionId ] ?? null : null
151+ const editingAnswerValue = editingQuestion ? responses [ editingQuestion . id ] : undefined
152+
134153 const currentStep = wizardSteps [ currentStepIndex ] ?? null
135154 const currentQuestion = currentStep ?. questions [ currentQuestionIndex ] ?? null
136155
@@ -147,13 +166,14 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
147166 const completionSummary = useMemo (
148167 ( ) =>
149168 buildCompletionSummary (
169+ fileSummaryQuestion ,
150170 selectedFile ?? null ,
151171 selectedFileFormatLabel ,
152172 wizardSteps ,
153173 responses ,
154174 autoFilledQuestionMap
155175 ) ,
156- [ selectedFile , selectedFileFormatLabel , wizardSteps , responses , autoFilledQuestionMap ]
176+ [ fileSummaryQuestion , selectedFile , selectedFileFormatLabel , wizardSteps , responses , autoFilledQuestionMap ]
157177 )
158178
159179 const currentAnswerValue = currentQuestion ? responses [ currentQuestion . id ] : undefined
@@ -218,6 +238,10 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
218238 } )
219239 } , [ ] )
220240
241+ const closeEditDialog = useCallback ( ( ) => {
242+ setActiveEditQuestionId ( null )
243+ } , [ ] )
244+
221245 if ( ! currentStep || ! currentQuestion ) {
222246 return null
223247 }
@@ -273,7 +297,7 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
273297 setIsComplete ( false )
274298 }
275299
276- const loadFrameworkQuestions = async ( frameworkId : string , frameworkLabel : string ) => {
300+ const loadFrameworkQuestions = async ( frameworkId : string , frameworkLabel ? : string ) => {
277301 try {
278302 setGeneratedFile ( null )
279303
@@ -294,10 +318,13 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
294318 setAutoFilledQuestionMap ( { } )
295319 setAutoFillNotice ( null )
296320
321+ const resolvedFrameworkLabel =
322+ frameworkLabel ?? frameworkQuestion ?. answers . find ( ( answer ) => answer . value === frameworkId ) ?. label ?? frameworkId
323+
297324 setDynamicSteps ( [
298325 {
299326 id : `framework-${ frameworkId } ` ,
300- title : `${ frameworkLabel } Preferences` ,
327+ title : `${ resolvedFrameworkLabel } Preferences` ,
301328 questions : mappedQuestions ,
302329 } ,
303330 ] )
@@ -387,46 +414,70 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
387414 setAutoFillNotice ( null )
388415 }
389416
390- const handleAnswerClick = async ( answer : WizardAnswer ) => {
417+ const handleEditEntry = ( entryId : string ) => {
418+ if ( ! entryId ) {
419+ return
420+ }
421+
422+ const question = wizardQuestionsById [ entryId ]
423+
424+ if ( ! question ) {
425+ return
426+ }
427+
428+ setActiveEditQuestionId ( entryId )
429+ }
430+
431+ const handleQuestionAnswerSelection = async (
432+ question : WizardQuestion ,
433+ answer : WizardAnswer ,
434+ { skipAutoAdvance = false } : { skipAutoAdvance ?: boolean } = { }
435+ ) => {
391436 if ( answer . disabled ) {
392437 return
393438 }
394439
395440 setGeneratedFile ( null )
396441
397- const previousValue = responses [ currentQuestion . id ]
398442 let nextValue : Responses [ keyof Responses ]
399443 let didAddSelection = false
400444
401- if ( currentQuestion . allowMultiple ) {
402- const prevArray = Array . isArray ( previousValue ) ? previousValue : [ ]
403- if ( prevArray . includes ( answer . value ) ) {
404- nextValue = prevArray . filter ( ( item ) => item !== answer . value )
445+ setResponses ( ( prev ) => {
446+ const prevValue = prev [ question . id ]
447+
448+ if ( question . allowMultiple ) {
449+ const prevArray = Array . isArray ( prevValue ) ? prevValue : [ ]
450+
451+ if ( prevArray . includes ( answer . value ) ) {
452+ nextValue = prevArray . filter ( ( item ) => item !== answer . value )
453+ } else {
454+ nextValue = [ ...prevArray , answer . value ]
455+ didAddSelection = true
456+ }
405457 } else {
406- nextValue = [ ...prevArray , answer . value ]
407- didAddSelection = true
458+ if ( prevValue === answer . value ) {
459+ nextValue = undefined
460+ } else {
461+ nextValue = answer . value
462+ didAddSelection = true
463+ }
408464 }
409- } else {
410- if ( previousValue === answer . value ) {
411- nextValue = undefined
412- } else {
413- nextValue = answer . value
414- didAddSelection = true
465+
466+ return {
467+ ...prev ,
468+ [ question . id ] : nextValue ,
415469 }
416- }
470+ } )
417471
418- setResponses ( ( prev ) => ( {
419- ...prev ,
420- [ currentQuestion . id ] : nextValue ,
421- } ) )
472+ clearAutoFilledFlag ( question . id )
422473
423- clearAutoFilledFlag ( currentQuestion . id )
474+ const isFrameworkQuestion = question . id === FRAMEWORK_QUESTION_ID
424475
425- const isFrameworkQuestion = currentQuestion . id === FRAMEWORK_QUESTION_ID
426476 const shouldAutoAdvance =
477+ ! skipAutoAdvance &&
427478 ! isFrameworkQuestion &&
428- ( ( currentQuestion . allowMultiple && Array . isArray ( nextValue ) && nextValue . length > 0 && didAddSelection ) ||
429- ( ! currentQuestion . allowMultiple && nextValue !== undefined && nextValue !== null && didAddSelection ) )
479+ ( ( question . allowMultiple && Array . isArray ( nextValue ) && nextValue . length > 0 && didAddSelection ) ||
480+ ( ! question . allowMultiple && nextValue !== undefined && nextValue !== null && didAddSelection ) )
430481
431482 if ( shouldAutoAdvance ) {
432483 setTimeout ( ( ) => {
@@ -444,6 +495,14 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
444495 }
445496 }
446497
498+ const handleAnswerClick = ( answer : WizardAnswer ) => {
499+ if ( ! currentQuestion ) {
500+ return
501+ }
502+
503+ void handleQuestionAnswerSelection ( currentQuestion , answer )
504+ }
505+
447506 const applyDefaultAnswer = async ( ) => {
448507 if ( ! defaultAnswer || defaultAnswer . disabled ) {
449508 return
@@ -703,6 +762,7 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
703762 onGenerate = { ( ) => void generateInstructionsFile ( ) }
704763 isGenerating = { isGenerating }
705764 autoFillNotice = { autoFillNotice }
765+ onEditEntry = { handleEditEntry }
706766 />
707767 ) : null }
708768
@@ -719,6 +779,20 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
719779 return (
720780 < >
721781 { wizardLayout }
782+ { editingQuestion ? (
783+ < WizardEditAnswerDialog
784+ question = { editingQuestion }
785+ value = { editingAnswerValue }
786+ onAnswerSelect = { async ( selectedAnswer ) => {
787+ await handleQuestionAnswerSelection ( editingQuestion , selectedAnswer , { skipAutoAdvance : true } )
788+
789+ if ( ! editingQuestion . allowMultiple ) {
790+ closeEditDialog ( )
791+ }
792+ } }
793+ onClose = { closeEditDialog }
794+ />
795+ ) : null }
722796 { generatedFile ? (
723797 < FinalOutputView
724798 fileName = { generatedFile . fileName }
0 commit comments