refactor(guides): replace NewGuide with unified GuideForm
- Merge useNewGuide and NewGuide into useGuideForm and GuideForm to handle both create and edit flows - Add /edit-guide/:id route pointing to GuideForm - Replace Edit button in Guide view with Link to edit route - Add defaultValue to Frame title input for edit pre-population - Delete NewGuide.tsx, NewGuide.css, useNewGuide.ts
This commit is contained in:
parent
4dd8ce8b17
commit
ea6adc236e
6 changed files with 79 additions and 58 deletions
|
|
@ -52,6 +52,7 @@ export default function Frame({
|
||||||
Title
|
Title
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
defaultValue={frame.title}
|
||||||
onChange={(e) => updateTitle(frame.id, e.target.value)}
|
onChange={(e) => updateTitle(frame.id, e.target.value)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,36 @@ import {
|
||||||
import { useIDB } from "../../../lib/contexts/dbinit.context";
|
import { useIDB } from "../../../lib/contexts/dbinit.context";
|
||||||
import { useGuides } from "../../../lib/contexts/guide.context";
|
import { useGuides } from "../../../lib/contexts/guide.context";
|
||||||
|
|
||||||
export default function useNewGuide() {
|
function makeEmptyGuide(): GuideType {
|
||||||
const { db } = useIDB();
|
return {
|
||||||
const { setGuides } = useGuides();
|
|
||||||
|
|
||||||
const [guide, setGuide] = useState<GuideType>({
|
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
title: "",
|
title: "",
|
||||||
description: "",
|
description: "",
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
deletedAt: undefined,
|
deletedAt: undefined,
|
||||||
});
|
};
|
||||||
const [frames, setFrames] = useState<FrameType[]>([]);
|
}
|
||||||
|
|
||||||
|
export default function useGuideForm(initial?: CompiledGuideType) {
|
||||||
|
const { db } = useIDB();
|
||||||
|
const { setGuides } = useGuides();
|
||||||
|
const isEditing = Boolean(initial);
|
||||||
|
|
||||||
|
const [guide, setGuide] = useState<GuideType>(
|
||||||
|
initial
|
||||||
|
? {
|
||||||
|
id: initial.id,
|
||||||
|
title: initial.title,
|
||||||
|
description: initial.description,
|
||||||
|
createdAt: initial.createdAt,
|
||||||
|
updatedAt: initial.updatedAt,
|
||||||
|
deletedAt: initial.deletedAt,
|
||||||
|
}
|
||||||
|
: makeEmptyGuide(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [frames, setFrames] = useState<FrameType[]>(initial?.frames ?? []);
|
||||||
|
|
||||||
const handleUpdateTitle = (title: string) => {
|
const handleUpdateTitle = (title: string) => {
|
||||||
setGuide((prev) => ({ ...prev, title }));
|
setGuide((prev) => ({ ...prev, title }));
|
||||||
|
|
@ -32,20 +49,16 @@ export default function useNewGuide() {
|
||||||
const handleAddFrames = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleAddFrames = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newFiles = e.target.files;
|
const newFiles = e.target.files;
|
||||||
if (!newFiles) return;
|
if (!newFiles) return;
|
||||||
|
const newFrameObjects = Array.from(newFiles).map((file) => {
|
||||||
const newFilesArray = Array.from(newFiles);
|
|
||||||
|
|
||||||
const newFrameObjects = newFilesArray.map((file) => {
|
|
||||||
const frameId = crypto.randomUUID();
|
const frameId = crypto.randomUUID();
|
||||||
return {
|
return {
|
||||||
id: frameId,
|
id: frameId,
|
||||||
file: file,
|
file,
|
||||||
title: "",
|
title: "",
|
||||||
guideId: guide.id,
|
guideId: guide.id,
|
||||||
steps: [{ id: crypto.randomUUID(), text: "", frameId: frameId }],
|
steps: [{ id: crypto.randomUUID(), text: "", frameId }],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
setFrames((prev) => [...prev, ...newFrameObjects]);
|
setFrames((prev) => [...prev, ...newFrameObjects]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -76,7 +89,7 @@ export default function useNewGuide() {
|
||||||
...frame,
|
...frame,
|
||||||
steps: [
|
steps: [
|
||||||
...frame.steps,
|
...frame.steps,
|
||||||
{ id: crypto.randomUUID(), text: "", frameId: frameId },
|
{ id: crypto.randomUUID(), text: "", frameId },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
: frame,
|
: frame,
|
||||||
|
|
@ -86,58 +99,45 @@ export default function useNewGuide() {
|
||||||
|
|
||||||
const handleUpdateStep = (stepId: string, text: string) => {
|
const handleUpdateStep = (stepId: string, text: string) => {
|
||||||
setFrames((prev) =>
|
setFrames((prev) =>
|
||||||
prev.map((frame) =>
|
prev.map((frame) => ({
|
||||||
frame.steps
|
...frame,
|
||||||
? {
|
steps: frame.steps.map((step) =>
|
||||||
...frame,
|
step.id === stepId ? { ...step, text } : step,
|
||||||
steps: frame.steps.map((step) =>
|
),
|
||||||
step.id === stepId ? { ...step, text } : step,
|
})),
|
||||||
),
|
|
||||||
}
|
|
||||||
: frame,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveStep = (stepId: string) => {
|
const handleRemoveStep = (stepId: string) => {
|
||||||
setFrames((prev) =>
|
setFrames((prev) =>
|
||||||
prev.map((frame) =>
|
prev.map((frame) => ({
|
||||||
frame.steps
|
...frame,
|
||||||
? {
|
steps: frame.steps.filter((step) => step.id !== stepId),
|
||||||
...frame,
|
})),
|
||||||
steps: frame.steps.filter((step) => step.id !== stepId),
|
|
||||||
}
|
|
||||||
: frame,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveGuide = async (e: React.SubmitEvent<HTMLFormElement>) => {
|
const handleSaveGuide = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!db) return;
|
||||||
|
|
||||||
const compiledGuide: CompiledGuideType = {
|
const compiledGuide: CompiledGuideType = {
|
||||||
id: guide.id,
|
...guide,
|
||||||
title: guide.title,
|
updatedAt: new Date(),
|
||||||
description: guide.description,
|
|
||||||
createdAt: guide.createdAt,
|
|
||||||
updatedAt: guide.updatedAt,
|
|
||||||
deletedAt: guide.deletedAt,
|
|
||||||
frames,
|
frames,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!db) return;
|
|
||||||
await db.put("guides", compiledGuide);
|
await db.put("guides", compiledGuide);
|
||||||
setGuide({
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
deletedAt: undefined,
|
|
||||||
});
|
|
||||||
setFrames([]);
|
|
||||||
|
|
||||||
setGuides((prev) => [compiledGuide, ...prev]);
|
if (isEditing) {
|
||||||
|
setGuides((prev) =>
|
||||||
|
prev.map((g) => (g.id === compiledGuide.id ? compiledGuide : g)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setGuides((prev) => [compiledGuide, ...prev]);
|
||||||
|
setGuide(makeEmptyGuide());
|
||||||
|
setFrames([]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -145,6 +145,7 @@ export default function useNewGuide() {
|
||||||
setGuide,
|
setGuide,
|
||||||
frames,
|
frames,
|
||||||
setFrames,
|
setFrames,
|
||||||
|
isEditing,
|
||||||
handleUpdateTitle,
|
handleUpdateTitle,
|
||||||
handleUpdateDescription,
|
handleUpdateDescription,
|
||||||
handleAddFrames,
|
handleAddFrames,
|
||||||
|
|
@ -157,3 +158,7 @@ export default function useNewGuide() {
|
||||||
handleSaveGuide,
|
handleSaveGuide,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* */
|
||||||
|
/* Figure out how to use this hook for both editing and creating. */
|
||||||
|
/* */
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { useGuides } from "../../../lib/contexts/guide.context";
|
import { useGuides } from "../../../lib/contexts/guide.context";
|
||||||
import useGuide from "../hooks/useGuide";
|
import useGuide from "../hooks/useGuide";
|
||||||
|
import { Link } from "react-router";
|
||||||
|
|
||||||
export default function Guide() {
|
export default function Guide() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
@ -31,7 +32,7 @@ export default function Guide() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onClick={() => deleteGuide(guide.id)}>Delete</button>
|
<button onClick={() => deleteGuide(guide.id)}>Delete</button>
|
||||||
<button>Edit</button>
|
<Link to={`/edit-guide/${guide.id}`}>Edit</Link>
|
||||||
<button onClick={exportGuide}>Export PDF</button>
|
<button onClick={exportGuide}>Export PDF</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
import "./NewGuide.css";
|
import "./GuideForm.css";
|
||||||
import FileUpload from "../components/FileUpload";
|
import FileUpload from "../components/FileUpload";
|
||||||
import useNewGuide from "../hooks/useNewGuide";
|
import useGuideForm from "../hooks/useGuideForm";
|
||||||
import Frame from "../components/Frame";
|
import Frame from "../components/Frame";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import { useGuides } from "../../../lib/contexts/guide.context";
|
||||||
|
|
||||||
|
export default function GuideForm() {
|
||||||
|
const { id } = useParams();
|
||||||
|
const { guides } = useGuides();
|
||||||
|
const g = guides.find((g) => g.id === id);
|
||||||
|
|
||||||
export default function NewGuide() {
|
|
||||||
const {
|
const {
|
||||||
|
guide,
|
||||||
frames,
|
frames,
|
||||||
handleUpdateTitle,
|
handleUpdateTitle,
|
||||||
handleUpdateDescription,
|
handleUpdateDescription,
|
||||||
|
|
@ -16,7 +23,8 @@ export default function NewGuide() {
|
||||||
handleRemoveStep,
|
handleRemoveStep,
|
||||||
handleUpdateStep,
|
handleUpdateStep,
|
||||||
handleSaveGuide,
|
handleSaveGuide,
|
||||||
} = useNewGuide();
|
isEditing,
|
||||||
|
} = useGuideForm(g);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="new-guide">
|
<div className="new-guide">
|
||||||
|
|
@ -26,6 +34,7 @@ export default function NewGuide() {
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="title"
|
name="title"
|
||||||
|
defaultValue={guide?.title}
|
||||||
onChange={(e) => handleUpdateTitle(e.target.value)}
|
onChange={(e) => handleUpdateTitle(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -33,6 +42,7 @@ export default function NewGuide() {
|
||||||
<span>Description</span>
|
<span>Description</span>
|
||||||
<textarea
|
<textarea
|
||||||
name="description"
|
name="description"
|
||||||
|
defaultValue={guide?.description}
|
||||||
onChange={(e) => handleUpdateDescription(e.target.value)}
|
onChange={(e) => handleUpdateDescription(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { createBrowserRouter } from "react-router";
|
import { createBrowserRouter } from "react-router";
|
||||||
import App from "./App.tsx";
|
import App from "./App.tsx";
|
||||||
import NewGuide from "./features/guides/routes/NewGuide.tsx";
|
import GuideForm from "./features/guides/routes/GuideForm.tsx";
|
||||||
import NotFound from "./components/not-found/NotFound.tsx";
|
import NotFound from "./components/not-found/NotFound.tsx";
|
||||||
import Guide from "./features/guides/routes/Guide.tsx";
|
import Guide from "./features/guides/routes/Guide.tsx";
|
||||||
|
|
||||||
|
|
@ -11,12 +11,16 @@ export const router = createBrowserRouter([
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/new-guide",
|
path: "/new-guide",
|
||||||
Component: NewGuide,
|
Component: GuideForm,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/guide/:id",
|
path: "/guide/:id",
|
||||||
Component: Guide,
|
Component: Guide,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/edit-guide/:id",
|
||||||
|
Component: GuideForm,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue