From 1bb6ddf40396dbea036e22be3807df8eb8d9b0de Mon Sep 17 00:00:00 2001 From: johannes-hernehult Date: Fri, 12 Jun 2026 11:10:02 +0200 Subject: [PATCH] refactor(guides): colocate steps within frames state - Remove standalone steps state; steps now live inside each Frame object - Initialize each frame with one default empty step on file upload - Update handleAddSteps, handleUpdateStep, handleRemoveStep to operate on frames array via nested map/filter - Rename handleUpdateStepText -> handleUpdateStep - Rename schema types to StepType, FrameType, GuideType - Add steps and title fields to FrameSchema - Wire FileUpload onChange and render Frame components in NewGuide - Add NewGuide.css and basic input/textarea border styles --- src/features/guides/components/FileUpload.tsx | 14 +++- src/features/guides/components/Frame.tsx | 33 +++++++++ src/features/guides/components/Step.tsx | 27 +++++++ src/features/guides/hooks/useNewGuide.ts | 74 +++++++++++++------ src/features/guides/routes/NewGuide.css | 19 +++++ src/features/guides/routes/NewGuide.tsx | 37 +++++++++- src/lib/schemas.ts | 8 +- src/styles/App.css | 12 +++ 8 files changed, 193 insertions(+), 31 deletions(-) create mode 100644 src/features/guides/components/Frame.tsx create mode 100644 src/features/guides/components/Step.tsx create mode 100644 src/features/guides/routes/NewGuide.css diff --git a/src/features/guides/components/FileUpload.tsx b/src/features/guides/components/FileUpload.tsx index e4cb250..a587aa0 100644 --- a/src/features/guides/components/FileUpload.tsx +++ b/src/features/guides/components/FileUpload.tsx @@ -1,8 +1,18 @@ -export default function FileUpload() { +export default function FileUpload({ + onChange, +}: { + onChange: (e: React.ChangeEvent) => void; +}) { return ( ); } diff --git a/src/features/guides/components/Frame.tsx b/src/features/guides/components/Frame.tsx new file mode 100644 index 0000000..110e5ed --- /dev/null +++ b/src/features/guides/components/Frame.tsx @@ -0,0 +1,33 @@ +import type { FrameType, StepType } from "../../../lib/schemas"; +import Step from "./Step"; + +export default function Frame({ + frame, + onRemove, + addStep, + removeStep, + updateStep, +}: { + frame: FrameType; + onRemove: (frameId: string) => void; + addStep: (frameId: string) => void; + removeStep: (stepId: string) => void; + updateStep: (stepId: string, text: string) => void; +}) { + return ( +
+ + {frame.title} + + {frame.steps.map((step: StepType, index: number) => ( + + ))} +
+ ); +} diff --git a/src/features/guides/components/Step.tsx b/src/features/guides/components/Step.tsx new file mode 100644 index 0000000..b70d3a7 --- /dev/null +++ b/src/features/guides/components/Step.tsx @@ -0,0 +1,27 @@ +import type { StepType } from "../../../lib/schemas"; + +export default function Step({ + step, + removeStep, + updateStep, + index, +}: { + step: StepType; + removeStep: (stepId: string) => void; + updateStep: (stepId: string, text: string) => void; + index: number; +}) { + return ( +
+ + +
+ ); +} diff --git a/src/features/guides/hooks/useNewGuide.ts b/src/features/guides/hooks/useNewGuide.ts index 3d8292f..2804bc5 100644 --- a/src/features/guides/hooks/useNewGuide.ts +++ b/src/features/guides/hooks/useNewGuide.ts @@ -11,7 +11,6 @@ export default function useNewGuide() { deletedAt: undefined, }); const [frames, setFrames] = useState([]); - const [steps, setSteps] = useState([]); const handleUpdateTitle = (title: string) => { setGuide((prev) => ({ ...prev, title })); @@ -21,12 +20,21 @@ export default function useNewGuide() { setGuide((prev) => ({ ...prev, description })); }; - const handleAddFrames = (newFiles: File[]) => { - const newFrameObjects = newFiles.map((file) => ({ - id: crypto.randomUUID(), - file: file, - guideId: guide.id, - })); + const handleAddFrames = (e: React.ChangeEvent) => { + const newFiles = e.target.files; + if (!newFiles) return; + + const newFilesArray = Array.from(newFiles); + + const newFrameObjects = newFilesArray.map((file) => { + const frameId = crypto.randomUUID(); + return { + id: frameId, + file: file, + guideId: guide.id, + steps: [{ id: crypto.randomUUID(), text: "", frameId: frameId }], + }; + }); setFrames((prev) => [...prev, ...newFrameObjects]); }; @@ -36,31 +44,53 @@ export default function useNewGuide() { }; const handleAddSteps = (frameId: string) => { - setSteps((prev) => [ - ...prev, - { - id: crypto.randomUUID(), - text: "", - frameId: frameId, - }, - ]); + setFrames((prev) => + prev.map((frame) => + frame.id === frameId + ? { + ...frame, + steps: [ + ...frame.steps, + { id: crypto.randomUUID(), text: "", frameId: frameId }, + ], + } + : frame, + ), + ); }; - const handleUpdateStepText = (stepId: string, text: string) => { - setSteps((prev) => - prev.map((step) => (step.id === stepId ? { ...step, text } : step)), + const handleUpdateStep = (stepId: string, text: string) => { + setFrames((prev) => + prev.map((frame) => + frame.steps + ? { + ...frame, + steps: frame.steps.map((step) => + step.id === stepId ? { ...step, text } : step, + ), + } + : frame, + ), ); }; const handleRemoveStep = (stepId: string) => { - setSteps((prev) => prev.filter((step) => step.id !== stepId)); + setFrames((prev) => + prev.map((frame) => + frame.steps + ? { + ...frame, + steps: frame.steps.filter((step) => step.id !== stepId), + } + : frame, + ), + ); }; const handleSaveGuide = () => { const compiledGuide = { ...guide, frames, - steps, }; console.log(compiledGuide); @@ -71,14 +101,12 @@ export default function useNewGuide() { setGuide, frames, setFrames, - steps, - setSteps, handleUpdateTitle, handleUpdateDescription, handleAddFrames, handleAddSteps, handleRemoveFrame, - handleUpdateStepText, + handleUpdateStep, handleRemoveStep, handleSaveGuide, }; diff --git a/src/features/guides/routes/NewGuide.css b/src/features/guides/routes/NewGuide.css new file mode 100644 index 0000000..2cfaa29 --- /dev/null +++ b/src/features/guides/routes/NewGuide.css @@ -0,0 +1,19 @@ +.new-guide { + display: flex; + flex-direction: column; + align-items: center; +} + +.new-guide__form { + display: flex; + flex-direction: column; + gap: 2rem; + + width: 100%; + max-width: 540px; +} + +.new-guide__form label { + display: flex; + flex-direction: column; +} diff --git a/src/features/guides/routes/NewGuide.tsx b/src/features/guides/routes/NewGuide.tsx index af7377c..fb06b09 100644 --- a/src/features/guides/routes/NewGuide.tsx +++ b/src/features/guides/routes/NewGuide.tsx @@ -1,9 +1,28 @@ +import "./NewGuide.css"; import FileUpload from "../components/FileUpload"; +import useNewGuide from "../hooks/useNewGuide"; +import Frame from "../components/Frame"; export default function NewGuide() { + const { + frames, + handleUpdateTitle, + handleUpdateDescription, + handleAddFrames, + handleAddSteps, + handleRemoveFrame, + handleRemoveStep, + handleUpdateStep, + handleSaveGuide, + } = useNewGuide(); + const handleSubmitForm = (e: React.SubmitEvent) => { + e.preventDefault(); + console.log(e.currentTarget); + }; + return ( -
-
+
+