Skip to content

Commit b000f76

Browse files
committed
feat: implement editing functionality in InstructionsWizard; enhance file and framework question handling; update UI components for better user experience
1 parent c714ffb commit b000f76

File tree

9 files changed

+328
-127
lines changed

9 files changed

+328
-127
lines changed

app/new/page.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,22 @@ import { AnimatedBackground } from "@/components/AnimatedBackground"
88
import { getHomeMainClasses } from "@/lib/utils"
99
import { ANALYTICS_EVENTS } from "@/lib/analytics-events"
1010
import { track } from "@/lib/mixpanel"
11-
import type { FileOutputConfig } from "@/types/wizard"
11+
import type { DataQuestionSource, FileOutputConfig } from "@/types/wizard"
1212
import { Github } from "lucide-react"
1313
import Link from "next/link"
1414

1515
import filesData from "@/data/files.json"
16+
import { buildFileOptionsFromQuestion } from "@/lib/wizard-utils"
17+
18+
const fileQuestionSet = filesData as DataQuestionSource[]
19+
const fileQuestion = fileQuestionSet[0] ?? null
20+
const fileOptionsFromData = buildFileOptionsFromQuestion(fileQuestion)
1621

1722
export default function NewInstructionsPage() {
1823
const [showWizard, setShowWizard] = useState(false)
1924
const [selectedFileId, setSelectedFileId] = useState<string | null>(null)
2025

21-
const fileOptions = useMemo(() => {
22-
return (filesData as FileOutputConfig[]).filter((file) => file.enabled !== false)
23-
}, [])
26+
const fileOptions = useMemo(() => fileOptionsFromData, [])
2427

2528
const handleFileCtaClick = (file: FileOutputConfig) => {
2629
setSelectedFileId(file.id)

components/instructions-wizard.tsx

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

3-
import { useCallback, useMemo, useState } from "react"
3+
import { useCallback, useEffect, useMemo, useState } from "react"
44
import { Button } from "@/components/ui/button"
55
import { 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"
99
import generalData from "@/data/general.json"
1010
import architectureData from "@/data/architecture.json"
1111
import performanceData from "@/data/performance.json"
@@ -16,43 +16,40 @@ import FinalOutputView from "./final-output-view"
1616
import { WizardAnswerGrid } from "./wizard-answer-grid"
1717
import { WizardCompletionSummary } from "./wizard-completion-summary"
1818
import { WizardConfirmationDialog } from "./wizard-confirmation-dialog"
19+
import { WizardEditAnswerDialog } from "./wizard-edit-answer-dialog"
1920

2021
import { generateInstructions } from "@/lib/instructions-api"
2122
import { buildCompletionSummary } from "@/lib/wizard-summary"
2223
import { serializeWizardResponses } from "@/lib/wizard-response"
23-
import { buildStepFromQuestionSet, getFormatLabel, mapAnswerSourceToWizard } from "@/lib/wizard-utils"
24+
import { buildFileOptionsFromQuestion, buildStepFromQuestionSet, getFormatLabel, mapAnswerSourceToWizard } from "@/lib/wizard-utils"
2425
import 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-
3327
const FRAMEWORK_STEP_ID = "frameworks"
3428
const FRAMEWORK_QUESTION_ID = "frameworkSelection"
3529
const 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

5754
const 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

Comments
 (0)